##// END OF EJS Templates
Merge pull request #12893 from Carreau/doc-reformat...
Carol Willing -
r26455:e0d15ef6 merge
parent child Browse files
Show More
@@ -1,212 +1,210 b''
1 """Common utilities for the various process_* implementations.
1 """Common utilities for the various process_* implementations.
2
2
3 This file is only meant to be imported by the platform-specific implementations
3 This file is only meant to be imported by the platform-specific implementations
4 of subprocess utilities, and it contains tools that are common to all of them.
4 of subprocess utilities, and it contains tools that are common to all of them.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import subprocess
17 import subprocess
18 import shlex
18 import shlex
19 import sys
19 import sys
20 import os
20 import os
21
21
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Function definitions
25 # Function definitions
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 def read_no_interrupt(p):
28 def read_no_interrupt(p):
29 """Read from a pipe ignoring EINTR errors.
29 """Read from a pipe ignoring EINTR errors.
30
30
31 This is necessary because when reading from pipes with GUI event loops
31 This is necessary because when reading from pipes with GUI event loops
32 running in the background, often interrupts are raised that stop the
32 running in the background, often interrupts are raised that stop the
33 command from completing."""
33 command from completing."""
34 import errno
34 import errno
35
35
36 try:
36 try:
37 return p.read()
37 return p.read()
38 except IOError as err:
38 except IOError as err:
39 if err.errno != errno.EINTR:
39 if err.errno != errno.EINTR:
40 raise
40 raise
41
41
42
42
43 def process_handler(cmd, callback, stderr=subprocess.PIPE):
43 def process_handler(cmd, callback, stderr=subprocess.PIPE):
44 """Open a command in a shell subprocess and execute a callback.
44 """Open a command in a shell subprocess and execute a callback.
45
45
46 This function provides common scaffolding for creating subprocess.Popen()
46 This function provides common scaffolding for creating subprocess.Popen()
47 calls. It creates a Popen object and then calls the callback with it.
47 calls. It creates a Popen object and then calls the callback with it.
48
48
49 Parameters
49 Parameters
50 ----------
50 ----------
51 cmd : str or list
51 cmd : str or list
52 A command to be executed by the system, using :class:`subprocess.Popen`.
52 A command to be executed by the system, using :class:`subprocess.Popen`.
53 If a string is passed, it will be run in the system shell. If a list is
53 If a string is passed, it will be run in the system shell. If a list is
54 passed, it will be used directly as arguments.
54 passed, it will be used directly as arguments.
55
56 callback : callable
55 callback : callable
57 A one-argument function that will be called with the Popen object.
56 A one-argument function that will be called with the Popen object.
58
59 stderr : file descriptor number, optional
57 stderr : file descriptor number, optional
60 By default this is set to ``subprocess.PIPE``, but you can also pass the
58 By default this is set to ``subprocess.PIPE``, but you can also pass the
61 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
62 the same file descriptor as its stdout. This is useful to read stdout
60 the same file descriptor as its stdout. This is useful to read stdout
63 and stderr combined in the order they are generated.
61 and stderr combined in the order they are generated.
64
62
65 Returns
63 Returns
66 -------
64 -------
67 The return value of the provided callback is returned.
65 The return value of the provided callback is returned.
68 """
66 """
69 sys.stdout.flush()
67 sys.stdout.flush()
70 sys.stderr.flush()
68 sys.stderr.flush()
71 # On win32, close_fds can't be true when using pipes for stdin/out/err
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
72 close_fds = sys.platform != 'win32'
70 close_fds = sys.platform != 'win32'
73 # Determine if cmd should be run with system shell.
71 # Determine if cmd should be run with system shell.
74 shell = isinstance(cmd, str)
72 shell = isinstance(cmd, str)
75 # On POSIX systems run shell commands with user-preferred shell.
73 # On POSIX systems run shell commands with user-preferred shell.
76 executable = None
74 executable = None
77 if shell and os.name == 'posix' and 'SHELL' in os.environ:
75 if shell and os.name == 'posix' and 'SHELL' in os.environ:
78 executable = os.environ['SHELL']
76 executable = os.environ['SHELL']
79 p = subprocess.Popen(cmd, shell=shell,
77 p = subprocess.Popen(cmd, shell=shell,
80 executable=executable,
78 executable=executable,
81 stdin=subprocess.PIPE,
79 stdin=subprocess.PIPE,
82 stdout=subprocess.PIPE,
80 stdout=subprocess.PIPE,
83 stderr=stderr,
81 stderr=stderr,
84 close_fds=close_fds)
82 close_fds=close_fds)
85
83
86 try:
84 try:
87 out = callback(p)
85 out = callback(p)
88 except KeyboardInterrupt:
86 except KeyboardInterrupt:
89 print('^C')
87 print('^C')
90 sys.stdout.flush()
88 sys.stdout.flush()
91 sys.stderr.flush()
89 sys.stderr.flush()
92 out = None
90 out = None
93 finally:
91 finally:
94 # Make really sure that we don't leave processes behind, in case the
92 # Make really sure that we don't leave processes behind, in case the
95 # call above raises an exception
93 # call above raises an exception
96 # We start by assuming the subprocess finished (to avoid NameErrors
94 # We start by assuming the subprocess finished (to avoid NameErrors
97 # later depending on the path taken)
95 # later depending on the path taken)
98 if p.returncode is None:
96 if p.returncode is None:
99 try:
97 try:
100 p.terminate()
98 p.terminate()
101 p.poll()
99 p.poll()
102 except OSError:
100 except OSError:
103 pass
101 pass
104 # One last try on our way out
102 # One last try on our way out
105 if p.returncode is None:
103 if p.returncode is None:
106 try:
104 try:
107 p.kill()
105 p.kill()
108 except OSError:
106 except OSError:
109 pass
107 pass
110
108
111 return out
109 return out
112
110
113
111
114 def getoutput(cmd):
112 def getoutput(cmd):
115 """Run a command and return its stdout/stderr as a string.
113 """Run a command and return its stdout/stderr as a string.
116
114
117 Parameters
115 Parameters
118 ----------
116 ----------
119 cmd : str or list
117 cmd : str or list
120 A command to be executed in the system shell.
118 A command to be executed in the system shell.
121
119
122 Returns
120 Returns
123 -------
121 -------
124 output : str
122 output : str
125 A string containing the combination of stdout and stderr from the
123 A string containing the combination of stdout and stderr from the
126 subprocess, in whatever order the subprocess originally wrote to its
124 subprocess, in whatever order the subprocess originally wrote to its
127 file descriptors (so the order of the information in this string is the
125 file descriptors (so the order of the information in this string is the
128 correct order as would be seen if running the command in a terminal).
126 correct order as would be seen if running the command in a terminal).
129 """
127 """
130 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
128 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
131 if out is None:
129 if out is None:
132 return ''
130 return ''
133 return py3compat.decode(out)
131 return py3compat.decode(out)
134
132
135
133
136 def getoutputerror(cmd):
134 def getoutputerror(cmd):
137 """Return (standard output, standard error) of executing cmd in a shell.
135 """Return (standard output, standard error) of executing cmd in a shell.
138
136
139 Accepts the same arguments as os.system().
137 Accepts the same arguments as os.system().
140
138
141 Parameters
139 Parameters
142 ----------
140 ----------
143 cmd : str or list
141 cmd : str or list
144 A command to be executed in the system shell.
142 A command to be executed in the system shell.
145
143
146 Returns
144 Returns
147 -------
145 -------
148 stdout : str
146 stdout : str
149 stderr : str
147 stderr : str
150 """
148 """
151 return get_output_error_code(cmd)[:2]
149 return get_output_error_code(cmd)[:2]
152
150
153 def get_output_error_code(cmd):
151 def get_output_error_code(cmd):
154 """Return (standard output, standard error, return code) of executing cmd
152 """Return (standard output, standard error, return code) of executing cmd
155 in a shell.
153 in a shell.
156
154
157 Accepts the same arguments as os.system().
155 Accepts the same arguments as os.system().
158
156
159 Parameters
157 Parameters
160 ----------
158 ----------
161 cmd : str or list
159 cmd : str or list
162 A command to be executed in the system shell.
160 A command to be executed in the system shell.
163
161
164 Returns
162 Returns
165 -------
163 -------
166 stdout : str
164 stdout : str
167 stderr : str
165 stderr : str
168 returncode: int
166 returncode: int
169 """
167 """
170
168
171 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
169 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
172 if out_err is None:
170 if out_err is None:
173 return '', '', p.returncode
171 return '', '', p.returncode
174 out, err = out_err
172 out, err = out_err
175 return py3compat.decode(out), py3compat.decode(err), p.returncode
173 return py3compat.decode(out), py3compat.decode(err), p.returncode
176
174
177 def arg_split(s, posix=False, strict=True):
175 def arg_split(s, posix=False, strict=True):
178 """Split a command line's arguments in a shell-like manner.
176 """Split a command line's arguments in a shell-like manner.
179
177
180 This is a modified version of the standard library's shlex.split()
178 This is a modified version of the standard library's shlex.split()
181 function, but with a default of posix=False for splitting, so that quotes
179 function, but with a default of posix=False for splitting, so that quotes
182 in inputs are respected.
180 in inputs are respected.
183
181
184 if strict=False, then any errors shlex.split would raise will result in the
182 if strict=False, then any errors shlex.split would raise will result in the
185 unparsed remainder being the last element of the list, rather than raising.
183 unparsed remainder being the last element of the list, rather than raising.
186 This is because we sometimes use arg_split to parse things other than
184 This is because we sometimes use arg_split to parse things other than
187 command-line args.
185 command-line args.
188 """
186 """
189
187
190 lex = shlex.shlex(s, posix=posix)
188 lex = shlex.shlex(s, posix=posix)
191 lex.whitespace_split = True
189 lex.whitespace_split = True
192 # Extract tokens, ensuring that things like leaving open quotes
190 # Extract tokens, ensuring that things like leaving open quotes
193 # does not cause this to raise. This is important, because we
191 # does not cause this to raise. This is important, because we
194 # sometimes pass Python source through this (e.g. %timeit f(" ")),
192 # sometimes pass Python source through this (e.g. %timeit f(" ")),
195 # and it shouldn't raise an exception.
193 # and it shouldn't raise an exception.
196 # It may be a bad idea to parse things that are not command-line args
194 # It may be a bad idea to parse things that are not command-line args
197 # through this function, but we do, so let's be safe about it.
195 # through this function, but we do, so let's be safe about it.
198 lex.commenters='' #fix for GH-1269
196 lex.commenters='' #fix for GH-1269
199 tokens = []
197 tokens = []
200 while True:
198 while True:
201 try:
199 try:
202 tokens.append(next(lex))
200 tokens.append(next(lex))
203 except StopIteration:
201 except StopIteration:
204 break
202 break
205 except ValueError:
203 except ValueError:
206 if strict:
204 if strict:
207 raise
205 raise
208 # couldn't parse, get remaining blob as last token
206 # couldn't parse, get remaining blob as last token
209 tokens.append(lex.token)
207 tokens.append(lex.token)
210 break
208 break
211
209
212 return tokens
210 return tokens
@@ -1,225 +1,225 b''
1 """Posix-specific implementation of process utilities.
1 """Posix-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 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 # Stdlib
17 # Stdlib
18 import errno
18 import errno
19 import os
19 import os
20 import subprocess as sp
20 import subprocess as sp
21 import sys
21 import sys
22
22
23 import pexpect
23 import pexpect
24
24
25 # Our own
25 # Our own
26 from ._process_common import getoutput, arg_split
26 from ._process_common import getoutput, arg_split
27 from IPython.utils import py3compat
27 from IPython.utils import py3compat
28 from IPython.utils.encoding import DEFAULT_ENCODING
28 from IPython.utils.encoding import DEFAULT_ENCODING
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Function definitions
31 # Function definitions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 def _find_cmd(cmd):
34 def _find_cmd(cmd):
35 """Find the full path to a command using which."""
35 """Find the full path to a command using which."""
36
36
37 path = sp.Popen(['/usr/bin/env', 'which', cmd],
37 path = sp.Popen(['/usr/bin/env', 'which', cmd],
38 stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
38 stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
39 return py3compat.decode(path)
39 return py3compat.decode(path)
40
40
41
41
42 class ProcessHandler(object):
42 class ProcessHandler(object):
43 """Execute subprocesses under the control of pexpect.
43 """Execute subprocesses under the control of pexpect.
44 """
44 """
45 # Timeout in seconds to wait on each reading of the subprocess' output.
45 # Timeout in seconds to wait on each reading of the subprocess' output.
46 # This should not be set too low to avoid cpu overusage from our side,
46 # This should not be set too low to avoid cpu overusage from our side,
47 # since we read in a loop whose period is controlled by this timeout.
47 # since we read in a loop whose period is controlled by this timeout.
48 read_timeout = 0.05
48 read_timeout = 0.05
49
49
50 # Timeout to give a process if we receive SIGINT, between sending the
50 # Timeout to give a process if we receive SIGINT, between sending the
51 # SIGINT to the process and forcefully terminating it.
51 # SIGINT to the process and forcefully terminating it.
52 terminate_timeout = 0.2
52 terminate_timeout = 0.2
53
53
54 # File object where stdout and stderr of the subprocess will be written
54 # File object where stdout and stderr of the subprocess will be written
55 logfile = None
55 logfile = None
56
56
57 # Shell to call for subprocesses to execute
57 # Shell to call for subprocesses to execute
58 _sh = None
58 _sh = None
59
59
60 @property
60 @property
61 def sh(self):
61 def sh(self):
62 if self._sh is None:
62 if self._sh is None:
63 shell_name = os.environ.get("SHELL", "sh")
63 shell_name = os.environ.get("SHELL", "sh")
64 self._sh = pexpect.which(shell_name)
64 self._sh = pexpect.which(shell_name)
65 if self._sh is None:
65 if self._sh is None:
66 raise OSError('"{}" shell not found'.format(shell_name))
66 raise OSError('"{}" shell not found'.format(shell_name))
67
67
68 return self._sh
68 return self._sh
69
69
70 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
70 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
71 """Arguments are used for pexpect calls."""
71 """Arguments are used for pexpect calls."""
72 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
72 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
73 None else read_timeout)
73 None else read_timeout)
74 self.terminate_timeout = (ProcessHandler.terminate_timeout if
74 self.terminate_timeout = (ProcessHandler.terminate_timeout if
75 terminate_timeout is None else
75 terminate_timeout is None else
76 terminate_timeout)
76 terminate_timeout)
77 self.logfile = sys.stdout if logfile is None else logfile
77 self.logfile = sys.stdout if logfile is None else logfile
78
78
79 def getoutput(self, cmd):
79 def getoutput(self, cmd):
80 """Run a command and return its stdout/stderr as a string.
80 """Run a command and return its stdout/stderr as a string.
81
81
82 Parameters
82 Parameters
83 ----------
83 ----------
84 cmd : str
84 cmd : str
85 A command to be executed in the system shell.
85 A command to be executed in the system shell.
86
86
87 Returns
87 Returns
88 -------
88 -------
89 output : str
89 output : str
90 A string containing the combination of stdout and stderr from the
90 A string containing the combination of stdout and stderr from the
91 subprocess, in whatever order the subprocess originally wrote to its
91 subprocess, in whatever order the subprocess originally wrote to its
92 file descriptors (so the order of the information in this string is the
92 file descriptors (so the order of the information in this string is the
93 correct order as would be seen if running the command in a terminal).
93 correct order as would be seen if running the command in a terminal).
94 """
94 """
95 try:
95 try:
96 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
96 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
97 except KeyboardInterrupt:
97 except KeyboardInterrupt:
98 print('^C', file=sys.stderr, end='')
98 print('^C', file=sys.stderr, end='')
99
99
100 def getoutput_pexpect(self, cmd):
100 def getoutput_pexpect(self, cmd):
101 """Run a command and return its stdout/stderr as a string.
101 """Run a command and return its stdout/stderr as a string.
102
102
103 Parameters
103 Parameters
104 ----------
104 ----------
105 cmd : str
105 cmd : str
106 A command to be executed in the system shell.
106 A command to be executed in the system shell.
107
107
108 Returns
108 Returns
109 -------
109 -------
110 output : str
110 output : str
111 A string containing the combination of stdout and stderr from the
111 A string containing the combination of stdout and stderr from the
112 subprocess, in whatever order the subprocess originally wrote to its
112 subprocess, in whatever order the subprocess originally wrote to its
113 file descriptors (so the order of the information in this string is the
113 file descriptors (so the order of the information in this string is the
114 correct order as would be seen if running the command in a terminal).
114 correct order as would be seen if running the command in a terminal).
115 """
115 """
116 try:
116 try:
117 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
117 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
118 except KeyboardInterrupt:
118 except KeyboardInterrupt:
119 print('^C', file=sys.stderr, end='')
119 print('^C', file=sys.stderr, end='')
120
120
121 def system(self, cmd):
121 def system(self, cmd):
122 """Execute a command in a subshell.
122 """Execute a command in a subshell.
123
123
124 Parameters
124 Parameters
125 ----------
125 ----------
126 cmd : str
126 cmd : str
127 A command to be executed in the system shell.
127 A command to be executed in the system shell.
128
128
129 Returns
129 Returns
130 -------
130 -------
131 int : child's exitstatus
131 int : child's exitstatus
132 """
132 """
133 # Get likely encoding for the output.
133 # Get likely encoding for the output.
134 enc = DEFAULT_ENCODING
134 enc = DEFAULT_ENCODING
135
135
136 # Patterns to match on the output, for pexpect. We read input and
136 # Patterns to match on the output, for pexpect. We read input and
137 # allow either a short timeout or EOF
137 # allow either a short timeout or EOF
138 patterns = [pexpect.TIMEOUT, pexpect.EOF]
138 patterns = [pexpect.TIMEOUT, pexpect.EOF]
139 # the index of the EOF pattern in the list.
139 # the index of the EOF pattern in the list.
140 # even though we know it's 1, this call means we don't have to worry if
140 # even though we know it's 1, this call means we don't have to worry if
141 # we change the above list, and forget to change this value:
141 # we change the above list, and forget to change this value:
142 EOF_index = patterns.index(pexpect.EOF)
142 EOF_index = patterns.index(pexpect.EOF)
143 # The size of the output stored so far in the process output buffer.
143 # The size of the output stored so far in the process output buffer.
144 # Since pexpect only appends to this buffer, each time we print we
144 # Since pexpect only appends to this buffer, each time we print we
145 # record how far we've printed, so that next time we only print *new*
145 # record how far we've printed, so that next time we only print *new*
146 # content from the buffer.
146 # content from the buffer.
147 out_size = 0
147 out_size = 0
148 try:
148 try:
149 # Since we're not really searching the buffer for text patterns, we
149 # Since we're not really searching the buffer for text patterns, we
150 # can set pexpect's search window to be tiny and it won't matter.
150 # can set pexpect's search window to be tiny and it won't matter.
151 # We only search for the 'patterns' timeout or EOF, which aren't in
151 # We only search for the 'patterns' timeout or EOF, which aren't in
152 # the text itself.
152 # the text itself.
153 #child = pexpect.spawn(pcmd, searchwindowsize=1)
153 #child = pexpect.spawn(pcmd, searchwindowsize=1)
154 if hasattr(pexpect, 'spawnb'):
154 if hasattr(pexpect, 'spawnb'):
155 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
155 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
156 else:
156 else:
157 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
157 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
158 flush = sys.stdout.flush
158 flush = sys.stdout.flush
159 while True:
159 while True:
160 # res is the index of the pattern that caused the match, so we
160 # res is the index of the pattern that caused the match, so we
161 # know whether we've finished (if we matched EOF) or not
161 # know whether we've finished (if we matched EOF) or not
162 res_idx = child.expect_list(patterns, self.read_timeout)
162 res_idx = child.expect_list(patterns, self.read_timeout)
163 print(child.before[out_size:].decode(enc, 'replace'), end='')
163 print(child.before[out_size:].decode(enc, 'replace'), end='')
164 flush()
164 flush()
165 if res_idx==EOF_index:
165 if res_idx==EOF_index:
166 break
166 break
167 # Update the pointer to what we've already printed
167 # Update the pointer to what we've already printed
168 out_size = len(child.before)
168 out_size = len(child.before)
169 except KeyboardInterrupt:
169 except KeyboardInterrupt:
170 # We need to send ^C to the process. The ascii code for '^C' is 3
170 # We need to send ^C to the process. The ascii code for '^C' is 3
171 # (the character is known as ETX for 'End of Text', see
171 # (the character is known as ETX for 'End of Text', see
172 # curses.ascii.ETX).
172 # curses.ascii.ETX).
173 child.sendline(chr(3))
173 child.sendline(chr(3))
174 # Read and print any more output the program might produce on its
174 # Read and print any more output the program might produce on its
175 # way out.
175 # way out.
176 try:
176 try:
177 out_size = len(child.before)
177 out_size = len(child.before)
178 child.expect_list(patterns, self.terminate_timeout)
178 child.expect_list(patterns, self.terminate_timeout)
179 print(child.before[out_size:].decode(enc, 'replace'), end='')
179 print(child.before[out_size:].decode(enc, 'replace'), end='')
180 sys.stdout.flush()
180 sys.stdout.flush()
181 except KeyboardInterrupt:
181 except KeyboardInterrupt:
182 # Impatient users tend to type it multiple times
182 # Impatient users tend to type it multiple times
183 pass
183 pass
184 finally:
184 finally:
185 # Ensure the subprocess really is terminated
185 # Ensure the subprocess really is terminated
186 child.terminate(force=True)
186 child.terminate(force=True)
187 # add isalive check, to ensure exitstatus is set:
187 # add isalive check, to ensure exitstatus is set:
188 child.isalive()
188 child.isalive()
189
189
190 # We follow the subprocess pattern, returning either the exit status
190 # We follow the subprocess pattern, returning either the exit status
191 # as a positive number, or the terminating signal as a negative
191 # as a positive number, or the terminating signal as a negative
192 # number.
192 # number.
193 # on Linux, sh returns 128+n for signals terminating child processes on Linux
193 # on Linux, sh returns 128+n for signals terminating child processes on Linux
194 # on BSD (OS X), the signal code is set instead
194 # on BSD (OS X), the signal code is set instead
195 if child.exitstatus is None:
195 if child.exitstatus is None:
196 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
196 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
197 if child.signalstatus is None:
197 if child.signalstatus is None:
198 # this condition may never occur,
198 # this condition may never occur,
199 # but let's be certain we always return an integer.
199 # but let's be certain we always return an integer.
200 return 0
200 return 0
201 return -child.signalstatus
201 return -child.signalstatus
202 if child.exitstatus > 128:
202 if child.exitstatus > 128:
203 return -(child.exitstatus - 128)
203 return -(child.exitstatus - 128)
204 return child.exitstatus
204 return child.exitstatus
205
205
206
206
207 # Make system() with a functional interface for outside use. Note that we use
207 # Make system() with a functional interface for outside use. Note that we use
208 # getoutput() from the _common utils, which is built on top of popen(). Using
208 # getoutput() from the _common utils, which is built on top of popen(). Using
209 # pexpect to get subprocess output produces difficult to parse output, since
209 # pexpect to get subprocess output produces difficult to parse output, since
210 # programs think they are talking to a tty and produce highly formatted output
210 # programs think they are talking to a tty and produce highly formatted output
211 # (ls is a good example) that makes them hard.
211 # (ls is a good example) that makes them hard.
212 system = ProcessHandler().system
212 system = ProcessHandler().system
213
213
214 def check_pid(pid):
214 def check_pid(pid):
215 try:
215 try:
216 os.kill(pid, 0)
216 os.kill(pid, 0)
217 except OSError as err:
217 except OSError as err:
218 if err.errno == errno.ESRCH:
218 if err.errno == errno.ESRCH:
219 return False
219 return False
220 elif err.errno == errno.EPERM:
220 elif err.errno == errno.EPERM:
221 # Don't have permission to signal the process - probably means it exists
221 # Don't have permission to signal the process - probably means it exists
222 return True
222 return True
223 raise
223 raise
224 else:
224 else:
225 return True
225 return True
@@ -1,205 +1,205 b''
1 """Windows-specific implementation of process utilities.
1 """Windows-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 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 # stdlib
17 # stdlib
18 import os
18 import os
19 import sys
19 import sys
20 import ctypes
20 import ctypes
21 import time
21 import time
22
22
23 from ctypes import c_int, POINTER
23 from ctypes import c_int, POINTER
24 from ctypes.wintypes import LPCWSTR, HLOCAL
24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 from subprocess import STDOUT, TimeoutExpired
25 from subprocess import STDOUT, TimeoutExpired
26 from threading import Thread
26 from threading import Thread
27
27
28 # our own imports
28 # our own imports
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 from . import py3compat
30 from . import py3compat
31 from .encoding import DEFAULT_ENCODING
31 from .encoding import DEFAULT_ENCODING
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Function definitions
34 # Function definitions
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class AvoidUNCPath(object):
37 class AvoidUNCPath(object):
38 """A context manager to protect command execution from UNC paths.
38 """A context manager to protect command execution from UNC paths.
39
39
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 This context manager temporarily changes directory to the 'C:' drive on
41 This context manager temporarily changes directory to the 'C:' drive on
42 entering, and restores the original working directory on exit.
42 entering, and restores the original working directory on exit.
43
43
44 The context manager returns the starting working directory *if* it made a
44 The context manager returns the starting working directory *if* it made a
45 change and None otherwise, so that users can apply the necessary adjustment
45 change and None otherwise, so that users can apply the necessary adjustment
46 to their system calls in the event of a change.
46 to their system calls in the event of a change.
47
47
48 Examples
48 Examples
49 --------
49 --------
50 ::
50 ::
51 cmd = 'dir'
51 cmd = 'dir'
52 with AvoidUNCPath() as path:
52 with AvoidUNCPath() as path:
53 if path is not None:
53 if path is not None:
54 cmd = '"pushd %s &&"%s' % (path, cmd)
54 cmd = '"pushd %s &&"%s' % (path, cmd)
55 os.system(cmd)
55 os.system(cmd)
56 """
56 """
57 def __enter__(self):
57 def __enter__(self):
58 self.path = os.getcwd()
58 self.path = os.getcwd()
59 self.is_unc_path = self.path.startswith(r"\\")
59 self.is_unc_path = self.path.startswith(r"\\")
60 if self.is_unc_path:
60 if self.is_unc_path:
61 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 # change to c drive (as cmd.exe cannot handle UNC addresses)
62 os.chdir("C:")
62 os.chdir("C:")
63 return self.path
63 return self.path
64 else:
64 else:
65 # We return None to signal that there was no change in the working
65 # We return None to signal that there was no change in the working
66 # directory
66 # directory
67 return None
67 return None
68
68
69 def __exit__(self, exc_type, exc_value, traceback):
69 def __exit__(self, exc_type, exc_value, traceback):
70 if self.is_unc_path:
70 if self.is_unc_path:
71 os.chdir(self.path)
71 os.chdir(self.path)
72
72
73
73
74 def _find_cmd(cmd):
74 def _find_cmd(cmd):
75 """Find the full path to a .bat or .exe using the win32api module."""
75 """Find the full path to a .bat or .exe using the win32api module."""
76 try:
76 try:
77 from win32api import SearchPath
77 from win32api import SearchPath
78 except ImportError as e:
78 except ImportError as e:
79 raise ImportError('you need to have pywin32 installed for this to work') from e
79 raise ImportError('you need to have pywin32 installed for this to work') from e
80 else:
80 else:
81 PATH = os.environ['PATH']
81 PATH = os.environ['PATH']
82 extensions = ['.exe', '.com', '.bat', '.py']
82 extensions = ['.exe', '.com', '.bat', '.py']
83 path = None
83 path = None
84 for ext in extensions:
84 for ext in extensions:
85 try:
85 try:
86 path = SearchPath(PATH, cmd, ext)[0]
86 path = SearchPath(PATH, cmd, ext)[0]
87 except:
87 except:
88 pass
88 pass
89 if path is None:
89 if path is None:
90 raise OSError("command %r not found" % cmd)
90 raise OSError("command %r not found" % cmd)
91 else:
91 else:
92 return path
92 return path
93
93
94
94
95 def _system_body(p):
95 def _system_body(p):
96 """Callback for _system."""
96 """Callback for _system."""
97 enc = DEFAULT_ENCODING
97 enc = DEFAULT_ENCODING
98
98
99 def stdout_read():
99 def stdout_read():
100 for line in read_no_interrupt(p.stdout).splitlines():
100 for line in read_no_interrupt(p.stdout).splitlines():
101 line = line.decode(enc, 'replace')
101 line = line.decode(enc, 'replace')
102 print(line, file=sys.stdout)
102 print(line, file=sys.stdout)
103
103
104 def stderr_read():
104 def stderr_read():
105 for line in read_no_interrupt(p.stderr).splitlines():
105 for line in read_no_interrupt(p.stderr).splitlines():
106 line = line.decode(enc, 'replace')
106 line = line.decode(enc, 'replace')
107 print(line, file=sys.stderr)
107 print(line, file=sys.stderr)
108
108
109 Thread(target=stdout_read).start()
109 Thread(target=stdout_read).start()
110 Thread(target=stderr_read).start()
110 Thread(target=stderr_read).start()
111
111
112 # Wait to finish for returncode. Unfortunately, Python has a bug where
112 # Wait to finish for returncode. Unfortunately, Python has a bug where
113 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
113 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
114 # a loop instead of just doing `return p.wait()`.
114 # a loop instead of just doing `return p.wait()`.
115 while True:
115 while True:
116 result = p.poll()
116 result = p.poll()
117 if result is None:
117 if result is None:
118 time.sleep(0.01)
118 time.sleep(0.01)
119 else:
119 else:
120 return result
120 return result
121
121
122
122
123 def system(cmd):
123 def system(cmd):
124 """Win32 version of os.system() that works with network shares.
124 """Win32 version of os.system() that works with network shares.
125
125
126 Note that this implementation returns None, as meant for use in IPython.
126 Note that this implementation returns None, as meant for use in IPython.
127
127
128 Parameters
128 Parameters
129 ----------
129 ----------
130 cmd : str or list
130 cmd : str or list
131 A command to be executed in the system shell.
131 A command to be executed in the system shell.
132
132
133 Returns
133 Returns
134 -------
134 -------
135 int : child process' exit code.
135 int : child process' exit code.
136 """
136 """
137 # The controller provides interactivity with both
137 # The controller provides interactivity with both
138 # stdin and stdout
138 # stdin and stdout
139 #import _process_win32_controller
139 #import _process_win32_controller
140 #_process_win32_controller.system(cmd)
140 #_process_win32_controller.system(cmd)
141
141
142 with AvoidUNCPath() as path:
142 with AvoidUNCPath() as path:
143 if path is not None:
143 if path is not None:
144 cmd = '"pushd %s &&"%s' % (path, cmd)
144 cmd = '"pushd %s &&"%s' % (path, cmd)
145 return process_handler(cmd, _system_body)
145 return process_handler(cmd, _system_body)
146
146
147 def getoutput(cmd):
147 def getoutput(cmd):
148 """Return standard output of executing cmd in a shell.
148 """Return standard output of executing cmd in a shell.
149
149
150 Accepts the same arguments as os.system().
150 Accepts the same arguments as os.system().
151
151
152 Parameters
152 Parameters
153 ----------
153 ----------
154 cmd : str or list
154 cmd : str or list
155 A command to be executed in the system shell.
155 A command to be executed in the system shell.
156
156
157 Returns
157 Returns
158 -------
158 -------
159 stdout : str
159 stdout : str
160 """
160 """
161
161
162 with AvoidUNCPath() as path:
162 with AvoidUNCPath() as path:
163 if path is not None:
163 if path is not None:
164 cmd = '"pushd %s &&"%s' % (path, cmd)
164 cmd = '"pushd %s &&"%s' % (path, cmd)
165 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
165 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
166
166
167 if out is None:
167 if out is None:
168 out = b''
168 out = b''
169 return py3compat.decode(out)
169 return py3compat.decode(out)
170
170
171 try:
171 try:
172 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
172 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
173 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
173 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
174 CommandLineToArgvW.restype = POINTER(LPCWSTR)
174 CommandLineToArgvW.restype = POINTER(LPCWSTR)
175 LocalFree = ctypes.windll.kernel32.LocalFree
175 LocalFree = ctypes.windll.kernel32.LocalFree
176 LocalFree.res_type = HLOCAL
176 LocalFree.res_type = HLOCAL
177 LocalFree.arg_types = [HLOCAL]
177 LocalFree.arg_types = [HLOCAL]
178
178
179 def arg_split(commandline, posix=False, strict=True):
179 def arg_split(commandline, posix=False, strict=True):
180 """Split a command line's arguments in a shell-like manner.
180 """Split a command line's arguments in a shell-like manner.
181
181
182 This is a special version for windows that use a ctypes call to CommandLineToArgvW
182 This is a special version for windows that use a ctypes call to CommandLineToArgvW
183 to do the argv splitting. The posix parameter is ignored.
183 to do the argv splitting. The posix parameter is ignored.
184
184
185 If strict=False, process_common.arg_split(...strict=False) is used instead.
185 If strict=False, process_common.arg_split(...strict=False) is used instead.
186 """
186 """
187 #CommandLineToArgvW returns path to executable if called with empty string.
187 #CommandLineToArgvW returns path to executable if called with empty string.
188 if commandline.strip() == "":
188 if commandline.strip() == "":
189 return []
189 return []
190 if not strict:
190 if not strict:
191 # not really a cl-arg, fallback on _process_common
191 # not really a cl-arg, fallback on _process_common
192 return py_arg_split(commandline, posix=posix, strict=strict)
192 return py_arg_split(commandline, posix=posix, strict=strict)
193 argvn = c_int()
193 argvn = c_int()
194 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
194 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
195 result_array_type = LPCWSTR * argvn.value
195 result_array_type = LPCWSTR * argvn.value
196 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
196 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
197 retval = LocalFree(result_pointer)
197 retval = LocalFree(result_pointer)
198 return result
198 return result
199 except AttributeError:
199 except AttributeError:
200 arg_split = py_arg_split
200 arg_split = py_arg_split
201
201
202 def check_pid(pid):
202 def check_pid(pid):
203 # OpenProcess returns 0 if no such process (of ours) exists
203 # OpenProcess returns 0 if no such process (of ours) exists
204 # positive int otherwise
204 # positive int otherwise
205 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
205 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
@@ -1,573 +1,573 b''
1 """Windows-specific implementation of process utilities with direct WinAPI.
1 """Windows-specific implementation of process utilities with direct WinAPI.
2
2
3 This file is meant to be used by process.py
3 This file is meant to be used by process.py
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 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 # stdlib
14 # stdlib
15 import os, sys, threading
15 import os, sys, threading
16 import ctypes, msvcrt
16 import ctypes, msvcrt
17
17
18 # Win32 API types needed for the API calls
18 # Win32 API types needed for the API calls
19 from ctypes import POINTER
19 from ctypes import POINTER
20 from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \
20 from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \
21 ULONG, LPCWSTR
21 ULONG, LPCWSTR
22 LPDWORD = POINTER(DWORD)
22 LPDWORD = POINTER(DWORD)
23 LPHANDLE = POINTER(HANDLE)
23 LPHANDLE = POINTER(HANDLE)
24 ULONG_PTR = POINTER(ULONG)
24 ULONG_PTR = POINTER(ULONG)
25 class SECURITY_ATTRIBUTES(ctypes.Structure):
25 class SECURITY_ATTRIBUTES(ctypes.Structure):
26 _fields_ = [("nLength", DWORD),
26 _fields_ = [("nLength", DWORD),
27 ("lpSecurityDescriptor", LPVOID),
27 ("lpSecurityDescriptor", LPVOID),
28 ("bInheritHandle", BOOL)]
28 ("bInheritHandle", BOOL)]
29 LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
29 LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
30 class STARTUPINFO(ctypes.Structure):
30 class STARTUPINFO(ctypes.Structure):
31 _fields_ = [("cb", DWORD),
31 _fields_ = [("cb", DWORD),
32 ("lpReserved", LPCWSTR),
32 ("lpReserved", LPCWSTR),
33 ("lpDesktop", LPCWSTR),
33 ("lpDesktop", LPCWSTR),
34 ("lpTitle", LPCWSTR),
34 ("lpTitle", LPCWSTR),
35 ("dwX", DWORD),
35 ("dwX", DWORD),
36 ("dwY", DWORD),
36 ("dwY", DWORD),
37 ("dwXSize", DWORD),
37 ("dwXSize", DWORD),
38 ("dwYSize", DWORD),
38 ("dwYSize", DWORD),
39 ("dwXCountChars", DWORD),
39 ("dwXCountChars", DWORD),
40 ("dwYCountChars", DWORD),
40 ("dwYCountChars", DWORD),
41 ("dwFillAttribute", DWORD),
41 ("dwFillAttribute", DWORD),
42 ("dwFlags", DWORD),
42 ("dwFlags", DWORD),
43 ("wShowWindow", WORD),
43 ("wShowWindow", WORD),
44 ("cbReserved2", WORD),
44 ("cbReserved2", WORD),
45 ("lpReserved2", LPVOID),
45 ("lpReserved2", LPVOID),
46 ("hStdInput", HANDLE),
46 ("hStdInput", HANDLE),
47 ("hStdOutput", HANDLE),
47 ("hStdOutput", HANDLE),
48 ("hStdError", HANDLE)]
48 ("hStdError", HANDLE)]
49 LPSTARTUPINFO = POINTER(STARTUPINFO)
49 LPSTARTUPINFO = POINTER(STARTUPINFO)
50 class PROCESS_INFORMATION(ctypes.Structure):
50 class PROCESS_INFORMATION(ctypes.Structure):
51 _fields_ = [("hProcess", HANDLE),
51 _fields_ = [("hProcess", HANDLE),
52 ("hThread", HANDLE),
52 ("hThread", HANDLE),
53 ("dwProcessId", DWORD),
53 ("dwProcessId", DWORD),
54 ("dwThreadId", DWORD)]
54 ("dwThreadId", DWORD)]
55 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
55 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
56
56
57 # Win32 API constants needed
57 # Win32 API constants needed
58 ERROR_HANDLE_EOF = 38
58 ERROR_HANDLE_EOF = 38
59 ERROR_BROKEN_PIPE = 109
59 ERROR_BROKEN_PIPE = 109
60 ERROR_NO_DATA = 232
60 ERROR_NO_DATA = 232
61 HANDLE_FLAG_INHERIT = 0x0001
61 HANDLE_FLAG_INHERIT = 0x0001
62 STARTF_USESTDHANDLES = 0x0100
62 STARTF_USESTDHANDLES = 0x0100
63 CREATE_SUSPENDED = 0x0004
63 CREATE_SUSPENDED = 0x0004
64 CREATE_NEW_CONSOLE = 0x0010
64 CREATE_NEW_CONSOLE = 0x0010
65 CREATE_NO_WINDOW = 0x08000000
65 CREATE_NO_WINDOW = 0x08000000
66 STILL_ACTIVE = 259
66 STILL_ACTIVE = 259
67 WAIT_TIMEOUT = 0x0102
67 WAIT_TIMEOUT = 0x0102
68 WAIT_FAILED = 0xFFFFFFFF
68 WAIT_FAILED = 0xFFFFFFFF
69 INFINITE = 0xFFFFFFFF
69 INFINITE = 0xFFFFFFFF
70 DUPLICATE_SAME_ACCESS = 0x00000002
70 DUPLICATE_SAME_ACCESS = 0x00000002
71 ENABLE_ECHO_INPUT = 0x0004
71 ENABLE_ECHO_INPUT = 0x0004
72 ENABLE_LINE_INPUT = 0x0002
72 ENABLE_LINE_INPUT = 0x0002
73 ENABLE_PROCESSED_INPUT = 0x0001
73 ENABLE_PROCESSED_INPUT = 0x0001
74
74
75 # Win32 API functions needed
75 # Win32 API functions needed
76 GetLastError = ctypes.windll.kernel32.GetLastError
76 GetLastError = ctypes.windll.kernel32.GetLastError
77 GetLastError.argtypes = []
77 GetLastError.argtypes = []
78 GetLastError.restype = DWORD
78 GetLastError.restype = DWORD
79
79
80 CreateFile = ctypes.windll.kernel32.CreateFileW
80 CreateFile = ctypes.windll.kernel32.CreateFileW
81 CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE]
81 CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE]
82 CreateFile.restype = HANDLE
82 CreateFile.restype = HANDLE
83
83
84 CreatePipe = ctypes.windll.kernel32.CreatePipe
84 CreatePipe = ctypes.windll.kernel32.CreatePipe
85 CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE),
85 CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE),
86 LPSECURITY_ATTRIBUTES, DWORD]
86 LPSECURITY_ATTRIBUTES, DWORD]
87 CreatePipe.restype = BOOL
87 CreatePipe.restype = BOOL
88
88
89 CreateProcess = ctypes.windll.kernel32.CreateProcessW
89 CreateProcess = ctypes.windll.kernel32.CreateProcessW
90 CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES,
90 CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES,
91 LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO,
91 LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO,
92 LPPROCESS_INFORMATION]
92 LPPROCESS_INFORMATION]
93 CreateProcess.restype = BOOL
93 CreateProcess.restype = BOOL
94
94
95 GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess
95 GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess
96 GetExitCodeProcess.argtypes = [HANDLE, LPDWORD]
96 GetExitCodeProcess.argtypes = [HANDLE, LPDWORD]
97 GetExitCodeProcess.restype = BOOL
97 GetExitCodeProcess.restype = BOOL
98
98
99 GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
99 GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
100 GetCurrentProcess.argtypes = []
100 GetCurrentProcess.argtypes = []
101 GetCurrentProcess.restype = HANDLE
101 GetCurrentProcess.restype = HANDLE
102
102
103 ResumeThread = ctypes.windll.kernel32.ResumeThread
103 ResumeThread = ctypes.windll.kernel32.ResumeThread
104 ResumeThread.argtypes = [HANDLE]
104 ResumeThread.argtypes = [HANDLE]
105 ResumeThread.restype = DWORD
105 ResumeThread.restype = DWORD
106
106
107 ReadFile = ctypes.windll.kernel32.ReadFile
107 ReadFile = ctypes.windll.kernel32.ReadFile
108 ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
108 ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
109 ReadFile.restype = BOOL
109 ReadFile.restype = BOOL
110
110
111 WriteFile = ctypes.windll.kernel32.WriteFile
111 WriteFile = ctypes.windll.kernel32.WriteFile
112 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
112 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
113 WriteFile.restype = BOOL
113 WriteFile.restype = BOOL
114
114
115 GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
115 GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
116 GetConsoleMode.argtypes = [HANDLE, LPDWORD]
116 GetConsoleMode.argtypes = [HANDLE, LPDWORD]
117 GetConsoleMode.restype = BOOL
117 GetConsoleMode.restype = BOOL
118
118
119 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
119 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
120 SetConsoleMode.argtypes = [HANDLE, DWORD]
120 SetConsoleMode.argtypes = [HANDLE, DWORD]
121 SetConsoleMode.restype = BOOL
121 SetConsoleMode.restype = BOOL
122
122
123 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
123 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
124 FlushConsoleInputBuffer.argtypes = [HANDLE]
124 FlushConsoleInputBuffer.argtypes = [HANDLE]
125 FlushConsoleInputBuffer.restype = BOOL
125 FlushConsoleInputBuffer.restype = BOOL
126
126
127 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
127 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
128 WaitForSingleObject.argtypes = [HANDLE, DWORD]
128 WaitForSingleObject.argtypes = [HANDLE, DWORD]
129 WaitForSingleObject.restype = DWORD
129 WaitForSingleObject.restype = DWORD
130
130
131 DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle
131 DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle
132 DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE,
132 DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE,
133 DWORD, BOOL, DWORD]
133 DWORD, BOOL, DWORD]
134 DuplicateHandle.restype = BOOL
134 DuplicateHandle.restype = BOOL
135
135
136 SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
136 SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
137 SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD]
137 SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD]
138 SetHandleInformation.restype = BOOL
138 SetHandleInformation.restype = BOOL
139
139
140 CloseHandle = ctypes.windll.kernel32.CloseHandle
140 CloseHandle = ctypes.windll.kernel32.CloseHandle
141 CloseHandle.argtypes = [HANDLE]
141 CloseHandle.argtypes = [HANDLE]
142 CloseHandle.restype = BOOL
142 CloseHandle.restype = BOOL
143
143
144 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
144 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
145 CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)]
145 CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)]
146 CommandLineToArgvW.restype = POINTER(LPCWSTR)
146 CommandLineToArgvW.restype = POINTER(LPCWSTR)
147
147
148 LocalFree = ctypes.windll.kernel32.LocalFree
148 LocalFree = ctypes.windll.kernel32.LocalFree
149 LocalFree.argtypes = [HLOCAL]
149 LocalFree.argtypes = [HLOCAL]
150 LocalFree.restype = HLOCAL
150 LocalFree.restype = HLOCAL
151
151
152 class AvoidUNCPath(object):
152 class AvoidUNCPath(object):
153 """A context manager to protect command execution from UNC paths.
153 """A context manager to protect command execution from UNC paths.
154
154
155 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
155 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
156 This context manager temporarily changes directory to the 'C:' drive on
156 This context manager temporarily changes directory to the 'C:' drive on
157 entering, and restores the original working directory on exit.
157 entering, and restores the original working directory on exit.
158
158
159 The context manager returns the starting working directory *if* it made a
159 The context manager returns the starting working directory *if* it made a
160 change and None otherwise, so that users can apply the necessary adjustment
160 change and None otherwise, so that users can apply the necessary adjustment
161 to their system calls in the event of a change.
161 to their system calls in the event of a change.
162
162
163 Examples
163 Examples
164 --------
164 --------
165 ::
165 ::
166 cmd = 'dir'
166 cmd = 'dir'
167 with AvoidUNCPath() as path:
167 with AvoidUNCPath() as path:
168 if path is not None:
168 if path is not None:
169 cmd = '"pushd %s &&"%s' % (path, cmd)
169 cmd = '"pushd %s &&"%s' % (path, cmd)
170 os.system(cmd)
170 os.system(cmd)
171 """
171 """
172 def __enter__(self):
172 def __enter__(self):
173 self.path = os.getcwd()
173 self.path = os.getcwd()
174 self.is_unc_path = self.path.startswith(r"\\")
174 self.is_unc_path = self.path.startswith(r"\\")
175 if self.is_unc_path:
175 if self.is_unc_path:
176 # change to c drive (as cmd.exe cannot handle UNC addresses)
176 # change to c drive (as cmd.exe cannot handle UNC addresses)
177 os.chdir("C:")
177 os.chdir("C:")
178 return self.path
178 return self.path
179 else:
179 else:
180 # We return None to signal that there was no change in the working
180 # We return None to signal that there was no change in the working
181 # directory
181 # directory
182 return None
182 return None
183
183
184 def __exit__(self, exc_type, exc_value, traceback):
184 def __exit__(self, exc_type, exc_value, traceback):
185 if self.is_unc_path:
185 if self.is_unc_path:
186 os.chdir(self.path)
186 os.chdir(self.path)
187
187
188
188
189 class Win32ShellCommandController(object):
189 class Win32ShellCommandController(object):
190 """Runs a shell command in a 'with' context.
190 """Runs a shell command in a 'with' context.
191
191
192 This implementation is Win32-specific.
192 This implementation is Win32-specific.
193
193
194 Example:
194 Example:
195 # Runs the command interactively with default console stdin/stdout
195 # Runs the command interactively with default console stdin/stdout
196 with ShellCommandController('python -i') as scc:
196 with ShellCommandController('python -i') as scc:
197 scc.run()
197 scc.run()
198
198
199 # Runs the command using the provided functions for stdin/stdout
199 # Runs the command using the provided functions for stdin/stdout
200 def my_stdout_func(s):
200 def my_stdout_func(s):
201 # print or save the string 's'
201 # print or save the string 's'
202 write_to_stdout(s)
202 write_to_stdout(s)
203 def my_stdin_func():
203 def my_stdin_func():
204 # If input is available, return it as a string.
204 # If input is available, return it as a string.
205 if input_available():
205 if input_available():
206 return get_input()
206 return get_input()
207 # If no input available, return None after a short delay to
207 # If no input available, return None after a short delay to
208 # keep from blocking.
208 # keep from blocking.
209 else:
209 else:
210 time.sleep(0.01)
210 time.sleep(0.01)
211 return None
211 return None
212
212
213 with ShellCommandController('python -i') as scc:
213 with ShellCommandController('python -i') as scc:
214 scc.run(my_stdout_func, my_stdin_func)
214 scc.run(my_stdout_func, my_stdin_func)
215 """
215 """
216
216
217 def __init__(self, cmd, mergeout = True):
217 def __init__(self, cmd, mergeout = True):
218 """Initializes the shell command controller.
218 """Initializes the shell command controller.
219
219
220 The cmd is the program to execute, and mergeout is
220 The cmd is the program to execute, and mergeout is
221 whether to blend stdout and stderr into one output
221 whether to blend stdout and stderr into one output
222 in stdout. Merging them together in this fashion more
222 in stdout. Merging them together in this fashion more
223 reliably keeps stdout and stderr in the correct order
223 reliably keeps stdout and stderr in the correct order
224 especially for interactive shell usage.
224 especially for interactive shell usage.
225 """
225 """
226 self.cmd = cmd
226 self.cmd = cmd
227 self.mergeout = mergeout
227 self.mergeout = mergeout
228
228
229 def __enter__(self):
229 def __enter__(self):
230 cmd = self.cmd
230 cmd = self.cmd
231 mergeout = self.mergeout
231 mergeout = self.mergeout
232
232
233 self.hstdout, self.hstdin, self.hstderr = None, None, None
233 self.hstdout, self.hstdin, self.hstderr = None, None, None
234 self.piProcInfo = None
234 self.piProcInfo = None
235 try:
235 try:
236 p_hstdout, c_hstdout, p_hstderr, \
236 p_hstdout, c_hstdout, p_hstderr, \
237 c_hstderr, p_hstdin, c_hstdin = [None]*6
237 c_hstderr, p_hstdin, c_hstdin = [None]*6
238
238
239 # SECURITY_ATTRIBUTES with inherit handle set to True
239 # SECURITY_ATTRIBUTES with inherit handle set to True
240 saAttr = SECURITY_ATTRIBUTES()
240 saAttr = SECURITY_ATTRIBUTES()
241 saAttr.nLength = ctypes.sizeof(saAttr)
241 saAttr.nLength = ctypes.sizeof(saAttr)
242 saAttr.bInheritHandle = True
242 saAttr.bInheritHandle = True
243 saAttr.lpSecurityDescriptor = None
243 saAttr.lpSecurityDescriptor = None
244
244
245 def create_pipe(uninherit):
245 def create_pipe(uninherit):
246 """Creates a Windows pipe, which consists of two handles.
246 """Creates a Windows pipe, which consists of two handles.
247
247
248 The 'uninherit' parameter controls which handle is not
248 The 'uninherit' parameter controls which handle is not
249 inherited by the child process.
249 inherited by the child process.
250 """
250 """
251 handles = HANDLE(), HANDLE()
251 handles = HANDLE(), HANDLE()
252 if not CreatePipe(ctypes.byref(handles[0]),
252 if not CreatePipe(ctypes.byref(handles[0]),
253 ctypes.byref(handles[1]), ctypes.byref(saAttr), 0):
253 ctypes.byref(handles[1]), ctypes.byref(saAttr), 0):
254 raise ctypes.WinError()
254 raise ctypes.WinError()
255 if not SetHandleInformation(handles[uninherit],
255 if not SetHandleInformation(handles[uninherit],
256 HANDLE_FLAG_INHERIT, 0):
256 HANDLE_FLAG_INHERIT, 0):
257 raise ctypes.WinError()
257 raise ctypes.WinError()
258 return handles[0].value, handles[1].value
258 return handles[0].value, handles[1].value
259
259
260 p_hstdout, c_hstdout = create_pipe(uninherit=0)
260 p_hstdout, c_hstdout = create_pipe(uninherit=0)
261 # 'mergeout' signals that stdout and stderr should be merged.
261 # 'mergeout' signals that stdout and stderr should be merged.
262 # We do that by using one pipe for both of them.
262 # We do that by using one pipe for both of them.
263 if mergeout:
263 if mergeout:
264 c_hstderr = HANDLE()
264 c_hstderr = HANDLE()
265 if not DuplicateHandle(GetCurrentProcess(), c_hstdout,
265 if not DuplicateHandle(GetCurrentProcess(), c_hstdout,
266 GetCurrentProcess(), ctypes.byref(c_hstderr),
266 GetCurrentProcess(), ctypes.byref(c_hstderr),
267 0, True, DUPLICATE_SAME_ACCESS):
267 0, True, DUPLICATE_SAME_ACCESS):
268 raise ctypes.WinError()
268 raise ctypes.WinError()
269 else:
269 else:
270 p_hstderr, c_hstderr = create_pipe(uninherit=0)
270 p_hstderr, c_hstderr = create_pipe(uninherit=0)
271 c_hstdin, p_hstdin = create_pipe(uninherit=1)
271 c_hstdin, p_hstdin = create_pipe(uninherit=1)
272
272
273 # Create the process object
273 # Create the process object
274 piProcInfo = PROCESS_INFORMATION()
274 piProcInfo = PROCESS_INFORMATION()
275 siStartInfo = STARTUPINFO()
275 siStartInfo = STARTUPINFO()
276 siStartInfo.cb = ctypes.sizeof(siStartInfo)
276 siStartInfo.cb = ctypes.sizeof(siStartInfo)
277 siStartInfo.hStdInput = c_hstdin
277 siStartInfo.hStdInput = c_hstdin
278 siStartInfo.hStdOutput = c_hstdout
278 siStartInfo.hStdOutput = c_hstdout
279 siStartInfo.hStdError = c_hstderr
279 siStartInfo.hStdError = c_hstderr
280 siStartInfo.dwFlags = STARTF_USESTDHANDLES
280 siStartInfo.dwFlags = STARTF_USESTDHANDLES
281 dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE
281 dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE
282
282
283 if not CreateProcess(None,
283 if not CreateProcess(None,
284 u"cmd.exe /c " + cmd,
284 u"cmd.exe /c " + cmd,
285 None, None, True, dwCreationFlags,
285 None, None, True, dwCreationFlags,
286 None, None, ctypes.byref(siStartInfo),
286 None, None, ctypes.byref(siStartInfo),
287 ctypes.byref(piProcInfo)):
287 ctypes.byref(piProcInfo)):
288 raise ctypes.WinError()
288 raise ctypes.WinError()
289
289
290 # Close this process's versions of the child handles
290 # Close this process's versions of the child handles
291 CloseHandle(c_hstdin)
291 CloseHandle(c_hstdin)
292 c_hstdin = None
292 c_hstdin = None
293 CloseHandle(c_hstdout)
293 CloseHandle(c_hstdout)
294 c_hstdout = None
294 c_hstdout = None
295 if c_hstderr is not None:
295 if c_hstderr is not None:
296 CloseHandle(c_hstderr)
296 CloseHandle(c_hstderr)
297 c_hstderr = None
297 c_hstderr = None
298
298
299 # Transfer ownership of the parent handles to the object
299 # Transfer ownership of the parent handles to the object
300 self.hstdin = p_hstdin
300 self.hstdin = p_hstdin
301 p_hstdin = None
301 p_hstdin = None
302 self.hstdout = p_hstdout
302 self.hstdout = p_hstdout
303 p_hstdout = None
303 p_hstdout = None
304 if not mergeout:
304 if not mergeout:
305 self.hstderr = p_hstderr
305 self.hstderr = p_hstderr
306 p_hstderr = None
306 p_hstderr = None
307 self.piProcInfo = piProcInfo
307 self.piProcInfo = piProcInfo
308
308
309 finally:
309 finally:
310 if p_hstdin:
310 if p_hstdin:
311 CloseHandle(p_hstdin)
311 CloseHandle(p_hstdin)
312 if c_hstdin:
312 if c_hstdin:
313 CloseHandle(c_hstdin)
313 CloseHandle(c_hstdin)
314 if p_hstdout:
314 if p_hstdout:
315 CloseHandle(p_hstdout)
315 CloseHandle(p_hstdout)
316 if c_hstdout:
316 if c_hstdout:
317 CloseHandle(c_hstdout)
317 CloseHandle(c_hstdout)
318 if p_hstderr:
318 if p_hstderr:
319 CloseHandle(p_hstderr)
319 CloseHandle(p_hstderr)
320 if c_hstderr:
320 if c_hstderr:
321 CloseHandle(c_hstderr)
321 CloseHandle(c_hstderr)
322
322
323 return self
323 return self
324
324
325 def _stdin_thread(self, handle, hprocess, func, stdout_func):
325 def _stdin_thread(self, handle, hprocess, func, stdout_func):
326 exitCode = DWORD()
326 exitCode = DWORD()
327 bytesWritten = DWORD(0)
327 bytesWritten = DWORD(0)
328 while True:
328 while True:
329 #print("stdin thread loop start")
329 #print("stdin thread loop start")
330 # Get the input string (may be bytes or unicode)
330 # Get the input string (may be bytes or unicode)
331 data = func()
331 data = func()
332
332
333 # None signals to poll whether the process has exited
333 # None signals to poll whether the process has exited
334 if data is None:
334 if data is None:
335 #print("checking for process completion")
335 #print("checking for process completion")
336 if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)):
336 if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)):
337 raise ctypes.WinError()
337 raise ctypes.WinError()
338 if exitCode.value != STILL_ACTIVE:
338 if exitCode.value != STILL_ACTIVE:
339 return
339 return
340 # TESTING: Does zero-sized writefile help?
340 # TESTING: Does zero-sized writefile help?
341 if not WriteFile(handle, "", 0,
341 if not WriteFile(handle, "", 0,
342 ctypes.byref(bytesWritten), None):
342 ctypes.byref(bytesWritten), None):
343 raise ctypes.WinError()
343 raise ctypes.WinError()
344 continue
344 continue
345 #print("\nGot str %s\n" % repr(data), file=sys.stderr)
345 #print("\nGot str %s\n" % repr(data), file=sys.stderr)
346
346
347 # Encode the string to the console encoding
347 # Encode the string to the console encoding
348 if isinstance(data, unicode): #FIXME: Python3
348 if isinstance(data, unicode): #FIXME: Python3
349 data = data.encode('utf_8')
349 data = data.encode('utf_8')
350
350
351 # What we have now must be a string of bytes
351 # What we have now must be a string of bytes
352 if not isinstance(data, str): #FIXME: Python3
352 if not isinstance(data, str): #FIXME: Python3
353 raise RuntimeError("internal stdin function string error")
353 raise RuntimeError("internal stdin function string error")
354
354
355 # An empty string signals EOF
355 # An empty string signals EOF
356 if len(data) == 0:
356 if len(data) == 0:
357 return
357 return
358
358
359 # In a windows console, sometimes the input is echoed,
359 # In a windows console, sometimes the input is echoed,
360 # but sometimes not. How do we determine when to do this?
360 # but sometimes not. How do we determine when to do this?
361 stdout_func(data)
361 stdout_func(data)
362 # WriteFile may not accept all the data at once.
362 # WriteFile may not accept all the data at once.
363 # Loop until everything is processed
363 # Loop until everything is processed
364 while len(data) != 0:
364 while len(data) != 0:
365 #print("Calling writefile")
365 #print("Calling writefile")
366 if not WriteFile(handle, data, len(data),
366 if not WriteFile(handle, data, len(data),
367 ctypes.byref(bytesWritten), None):
367 ctypes.byref(bytesWritten), None):
368 # This occurs at exit
368 # This occurs at exit
369 if GetLastError() == ERROR_NO_DATA:
369 if GetLastError() == ERROR_NO_DATA:
370 return
370 return
371 raise ctypes.WinError()
371 raise ctypes.WinError()
372 #print("Called writefile")
372 #print("Called writefile")
373 data = data[bytesWritten.value:]
373 data = data[bytesWritten.value:]
374
374
375 def _stdout_thread(self, handle, func):
375 def _stdout_thread(self, handle, func):
376 # Allocate the output buffer
376 # Allocate the output buffer
377 data = ctypes.create_string_buffer(4096)
377 data = ctypes.create_string_buffer(4096)
378 while True:
378 while True:
379 bytesRead = DWORD(0)
379 bytesRead = DWORD(0)
380 if not ReadFile(handle, data, 4096,
380 if not ReadFile(handle, data, 4096,
381 ctypes.byref(bytesRead), None):
381 ctypes.byref(bytesRead), None):
382 le = GetLastError()
382 le = GetLastError()
383 if le == ERROR_BROKEN_PIPE:
383 if le == ERROR_BROKEN_PIPE:
384 return
384 return
385 else:
385 else:
386 raise ctypes.WinError()
386 raise ctypes.WinError()
387 # FIXME: Python3
387 # FIXME: Python3
388 s = data.value[0:bytesRead.value]
388 s = data.value[0:bytesRead.value]
389 #print("\nv: %s" % repr(s), file=sys.stderr)
389 #print("\nv: %s" % repr(s), file=sys.stderr)
390 func(s.decode('utf_8', 'replace'))
390 func(s.decode('utf_8', 'replace'))
391
391
392 def run(self, stdout_func = None, stdin_func = None, stderr_func = None):
392 def run(self, stdout_func = None, stdin_func = None, stderr_func = None):
393 """Runs the process, using the provided functions for I/O.
393 """Runs the process, using the provided functions for I/O.
394
394
395 The function stdin_func should return strings whenever a
395 The function stdin_func should return strings whenever a
396 character or characters become available.
396 character or characters become available.
397 The functions stdout_func and stderr_func are called whenever
397 The functions stdout_func and stderr_func are called whenever
398 something is printed to stdout or stderr, respectively.
398 something is printed to stdout or stderr, respectively.
399 These functions are called from different threads (but not
399 These functions are called from different threads (but not
400 concurrently, because of the GIL).
400 concurrently, because of the GIL).
401 """
401 """
402 if stdout_func is None and stdin_func is None and stderr_func is None:
402 if stdout_func is None and stdin_func is None and stderr_func is None:
403 return self._run_stdio()
403 return self._run_stdio()
404
404
405 if stderr_func is not None and self.mergeout:
405 if stderr_func is not None and self.mergeout:
406 raise RuntimeError("Shell command was initiated with "
406 raise RuntimeError("Shell command was initiated with "
407 "merged stdin/stdout, but a separate stderr_func "
407 "merged stdin/stdout, but a separate stderr_func "
408 "was provided to the run() method")
408 "was provided to the run() method")
409
409
410 # Create a thread for each input/output handle
410 # Create a thread for each input/output handle
411 stdin_thread = None
411 stdin_thread = None
412 threads = []
412 threads = []
413 if stdin_func:
413 if stdin_func:
414 stdin_thread = threading.Thread(target=self._stdin_thread,
414 stdin_thread = threading.Thread(target=self._stdin_thread,
415 args=(self.hstdin, self.piProcInfo.hProcess,
415 args=(self.hstdin, self.piProcInfo.hProcess,
416 stdin_func, stdout_func))
416 stdin_func, stdout_func))
417 threads.append(threading.Thread(target=self._stdout_thread,
417 threads.append(threading.Thread(target=self._stdout_thread,
418 args=(self.hstdout, stdout_func)))
418 args=(self.hstdout, stdout_func)))
419 if not self.mergeout:
419 if not self.mergeout:
420 if stderr_func is None:
420 if stderr_func is None:
421 stderr_func = stdout_func
421 stderr_func = stdout_func
422 threads.append(threading.Thread(target=self._stdout_thread,
422 threads.append(threading.Thread(target=self._stdout_thread,
423 args=(self.hstderr, stderr_func)))
423 args=(self.hstderr, stderr_func)))
424 # Start the I/O threads and the process
424 # Start the I/O threads and the process
425 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
425 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
426 raise ctypes.WinError()
426 raise ctypes.WinError()
427 if stdin_thread is not None:
427 if stdin_thread is not None:
428 stdin_thread.start()
428 stdin_thread.start()
429 for thread in threads:
429 for thread in threads:
430 thread.start()
430 thread.start()
431 # Wait for the process to complete
431 # Wait for the process to complete
432 if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \
432 if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \
433 WAIT_FAILED:
433 WAIT_FAILED:
434 raise ctypes.WinError()
434 raise ctypes.WinError()
435 # Wait for the I/O threads to complete
435 # Wait for the I/O threads to complete
436 for thread in threads:
436 for thread in threads:
437 thread.join()
437 thread.join()
438
438
439 # Wait for the stdin thread to complete
439 # Wait for the stdin thread to complete
440 if stdin_thread is not None:
440 if stdin_thread is not None:
441 stdin_thread.join()
441 stdin_thread.join()
442
442
443 def _stdin_raw_nonblock(self):
443 def _stdin_raw_nonblock(self):
444 """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
444 """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
445 # WARNING: This is experimental, and produces inconsistent results.
445 # WARNING: This is experimental, and produces inconsistent results.
446 # It's possible for the handle not to be appropriate for use
446 # It's possible for the handle not to be appropriate for use
447 # with WaitForSingleObject, among other things.
447 # with WaitForSingleObject, among other things.
448 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
448 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
449 result = WaitForSingleObject(handle, 100)
449 result = WaitForSingleObject(handle, 100)
450 if result == WAIT_FAILED:
450 if result == WAIT_FAILED:
451 raise ctypes.WinError()
451 raise ctypes.WinError()
452 elif result == WAIT_TIMEOUT:
452 elif result == WAIT_TIMEOUT:
453 print(".", end='')
453 print(".", end='')
454 return None
454 return None
455 else:
455 else:
456 data = ctypes.create_string_buffer(256)
456 data = ctypes.create_string_buffer(256)
457 bytesRead = DWORD(0)
457 bytesRead = DWORD(0)
458 print('?', end='')
458 print('?', end='')
459
459
460 if not ReadFile(handle, data, 256,
460 if not ReadFile(handle, data, 256,
461 ctypes.byref(bytesRead), None):
461 ctypes.byref(bytesRead), None):
462 raise ctypes.WinError()
462 raise ctypes.WinError()
463 # This ensures the non-blocking works with an actual console
463 # This ensures the non-blocking works with an actual console
464 # Not checking the error, so the processing will still work with
464 # Not checking the error, so the processing will still work with
465 # other handle types
465 # other handle types
466 FlushConsoleInputBuffer(handle)
466 FlushConsoleInputBuffer(handle)
467
467
468 data = data.value
468 data = data.value
469 data = data.replace('\r\n', '\n')
469 data = data.replace('\r\n', '\n')
470 data = data.replace('\r', '\n')
470 data = data.replace('\r', '\n')
471 print(repr(data) + " ", end='')
471 print(repr(data) + " ", end='')
472 return data
472 return data
473
473
474 def _stdin_raw_block(self):
474 def _stdin_raw_block(self):
475 """Use a blocking stdin read"""
475 """Use a blocking stdin read"""
476 # The big problem with the blocking read is that it doesn't
476 # The big problem with the blocking read is that it doesn't
477 # exit when it's supposed to in all contexts. An extra
477 # exit when it's supposed to in all contexts. An extra
478 # key-press may be required to trigger the exit.
478 # key-press may be required to trigger the exit.
479 try:
479 try:
480 data = sys.stdin.read(1)
480 data = sys.stdin.read(1)
481 data = data.replace('\r', '\n')
481 data = data.replace('\r', '\n')
482 return data
482 return data
483 except WindowsError as we:
483 except WindowsError as we:
484 if we.winerror == ERROR_NO_DATA:
484 if we.winerror == ERROR_NO_DATA:
485 # This error occurs when the pipe is closed
485 # This error occurs when the pipe is closed
486 return None
486 return None
487 else:
487 else:
488 # Otherwise let the error propagate
488 # Otherwise let the error propagate
489 raise we
489 raise we
490
490
491 def _stdout_raw(self, s):
491 def _stdout_raw(self, s):
492 """Writes the string to stdout"""
492 """Writes the string to stdout"""
493 print(s, end='', file=sys.stdout)
493 print(s, end='', file=sys.stdout)
494 sys.stdout.flush()
494 sys.stdout.flush()
495
495
496 def _stderr_raw(self, s):
496 def _stderr_raw(self, s):
497 """Writes the string to stdout"""
497 """Writes the string to stdout"""
498 print(s, end='', file=sys.stderr)
498 print(s, end='', file=sys.stderr)
499 sys.stderr.flush()
499 sys.stderr.flush()
500
500
501 def _run_stdio(self):
501 def _run_stdio(self):
502 """Runs the process using the system standard I/O.
502 """Runs the process using the system standard I/O.
503
503
504 IMPORTANT: stdin needs to be asynchronous, so the Python
504 IMPORTANT: stdin needs to be asynchronous, so the Python
505 sys.stdin object is not used. Instead,
505 sys.stdin object is not used. Instead,
506 msvcrt.kbhit/getwch are used asynchronously.
506 msvcrt.kbhit/getwch are used asynchronously.
507 """
507 """
508 # Disable Line and Echo mode
508 # Disable Line and Echo mode
509 #lpMode = DWORD()
509 #lpMode = DWORD()
510 #handle = msvcrt.get_osfhandle(sys.stdin.fileno())
510 #handle = msvcrt.get_osfhandle(sys.stdin.fileno())
511 #if GetConsoleMode(handle, ctypes.byref(lpMode)):
511 #if GetConsoleMode(handle, ctypes.byref(lpMode)):
512 # set_console_mode = True
512 # set_console_mode = True
513 # if not SetConsoleMode(handle, lpMode.value &
513 # if not SetConsoleMode(handle, lpMode.value &
514 # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)):
514 # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)):
515 # raise ctypes.WinError()
515 # raise ctypes.WinError()
516
516
517 if self.mergeout:
517 if self.mergeout:
518 return self.run(stdout_func = self._stdout_raw,
518 return self.run(stdout_func = self._stdout_raw,
519 stdin_func = self._stdin_raw_block)
519 stdin_func = self._stdin_raw_block)
520 else:
520 else:
521 return self.run(stdout_func = self._stdout_raw,
521 return self.run(stdout_func = self._stdout_raw,
522 stdin_func = self._stdin_raw_block,
522 stdin_func = self._stdin_raw_block,
523 stderr_func = self._stderr_raw)
523 stderr_func = self._stderr_raw)
524
524
525 # Restore the previous console mode
525 # Restore the previous console mode
526 #if set_console_mode:
526 #if set_console_mode:
527 # if not SetConsoleMode(handle, lpMode.value):
527 # if not SetConsoleMode(handle, lpMode.value):
528 # raise ctypes.WinError()
528 # raise ctypes.WinError()
529
529
530 def __exit__(self, exc_type, exc_value, traceback):
530 def __exit__(self, exc_type, exc_value, traceback):
531 if self.hstdin:
531 if self.hstdin:
532 CloseHandle(self.hstdin)
532 CloseHandle(self.hstdin)
533 self.hstdin = None
533 self.hstdin = None
534 if self.hstdout:
534 if self.hstdout:
535 CloseHandle(self.hstdout)
535 CloseHandle(self.hstdout)
536 self.hstdout = None
536 self.hstdout = None
537 if self.hstderr:
537 if self.hstderr:
538 CloseHandle(self.hstderr)
538 CloseHandle(self.hstderr)
539 self.hstderr = None
539 self.hstderr = None
540 if self.piProcInfo is not None:
540 if self.piProcInfo is not None:
541 CloseHandle(self.piProcInfo.hProcess)
541 CloseHandle(self.piProcInfo.hProcess)
542 CloseHandle(self.piProcInfo.hThread)
542 CloseHandle(self.piProcInfo.hThread)
543 self.piProcInfo = None
543 self.piProcInfo = None
544
544
545
545
546 def system(cmd):
546 def system(cmd):
547 """Win32 version of os.system() that works with network shares.
547 """Win32 version of os.system() that works with network shares.
548
548
549 Note that this implementation returns None, as meant for use in IPython.
549 Note that this implementation returns None, as meant for use in IPython.
550
550
551 Parameters
551 Parameters
552 ----------
552 ----------
553 cmd : str
553 cmd : str
554 A command to be executed in the system shell.
554 A command to be executed in the system shell.
555
555
556 Returns
556 Returns
557 -------
557 -------
558 None : we explicitly do NOT return the subprocess status code, as this
558 None : we explicitly do NOT return the subprocess status code, as this
559 utility is meant to be used extensively in IPython, where any return value
559 utility is meant to be used extensively in IPython, where any return value
560 would trigger :func:`sys.displayhook` calls.
560 would trigger : func:`sys.displayhook` calls.
561 """
561 """
562 with AvoidUNCPath() as path:
562 with AvoidUNCPath() as path:
563 if path is not None:
563 if path is not None:
564 cmd = '"pushd %s &&"%s' % (path, cmd)
564 cmd = '"pushd %s &&"%s' % (path, cmd)
565 with Win32ShellCommandController(cmd) as scc:
565 with Win32ShellCommandController(cmd) as scc:
566 scc.run()
566 scc.run()
567
567
568
568
569 if __name__ == "__main__":
569 if __name__ == "__main__":
570 print("Test starting!")
570 print("Test starting!")
571 #system("cmd")
571 #system("cmd")
572 system("python -i")
572 system("python -i")
573 print("Test finished!")
573 print("Test finished!")
@@ -1,58 +1,58 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Decorators that don't go anywhere else.
2 """Decorators that don't go anywhere else.
3
3
4 This module contains misc. decorators that don't really go with another module
4 This module contains misc. decorators that don't really go with another module
5 in :mod:`IPython.utils`. Beore putting something here please see if it should
5 in :mod:`IPython.utils`. Beore putting something here please see if it should
6 go into another topical module in :mod:`IPython.utils`.
6 go into another topical module in :mod:`IPython.utils`.
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2011 The IPython Development Team
10 # Copyright (C) 2008-2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Code
21 # Code
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 def flag_calls(func):
24 def flag_calls(func):
25 """Wrap a function to detect and flag when it gets called.
25 """Wrap a function to detect and flag when it gets called.
26
26
27 This is a decorator which takes a function and wraps it in a function with
27 This is a decorator which takes a function and wraps it in a function with
28 a 'called' attribute. wrapper.called is initialized to False.
28 a 'called' attribute. wrapper.called is initialized to False.
29
29
30 The wrapper.called attribute is set to False right before each call to the
30 The wrapper.called attribute is set to False right before each call to the
31 wrapped function, so if the call fails it remains False. After the call
31 wrapped function, so if the call fails it remains False. After the call
32 completes, wrapper.called is set to True and the output is returned.
32 completes, wrapper.called is set to True and the output is returned.
33
33
34 Testing for truth in wrapper.called allows you to determine if a call to
34 Testing for truth in wrapper.called allows you to determine if a call to
35 func() was attempted and succeeded."""
35 func() was attempted and succeeded."""
36
36
37 # don't wrap twice
37 # don't wrap twice
38 if hasattr(func, 'called'):
38 if hasattr(func, 'called'):
39 return func
39 return func
40
40
41 def wrapper(*args,**kw):
41 def wrapper(*args,**kw):
42 wrapper.called = False
42 wrapper.called = False
43 out = func(*args,**kw)
43 out = func(*args,**kw)
44 wrapper.called = True
44 wrapper.called = True
45 return out
45 return out
46
46
47 wrapper.called = False
47 wrapper.called = False
48 wrapper.__doc__ = func.__doc__
48 wrapper.__doc__ = func.__doc__
49 return wrapper
49 return wrapper
50
50
51 def undoc(func):
51 def undoc(func):
52 """Mark a function or class as undocumented.
52 """Mark a function or class as undocumented.
53
53
54 This is found by inspecting the AST, so for now it must be used directly
54 This is found by inspecting the AST, so for now it must be used directly
55 as @undoc, not as e.g. @decorators.undoc
55 as @undoc, not as e.g. @decorators.undoc
56 """
56 """
57 return func
57 return func
58
58
@@ -1,71 +1,71 b''
1 # coding: utf-8
1 # coding: utf-8
2 """
2 """
3 Utilities for dealing with text encodings
3 Utilities for dealing with text encodings
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2012 The IPython Development Team
7 # Copyright (C) 2008-2012 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 import sys
16 import sys
17 import locale
17 import locale
18 import warnings
18 import warnings
19
19
20 # to deal with the possibility of sys.std* not being a stream at all
20 # to deal with the possibility of sys.std* not being a stream at all
21 def get_stream_enc(stream, default=None):
21 def get_stream_enc(stream, default=None):
22 """Return the given stream's encoding or a default.
22 """Return the given stream's encoding or a default.
23
23
24 There are cases where ``sys.std*`` might not actually be a stream, so
24 There are cases where ``sys.std*`` might not actually be a stream, so
25 check for the encoding attribute prior to returning it, and return
25 check for the encoding attribute prior to returning it, and return
26 a default if it doesn't exist or evaluates as False. ``default``
26 a default if it doesn't exist or evaluates as False. ``default``
27 is None if not provided.
27 is None if not provided.
28 """
28 """
29 if not hasattr(stream, 'encoding') or not stream.encoding:
29 if not hasattr(stream, 'encoding') or not stream.encoding:
30 return default
30 return default
31 else:
31 else:
32 return stream.encoding
32 return stream.encoding
33
33
34 # Less conservative replacement for sys.getdefaultencoding, that will try
34 # Less conservative replacement for sys.getdefaultencoding, that will try
35 # to match the environment.
35 # to match the environment.
36 # Defined here as central function, so if we find better choices, we
36 # Defined here as central function, so if we find better choices, we
37 # won't need to make changes all over IPython.
37 # won't need to make changes all over IPython.
38 def getdefaultencoding(prefer_stream=True):
38 def getdefaultencoding(prefer_stream=True):
39 """Return IPython's guess for the default encoding for bytes as text.
39 """Return IPython's guess for the default encoding for bytes as text.
40
40
41 If prefer_stream is True (default), asks for stdin.encoding first,
41 If prefer_stream is True (default), asks for stdin.encoding first,
42 to match the calling Terminal, but that is often None for subprocesses.
42 to match the calling Terminal, but that is often None for subprocesses.
43
43
44 Then fall back on locale.getpreferredencoding(),
44 Then fall back on locale.getpreferredencoding(),
45 which should be a sensible platform default (that respects LANG environment),
45 which should be a sensible platform default (that respects LANG environment),
46 and finally to sys.getdefaultencoding() which is the most conservative option,
46 and finally to sys.getdefaultencoding() which is the most conservative option,
47 and usually UTF8 as of Python 3.
47 and usually UTF8 as of Python 3.
48 """
48 """
49 enc = None
49 enc = None
50 if prefer_stream:
50 if prefer_stream:
51 enc = get_stream_enc(sys.stdin)
51 enc = get_stream_enc(sys.stdin)
52 if not enc or enc=='ascii':
52 if not enc or enc=='ascii':
53 try:
53 try:
54 # There are reports of getpreferredencoding raising errors
54 # There are reports of getpreferredencoding raising errors
55 # in some cases, which may well be fixed, but let's be conservative here.
55 # in some cases, which may well be fixed, but let's be conservative here.
56 enc = locale.getpreferredencoding()
56 enc = locale.getpreferredencoding()
57 except Exception:
57 except Exception:
58 pass
58 pass
59 enc = enc or sys.getdefaultencoding()
59 enc = enc or sys.getdefaultencoding()
60 # On windows `cp0` can be returned to indicate that there is no code page.
60 # On windows `cp0` can be returned to indicate that there is no code page.
61 # Since cp0 is an invalid encoding return instead cp1252 which is the
61 # Since cp0 is an invalid encoding return instead cp1252 which is the
62 # Western European default.
62 # Western European default.
63 if enc == 'cp0':
63 if enc == 'cp0':
64 warnings.warn(
64 warnings.warn(
65 "Invalid code page cp0 detected - using cp1252 instead."
65 "Invalid code page cp0 detected - using cp1252 instead."
66 "If cp1252 is incorrect please ensure a valid code page "
66 "If cp1252 is incorrect please ensure a valid code page "
67 "is defined for the process.", RuntimeWarning)
67 "is defined for the process.", RuntimeWarning)
68 return 'cp1252'
68 return 'cp1252'
69 return enc
69 return enc
70
70
71 DEFAULT_ENCODING = getdefaultencoding()
71 DEFAULT_ENCODING = getdefaultencoding()
@@ -1,94 +1,92 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with stack frames.
3 Utilities for working with stack frames.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 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
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Code
20 # Code
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 def extract_vars(*names,**kw):
23 def extract_vars(*names,**kw):
24 """Extract a set of variables by name from another frame.
24 """Extract a set of variables by name from another frame.
25
25
26 Parameters
26 Parameters
27 ----------
27 ----------
28 *names : str
28 *names : str
29 One or more variable names which will be extracted from the caller's
29 One or more variable names which will be extracted from the caller's
30 frame.
30 frame.
31
31 **kw : integer, optional
32 depth : integer, optional
33 How many frames in the stack to walk when looking for your variables.
32 How many frames in the stack to walk when looking for your variables.
34 The default is 0, which will use the frame where the call was made.
33 The default is 0, which will use the frame where the call was made.
35
34
36
37 Examples
35 Examples
38 --------
36 --------
39 ::
37 ::
40
38
41 In [2]: def func(x):
39 In [2]: def func(x):
42 ...: y = 1
40 ...: y = 1
43 ...: print(sorted(extract_vars('x','y').items()))
41 ...: print(sorted(extract_vars('x','y').items()))
44 ...:
42 ...:
45
43
46 In [3]: func('hello')
44 In [3]: func('hello')
47 [('x', 'hello'), ('y', 1)]
45 [('x', 'hello'), ('y', 1)]
48 """
46 """
49
47
50 depth = kw.get('depth',0)
48 depth = kw.get('depth',0)
51
49
52 callerNS = sys._getframe(depth+1).f_locals
50 callerNS = sys._getframe(depth+1).f_locals
53 return dict((k,callerNS[k]) for k in names)
51 return dict((k,callerNS[k]) for k in names)
54
52
55
53
56 def extract_vars_above(*names):
54 def extract_vars_above(*names):
57 """Extract a set of variables by name from another frame.
55 """Extract a set of variables by name from another frame.
58
56
59 Similar to extractVars(), but with a specified depth of 1, so that names
57 Similar to extractVars(), but with a specified depth of 1, so that names
60 are extracted exactly from above the caller.
58 are extracted exactly from above the caller.
61
59
62 This is simply a convenience function so that the very common case (for us)
60 This is simply a convenience function so that the very common case (for us)
63 of skipping exactly 1 frame doesn't have to construct a special dict for
61 of skipping exactly 1 frame doesn't have to construct a special dict for
64 keyword passing."""
62 keyword passing."""
65
63
66 callerNS = sys._getframe(2).f_locals
64 callerNS = sys._getframe(2).f_locals
67 return dict((k,callerNS[k]) for k in names)
65 return dict((k,callerNS[k]) for k in names)
68
66
69
67
70 def debugx(expr,pre_msg=''):
68 def debugx(expr,pre_msg=''):
71 """Print the value of an expression from the caller's frame.
69 """Print the value of an expression from the caller's frame.
72
70
73 Takes an expression, evaluates it in the caller's frame and prints both
71 Takes an expression, evaluates it in the caller's frame and prints both
74 the given expression and the resulting value (as well as a debug mark
72 the given expression and the resulting value (as well as a debug mark
75 indicating the name of the calling function. The input must be of a form
73 indicating the name of the calling function. The input must be of a form
76 suitable for eval().
74 suitable for eval().
77
75
78 An optional message can be passed, which will be prepended to the printed
76 An optional message can be passed, which will be prepended to the printed
79 expr->value pair."""
77 expr->value pair."""
80
78
81 cf = sys._getframe(1)
79 cf = sys._getframe(1)
82 print('[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
80 print('[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
83 eval(expr,cf.f_globals,cf.f_locals)))
81 eval(expr,cf.f_globals,cf.f_locals)))
84
82
85
83
86 # deactivate it by uncommenting the following line, which makes it a no-op
84 # deactivate it by uncommenting the following line, which makes it a no-op
87 #def debugx(expr,pre_msg=''): pass
85 #def debugx(expr,pre_msg=''): pass
88
86
89 def extract_module_locals(depth=0):
87 def extract_module_locals(depth=0):
90 """Returns (module, locals) of the function `depth` frames away from the caller"""
88 """Returns (module, locals) of the function `depth` frames away from the caller"""
91 f = sys._getframe(depth + 1)
89 f = sys._getframe(depth + 1)
92 global_ns = f.f_globals
90 global_ns = f.f_globals
93 module = sys.modules[global_ns['__name__']]
91 module = sys.modules[global_ns['__name__']]
94 return (module, f.f_locals)
92 return (module, f.f_locals)
@@ -1,30 +1,29 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Generic functions for extending IPython.
2 """Generic functions for extending IPython.
3 """
3 """
4
4
5 from IPython.core.error import TryNext
5 from IPython.core.error import TryNext
6 from functools import singledispatch
6 from functools import singledispatch
7
7
8
8
9 @singledispatch
9 @singledispatch
10 def inspect_object(obj):
10 def inspect_object(obj):
11 """Called when you do obj?"""
11 """Called when you do obj?"""
12 raise TryNext
12 raise TryNext
13
13
14
14
15 @singledispatch
15 @singledispatch
16 def complete_object(obj, prev_completions):
16 def complete_object(obj, prev_completions):
17 """Custom completer dispatching for python objects.
17 """Custom completer dispatching for python objects.
18
18
19 Parameters
19 Parameters
20 ----------
20 ----------
21 obj : object
21 obj : object
22 The object to complete.
22 The object to complete.
23 prev_completions : list
23 prev_completions : list
24 List of attributes discovered so far.
24 List of attributes discovered so far.
25
26 This should return the list of attributes in obj. If you only wish to
25 This should return the list of attributes in obj. If you only wish to
27 add to the attributes already discovered normally, return
26 add to the attributes already discovered normally, return
28 own_attrs + prev_completions.
27 own_attrs + prev_completions.
29 """
28 """
30 raise TryNext
29 raise TryNext
@@ -1,39 +1,39 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A simple utility to import something by its string name.
3 A simple utility to import something by its string name.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9
9
10 def import_item(name):
10 def import_item(name):
11 """Import and return ``bar`` given the string ``foo.bar``.
11 """Import and return ``bar`` given the string ``foo.bar``.
12
12
13 Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
13 Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
14 executing the code ``from foo import bar``.
14 executing the code ``from foo import bar``.
15
15
16 Parameters
16 Parameters
17 ----------
17 ----------
18 name : string
18 name : string
19 The fully qualified name of the module/package being imported.
19 The fully qualified name of the module/package being imported.
20
20
21 Returns
21 Returns
22 -------
22 -------
23 mod : module object
23 mod : module object
24 The module that was imported.
24 The module that was imported.
25 """
25 """
26
26
27 parts = name.rsplit('.', 1)
27 parts = name.rsplit('.', 1)
28 if len(parts) == 2:
28 if len(parts) == 2:
29 # called with 'foo.bar....'
29 # called with 'foo.bar....'
30 package, obj = parts
30 package, obj = parts
31 module = __import__(package, fromlist=[obj])
31 module = __import__(package, fromlist=[obj])
32 try:
32 try:
33 pak = getattr(module, obj)
33 pak = getattr(module, obj)
34 except AttributeError as e:
34 except AttributeError as e:
35 raise ImportError('No module named %s' % obj) from e
35 raise ImportError('No module named %s' % obj) from e
36 return pak
36 return pak
37 else:
37 else:
38 # called with un-dotted string
38 # called with un-dotted string
39 return __import__(parts[0])
39 return __import__(parts[0])
@@ -1,249 +1,246 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9
9
10
10
11 import atexit
11 import atexit
12 import os
12 import os
13 import sys
13 import sys
14 import tempfile
14 import tempfile
15 import warnings
15 import warnings
16 from pathlib import Path
16 from pathlib import Path
17 from warnings import warn
17 from warnings import warn
18
18
19 from IPython.utils.decorators import undoc
19 from IPython.utils.decorators import undoc
20 from .capture import CapturedIO, capture_output
20 from .capture import CapturedIO, capture_output
21
21
22 @undoc
22 @undoc
23 class IOStream:
23 class IOStream:
24
24
25 def __init__(self, stream, fallback=None):
25 def __init__(self, stream, fallback=None):
26 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
26 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
27 DeprecationWarning, stacklevel=2)
27 DeprecationWarning, stacklevel=2)
28 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
29 if fallback is not None:
29 if fallback is not None:
30 stream = fallback
30 stream = fallback
31 else:
31 else:
32 raise ValueError("fallback required, but not specified")
32 raise ValueError("fallback required, but not specified")
33 self.stream = stream
33 self.stream = stream
34 self._swrite = stream.write
34 self._swrite = stream.write
35
35
36 # clone all methods not overridden:
36 # clone all methods not overridden:
37 def clone(meth):
37 def clone(meth):
38 return not hasattr(self, meth) and not meth.startswith('_')
38 return not hasattr(self, meth) and not meth.startswith('_')
39 for meth in filter(clone, dir(stream)):
39 for meth in filter(clone, dir(stream)):
40 try:
40 try:
41 val = getattr(stream, meth)
41 val = getattr(stream, meth)
42 except AttributeError:
42 except AttributeError:
43 pass
43 pass
44 else:
44 else:
45 setattr(self, meth, val)
45 setattr(self, meth, val)
46
46
47 def __repr__(self):
47 def __repr__(self):
48 cls = self.__class__
48 cls = self.__class__
49 tpl = '{mod}.{cls}({args})'
49 tpl = '{mod}.{cls}({args})'
50 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
50 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
51
51
52 def write(self,data):
52 def write(self,data):
53 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
53 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
54 DeprecationWarning, stacklevel=2)
54 DeprecationWarning, stacklevel=2)
55 try:
55 try:
56 self._swrite(data)
56 self._swrite(data)
57 except:
57 except:
58 try:
58 try:
59 # print handles some unicode issues which may trip a plain
59 # print handles some unicode issues which may trip a plain
60 # write() call. Emulate write() by using an empty end
60 # write() call. Emulate write() by using an empty end
61 # argument.
61 # argument.
62 print(data, end='', file=self.stream)
62 print(data, end='', file=self.stream)
63 except:
63 except:
64 # if we get here, something is seriously broken.
64 # if we get here, something is seriously broken.
65 print('ERROR - failed to write data to stream:', self.stream,
65 print('ERROR - failed to write data to stream:', self.stream,
66 file=sys.stderr)
66 file=sys.stderr)
67
67
68 def writelines(self, lines):
68 def writelines(self, lines):
69 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
69 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
70 DeprecationWarning, stacklevel=2)
70 DeprecationWarning, stacklevel=2)
71 if isinstance(lines, str):
71 if isinstance(lines, str):
72 lines = [lines]
72 lines = [lines]
73 for line in lines:
73 for line in lines:
74 self.write(line)
74 self.write(line)
75
75
76 # This class used to have a writeln method, but regular files and streams
76 # This class used to have a writeln method, but regular files and streams
77 # in Python don't have this method. We need to keep this completely
77 # in Python don't have this method. We need to keep this completely
78 # compatible so we removed it.
78 # compatible so we removed it.
79
79
80 @property
80 @property
81 def closed(self):
81 def closed(self):
82 return self.stream.closed
82 return self.stream.closed
83
83
84 def close(self):
84 def close(self):
85 pass
85 pass
86
86
87 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
87 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
88 devnull = open(os.devnull, 'w')
88 devnull = open(os.devnull, 'w')
89 atexit.register(devnull.close)
89 atexit.register(devnull.close)
90
90
91 # io.std* are deprecated, but don't show our own deprecation warnings
91 # io.std* are deprecated, but don't show our own deprecation warnings
92 # during initialization of the deprecated API.
92 # during initialization of the deprecated API.
93 with warnings.catch_warnings():
93 with warnings.catch_warnings():
94 warnings.simplefilter('ignore', DeprecationWarning)
94 warnings.simplefilter('ignore', DeprecationWarning)
95 stdin = IOStream(sys.stdin, fallback=devnull)
95 stdin = IOStream(sys.stdin, fallback=devnull)
96 stdout = IOStream(sys.stdout, fallback=devnull)
96 stdout = IOStream(sys.stdout, fallback=devnull)
97 stderr = IOStream(sys.stderr, fallback=devnull)
97 stderr = IOStream(sys.stderr, fallback=devnull)
98
98
99 class Tee(object):
99 class Tee(object):
100 """A class to duplicate an output stream to stdout/err.
100 """A class to duplicate an output stream to stdout/err.
101
101
102 This works in a manner very similar to the Unix 'tee' command.
102 This works in a manner very similar to the Unix 'tee' command.
103
103
104 When the object is closed or deleted, it closes the original file given to
104 When the object is closed or deleted, it closes the original file given to
105 it for duplication.
105 it for duplication.
106 """
106 """
107 # Inspired by:
107 # Inspired by:
108 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
108 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
109
109
110 def __init__(self, file_or_name, mode="w", channel='stdout'):
110 def __init__(self, file_or_name, mode="w", channel='stdout'):
111 """Construct a new Tee object.
111 """Construct a new Tee object.
112
112
113 Parameters
113 Parameters
114 ----------
114 ----------
115 file_or_name : filename or open filehandle (writable)
115 file_or_name : filename or open filehandle (writable)
116 File that will be duplicated
116 File that will be duplicated
117
118 mode : optional, valid mode for open().
117 mode : optional, valid mode for open().
119 If a filename was give, open with this mode.
118 If a filename was give, open with this mode.
120
121 channel : str, one of ['stdout', 'stderr']
119 channel : str, one of ['stdout', 'stderr']
122 """
120 """
123 if channel not in ['stdout', 'stderr']:
121 if channel not in ['stdout', 'stderr']:
124 raise ValueError('Invalid channel spec %s' % channel)
122 raise ValueError('Invalid channel spec %s' % channel)
125
123
126 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
124 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
127 self.file = file_or_name
125 self.file = file_or_name
128 else:
126 else:
129 self.file = open(file_or_name, mode)
127 self.file = open(file_or_name, mode)
130 self.channel = channel
128 self.channel = channel
131 self.ostream = getattr(sys, channel)
129 self.ostream = getattr(sys, channel)
132 setattr(sys, channel, self)
130 setattr(sys, channel, self)
133 self._closed = False
131 self._closed = False
134
132
135 def close(self):
133 def close(self):
136 """Close the file and restore the channel."""
134 """Close the file and restore the channel."""
137 self.flush()
135 self.flush()
138 setattr(sys, self.channel, self.ostream)
136 setattr(sys, self.channel, self.ostream)
139 self.file.close()
137 self.file.close()
140 self._closed = True
138 self._closed = True
141
139
142 def write(self, data):
140 def write(self, data):
143 """Write data to both channels."""
141 """Write data to both channels."""
144 self.file.write(data)
142 self.file.write(data)
145 self.ostream.write(data)
143 self.ostream.write(data)
146 self.ostream.flush()
144 self.ostream.flush()
147
145
148 def flush(self):
146 def flush(self):
149 """Flush both channels."""
147 """Flush both channels."""
150 self.file.flush()
148 self.file.flush()
151 self.ostream.flush()
149 self.ostream.flush()
152
150
153 def __del__(self):
151 def __del__(self):
154 if not self._closed:
152 if not self._closed:
155 self.close()
153 self.close()
156
154
157
155
158 def ask_yes_no(prompt, default=None, interrupt=None):
156 def ask_yes_no(prompt, default=None, interrupt=None):
159 """Asks a question and returns a boolean (y/n) answer.
157 """Asks a question and returns a boolean (y/n) answer.
160
158
161 If default is given (one of 'y','n'), it is used if the user input is
159 If default is given (one of 'y','n'), it is used if the user input is
162 empty. If interrupt is given (one of 'y','n'), it is used if the user
160 empty. If interrupt is given (one of 'y','n'), it is used if the user
163 presses Ctrl-C. Otherwise the question is repeated until an answer is
161 presses Ctrl-C. Otherwise the question is repeated until an answer is
164 given.
162 given.
165
163
166 An EOF is treated as the default answer. If there is no default, an
164 An EOF is treated as the default answer. If there is no default, an
167 exception is raised to prevent infinite loops.
165 exception is raised to prevent infinite loops.
168
166
169 Valid answers are: y/yes/n/no (match is not case sensitive)."""
167 Valid answers are: y/yes/n/no (match is not case sensitive)."""
170
168
171 answers = {'y':True,'n':False,'yes':True,'no':False}
169 answers = {'y':True,'n':False,'yes':True,'no':False}
172 ans = None
170 ans = None
173 while ans not in answers.keys():
171 while ans not in answers.keys():
174 try:
172 try:
175 ans = input(prompt+' ').lower()
173 ans = input(prompt+' ').lower()
176 if not ans: # response was an empty string
174 if not ans: # response was an empty string
177 ans = default
175 ans = default
178 except KeyboardInterrupt:
176 except KeyboardInterrupt:
179 if interrupt:
177 if interrupt:
180 ans = interrupt
178 ans = interrupt
181 print("\r")
179 print("\r")
182 except EOFError:
180 except EOFError:
183 if default in answers.keys():
181 if default in answers.keys():
184 ans = default
182 ans = default
185 print()
183 print()
186 else:
184 else:
187 raise
185 raise
188
186
189 return answers[ans]
187 return answers[ans]
190
188
191
189
192 def temp_pyfile(src, ext='.py'):
190 def temp_pyfile(src, ext='.py'):
193 """Make a temporary python file, return filename and filehandle.
191 """Make a temporary python file, return filename and filehandle.
194
192
195 Parameters
193 Parameters
196 ----------
194 ----------
197 src : string or list of strings (no need for ending newlines if list)
195 src : string or list of strings (no need for ending newlines if list)
198 Source code to be written to the file.
196 Source code to be written to the file.
199
200 ext : optional, string
197 ext : optional, string
201 Extension for the generated file.
198 Extension for the generated file.
202
199
203 Returns
200 Returns
204 -------
201 -------
205 (filename, open filehandle)
202 (filename, open filehandle)
206 It is the caller's responsibility to close the open file and unlink it.
203 It is the caller's responsibility to close the open file and unlink it.
207 """
204 """
208 fname = tempfile.mkstemp(ext)[1]
205 fname = tempfile.mkstemp(ext)[1]
209 with open(Path(fname), "w") as f:
206 with open(Path(fname), "w") as f:
210 f.write(src)
207 f.write(src)
211 f.flush()
208 f.flush()
212 return fname
209 return fname
213
210
214 @undoc
211 @undoc
215 def atomic_writing(*args, **kwargs):
212 def atomic_writing(*args, **kwargs):
216 """DEPRECATED: moved to notebook.services.contents.fileio"""
213 """DEPRECATED: moved to notebook.services.contents.fileio"""
217 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
214 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
218 from notebook.services.contents.fileio import atomic_writing
215 from notebook.services.contents.fileio import atomic_writing
219 return atomic_writing(*args, **kwargs)
216 return atomic_writing(*args, **kwargs)
220
217
221 @undoc
218 @undoc
222 def raw_print(*args, **kw):
219 def raw_print(*args, **kw):
223 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
220 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
224 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
221 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
225
222
226 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
223 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
227 file=sys.__stdout__)
224 file=sys.__stdout__)
228 sys.__stdout__.flush()
225 sys.__stdout__.flush()
229
226
230 @undoc
227 @undoc
231 def raw_print_err(*args, **kw):
228 def raw_print_err(*args, **kw):
232 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
229 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
233 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
230 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
234
231
235 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
232 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
236 file=sys.__stderr__)
233 file=sys.__stderr__)
237 sys.__stderr__.flush()
234 sys.__stderr__.flush()
238
235
239 # used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
236 # used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
240 # Keep for a version or two then should remove
237 # Keep for a version or two then should remove
241 rprint = raw_print
238 rprint = raw_print
242 rprinte = raw_print_err
239 rprinte = raw_print_err
243
240
244 @undoc
241 @undoc
245 def unicode_std_stream(stream='stdout'):
242 def unicode_std_stream(stream='stdout'):
246 """DEPRECATED, moved to nbconvert.utils.io"""
243 """DEPRECATED, moved to nbconvert.utils.io"""
247 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
244 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
248 from nbconvert.utils.io import unicode_std_stream
245 from nbconvert.utils.io import unicode_std_stream
249 return unicode_std_stream(stream)
246 return unicode_std_stream(stream)
@@ -1,391 +1,379 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """A dict subclass that supports attribute style access.
2 """A dict subclass that supports attribute style access.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez (original)
6 * Fernando Perez (original)
7 * Brian Granger (refactoring to a dict subclass)
7 * Brian Granger (refactoring to a dict subclass)
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 __all__ = ['Struct']
21 __all__ = ['Struct']
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Code
24 # Code
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27
27
28 class Struct(dict):
28 class Struct(dict):
29 """A dict subclass with attribute style access.
29 """A dict subclass with attribute style access.
30
30
31 This dict subclass has a a few extra features:
31 This dict subclass has a a few extra features:
32
32
33 * Attribute style access.
33 * Attribute style access.
34 * Protection of class members (like keys, items) when using attribute
34 * Protection of class members (like keys, items) when using attribute
35 style access.
35 style access.
36 * The ability to restrict assignment to only existing keys.
36 * The ability to restrict assignment to only existing keys.
37 * Intelligent merging.
37 * Intelligent merging.
38 * Overloaded operators.
38 * Overloaded operators.
39 """
39 """
40 _allownew = True
40 _allownew = True
41 def __init__(self, *args, **kw):
41 def __init__(self, *args, **kw):
42 """Initialize with a dictionary, another Struct, or data.
42 """Initialize with a dictionary, another Struct, or data.
43
43
44 Parameters
44 Parameters
45 ----------
45 ----------
46 args : dict, Struct
46 *args : dict, Struct
47 Initialize with one dict or Struct
47 Initialize with one dict or Struct
48 kw : dict
48 **kw : dict
49 Initialize with key, value pairs.
49 Initialize with key, value pairs.
50
50
51 Examples
51 Examples
52 --------
52 --------
53
54 >>> s = Struct(a=10,b=30)
53 >>> s = Struct(a=10,b=30)
55 >>> s.a
54 >>> s.a
56 10
55 10
57 >>> s.b
56 >>> s.b
58 30
57 30
59 >>> s2 = Struct(s,c=30)
58 >>> s2 = Struct(s,c=30)
60 >>> sorted(s2.keys())
59 >>> sorted(s2.keys())
61 ['a', 'b', 'c']
60 ['a', 'b', 'c']
62 """
61 """
63 object.__setattr__(self, '_allownew', True)
62 object.__setattr__(self, '_allownew', True)
64 dict.__init__(self, *args, **kw)
63 dict.__init__(self, *args, **kw)
65
64
66 def __setitem__(self, key, value):
65 def __setitem__(self, key, value):
67 """Set an item with check for allownew.
66 """Set an item with check for allownew.
68
67
69 Examples
68 Examples
70 --------
69 --------
71
72 >>> s = Struct()
70 >>> s = Struct()
73 >>> s['a'] = 10
71 >>> s['a'] = 10
74 >>> s.allow_new_attr(False)
72 >>> s.allow_new_attr(False)
75 >>> s['a'] = 10
73 >>> s['a'] = 10
76 >>> s['a']
74 >>> s['a']
77 10
75 10
78 >>> try:
76 >>> try:
79 ... s['b'] = 20
77 ... s['b'] = 20
80 ... except KeyError:
78 ... except KeyError:
81 ... print('this is not allowed')
79 ... print('this is not allowed')
82 ...
80 ...
83 this is not allowed
81 this is not allowed
84 """
82 """
85 if not self._allownew and key not in self:
83 if not self._allownew and key not in self:
86 raise KeyError(
84 raise KeyError(
87 "can't create new attribute %s when allow_new_attr(False)" % key)
85 "can't create new attribute %s when allow_new_attr(False)" % key)
88 dict.__setitem__(self, key, value)
86 dict.__setitem__(self, key, value)
89
87
90 def __setattr__(self, key, value):
88 def __setattr__(self, key, value):
91 """Set an attr with protection of class members.
89 """Set an attr with protection of class members.
92
90
93 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
91 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
94 :exc:`AttributeError`.
92 :exc:`AttributeError`.
95
93
96 Examples
94 Examples
97 --------
95 --------
98
99 >>> s = Struct()
96 >>> s = Struct()
100 >>> s.a = 10
97 >>> s.a = 10
101 >>> s.a
98 >>> s.a
102 10
99 10
103 >>> try:
100 >>> try:
104 ... s.get = 10
101 ... s.get = 10
105 ... except AttributeError:
102 ... except AttributeError:
106 ... print("you can't set a class member")
103 ... print("you can't set a class member")
107 ...
104 ...
108 you can't set a class member
105 you can't set a class member
109 """
106 """
110 # If key is an str it might be a class member or instance var
107 # If key is an str it might be a class member or instance var
111 if isinstance(key, str):
108 if isinstance(key, str):
112 # I can't simply call hasattr here because it calls getattr, which
109 # I can't simply call hasattr here because it calls getattr, which
113 # calls self.__getattr__, which returns True for keys in
110 # calls self.__getattr__, which returns True for keys in
114 # self._data. But I only want keys in the class and in
111 # self._data. But I only want keys in the class and in
115 # self.__dict__
112 # self.__dict__
116 if key in self.__dict__ or hasattr(Struct, key):
113 if key in self.__dict__ or hasattr(Struct, key):
117 raise AttributeError(
114 raise AttributeError(
118 'attr %s is a protected member of class Struct.' % key
115 'attr %s is a protected member of class Struct.' % key
119 )
116 )
120 try:
117 try:
121 self.__setitem__(key, value)
118 self.__setitem__(key, value)
122 except KeyError as e:
119 except KeyError as e:
123 raise AttributeError(e) from e
120 raise AttributeError(e) from e
124
121
125 def __getattr__(self, key):
122 def __getattr__(self, key):
126 """Get an attr by calling :meth:`dict.__getitem__`.
123 """Get an attr by calling :meth:`dict.__getitem__`.
127
124
128 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
125 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
129 :exc:`AttributeError`.
126 :exc:`AttributeError`.
130
127
131 Examples
128 Examples
132 --------
129 --------
133
134 >>> s = Struct(a=10)
130 >>> s = Struct(a=10)
135 >>> s.a
131 >>> s.a
136 10
132 10
137 >>> type(s.get)
133 >>> type(s.get)
138 <... 'builtin_function_or_method'>
134 <... 'builtin_function_or_method'>
139 >>> try:
135 >>> try:
140 ... s.b
136 ... s.b
141 ... except AttributeError:
137 ... except AttributeError:
142 ... print("I don't have that key")
138 ... print("I don't have that key")
143 ...
139 ...
144 I don't have that key
140 I don't have that key
145 """
141 """
146 try:
142 try:
147 result = self[key]
143 result = self[key]
148 except KeyError as e:
144 except KeyError as e:
149 raise AttributeError(key) from e
145 raise AttributeError(key) from e
150 else:
146 else:
151 return result
147 return result
152
148
153 def __iadd__(self, other):
149 def __iadd__(self, other):
154 """s += s2 is a shorthand for s.merge(s2).
150 """s += s2 is a shorthand for s.merge(s2).
155
151
156 Examples
152 Examples
157 --------
153 --------
158
159 >>> s = Struct(a=10,b=30)
154 >>> s = Struct(a=10,b=30)
160 >>> s2 = Struct(a=20,c=40)
155 >>> s2 = Struct(a=20,c=40)
161 >>> s += s2
156 >>> s += s2
162 >>> sorted(s.keys())
157 >>> sorted(s.keys())
163 ['a', 'b', 'c']
158 ['a', 'b', 'c']
164 """
159 """
165 self.merge(other)
160 self.merge(other)
166 return self
161 return self
167
162
168 def __add__(self,other):
163 def __add__(self,other):
169 """s + s2 -> New Struct made from s.merge(s2).
164 """s + s2 -> New Struct made from s.merge(s2).
170
165
171 Examples
166 Examples
172 --------
167 --------
173
174 >>> s1 = Struct(a=10,b=30)
168 >>> s1 = Struct(a=10,b=30)
175 >>> s2 = Struct(a=20,c=40)
169 >>> s2 = Struct(a=20,c=40)
176 >>> s = s1 + s2
170 >>> s = s1 + s2
177 >>> sorted(s.keys())
171 >>> sorted(s.keys())
178 ['a', 'b', 'c']
172 ['a', 'b', 'c']
179 """
173 """
180 sout = self.copy()
174 sout = self.copy()
181 sout.merge(other)
175 sout.merge(other)
182 return sout
176 return sout
183
177
184 def __sub__(self,other):
178 def __sub__(self,other):
185 """s1 - s2 -> remove keys in s2 from s1.
179 """s1 - s2 -> remove keys in s2 from s1.
186
180
187 Examples
181 Examples
188 --------
182 --------
189
190 >>> s1 = Struct(a=10,b=30)
183 >>> s1 = Struct(a=10,b=30)
191 >>> s2 = Struct(a=40)
184 >>> s2 = Struct(a=40)
192 >>> s = s1 - s2
185 >>> s = s1 - s2
193 >>> s
186 >>> s
194 {'b': 30}
187 {'b': 30}
195 """
188 """
196 sout = self.copy()
189 sout = self.copy()
197 sout -= other
190 sout -= other
198 return sout
191 return sout
199
192
200 def __isub__(self,other):
193 def __isub__(self,other):
201 """Inplace remove keys from self that are in other.
194 """Inplace remove keys from self that are in other.
202
195
203 Examples
196 Examples
204 --------
197 --------
205
206 >>> s1 = Struct(a=10,b=30)
198 >>> s1 = Struct(a=10,b=30)
207 >>> s2 = Struct(a=40)
199 >>> s2 = Struct(a=40)
208 >>> s1 -= s2
200 >>> s1 -= s2
209 >>> s1
201 >>> s1
210 {'b': 30}
202 {'b': 30}
211 """
203 """
212 for k in other.keys():
204 for k in other.keys():
213 if k in self:
205 if k in self:
214 del self[k]
206 del self[k]
215 return self
207 return self
216
208
217 def __dict_invert(self, data):
209 def __dict_invert(self, data):
218 """Helper function for merge.
210 """Helper function for merge.
219
211
220 Takes a dictionary whose values are lists and returns a dict with
212 Takes a dictionary whose values are lists and returns a dict with
221 the elements of each list as keys and the original keys as values.
213 the elements of each list as keys and the original keys as values.
222 """
214 """
223 outdict = {}
215 outdict = {}
224 for k,lst in data.items():
216 for k,lst in data.items():
225 if isinstance(lst, str):
217 if isinstance(lst, str):
226 lst = lst.split()
218 lst = lst.split()
227 for entry in lst:
219 for entry in lst:
228 outdict[entry] = k
220 outdict[entry] = k
229 return outdict
221 return outdict
230
222
231 def dict(self):
223 def dict(self):
232 return self
224 return self
233
225
234 def copy(self):
226 def copy(self):
235 """Return a copy as a Struct.
227 """Return a copy as a Struct.
236
228
237 Examples
229 Examples
238 --------
230 --------
239
240 >>> s = Struct(a=10,b=30)
231 >>> s = Struct(a=10,b=30)
241 >>> s2 = s.copy()
232 >>> s2 = s.copy()
242 >>> type(s2) is Struct
233 >>> type(s2) is Struct
243 True
234 True
244 """
235 """
245 return Struct(dict.copy(self))
236 return Struct(dict.copy(self))
246
237
247 def hasattr(self, key):
238 def hasattr(self, key):
248 """hasattr function available as a method.
239 """hasattr function available as a method.
249
240
250 Implemented like has_key.
241 Implemented like has_key.
251
242
252 Examples
243 Examples
253 --------
244 --------
254
255 >>> s = Struct(a=10)
245 >>> s = Struct(a=10)
256 >>> s.hasattr('a')
246 >>> s.hasattr('a')
257 True
247 True
258 >>> s.hasattr('b')
248 >>> s.hasattr('b')
259 False
249 False
260 >>> s.hasattr('get')
250 >>> s.hasattr('get')
261 False
251 False
262 """
252 """
263 return key in self
253 return key in self
264
254
265 def allow_new_attr(self, allow = True):
255 def allow_new_attr(self, allow = True):
266 """Set whether new attributes can be created in this Struct.
256 """Set whether new attributes can be created in this Struct.
267
257
268 This can be used to catch typos by verifying that the attribute user
258 This can be used to catch typos by verifying that the attribute user
269 tries to change already exists in this Struct.
259 tries to change already exists in this Struct.
270 """
260 """
271 object.__setattr__(self, '_allownew', allow)
261 object.__setattr__(self, '_allownew', allow)
272
262
273 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
263 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
274 """Merge two Structs with customizable conflict resolution.
264 """Merge two Structs with customizable conflict resolution.
275
265
276 This is similar to :meth:`update`, but much more flexible. First, a
266 This is similar to :meth:`update`, but much more flexible. First, a
277 dict is made from data+key=value pairs. When merging this dict with
267 dict is made from data+key=value pairs. When merging this dict with
278 the Struct S, the optional dictionary 'conflict' is used to decide
268 the Struct S, the optional dictionary 'conflict' is used to decide
279 what to do.
269 what to do.
280
270
281 If conflict is not given, the default behavior is to preserve any keys
271 If conflict is not given, the default behavior is to preserve any keys
282 with their current value (the opposite of the :meth:`update` method's
272 with their current value (the opposite of the :meth:`update` method's
283 behavior).
273 behavior).
284
274
285 Parameters
275 Parameters
286 ----------
276 ----------
287 __loc_data : dict, Struct
277 __loc_data__ : dict, Struct
288 The data to merge into self
278 The data to merge into self
289 __conflict_solve : dict
279 __conflict_solve : dict
290 The conflict policy dict. The keys are binary functions used to
280 The conflict policy dict. The keys are binary functions used to
291 resolve the conflict and the values are lists of strings naming
281 resolve the conflict and the values are lists of strings naming
292 the keys the conflict resolution function applies to. Instead of
282 the keys the conflict resolution function applies to. Instead of
293 a list of strings a space separated string can be used, like
283 a list of strings a space separated string can be used, like
294 'a b c'.
284 'a b c'.
295 kw : dict
285 **kw : dict
296 Additional key, value pairs to merge in
286 Additional key, value pairs to merge in
297
287
298 Notes
288 Notes
299 -----
289 -----
300
301 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
290 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
302 solve key conflicts. Here is an example::
291 solve key conflicts. Here is an example::
303
292
304 __conflict_solve = dict(
293 __conflict_solve = dict(
305 func1=['a','b','c'],
294 func1=['a','b','c'],
306 func2=['d','e']
295 func2=['d','e']
307 )
296 )
308
297
309 In this case, the function :func:`func1` will be used to resolve
298 In this case, the function :func:`func1` will be used to resolve
310 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
299 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
311 keys 'd' and 'e'. This could also be written as::
300 keys 'd' and 'e'. This could also be written as::
312
301
313 __conflict_solve = dict(func1='a b c',func2='d e')
302 __conflict_solve = dict(func1='a b c',func2='d e')
314
303
315 These functions will be called for each key they apply to with the
304 These functions will be called for each key they apply to with the
316 form::
305 form::
317
306
318 func1(self['a'], other['a'])
307 func1(self['a'], other['a'])
319
308
320 The return value is used as the final merged value.
309 The return value is used as the final merged value.
321
310
322 As a convenience, merge() provides five (the most commonly needed)
311 As a convenience, merge() provides five (the most commonly needed)
323 pre-defined policies: preserve, update, add, add_flip and add_s. The
312 pre-defined policies: preserve, update, add, add_flip and add_s. The
324 easiest explanation is their implementation::
313 easiest explanation is their implementation::
325
314
326 preserve = lambda old,new: old
315 preserve = lambda old,new: old
327 update = lambda old,new: new
316 update = lambda old,new: new
328 add = lambda old,new: old + new
317 add = lambda old,new: old + new
329 add_flip = lambda old,new: new + old # note change of order!
318 add_flip = lambda old,new: new + old # note change of order!
330 add_s = lambda old,new: old + ' ' + new # only for str!
319 add_s = lambda old,new: old + ' ' + new # only for str!
331
320
332 You can use those four words (as strings) as keys instead
321 You can use those four words (as strings) as keys instead
333 of defining them as functions, and the merge method will substitute
322 of defining them as functions, and the merge method will substitute
334 the appropriate functions for you.
323 the appropriate functions for you.
335
324
336 For more complicated conflict resolution policies, you still need to
325 For more complicated conflict resolution policies, you still need to
337 construct your own functions.
326 construct your own functions.
338
327
339 Examples
328 Examples
340 --------
329 --------
341
342 This show the default policy:
330 This show the default policy:
343
331
344 >>> s = Struct(a=10,b=30)
332 >>> s = Struct(a=10,b=30)
345 >>> s2 = Struct(a=20,c=40)
333 >>> s2 = Struct(a=20,c=40)
346 >>> s.merge(s2)
334 >>> s.merge(s2)
347 >>> sorted(s.items())
335 >>> sorted(s.items())
348 [('a', 10), ('b', 30), ('c', 40)]
336 [('a', 10), ('b', 30), ('c', 40)]
349
337
350 Now, show how to specify a conflict dict:
338 Now, show how to specify a conflict dict:
351
339
352 >>> s = Struct(a=10,b=30)
340 >>> s = Struct(a=10,b=30)
353 >>> s2 = Struct(a=20,b=40)
341 >>> s2 = Struct(a=20,b=40)
354 >>> conflict = {'update':'a','add':'b'}
342 >>> conflict = {'update':'a','add':'b'}
355 >>> s.merge(s2,conflict)
343 >>> s.merge(s2,conflict)
356 >>> sorted(s.items())
344 >>> sorted(s.items())
357 [('a', 20), ('b', 70)]
345 [('a', 20), ('b', 70)]
358 """
346 """
359
347
360 data_dict = dict(__loc_data__,**kw)
348 data_dict = dict(__loc_data__,**kw)
361
349
362 # policies for conflict resolution: two argument functions which return
350 # policies for conflict resolution: two argument functions which return
363 # the value that will go in the new struct
351 # the value that will go in the new struct
364 preserve = lambda old,new: old
352 preserve = lambda old,new: old
365 update = lambda old,new: new
353 update = lambda old,new: new
366 add = lambda old,new: old + new
354 add = lambda old,new: old + new
367 add_flip = lambda old,new: new + old # note change of order!
355 add_flip = lambda old,new: new + old # note change of order!
368 add_s = lambda old,new: old + ' ' + new
356 add_s = lambda old,new: old + ' ' + new
369
357
370 # default policy is to keep current keys when there's a conflict
358 # default policy is to keep current keys when there's a conflict
371 conflict_solve = dict.fromkeys(self, preserve)
359 conflict_solve = dict.fromkeys(self, preserve)
372
360
373 # the conflict_solve dictionary is given by the user 'inverted': we
361 # the conflict_solve dictionary is given by the user 'inverted': we
374 # need a name-function mapping, it comes as a function -> names
362 # need a name-function mapping, it comes as a function -> names
375 # dict. Make a local copy (b/c we'll make changes), replace user
363 # dict. Make a local copy (b/c we'll make changes), replace user
376 # strings for the three builtin policies and invert it.
364 # strings for the three builtin policies and invert it.
377 if __conflict_solve:
365 if __conflict_solve:
378 inv_conflict_solve_user = __conflict_solve.copy()
366 inv_conflict_solve_user = __conflict_solve.copy()
379 for name, func in [('preserve',preserve), ('update',update),
367 for name, func in [('preserve',preserve), ('update',update),
380 ('add',add), ('add_flip',add_flip),
368 ('add',add), ('add_flip',add_flip),
381 ('add_s',add_s)]:
369 ('add_s',add_s)]:
382 if name in inv_conflict_solve_user.keys():
370 if name in inv_conflict_solve_user.keys():
383 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
371 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
384 del inv_conflict_solve_user[name]
372 del inv_conflict_solve_user[name]
385 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
373 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
386 for key in data_dict:
374 for key in data_dict:
387 if key not in self:
375 if key not in self:
388 self[key] = data_dict[key]
376 self[key] = data_dict[key]
389 else:
377 else:
390 self[key] = conflict_solve[key](self[key],data_dict[key])
378 self[key] = conflict_solve[key](self[key],data_dict[key])
391
379
@@ -1,73 +1,73 b''
1 """Utility functions for finding modules
1 """Utility functions for finding modules
2
2
3 Utility functions for finding modules on sys.path.
3 Utility functions for finding modules on sys.path.
4
4
5 `find_module` returns a path to module or None, given certain conditions.
5 `find_module` returns a path to module or None, given certain conditions.
6
6
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (c) 2011, the IPython Development Team.
9 # Copyright (c) 2011, the IPython Development Team.
10 #
10 #
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12 #
12 #
13 # The full license is in the file COPYING.txt, distributed with this software.
13 # The full license is in the file COPYING.txt, distributed with this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # Stdlib imports
20 # Stdlib imports
21 import importlib
21 import importlib
22 import os
22 import os
23 import sys
23 import sys
24
24
25 # Third-party imports
25 # Third-party imports
26
26
27 # Our own imports
27 # Our own imports
28
28
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Globals and constants
31 # Globals and constants
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Local utilities
35 # Local utilities
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Classes and functions
39 # Classes and functions
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 def find_mod(module_name):
42 def find_mod(module_name):
43 """
43 """
44 Find module `module_name` on sys.path, and return the path to module `module_name`.
44 Find module `module_name` on sys.path, and return the path to module `module_name`.
45
45
46 - If `module_name` refers to a module directory, then return path to __init__ file.
46 - If `module_name` refers to a module directory, then return path to __init__ file.
47 - If `module_name` is a directory without an __init__file, return None.
47 - If `module_name` is a directory without an __init__file, return None.
48 - If module is missing or does not have a `.py` or `.pyw` extension, return None.
48 - If module is missing or does not have a `.py` or `.pyw` extension, return None.
49 - Note that we are not interested in running bytecode.
49 - Note that we are not interested in running bytecode.
50 - Otherwise, return the fill path of the module.
50 - Otherwise, return the fill path of the module.
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54 module_name : str
54 module_name : str
55
55
56 Returns
56 Returns
57 -------
57 -------
58 module_path : str
58 module_path : str
59 Path to module `module_name`, its __init__.py, or None,
59 Path to module `module_name`, its __init__.py, or None,
60 depending on above conditions.
60 depending on above conditions.
61 """
61 """
62 loader = importlib.util.find_spec(module_name)
62 loader = importlib.util.find_spec(module_name)
63 module_path = loader.origin
63 module_path = loader.origin
64 if module_path is None:
64 if module_path is None:
65 if loader.loader in sys.meta_path:
65 if loader.loader in sys.meta_path:
66 return loader.loader
66 return loader.loader
67 return None
67 return None
68 else:
68 else:
69 split_path = module_path.split(".")
69 split_path = module_path.split(".")
70 if split_path[-1] in ["py", "pyw"]:
70 if split_path[-1] in ["py", "pyw"]:
71 return module_path
71 return module_path
72 else:
72 else:
73 return None
73 return None
@@ -1,105 +1,105 b''
1 """
1 """
2 Tools to open .py files as Unicode, using the encoding specified within the file,
2 Tools to open .py files as Unicode, using the encoding specified within the file,
3 as per PEP 263.
3 as per PEP 263.
4
4
5 Much of the code is taken from the tokenize module in Python 3.2.
5 Much of the code is taken from the tokenize module in Python 3.2.
6 """
6 """
7
7
8 import io
8 import io
9 from io import TextIOWrapper, BytesIO
9 from io import TextIOWrapper, BytesIO
10 from pathlib import Path
10 from pathlib import Path
11 import re
11 import re
12 from tokenize import open, detect_encoding
12 from tokenize import open, detect_encoding
13
13
14 cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)", re.UNICODE)
14 cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)", re.UNICODE)
15 cookie_comment_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE)
15 cookie_comment_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE)
16
16
17 def source_to_unicode(txt, errors='replace', skip_encoding_cookie=True):
17 def source_to_unicode(txt, errors='replace', skip_encoding_cookie=True):
18 """Converts a bytes string with python source code to unicode.
18 """Converts a bytes string with python source code to unicode.
19
19
20 Unicode strings are passed through unchanged. Byte strings are checked
20 Unicode strings are passed through unchanged. Byte strings are checked
21 for the python source file encoding cookie to determine encoding.
21 for the python source file encoding cookie to determine encoding.
22 txt can be either a bytes buffer or a string containing the source
22 txt can be either a bytes buffer or a string containing the source
23 code.
23 code.
24 """
24 """
25 if isinstance(txt, str):
25 if isinstance(txt, str):
26 return txt
26 return txt
27 if isinstance(txt, bytes):
27 if isinstance(txt, bytes):
28 buffer = BytesIO(txt)
28 buffer = BytesIO(txt)
29 else:
29 else:
30 buffer = txt
30 buffer = txt
31 try:
31 try:
32 encoding, _ = detect_encoding(buffer.readline)
32 encoding, _ = detect_encoding(buffer.readline)
33 except SyntaxError:
33 except SyntaxError:
34 encoding = "ascii"
34 encoding = "ascii"
35 buffer.seek(0)
35 buffer.seek(0)
36 with TextIOWrapper(buffer, encoding, errors=errors, line_buffering=True) as text:
36 with TextIOWrapper(buffer, encoding, errors=errors, line_buffering=True) as text:
37 text.mode = 'r'
37 text.mode = 'r'
38 if skip_encoding_cookie:
38 if skip_encoding_cookie:
39 return u"".join(strip_encoding_cookie(text))
39 return u"".join(strip_encoding_cookie(text))
40 else:
40 else:
41 return text.read()
41 return text.read()
42
42
43 def strip_encoding_cookie(filelike):
43 def strip_encoding_cookie(filelike):
44 """Generator to pull lines from a text-mode file, skipping the encoding
44 """Generator to pull lines from a text-mode file, skipping the encoding
45 cookie if it is found in the first two lines.
45 cookie if it is found in the first two lines.
46 """
46 """
47 it = iter(filelike)
47 it = iter(filelike)
48 try:
48 try:
49 first = next(it)
49 first = next(it)
50 if not cookie_comment_re.match(first):
50 if not cookie_comment_re.match(first):
51 yield first
51 yield first
52 second = next(it)
52 second = next(it)
53 if not cookie_comment_re.match(second):
53 if not cookie_comment_re.match(second):
54 yield second
54 yield second
55 except StopIteration:
55 except StopIteration:
56 return
56 return
57
57
58 for line in it:
58 for line in it:
59 yield line
59 yield line
60
60
61 def read_py_file(filename, skip_encoding_cookie=True):
61 def read_py_file(filename, skip_encoding_cookie=True):
62 """Read a Python file, using the encoding declared inside the file.
62 """Read a Python file, using the encoding declared inside the file.
63
63
64 Parameters
64 Parameters
65 ----------
65 ----------
66 filename : str
66 filename : str
67 The path to the file to read.
67 The path to the file to read.
68 skip_encoding_cookie : bool
68 skip_encoding_cookie : bool
69 If True (the default), and the encoding declaration is found in the first
69 If True (the default), and the encoding declaration is found in the first
70 two lines, that line will be excluded from the output.
70 two lines, that line will be excluded from the output.
71
71
72 Returns
72 Returns
73 -------
73 -------
74 A unicode string containing the contents of the file.
74 A unicode string containing the contents of the file.
75 """
75 """
76 filepath = Path(filename)
76 filepath = Path(filename)
77 with open(filepath) as f: # the open function defined in this module.
77 with open(filepath) as f: # the open function defined in this module.
78 if skip_encoding_cookie:
78 if skip_encoding_cookie:
79 return "".join(strip_encoding_cookie(f))
79 return "".join(strip_encoding_cookie(f))
80 else:
80 else:
81 return f.read()
81 return f.read()
82
82
83 def read_py_url(url, errors='replace', skip_encoding_cookie=True):
83 def read_py_url(url, errors='replace', skip_encoding_cookie=True):
84 """Read a Python file from a URL, using the encoding declared inside the file.
84 """Read a Python file from a URL, using the encoding declared inside the file.
85
85
86 Parameters
86 Parameters
87 ----------
87 ----------
88 url : str
88 url : str
89 The URL from which to fetch the file.
89 The URL from which to fetch the file.
90 errors : str
90 errors : str
91 How to handle decoding errors in the file. Options are the same as for
91 How to handle decoding errors in the file. Options are the same as for
92 bytes.decode(), but here 'replace' is the default.
92 bytes.decode(), but here 'replace' is the default.
93 skip_encoding_cookie : bool
93 skip_encoding_cookie : bool
94 If True (the default), and the encoding declaration is found in the first
94 If True (the default), and the encoding declaration is found in the first
95 two lines, that line will be excluded from the output.
95 two lines, that line will be excluded from the output.
96
96
97 Returns
97 Returns
98 -------
98 -------
99 A unicode string containing the contents of the file.
99 A unicode string containing the contents of the file.
100 """
100 """
101 # Deferred import for faster start
101 # Deferred import for faster start
102 from urllib.request import urlopen
102 from urllib.request import urlopen
103 response = urlopen(url)
103 response = urlopen(url)
104 buffer = io.BytesIO(response.read())
104 buffer = io.BytesIO(response.read())
105 return source_to_unicode(buffer, errors, skip_encoding_cookie)
105 return source_to_unicode(buffer, errors, skip_encoding_cookie)
@@ -1,436 +1,440 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import errno
11 import errno
12 import shutil
12 import shutil
13 import random
13 import random
14 import glob
14 import glob
15 from warnings import warn
15 from warnings import warn
16
16
17 from IPython.utils.process import system
17 from IPython.utils.process import system
18 from IPython.utils.decorators import undoc
18 from IPython.utils.decorators import undoc
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Code
21 # Code
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 fs_encoding = sys.getfilesystemencoding()
23 fs_encoding = sys.getfilesystemencoding()
24
24
25 def _writable_dir(path):
25 def _writable_dir(path):
26 """Whether `path` is a directory, to which the user has write access."""
26 """Whether `path` is a directory, to which the user has write access."""
27 return os.path.isdir(path) and os.access(path, os.W_OK)
27 return os.path.isdir(path) and os.access(path, os.W_OK)
28
28
29 if sys.platform == 'win32':
29 if sys.platform == 'win32':
30 def _get_long_path_name(path):
30 def _get_long_path_name(path):
31 """Get a long path name (expand ~) on Windows using ctypes.
31 """Get a long path name (expand ~) on Windows using ctypes.
32
32
33 Examples
33 Examples
34 --------
34 --------
35
35
36 >>> get_long_path_name('c:\\docume~1')
36 >>> get_long_path_name('c:\\docume~1')
37 'c:\\\\Documents and Settings'
37 'c:\\\\Documents and Settings'
38
38
39 """
39 """
40 try:
40 try:
41 import ctypes
41 import ctypes
42 except ImportError as e:
42 except ImportError as e:
43 raise ImportError('you need to have ctypes installed for this to work') from e
43 raise ImportError('you need to have ctypes installed for this to work') from e
44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
46 ctypes.c_uint ]
46 ctypes.c_uint ]
47
47
48 buf = ctypes.create_unicode_buffer(260)
48 buf = ctypes.create_unicode_buffer(260)
49 rv = _GetLongPathName(path, buf, 260)
49 rv = _GetLongPathName(path, buf, 260)
50 if rv == 0 or rv > 260:
50 if rv == 0 or rv > 260:
51 return path
51 return path
52 else:
52 else:
53 return buf.value
53 return buf.value
54 else:
54 else:
55 def _get_long_path_name(path):
55 def _get_long_path_name(path):
56 """Dummy no-op."""
56 """Dummy no-op."""
57 return path
57 return path
58
58
59
59
60
60
61 def get_long_path_name(path):
61 def get_long_path_name(path):
62 """Expand a path into its long form.
62 """Expand a path into its long form.
63
63
64 On Windows this expands any ~ in the paths. On other platforms, it is
64 On Windows this expands any ~ in the paths. On other platforms, it is
65 a null operation.
65 a null operation.
66 """
66 """
67 return _get_long_path_name(path)
67 return _get_long_path_name(path)
68
68
69
69
70 def unquote_filename(name, win32=(sys.platform=='win32')):
70 def unquote_filename(name, win32=(sys.platform=='win32')):
71 """ On Windows, remove leading and trailing quotes from filenames.
71 """ On Windows, remove leading and trailing quotes from filenames.
72
72
73 This function has been deprecated and should not be used any more:
73 This function has been deprecated and should not be used any more:
74 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
74 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
75 """
75 """
76 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
76 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
77 "be used anymore", DeprecationWarning, stacklevel=2)
77 "be used anymore", DeprecationWarning, stacklevel=2)
78 if win32:
78 if win32:
79 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
79 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
80 name = name[1:-1]
80 name = name[1:-1]
81 return name
81 return name
82
82
83
83
84 def compress_user(path):
84 def compress_user(path):
85 """Reverse of :func:`os.path.expanduser`
85 """Reverse of :func:`os.path.expanduser`
86 """
86 """
87 home = os.path.expanduser('~')
87 home = os.path.expanduser('~')
88 if path.startswith(home):
88 if path.startswith(home):
89 path = "~" + path[len(home):]
89 path = "~" + path[len(home):]
90 return path
90 return path
91
91
92 def get_py_filename(name, force_win32=None):
92 def get_py_filename(name, force_win32=None):
93 """Return a valid python filename in the current directory.
93 """Return a valid python filename in the current directory.
94
94
95 If the given name is not a file, it adds '.py' and searches again.
95 If the given name is not a file, it adds '.py' and searches again.
96 Raises IOError with an informative message if the file isn't found.
96 Raises IOError with an informative message if the file isn't found.
97 """
97 """
98
98
99 name = os.path.expanduser(name)
99 name = os.path.expanduser(name)
100 if force_win32 is not None:
100 if force_win32 is not None:
101 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
101 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
102 "since IPython 5.0 and should not be used anymore",
102 "since IPython 5.0 and should not be used anymore",
103 DeprecationWarning, stacklevel=2)
103 DeprecationWarning, stacklevel=2)
104 if not os.path.isfile(name) and not name.endswith('.py'):
104 if not os.path.isfile(name) and not name.endswith('.py'):
105 name += '.py'
105 name += '.py'
106 if os.path.isfile(name):
106 if os.path.isfile(name):
107 return name
107 return name
108 else:
108 else:
109 raise IOError('File `%r` not found.' % name)
109 raise IOError('File `%r` not found.' % name)
110
110
111
111
112 def filefind(filename, path_dirs=None):
112 def filefind(filename: str, path_dirs=None) -> str:
113 """Find a file by looking through a sequence of paths.
113 """Find a file by looking through a sequence of paths.
114
114
115 This iterates through a sequence of paths looking for a file and returns
115 This iterates through a sequence of paths looking for a file and returns
116 the full, absolute path of the first occurrence of the file. If no set of
116 the full, absolute path of the first occurrence of the file. If no set of
117 path dirs is given, the filename is tested as is, after running through
117 path dirs is given, the filename is tested as is, after running through
118 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
118 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
119
119
120 filefind('myfile.txt')
120 filefind('myfile.txt')
121
121
122 will find the file in the current working dir, but::
122 will find the file in the current working dir, but::
123
123
124 filefind('~/myfile.txt')
124 filefind('~/myfile.txt')
125
125
126 Will find the file in the users home directory. This function does not
126 Will find the file in the users home directory. This function does not
127 automatically try any paths, such as the cwd or the user's home directory.
127 automatically try any paths, such as the cwd or the user's home directory.
128
128
129 Parameters
129 Parameters
130 ----------
130 ----------
131 filename : str
131 filename : str
132 The filename to look for.
132 The filename to look for.
133 path_dirs : str, None or sequence of str
133 path_dirs : str, None or sequence of str
134 The sequence of paths to look for the file in. If None, the filename
134 The sequence of paths to look for the file in. If None, the filename
135 need to be absolute or be in the cwd. If a string, the string is
135 need to be absolute or be in the cwd. If a string, the string is
136 put into a sequence and the searched. If a sequence, walk through
136 put into a sequence and the searched. If a sequence, walk through
137 each element and join with ``filename``, calling :func:`expandvars`
137 each element and join with ``filename``, calling :func:`expandvars`
138 and :func:`expanduser` before testing for existence.
138 and :func:`expanduser` before testing for existence.
139
139
140 Returns
140 Returns
141 -------
141 -------
142 Raises :exc:`IOError` or returns absolute path to file.
142 path : str
143 returns absolute path to file.
144
145 Raises
146 ------
147 IOError
143 """
148 """
144
149
145 # If paths are quoted, abspath gets confused, strip them...
150 # If paths are quoted, abspath gets confused, strip them...
146 filename = filename.strip('"').strip("'")
151 filename = filename.strip('"').strip("'")
147 # If the input is an absolute path, just check it exists
152 # If the input is an absolute path, just check it exists
148 if os.path.isabs(filename) and os.path.isfile(filename):
153 if os.path.isabs(filename) and os.path.isfile(filename):
149 return filename
154 return filename
150
155
151 if path_dirs is None:
156 if path_dirs is None:
152 path_dirs = ("",)
157 path_dirs = ("",)
153 elif isinstance(path_dirs, str):
158 elif isinstance(path_dirs, str):
154 path_dirs = (path_dirs,)
159 path_dirs = (path_dirs,)
155
160
156 for path in path_dirs:
161 for path in path_dirs:
157 if path == '.': path = os.getcwd()
162 if path == '.': path = os.getcwd()
158 testname = expand_path(os.path.join(path, filename))
163 testname = expand_path(os.path.join(path, filename))
159 if os.path.isfile(testname):
164 if os.path.isfile(testname):
160 return os.path.abspath(testname)
165 return os.path.abspath(testname)
161
166
162 raise IOError("File %r does not exist in any of the search paths: %r" %
167 raise IOError("File %r does not exist in any of the search paths: %r" %
163 (filename, path_dirs) )
168 (filename, path_dirs) )
164
169
165
170
166 class HomeDirError(Exception):
171 class HomeDirError(Exception):
167 pass
172 pass
168
173
169
174
170 def get_home_dir(require_writable=False) -> str:
175 def get_home_dir(require_writable=False) -> str:
171 """Return the 'home' directory, as a unicode string.
176 """Return the 'home' directory, as a unicode string.
172
177
173 Uses os.path.expanduser('~'), and checks for writability.
178 Uses os.path.expanduser('~'), and checks for writability.
174
179
175 See stdlib docs for how this is determined.
180 See stdlib docs for how this is determined.
176 For Python <3.8, $HOME is first priority on *ALL* platforms.
181 For Python <3.8, $HOME is first priority on *ALL* platforms.
177 For Python >=3.8 on Windows, %HOME% is no longer considered.
182 For Python >=3.8 on Windows, %HOME% is no longer considered.
178
183
179 Parameters
184 Parameters
180 ----------
185 ----------
181
182 require_writable : bool [default: False]
186 require_writable : bool [default: False]
183 if True:
187 if True:
184 guarantees the return value is a writable directory, otherwise
188 guarantees the return value is a writable directory, otherwise
185 raises HomeDirError
189 raises HomeDirError
186 if False:
190 if False:
187 The path is resolved, but it is not guaranteed to exist or be writable.
191 The path is resolved, but it is not guaranteed to exist or be writable.
188 """
192 """
189
193
190 homedir = os.path.expanduser('~')
194 homedir = os.path.expanduser('~')
191 # Next line will make things work even when /home/ is a symlink to
195 # Next line will make things work even when /home/ is a symlink to
192 # /usr/home as it is on FreeBSD, for example
196 # /usr/home as it is on FreeBSD, for example
193 homedir = os.path.realpath(homedir)
197 homedir = os.path.realpath(homedir)
194
198
195 if not _writable_dir(homedir) and os.name == 'nt':
199 if not _writable_dir(homedir) and os.name == 'nt':
196 # expanduser failed, use the registry to get the 'My Documents' folder.
200 # expanduser failed, use the registry to get the 'My Documents' folder.
197 try:
201 try:
198 import winreg as wreg
202 import winreg as wreg
199 with wreg.OpenKey(
203 with wreg.OpenKey(
200 wreg.HKEY_CURRENT_USER,
204 wreg.HKEY_CURRENT_USER,
201 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
205 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
202 ) as key:
206 ) as key:
203 homedir = wreg.QueryValueEx(key,'Personal')[0]
207 homedir = wreg.QueryValueEx(key,'Personal')[0]
204 except:
208 except:
205 pass
209 pass
206
210
207 if (not require_writable) or _writable_dir(homedir):
211 if (not require_writable) or _writable_dir(homedir):
208 assert isinstance(homedir, str), "Homedir shoudl be unicode not bytes"
212 assert isinstance(homedir, str), "Homedir shoudl be unicode not bytes"
209 return homedir
213 return homedir
210 else:
214 else:
211 raise HomeDirError('%s is not a writable dir, '
215 raise HomeDirError('%s is not a writable dir, '
212 'set $HOME environment variable to override' % homedir)
216 'set $HOME environment variable to override' % homedir)
213
217
214 def get_xdg_dir():
218 def get_xdg_dir():
215 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
219 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
216
220
217 This is only for non-OS X posix (Linux,Unix,etc.) systems.
221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
218 """
222 """
219
223
220 env = os.environ
224 env = os.environ
221
225
222 if os.name == 'posix' and sys.platform != 'darwin':
226 if os.name == 'posix' and sys.platform != 'darwin':
223 # Linux, Unix, AIX, etc.
227 # Linux, Unix, AIX, etc.
224 # use ~/.config if empty OR not set
228 # use ~/.config if empty OR not set
225 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
229 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
226 if xdg and _writable_dir(xdg):
230 if xdg and _writable_dir(xdg):
227 assert isinstance(xdg, str)
231 assert isinstance(xdg, str)
228 return xdg
232 return xdg
229
233
230 return None
234 return None
231
235
232
236
233 def get_xdg_cache_dir():
237 def get_xdg_cache_dir():
234 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
238 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
235
239
236 This is only for non-OS X posix (Linux,Unix,etc.) systems.
240 This is only for non-OS X posix (Linux,Unix,etc.) systems.
237 """
241 """
238
242
239 env = os.environ
243 env = os.environ
240
244
241 if os.name == 'posix' and sys.platform != 'darwin':
245 if os.name == 'posix' and sys.platform != 'darwin':
242 # Linux, Unix, AIX, etc.
246 # Linux, Unix, AIX, etc.
243 # use ~/.cache if empty OR not set
247 # use ~/.cache if empty OR not set
244 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
248 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
245 if xdg and _writable_dir(xdg):
249 if xdg and _writable_dir(xdg):
246 assert isinstance(xdg, str)
250 assert isinstance(xdg, str)
247 return xdg
251 return xdg
248
252
249 return None
253 return None
250
254
251
255
252 @undoc
256 @undoc
253 def get_ipython_dir():
257 def get_ipython_dir():
254 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
258 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
255 from IPython.paths import get_ipython_dir
259 from IPython.paths import get_ipython_dir
256 return get_ipython_dir()
260 return get_ipython_dir()
257
261
258 @undoc
262 @undoc
259 def get_ipython_cache_dir():
263 def get_ipython_cache_dir():
260 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
264 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
261 from IPython.paths import get_ipython_cache_dir
265 from IPython.paths import get_ipython_cache_dir
262 return get_ipython_cache_dir()
266 return get_ipython_cache_dir()
263
267
264 @undoc
268 @undoc
265 def get_ipython_package_dir():
269 def get_ipython_package_dir():
266 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
270 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
267 from IPython.paths import get_ipython_package_dir
271 from IPython.paths import get_ipython_package_dir
268 return get_ipython_package_dir()
272 return get_ipython_package_dir()
269
273
270 @undoc
274 @undoc
271 def get_ipython_module_path(module_str):
275 def get_ipython_module_path(module_str):
272 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
276 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
273 from IPython.paths import get_ipython_module_path
277 from IPython.paths import get_ipython_module_path
274 return get_ipython_module_path(module_str)
278 return get_ipython_module_path(module_str)
275
279
276 @undoc
280 @undoc
277 def locate_profile(profile='default'):
281 def locate_profile(profile='default'):
278 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
282 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
279 from IPython.paths import locate_profile
283 from IPython.paths import locate_profile
280 return locate_profile(profile=profile)
284 return locate_profile(profile=profile)
281
285
282 def expand_path(s):
286 def expand_path(s):
283 """Expand $VARS and ~names in a string, like a shell
287 """Expand $VARS and ~names in a string, like a shell
284
288
285 :Examples:
289 :Examples:
286
290
287 In [2]: os.environ['FOO']='test'
291 In [2]: os.environ['FOO']='test'
288
292
289 In [3]: expand_path('variable FOO is $FOO')
293 In [3]: expand_path('variable FOO is $FOO')
290 Out[3]: 'variable FOO is test'
294 Out[3]: 'variable FOO is test'
291 """
295 """
292 # This is a pretty subtle hack. When expand user is given a UNC path
296 # This is a pretty subtle hack. When expand user is given a UNC path
293 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
297 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
294 # the $ to get (\\server\share\%username%). I think it considered $
298 # the $ to get (\\server\share\%username%). I think it considered $
295 # alone an empty var. But, we need the $ to remains there (it indicates
299 # alone an empty var. But, we need the $ to remains there (it indicates
296 # a hidden share).
300 # a hidden share).
297 if os.name=='nt':
301 if os.name=='nt':
298 s = s.replace('$\\', 'IPYTHON_TEMP')
302 s = s.replace('$\\', 'IPYTHON_TEMP')
299 s = os.path.expandvars(os.path.expanduser(s))
303 s = os.path.expandvars(os.path.expanduser(s))
300 if os.name=='nt':
304 if os.name=='nt':
301 s = s.replace('IPYTHON_TEMP', '$\\')
305 s = s.replace('IPYTHON_TEMP', '$\\')
302 return s
306 return s
303
307
304
308
305 def unescape_glob(string):
309 def unescape_glob(string):
306 """Unescape glob pattern in `string`."""
310 """Unescape glob pattern in `string`."""
307 def unescape(s):
311 def unescape(s):
308 for pattern in '*[]!?':
312 for pattern in '*[]!?':
309 s = s.replace(r'\{0}'.format(pattern), pattern)
313 s = s.replace(r'\{0}'.format(pattern), pattern)
310 return s
314 return s
311 return '\\'.join(map(unescape, string.split('\\\\')))
315 return '\\'.join(map(unescape, string.split('\\\\')))
312
316
313
317
314 def shellglob(args):
318 def shellglob(args):
315 """
319 """
316 Do glob expansion for each element in `args` and return a flattened list.
320 Do glob expansion for each element in `args` and return a flattened list.
317
321
318 Unmatched glob pattern will remain as-is in the returned list.
322 Unmatched glob pattern will remain as-is in the returned list.
319
323
320 """
324 """
321 expanded = []
325 expanded = []
322 # Do not unescape backslash in Windows as it is interpreted as
326 # Do not unescape backslash in Windows as it is interpreted as
323 # path separator:
327 # path separator:
324 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
328 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
325 for a in args:
329 for a in args:
326 expanded.extend(glob.glob(a) or [unescape(a)])
330 expanded.extend(glob.glob(a) or [unescape(a)])
327 return expanded
331 return expanded
328
332
329
333
330 def target_outdated(target,deps):
334 def target_outdated(target,deps):
331 """Determine whether a target is out of date.
335 """Determine whether a target is out of date.
332
336
333 target_outdated(target,deps) -> 1/0
337 target_outdated(target,deps) -> 1/0
334
338
335 deps: list of filenames which MUST exist.
339 deps: list of filenames which MUST exist.
336 target: single filename which may or may not exist.
340 target: single filename which may or may not exist.
337
341
338 If target doesn't exist or is older than any file listed in deps, return
342 If target doesn't exist or is older than any file listed in deps, return
339 true, otherwise return false.
343 true, otherwise return false.
340 """
344 """
341 try:
345 try:
342 target_time = os.path.getmtime(target)
346 target_time = os.path.getmtime(target)
343 except os.error:
347 except os.error:
344 return 1
348 return 1
345 for dep in deps:
349 for dep in deps:
346 dep_time = os.path.getmtime(dep)
350 dep_time = os.path.getmtime(dep)
347 if dep_time > target_time:
351 if dep_time > target_time:
348 #print "For target",target,"Dep failed:",dep # dbg
352 #print "For target",target,"Dep failed:",dep # dbg
349 #print "times (dep,tar):",dep_time,target_time # dbg
353 #print "times (dep,tar):",dep_time,target_time # dbg
350 return 1
354 return 1
351 return 0
355 return 0
352
356
353
357
354 def target_update(target,deps,cmd):
358 def target_update(target,deps,cmd):
355 """Update a target with a given command given a list of dependencies.
359 """Update a target with a given command given a list of dependencies.
356
360
357 target_update(target,deps,cmd) -> runs cmd if target is outdated.
361 target_update(target,deps,cmd) -> runs cmd if target is outdated.
358
362
359 This is just a wrapper around target_outdated() which calls the given
363 This is just a wrapper around target_outdated() which calls the given
360 command if target is outdated."""
364 command if target is outdated."""
361
365
362 if target_outdated(target,deps):
366 if target_outdated(target,deps):
363 system(cmd)
367 system(cmd)
364
368
365
369
366 ENOLINK = 1998
370 ENOLINK = 1998
367
371
368 def link(src, dst):
372 def link(src, dst):
369 """Hard links ``src`` to ``dst``, returning 0 or errno.
373 """Hard links ``src`` to ``dst``, returning 0 or errno.
370
374
371 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
375 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
372 supported by the operating system.
376 supported by the operating system.
373 """
377 """
374
378
375 if not hasattr(os, "link"):
379 if not hasattr(os, "link"):
376 return ENOLINK
380 return ENOLINK
377 link_errno = 0
381 link_errno = 0
378 try:
382 try:
379 os.link(src, dst)
383 os.link(src, dst)
380 except OSError as e:
384 except OSError as e:
381 link_errno = e.errno
385 link_errno = e.errno
382 return link_errno
386 return link_errno
383
387
384
388
385 def link_or_copy(src, dst):
389 def link_or_copy(src, dst):
386 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
390 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
387
391
388 Attempts to maintain the semantics of ``shutil.copy``.
392 Attempts to maintain the semantics of ``shutil.copy``.
389
393
390 Because ``os.link`` does not overwrite files, a unique temporary file
394 Because ``os.link`` does not overwrite files, a unique temporary file
391 will be used if the target already exists, then that file will be moved
395 will be used if the target already exists, then that file will be moved
392 into place.
396 into place.
393 """
397 """
394
398
395 if os.path.isdir(dst):
399 if os.path.isdir(dst):
396 dst = os.path.join(dst, os.path.basename(src))
400 dst = os.path.join(dst, os.path.basename(src))
397
401
398 link_errno = link(src, dst)
402 link_errno = link(src, dst)
399 if link_errno == errno.EEXIST:
403 if link_errno == errno.EEXIST:
400 if os.stat(src).st_ino == os.stat(dst).st_ino:
404 if os.stat(src).st_ino == os.stat(dst).st_ino:
401 # dst is already a hard link to the correct file, so we don't need
405 # dst is already a hard link to the correct file, so we don't need
402 # to do anything else. If we try to link and rename the file
406 # to do anything else. If we try to link and rename the file
403 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
407 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
404 return
408 return
405
409
406 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
410 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
407 try:
411 try:
408 link_or_copy(src, new_dst)
412 link_or_copy(src, new_dst)
409 except:
413 except:
410 try:
414 try:
411 os.remove(new_dst)
415 os.remove(new_dst)
412 except OSError:
416 except OSError:
413 pass
417 pass
414 raise
418 raise
415 os.rename(new_dst, dst)
419 os.rename(new_dst, dst)
416 elif link_errno != 0:
420 elif link_errno != 0:
417 # Either link isn't supported, or the filesystem doesn't support
421 # Either link isn't supported, or the filesystem doesn't support
418 # linking, or 'src' and 'dst' are on different filesystems.
422 # linking, or 'src' and 'dst' are on different filesystems.
419 shutil.copy(src, dst)
423 shutil.copy(src, dst)
420
424
421 def ensure_dir_exists(path, mode=0o755):
425 def ensure_dir_exists(path, mode=0o755):
422 """ensure that a directory exists
426 """ensure that a directory exists
423
427
424 If it doesn't exist, try to create it and protect against a race condition
428 If it doesn't exist, try to create it and protect against a race condition
425 if another process is doing the same.
429 if another process is doing the same.
426
430
427 The default permissions are 755, which differ from os.makedirs default of 777.
431 The default permissions are 755, which differ from os.makedirs default of 777.
428 """
432 """
429 if not os.path.exists(path):
433 if not os.path.exists(path):
430 try:
434 try:
431 os.makedirs(path, mode=mode)
435 os.makedirs(path, mode=mode)
432 except OSError as e:
436 except OSError as e:
433 if e.errno != errno.EEXIST:
437 if e.errno != errno.EEXIST:
434 raise
438 raise
435 elif not os.path.isdir(path):
439 elif not os.path.isdir(path):
436 raise IOError("%r exists but is not a directory" % path)
440 raise IOError("%r exists but is not a directory" % path)
@@ -1,166 +1,166 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for getting information about IPython and the system it's running in.
3 Utilities for getting information about IPython and the system it's running in.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 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 os
17 import os
18 import platform
18 import platform
19 import pprint
19 import pprint
20 import sys
20 import sys
21 import subprocess
21 import subprocess
22
22
23 from IPython.core import release
23 from IPython.core import release
24 from IPython.utils import _sysinfo, encoding
24 from IPython.utils import _sysinfo, encoding
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Code
27 # Code
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 def pkg_commit_hash(pkg_path):
30 def pkg_commit_hash(pkg_path):
31 """Get short form of commit hash given directory `pkg_path`
31 """Get short form of commit hash given directory `pkg_path`
32
32
33 We get the commit hash from (in order of preference):
33 We get the commit hash from (in order of preference):
34
34
35 * IPython.utils._sysinfo.commit
35 * IPython.utils._sysinfo.commit
36 * git output, if we are in a git repository
36 * git output, if we are in a git repository
37
37
38 If these fail, we return a not-found placeholder tuple
38 If these fail, we return a not-found placeholder tuple
39
39
40 Parameters
40 Parameters
41 ----------
41 ----------
42 pkg_path : str
42 pkg_path : str
43 directory containing package
43 directory containing package
44 only used for getting commit from active repo
44 only used for getting commit from active repo
45
45
46 Returns
46 Returns
47 -------
47 -------
48 hash_from : str
48 hash_from : str
49 Where we got the hash from - description
49 Where we got the hash from - description
50 hash_str : str
50 hash_str : str
51 short form of hash
51 short form of hash
52 """
52 """
53 # Try and get commit from written commit text file
53 # Try and get commit from written commit text file
54 if _sysinfo.commit:
54 if _sysinfo.commit:
55 return "installation", _sysinfo.commit
55 return "installation", _sysinfo.commit
56
56
57 # maybe we are in a repository
57 # maybe we are in a repository
58 proc = subprocess.Popen('git rev-parse --short HEAD'.split(' '),
58 proc = subprocess.Popen('git rev-parse --short HEAD'.split(' '),
59 stdout=subprocess.PIPE,
59 stdout=subprocess.PIPE,
60 stderr=subprocess.PIPE,
60 stderr=subprocess.PIPE,
61 cwd=pkg_path)
61 cwd=pkg_path)
62 repo_commit, _ = proc.communicate()
62 repo_commit, _ = proc.communicate()
63 if repo_commit:
63 if repo_commit:
64 return 'repository', repo_commit.strip().decode('ascii')
64 return 'repository', repo_commit.strip().decode('ascii')
65 return '(none found)', '<not found>'
65 return '(none found)', '<not found>'
66
66
67
67
68 def pkg_info(pkg_path):
68 def pkg_info(pkg_path):
69 """Return dict describing the context of this package
69 """Return dict describing the context of this package
70
70
71 Parameters
71 Parameters
72 ----------
72 ----------
73 pkg_path : str
73 pkg_path : str
74 path containing __init__.py for package
74 path containing __init__.py for package
75
75
76 Returns
76 Returns
77 -------
77 -------
78 context : dict
78 context : dict
79 with named parameters of interest
79 with named parameters of interest
80 """
80 """
81 src, hsh = pkg_commit_hash(pkg_path)
81 src, hsh = pkg_commit_hash(pkg_path)
82 return dict(
82 return dict(
83 ipython_version=release.version,
83 ipython_version=release.version,
84 ipython_path=pkg_path,
84 ipython_path=pkg_path,
85 commit_source=src,
85 commit_source=src,
86 commit_hash=hsh,
86 commit_hash=hsh,
87 sys_version=sys.version,
87 sys_version=sys.version,
88 sys_executable=sys.executable,
88 sys_executable=sys.executable,
89 sys_platform=sys.platform,
89 sys_platform=sys.platform,
90 platform=platform.platform(),
90 platform=platform.platform(),
91 os_name=os.name,
91 os_name=os.name,
92 default_encoding=encoding.DEFAULT_ENCODING,
92 default_encoding=encoding.DEFAULT_ENCODING,
93 )
93 )
94
94
95 def get_sys_info():
95 def get_sys_info():
96 """Return useful information about IPython and the system, as a dict."""
96 """Return useful information about IPython and the system, as a dict."""
97 p = os.path
97 p = os.path
98 path = p.realpath(p.dirname(p.abspath(p.join(__file__, '..'))))
98 path = p.realpath(p.dirname(p.abspath(p.join(__file__, '..'))))
99 return pkg_info(path)
99 return pkg_info(path)
100
100
101 def sys_info():
101 def sys_info():
102 """Return useful information about IPython and the system, as a string.
102 """Return useful information about IPython and the system, as a string.
103
103
104 Examples
104 Examples
105 --------
105 --------
106 ::
106 ::
107
107
108 In [2]: print(sys_info())
108 In [2]: print(sys_info())
109 {'commit_hash': '144fdae', # random
109 {'commit_hash': '144fdae', # random
110 'commit_source': 'repository',
110 'commit_source': 'repository',
111 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
111 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
112 'ipython_version': '0.11.dev',
112 'ipython_version': '0.11.dev',
113 'os_name': 'posix',
113 'os_name': 'posix',
114 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
114 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
115 'sys_executable': '/usr/bin/python',
115 'sys_executable': '/usr/bin/python',
116 'sys_platform': 'linux2',
116 'sys_platform': 'linux2',
117 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
117 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
118 """
118 """
119 return pprint.pformat(get_sys_info())
119 return pprint.pformat(get_sys_info())
120
120
121 def _num_cpus_unix():
121 def _num_cpus_unix():
122 """Return the number of active CPUs on a Unix system."""
122 """Return the number of active CPUs on a Unix system."""
123 return os.sysconf("SC_NPROCESSORS_ONLN")
123 return os.sysconf("SC_NPROCESSORS_ONLN")
124
124
125
125
126 def _num_cpus_darwin():
126 def _num_cpus_darwin():
127 """Return the number of active CPUs on a Darwin system."""
127 """Return the number of active CPUs on a Darwin system."""
128 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
128 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
129 return p.stdout.read()
129 return p.stdout.read()
130
130
131
131
132 def _num_cpus_windows():
132 def _num_cpus_windows():
133 """Return the number of active CPUs on a Windows system."""
133 """Return the number of active CPUs on a Windows system."""
134 return os.environ.get("NUMBER_OF_PROCESSORS")
134 return os.environ.get("NUMBER_OF_PROCESSORS")
135
135
136
136
137 def num_cpus():
137 def num_cpus():
138 """Return the effective number of CPUs in the system as an integer.
138 """Return the effective number of CPUs in the system as an integer.
139
139
140 This cross-platform function makes an attempt at finding the total number of
140 This cross-platform function makes an attempt at finding the total number of
141 available CPUs in the system, as returned by various underlying system and
141 available CPUs in the system, as returned by various underlying system and
142 python calls.
142 python calls.
143
143
144 If it can't find a sensible answer, it returns 1 (though an error *may* make
144 If it can't find a sensible answer, it returns 1 (though an error *may* make
145 it return a large positive number that's actually incorrect).
145 it return a large positive number that's actually incorrect).
146 """
146 """
147
147
148 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
148 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
149 # for the names of the keys we needed to look up for this function. This
149 # for the names of the keys we needed to look up for this function. This
150 # code was inspired by their equivalent function.
150 # code was inspired by their equivalent function.
151
151
152 ncpufuncs = {'Linux':_num_cpus_unix,
152 ncpufuncs = {'Linux':_num_cpus_unix,
153 'Darwin':_num_cpus_darwin,
153 'Darwin':_num_cpus_darwin,
154 'Windows':_num_cpus_windows
154 'Windows':_num_cpus_windows
155 }
155 }
156
156
157 ncpufunc = ncpufuncs.get(platform.system(),
157 ncpufunc = ncpufuncs.get(platform.system(),
158 # default to unix version (Solaris, AIX, etc)
158 # default to unix version (Solaris, AIX, etc)
159 _num_cpus_unix)
159 _num_cpus_unix)
160
160
161 try:
161 try:
162 ncpus = max(1,int(ncpufunc()))
162 ncpus = max(1,int(ncpufunc()))
163 except:
163 except:
164 ncpus = 1
164 ncpus = 1
165 return ncpus
165 return ncpus
166
166
@@ -1,129 +1,129 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with terminals.
3 Utilities for working with terminals.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian E. Granger
7 * Brian E. Granger
8 * Fernando Perez
8 * Fernando Perez
9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 import os
15 import os
16 import sys
16 import sys
17 import warnings
17 import warnings
18 from shutil import get_terminal_size as _get_terminal_size
18 from shutil import get_terminal_size as _get_terminal_size
19
19
20 # This variable is part of the expected API of the module:
20 # This variable is part of the expected API of the module:
21 ignore_termtitle = True
21 ignore_termtitle = True
22
22
23
23
24
24
25 if os.name == 'posix':
25 if os.name == 'posix':
26 def _term_clear():
26 def _term_clear():
27 os.system('clear')
27 os.system('clear')
28 elif sys.platform == 'win32':
28 elif sys.platform == 'win32':
29 def _term_clear():
29 def _term_clear():
30 os.system('cls')
30 os.system('cls')
31 else:
31 else:
32 def _term_clear():
32 def _term_clear():
33 pass
33 pass
34
34
35
35
36
36
37 def toggle_set_term_title(val):
37 def toggle_set_term_title(val):
38 """Control whether set_term_title is active or not.
38 """Control whether set_term_title is active or not.
39
39
40 set_term_title() allows writing to the console titlebar. In embedded
40 set_term_title() allows writing to the console titlebar. In embedded
41 widgets this can cause problems, so this call can be used to toggle it on
41 widgets this can cause problems, so this call can be used to toggle it on
42 or off as needed.
42 or off as needed.
43
43
44 The default state of the module is for the function to be disabled.
44 The default state of the module is for the function to be disabled.
45
45
46 Parameters
46 Parameters
47 ----------
47 ----------
48 val : bool
48 val : bool
49 If True, set_term_title() actually writes to the terminal (using the
49 If True, set_term_title() actually writes to the terminal (using the
50 appropriate platform-specific module). If False, it is a no-op.
50 appropriate platform-specific module). If False, it is a no-op.
51 """
51 """
52 global ignore_termtitle
52 global ignore_termtitle
53 ignore_termtitle = not(val)
53 ignore_termtitle = not(val)
54
54
55
55
56 def _set_term_title(*args,**kw):
56 def _set_term_title(*args,**kw):
57 """Dummy no-op."""
57 """Dummy no-op."""
58 pass
58 pass
59
59
60
60
61 def _restore_term_title():
61 def _restore_term_title():
62 pass
62 pass
63
63
64
64
65 def _set_term_title_xterm(title):
65 def _set_term_title_xterm(title):
66 """ Change virtual terminal title in xterm-workalikes """
66 """ Change virtual terminal title in xterm-workalikes """
67 # save the current title to the xterm "stack"
67 # save the current title to the xterm "stack"
68 sys.stdout.write('\033[22;0t')
68 sys.stdout.write('\033[22;0t')
69 sys.stdout.write('\033]0;%s\007' % title)
69 sys.stdout.write('\033]0;%s\007' % title)
70
70
71
71
72 def _restore_term_title_xterm():
72 def _restore_term_title_xterm():
73 sys.stdout.write('\033[23;0t')
73 sys.stdout.write('\033[23;0t')
74
74
75
75
76 if os.name == 'posix':
76 if os.name == 'posix':
77 TERM = os.environ.get('TERM','')
77 TERM = os.environ.get('TERM','')
78 if TERM.startswith('xterm'):
78 if TERM.startswith('xterm'):
79 _set_term_title = _set_term_title_xterm
79 _set_term_title = _set_term_title_xterm
80 _restore_term_title = _restore_term_title_xterm
80 _restore_term_title = _restore_term_title_xterm
81 elif sys.platform == 'win32':
81 elif sys.platform == 'win32':
82 try:
82 try:
83 import ctypes
83 import ctypes
84
84
85 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
85 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
86 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
86 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
87
87
88 def _set_term_title(title):
88 def _set_term_title(title):
89 """Set terminal title using ctypes to access the Win32 APIs."""
89 """Set terminal title using ctypes to access the Win32 APIs."""
90 SetConsoleTitleW(title)
90 SetConsoleTitleW(title)
91 except ImportError:
91 except ImportError:
92 def _set_term_title(title):
92 def _set_term_title(title):
93 """Set terminal title using the 'title' command."""
93 """Set terminal title using the 'title' command."""
94 global ignore_termtitle
94 global ignore_termtitle
95
95
96 try:
96 try:
97 # Cannot be on network share when issuing system commands
97 # Cannot be on network share when issuing system commands
98 curr = os.getcwd()
98 curr = os.getcwd()
99 os.chdir("C:")
99 os.chdir("C:")
100 ret = os.system("title " + title)
100 ret = os.system("title " + title)
101 finally:
101 finally:
102 os.chdir(curr)
102 os.chdir(curr)
103 if ret:
103 if ret:
104 # non-zero return code signals error, don't try again
104 # non-zero return code signals error, don't try again
105 ignore_termtitle = True
105 ignore_termtitle = True
106
106
107
107
108 def set_term_title(title):
108 def set_term_title(title):
109 """Set terminal title using the necessary platform-dependent calls."""
109 """Set terminal title using the necessary platform-dependent calls."""
110 if ignore_termtitle:
110 if ignore_termtitle:
111 return
111 return
112 _set_term_title(title)
112 _set_term_title(title)
113
113
114
114
115 def restore_term_title():
115 def restore_term_title():
116 """Restore, if possible, terminal title to the original state"""
116 """Restore, if possible, terminal title to the original state"""
117 if ignore_termtitle:
117 if ignore_termtitle:
118 return
118 return
119 _restore_term_title()
119 _restore_term_title()
120
120
121
121
122 def freeze_term_title():
122 def freeze_term_title():
123 warnings.warn("This function is deprecated, use toggle_set_term_title()")
123 warnings.warn("This function is deprecated, use toggle_set_term_title()")
124 global ignore_termtitle
124 global ignore_termtitle
125 ignore_termtitle = True
125 ignore_termtitle = True
126
126
127
127
128 def get_terminal_size(defaultx=80, defaulty=25):
128 def get_terminal_size(defaultx=80, defaulty=25):
129 return _get_terminal_size((defaultx, defaulty))
129 return _get_terminal_size((defaultx, defaulty))
@@ -1,172 +1,172 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.capture"""
2 """Tests for IPython.utils.capture"""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15
15
16 import sys
16 import sys
17
17
18 import nose.tools as nt
18 import nose.tools as nt
19 import pytest
19 import pytest
20
20
21 from IPython.testing.decorators import skip_iptest_but_not_pytest
21 from IPython.testing.decorators import skip_iptest_but_not_pytest
22
22
23 from IPython.utils import capture
23 from IPython.utils import capture
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Globals
26 # Globals
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 _mime_map = dict(
29 _mime_map = dict(
30 _repr_png_="image/png",
30 _repr_png_="image/png",
31 _repr_jpeg_="image/jpeg",
31 _repr_jpeg_="image/jpeg",
32 _repr_svg_="image/svg+xml",
32 _repr_svg_="image/svg+xml",
33 _repr_html_="text/html",
33 _repr_html_="text/html",
34 _repr_json_="application/json",
34 _repr_json_="application/json",
35 _repr_javascript_="application/javascript",
35 _repr_javascript_="application/javascript",
36 )
36 )
37
37
38 basic_data = {
38 basic_data = {
39 'image/png' : b'binarydata',
39 'image/png' : b'binarydata',
40 'text/html' : "<b>bold</b>",
40 'text/html' : "<b>bold</b>",
41 }
41 }
42 basic_metadata = {
42 basic_metadata = {
43 'image/png' : {
43 'image/png' : {
44 'width' : 10,
44 'width' : 10,
45 'height' : 20,
45 'height' : 20,
46 },
46 },
47 }
47 }
48
48
49 full_data = {
49 full_data = {
50 'image/png' : b'binarydata',
50 'image/png' : b'binarydata',
51 'image/jpeg' : b'binarydata',
51 'image/jpeg' : b'binarydata',
52 'image/svg+xml' : "<svg>",
52 'image/svg+xml' : "<svg>",
53 'text/html' : "<b>bold</b>",
53 'text/html' : "<b>bold</b>",
54 'application/javascript' : "alert();",
54 'application/javascript' : "alert();",
55 'application/json' : "{}",
55 'application/json' : "{}",
56 }
56 }
57 full_metadata = {
57 full_metadata = {
58 'image/png' : {"png" : "exists"},
58 'image/png' : {"png" : "exists"},
59 'image/jpeg' : {"jpeg" : "exists"},
59 'image/jpeg' : {"jpeg" : "exists"},
60 'image/svg+xml' : {"svg" : "exists"},
60 'image/svg+xml' : {"svg" : "exists"},
61 'text/html' : {"html" : "exists"},
61 'text/html' : {"html" : "exists"},
62 'application/javascript' : {"js" : "exists"},
62 'application/javascript' : {"js" : "exists"},
63 'application/json' : {"json" : "exists"},
63 'application/json' : {"json" : "exists"},
64 }
64 }
65
65
66 hello_stdout = "hello, stdout"
66 hello_stdout = "hello, stdout"
67 hello_stderr = "hello, stderr"
67 hello_stderr = "hello, stderr"
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Test Functions
70 # Test Functions
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72 @pytest.mark.parametrize("method_mime", _mime_map.items())
72 @pytest.mark.parametrize("method_mime", _mime_map.items())
73 @skip_iptest_but_not_pytest
73 @skip_iptest_but_not_pytest
74 def test_rich_output_empty(method_mime):
74 def test_rich_output_empty(method_mime):
75 """RichOutput with no args"""
75 """RichOutput with no args"""
76 rich = capture.RichOutput()
76 rich = capture.RichOutput()
77 method, mime = method_mime
77 method, mime = method_mime
78 nt.assert_equal(getattr(rich, method)(), None)
78 nt.assert_equal(getattr(rich, method)(), None)
79
79
80 def test_rich_output():
80 def test_rich_output():
81 """test RichOutput basics"""
81 """test RichOutput basics"""
82 data = basic_data
82 data = basic_data
83 metadata = basic_metadata
83 metadata = basic_metadata
84 rich = capture.RichOutput(data=data, metadata=metadata)
84 rich = capture.RichOutput(data=data, metadata=metadata)
85 nt.assert_equal(rich._repr_html_(), data["text/html"])
85 nt.assert_equal(rich._repr_html_(), data["text/html"])
86 nt.assert_equal(rich._repr_png_(), (data["image/png"], metadata["image/png"]))
86 nt.assert_equal(rich._repr_png_(), (data["image/png"], metadata["image/png"]))
87 nt.assert_equal(rich._repr_latex_(), None)
87 nt.assert_equal(rich._repr_latex_(), None)
88 nt.assert_equal(rich._repr_javascript_(), None)
88 nt.assert_equal(rich._repr_javascript_(), None)
89 nt.assert_equal(rich._repr_svg_(), None)
89 nt.assert_equal(rich._repr_svg_(), None)
90
90
91
91
92 @skip_iptest_but_not_pytest
92 @skip_iptest_but_not_pytest
93 @pytest.mark.parametrize("method_mime", _mime_map.items())
93 @pytest.mark.parametrize("method_mime", _mime_map.items())
94 def test_rich_output_no_metadata(method_mime):
94 def test_rich_output_no_metadata(method_mime):
95 """test RichOutput with no metadata"""
95 """test RichOutput with no metadata"""
96 data = full_data
96 data = full_data
97 rich = capture.RichOutput(data=data)
97 rich = capture.RichOutput(data=data)
98 method, mime = method_mime
98 method, mime = method_mime
99 nt.assert_equal(getattr(rich, method)(), data[mime])
99 nt.assert_equal(getattr(rich, method)(), data[mime])
100
100
101
101
102 @skip_iptest_but_not_pytest
102 @skip_iptest_but_not_pytest
103 @pytest.mark.parametrize("method_mime", _mime_map.items())
103 @pytest.mark.parametrize("method_mime", _mime_map.items())
104 def test_rich_output_metadata(method_mime):
104 def test_rich_output_metadata(method_mime):
105 """test RichOutput with metadata"""
105 """test RichOutput with metadata"""
106 data = full_data
106 data = full_data
107 metadata = full_metadata
107 metadata = full_metadata
108 rich = capture.RichOutput(data=data, metadata=metadata)
108 rich = capture.RichOutput(data=data, metadata=metadata)
109 method, mime = method_mime
109 method, mime = method_mime
110 nt.assert_equal(getattr(rich, method)(), (data[mime], metadata[mime]))
110 nt.assert_equal(getattr(rich, method)(), (data[mime], metadata[mime]))
111
111
112 def test_rich_output_display():
112 def test_rich_output_display():
113 """test RichOutput.display
113 """test RichOutput.display
114
114
115 This is a bit circular, because we are actually using the capture code we are testing
115 This is a bit circular, because we are actually using the capture code we are testing
116 to test itself.
116 to test itself.
117 """
117 """
118 data = full_data
118 data = full_data
119 rich = capture.RichOutput(data=data)
119 rich = capture.RichOutput(data=data)
120 with capture.capture_output() as cap:
120 with capture.capture_output() as cap:
121 rich.display()
121 rich.display()
122 nt.assert_equal(len(cap.outputs), 1)
122 nt.assert_equal(len(cap.outputs), 1)
123 rich2 = cap.outputs[0]
123 rich2 = cap.outputs[0]
124 nt.assert_equal(rich2.data, rich.data)
124 nt.assert_equal(rich2.data, rich.data)
125 nt.assert_equal(rich2.metadata, rich.metadata)
125 nt.assert_equal(rich2.metadata, rich.metadata)
126
126
127 def test_capture_output():
127 def test_capture_output():
128 """capture_output works"""
128 """capture_output works"""
129 rich = capture.RichOutput(data=full_data)
129 rich = capture.RichOutput(data=full_data)
130 with capture.capture_output() as cap:
130 with capture.capture_output() as cap:
131 print(hello_stdout, end="")
131 print(hello_stdout, end="")
132 print(hello_stderr, end="", file=sys.stderr)
132 print(hello_stderr, end="", file=sys.stderr)
133 rich.display()
133 rich.display()
134 nt.assert_equal(hello_stdout, cap.stdout)
134 nt.assert_equal(hello_stdout, cap.stdout)
135 nt.assert_equal(hello_stderr, cap.stderr)
135 nt.assert_equal(hello_stderr, cap.stderr)
136
136
137
137
138 def test_capture_output_no_stdout():
138 def test_capture_output_no_stdout():
139 """test capture_output(stdout=False)"""
139 """test capture_output(stdout=False)"""
140 rich = capture.RichOutput(data=full_data)
140 rich = capture.RichOutput(data=full_data)
141 with capture.capture_output(stdout=False) as cap:
141 with capture.capture_output(stdout=False) as cap:
142 print(hello_stdout, end="")
142 print(hello_stdout, end="")
143 print(hello_stderr, end="", file=sys.stderr)
143 print(hello_stderr, end="", file=sys.stderr)
144 rich.display()
144 rich.display()
145 nt.assert_equal("", cap.stdout)
145 nt.assert_equal("", cap.stdout)
146 nt.assert_equal(hello_stderr, cap.stderr)
146 nt.assert_equal(hello_stderr, cap.stderr)
147 nt.assert_equal(len(cap.outputs), 1)
147 nt.assert_equal(len(cap.outputs), 1)
148
148
149
149
150 def test_capture_output_no_stderr():
150 def test_capture_output_no_stderr():
151 """test capture_output(stderr=False)"""
151 """test capture_output(stderr=False)"""
152 rich = capture.RichOutput(data=full_data)
152 rich = capture.RichOutput(data=full_data)
153 # add nested capture_output so stderr doesn't make it to nose output
153 # add nested capture_output so stderr doesn't make it to nose output
154 with capture.capture_output(), capture.capture_output(stderr=False) as cap:
154 with capture.capture_output(), capture.capture_output(stderr=False) as cap:
155 print(hello_stdout, end="")
155 print(hello_stdout, end="")
156 print(hello_stderr, end="", file=sys.stderr)
156 print(hello_stderr, end="", file=sys.stderr)
157 rich.display()
157 rich.display()
158 nt.assert_equal(hello_stdout, cap.stdout)
158 nt.assert_equal(hello_stdout, cap.stdout)
159 nt.assert_equal("", cap.stderr)
159 nt.assert_equal("", cap.stderr)
160 nt.assert_equal(len(cap.outputs), 1)
160 nt.assert_equal(len(cap.outputs), 1)
161
161
162
162
163 def test_capture_output_no_display():
163 def test_capture_output_no_display():
164 """test capture_output(display=False)"""
164 """test capture_output(display=False)"""
165 rich = capture.RichOutput(data=full_data)
165 rich = capture.RichOutput(data=full_data)
166 with capture.capture_output(display=False) as cap:
166 with capture.capture_output(display=False) as cap:
167 print(hello_stdout, end="")
167 print(hello_stdout, end="")
168 print(hello_stderr, end="", file=sys.stderr)
168 print(hello_stderr, end="", file=sys.stderr)
169 rich.display()
169 rich.display()
170 nt.assert_equal(hello_stdout, cap.stdout)
170 nt.assert_equal(hello_stdout, cap.stdout)
171 nt.assert_equal(hello_stderr, cap.stderr)
171 nt.assert_equal(hello_stderr, cap.stderr)
172 nt.assert_equal(cap.outputs, [])
172 nt.assert_equal(cap.outputs, [])
@@ -1,111 +1,111 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.module_paths.py"""
2 """Tests for IPython.utils.module_paths.py"""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2011 The IPython Development Team
5 # Copyright (C) 2008-2011 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import shutil
15 import shutil
16 import sys
16 import sys
17 import tempfile
17 import tempfile
18
18
19 from pathlib import Path
19 from pathlib import Path
20
20
21 from IPython.testing.tools import make_tempfile
21 from IPython.testing.tools import make_tempfile
22
22
23 import IPython.utils.module_paths as mp
23 import IPython.utils.module_paths as mp
24
24
25 import nose.tools as nt
25 import nose.tools as nt
26
26
27 TEST_FILE_PATH = Path(__file__).resolve().parent
27 TEST_FILE_PATH = Path(__file__).resolve().parent
28
28
29 TMP_TEST_DIR = Path(tempfile.mkdtemp(suffix="with.dot"))
29 TMP_TEST_DIR = Path(tempfile.mkdtemp(suffix="with.dot"))
30 #
30 #
31 # Setup/teardown functions/decorators
31 # Setup/teardown functions/decorators
32 #
32 #
33
33
34 old_syspath = sys.path
34 old_syspath = sys.path
35
35
36 def make_empty_file(fname):
36 def make_empty_file(fname):
37 open(fname, 'w').close()
37 open(fname, 'w').close()
38
38
39
39
40 def setup_module():
40 def setup_module():
41 """Setup testenvironment for the module:
41 """Setup testenvironment for the module:
42
42
43 """
43 """
44 # Do not mask exceptions here. In particular, catching WindowsError is a
44 # Do not mask exceptions here. In particular, catching WindowsError is a
45 # problem because that exception is only defined on Windows...
45 # problem because that exception is only defined on Windows...
46 Path(TMP_TEST_DIR / "xmod").mkdir(parents=True)
46 Path(TMP_TEST_DIR / "xmod").mkdir(parents=True)
47 Path(TMP_TEST_DIR / "nomod").mkdir(parents=True)
47 Path(TMP_TEST_DIR / "nomod").mkdir(parents=True)
48 make_empty_file(TMP_TEST_DIR / "xmod/__init__.py")
48 make_empty_file(TMP_TEST_DIR / "xmod/__init__.py")
49 make_empty_file(TMP_TEST_DIR / "xmod/sub.py")
49 make_empty_file(TMP_TEST_DIR / "xmod/sub.py")
50 make_empty_file(TMP_TEST_DIR / "pack.py")
50 make_empty_file(TMP_TEST_DIR / "pack.py")
51 make_empty_file(TMP_TEST_DIR / "packpyc.pyc")
51 make_empty_file(TMP_TEST_DIR / "packpyc.pyc")
52 sys.path = [str(TMP_TEST_DIR)]
52 sys.path = [str(TMP_TEST_DIR)]
53
53
54 def teardown_module():
54 def teardown_module():
55 """Teardown testenvironment for the module:
55 """Teardown testenvironment for the module:
56
56
57 - Remove tempdir
57 - Remove tempdir
58 - restore sys.path
58 - restore sys.path
59 """
59 """
60 # Note: we remove the parent test dir, which is the root of all test
60 # Note: we remove the parent test dir, which is the root of all test
61 # subdirs we may have created. Use shutil instead of os.removedirs, so
61 # subdirs we may have created. Use shutil instead of os.removedirs, so
62 # that non-empty directories are all recursively removed.
62 # that non-empty directories are all recursively removed.
63 shutil.rmtree(TMP_TEST_DIR)
63 shutil.rmtree(TMP_TEST_DIR)
64 sys.path = old_syspath
64 sys.path = old_syspath
65
65
66 def test_tempdir():
66 def test_tempdir():
67 """
67 """
68 Ensure the test are done with a temporary file that have a dot somewhere.
68 Ensure the test are done with a temporary file that have a dot somewhere.
69 """
69 """
70 nt.assert_in(".", str(TMP_TEST_DIR))
70 nt.assert_in(".", str(TMP_TEST_DIR))
71
71
72
72
73 def test_find_mod_1():
73 def test_find_mod_1():
74 """
74 """
75 Search for a directory's file path.
75 Search for a directory's file path.
76 Expected output: a path to that directory's __init__.py file.
76 Expected output: a path to that directory's __init__.py file.
77 """
77 """
78 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
78 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
79 nt.assert_equal(Path(mp.find_mod("xmod")), modpath)
79 nt.assert_equal(Path(mp.find_mod("xmod")), modpath)
80
80
81 def test_find_mod_2():
81 def test_find_mod_2():
82 """
82 """
83 Search for a directory's file path.
83 Search for a directory's file path.
84 Expected output: a path to that directory's __init__.py file.
84 Expected output: a path to that directory's __init__.py file.
85 TODO: Confirm why this is a duplicate test.
85 TODO: Confirm why this is a duplicate test.
86 """
86 """
87 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
87 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
88 nt.assert_equal(Path(mp.find_mod("xmod")), modpath)
88 nt.assert_equal(Path(mp.find_mod("xmod")), modpath)
89
89
90 def test_find_mod_3():
90 def test_find_mod_3():
91 """
91 """
92 Search for a directory + a filename without its .py extension
92 Search for a directory + a filename without its .py extension
93 Expected output: full path with .py extension.
93 Expected output: full path with .py extension.
94 """
94 """
95 modpath = TMP_TEST_DIR / "xmod" / "sub.py"
95 modpath = TMP_TEST_DIR / "xmod" / "sub.py"
96 nt.assert_equal(Path(mp.find_mod("xmod.sub")), modpath)
96 nt.assert_equal(Path(mp.find_mod("xmod.sub")), modpath)
97
97
98 def test_find_mod_4():
98 def test_find_mod_4():
99 """
99 """
100 Search for a filename without its .py extension
100 Search for a filename without its .py extension
101 Expected output: full path with .py extension
101 Expected output: full path with .py extension
102 """
102 """
103 modpath = TMP_TEST_DIR / "pack.py"
103 modpath = TMP_TEST_DIR / "pack.py"
104 nt.assert_equal(Path(mp.find_mod("pack")), modpath)
104 nt.assert_equal(Path(mp.find_mod("pack")), modpath)
105
105
106 def test_find_mod_5():
106 def test_find_mod_5():
107 """
107 """
108 Search for a filename with a .pyc extension
108 Search for a filename with a .pyc extension
109 Expected output: TODO: do we exclude or include .pyc files?
109 Expected output: TODO: do we exclude or include .pyc files?
110 """
110 """
111 nt.assert_equal(mp.find_mod("packpyc"), None)
111 nt.assert_equal(mp.find_mod("packpyc"), None)
@@ -1,492 +1,492 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import shutil
8 import shutil
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11 import unittest
11 import unittest
12 from contextlib import contextmanager
12 from contextlib import contextmanager
13 from unittest.mock import patch
13 from unittest.mock import patch
14 from os.path import join, abspath
14 from os.path import join, abspath
15 from imp import reload
15 from imp import reload
16
16
17 from nose import SkipTest, with_setup
17 from nose import SkipTest, with_setup
18 import nose.tools as nt
18 import nose.tools as nt
19
19
20 import IPython
20 import IPython
21 from IPython import paths
21 from IPython import paths
22 from IPython.testing import decorators as dec
22 from IPython.testing import decorators as dec
23 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
23 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
24 onlyif_unicode_paths,
24 onlyif_unicode_paths,
25 skip_win32_py38,)
25 skip_win32_py38,)
26 from IPython.testing.tools import make_tempfile
26 from IPython.testing.tools import make_tempfile
27 from IPython.utils import path
27 from IPython.utils import path
28 from IPython.utils.tempdir import TemporaryDirectory
28 from IPython.utils.tempdir import TemporaryDirectory
29
29
30
30
31 # Platform-dependent imports
31 # Platform-dependent imports
32 try:
32 try:
33 import winreg as wreg
33 import winreg as wreg
34 except ImportError:
34 except ImportError:
35 #Fake _winreg module on non-windows platforms
35 #Fake _winreg module on non-windows platforms
36 import types
36 import types
37 wr_name = "winreg"
37 wr_name = "winreg"
38 sys.modules[wr_name] = types.ModuleType(wr_name)
38 sys.modules[wr_name] = types.ModuleType(wr_name)
39 try:
39 try:
40 import winreg as wreg
40 import winreg as wreg
41 except ImportError:
41 except ImportError:
42 import _winreg as wreg
42 import _winreg as wreg
43 #Add entries that needs to be stubbed by the testing code
43 #Add entries that needs to be stubbed by the testing code
44 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
44 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Globals
47 # Globals
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 env = os.environ
49 env = os.environ
50 TMP_TEST_DIR = tempfile.mkdtemp()
50 TMP_TEST_DIR = tempfile.mkdtemp()
51 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
51 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
52 #
52 #
53 # Setup/teardown functions/decorators
53 # Setup/teardown functions/decorators
54 #
54 #
55
55
56 def setup_module():
56 def setup_module():
57 """Setup testenvironment for the module:
57 """Setup testenvironment for the module:
58
58
59 - Adds dummy home dir tree
59 - Adds dummy home dir tree
60 """
60 """
61 # Do not mask exceptions here. In particular, catching WindowsError is a
61 # Do not mask exceptions here. In particular, catching WindowsError is a
62 # problem because that exception is only defined on Windows...
62 # problem because that exception is only defined on Windows...
63 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
63 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
64
64
65
65
66 def teardown_module():
66 def teardown_module():
67 """Teardown testenvironment for the module:
67 """Teardown testenvironment for the module:
68
68
69 - Remove dummy home dir tree
69 - Remove dummy home dir tree
70 """
70 """
71 # Note: we remove the parent test dir, which is the root of all test
71 # Note: we remove the parent test dir, which is the root of all test
72 # subdirs we may have created. Use shutil instead of os.removedirs, so
72 # subdirs we may have created. Use shutil instead of os.removedirs, so
73 # that non-empty directories are all recursively removed.
73 # that non-empty directories are all recursively removed.
74 shutil.rmtree(TMP_TEST_DIR)
74 shutil.rmtree(TMP_TEST_DIR)
75
75
76
76
77 def setup_environment():
77 def setup_environment():
78 """Setup testenvironment for some functions that are tested
78 """Setup testenvironment for some functions that are tested
79 in this module. In particular this functions stores attributes
79 in this module. In particular this functions stores attributes
80 and other things that we need to stub in some test functions.
80 and other things that we need to stub in some test functions.
81 This needs to be done on a function level and not module level because
81 This needs to be done on a function level and not module level because
82 each testfunction needs a pristine environment.
82 each testfunction needs a pristine environment.
83 """
83 """
84 global oldstuff, platformstuff
84 global oldstuff, platformstuff
85 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
85 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
86
86
87 def teardown_environment():
87 def teardown_environment():
88 """Restore things that were remembered by the setup_environment function
88 """Restore things that were remembered by the setup_environment function
89 """
89 """
90 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
90 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
91 os.chdir(old_wd)
91 os.chdir(old_wd)
92 reload(path)
92 reload(path)
93
93
94 for key in list(env):
94 for key in list(env):
95 if key not in oldenv:
95 if key not in oldenv:
96 del env[key]
96 del env[key]
97 env.update(oldenv)
97 env.update(oldenv)
98 if hasattr(sys, 'frozen'):
98 if hasattr(sys, 'frozen'):
99 del sys.frozen
99 del sys.frozen
100
100
101 # Build decorator that uses the setup_environment/setup_environment
101 # Build decorator that uses the setup_environment/setup_environment
102 with_environment = with_setup(setup_environment, teardown_environment)
102 with_environment = with_setup(setup_environment, teardown_environment)
103
103
104 @skip_if_not_win32
104 @skip_if_not_win32
105 @with_environment
105 @with_environment
106 def test_get_home_dir_1():
106 def test_get_home_dir_1():
107 """Testcase for py2exe logic, un-compressed lib
107 """Testcase for py2exe logic, un-compressed lib
108 """
108 """
109 unfrozen = path.get_home_dir()
109 unfrozen = path.get_home_dir()
110 sys.frozen = True
110 sys.frozen = True
111
111
112 #fake filename for IPython.__init__
112 #fake filename for IPython.__init__
113 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
113 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
114
114
115 home_dir = path.get_home_dir()
115 home_dir = path.get_home_dir()
116 nt.assert_equal(home_dir, unfrozen)
116 nt.assert_equal(home_dir, unfrozen)
117
117
118
118
119 @skip_if_not_win32
119 @skip_if_not_win32
120 @with_environment
120 @with_environment
121 def test_get_home_dir_2():
121 def test_get_home_dir_2():
122 """Testcase for py2exe logic, compressed lib
122 """Testcase for py2exe logic, compressed lib
123 """
123 """
124 unfrozen = path.get_home_dir()
124 unfrozen = path.get_home_dir()
125 sys.frozen = True
125 sys.frozen = True
126 #fake filename for IPython.__init__
126 #fake filename for IPython.__init__
127 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
127 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
128
128
129 home_dir = path.get_home_dir(True)
129 home_dir = path.get_home_dir(True)
130 nt.assert_equal(home_dir, unfrozen)
130 nt.assert_equal(home_dir, unfrozen)
131
131
132
132
133 @skip_win32_py38
133 @skip_win32_py38
134 @with_environment
134 @with_environment
135 def test_get_home_dir_3():
135 def test_get_home_dir_3():
136 """get_home_dir() uses $HOME if set"""
136 """get_home_dir() uses $HOME if set"""
137 env["HOME"] = HOME_TEST_DIR
137 env["HOME"] = HOME_TEST_DIR
138 home_dir = path.get_home_dir(True)
138 home_dir = path.get_home_dir(True)
139 # get_home_dir expands symlinks
139 # get_home_dir expands symlinks
140 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
140 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
141
141
142
142
143 @with_environment
143 @with_environment
144 def test_get_home_dir_4():
144 def test_get_home_dir_4():
145 """get_home_dir() still works if $HOME is not set"""
145 """get_home_dir() still works if $HOME is not set"""
146
146
147 if 'HOME' in env: del env['HOME']
147 if 'HOME' in env: del env['HOME']
148 # this should still succeed, but we don't care what the answer is
148 # this should still succeed, but we don't care what the answer is
149 home = path.get_home_dir(False)
149 home = path.get_home_dir(False)
150
150
151 @skip_win32_py38
151 @skip_win32_py38
152 @with_environment
152 @with_environment
153 def test_get_home_dir_5():
153 def test_get_home_dir_5():
154 """raise HomeDirError if $HOME is specified, but not a writable dir"""
154 """raise HomeDirError if $HOME is specified, but not a writable dir"""
155 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
155 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
156 # set os.name = posix, to prevent My Documents fallback on Windows
156 # set os.name = posix, to prevent My Documents fallback on Windows
157 os.name = 'posix'
157 os.name = 'posix'
158 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
158 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
159
159
160 # Should we stub wreg fully so we can run the test on all platforms?
160 # Should we stub wreg fully so we can run the test on all platforms?
161 @skip_if_not_win32
161 @skip_if_not_win32
162 @with_environment
162 @with_environment
163 def test_get_home_dir_8():
163 def test_get_home_dir_8():
164 """Using registry hack for 'My Documents', os=='nt'
164 """Using registry hack for 'My Documents', os=='nt'
165
165
166 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
166 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
167 """
167 """
168 os.name = 'nt'
168 os.name = 'nt'
169 # Remove from stub environment all keys that may be set
169 # Remove from stub environment all keys that may be set
170 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
170 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
171 env.pop(key, None)
171 env.pop(key, None)
172
172
173 class key:
173 class key:
174 def __enter__(self):
174 def __enter__(self):
175 pass
175 pass
176 def Close(self):
176 def Close(self):
177 pass
177 pass
178 def __exit__(*args, **kwargs):
178 def __exit__(*args, **kwargs):
179 pass
179 pass
180
180
181 with patch.object(wreg, 'OpenKey', return_value=key()), \
181 with patch.object(wreg, 'OpenKey', return_value=key()), \
182 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
182 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
183 home_dir = path.get_home_dir()
183 home_dir = path.get_home_dir()
184 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
184 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
185
185
186 @with_environment
186 @with_environment
187 def test_get_xdg_dir_0():
187 def test_get_xdg_dir_0():
188 """test_get_xdg_dir_0, check xdg_dir"""
188 """test_get_xdg_dir_0, check xdg_dir"""
189 reload(path)
189 reload(path)
190 path._writable_dir = lambda path: True
190 path._writable_dir = lambda path: True
191 path.get_home_dir = lambda : 'somewhere'
191 path.get_home_dir = lambda : 'somewhere'
192 os.name = "posix"
192 os.name = "posix"
193 sys.platform = "linux2"
193 sys.platform = "linux2"
194 env.pop('IPYTHON_DIR', None)
194 env.pop('IPYTHON_DIR', None)
195 env.pop('IPYTHONDIR', None)
195 env.pop('IPYTHONDIR', None)
196 env.pop('XDG_CONFIG_HOME', None)
196 env.pop('XDG_CONFIG_HOME', None)
197
197
198 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
198 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
199
199
200
200
201 @with_environment
201 @with_environment
202 def test_get_xdg_dir_1():
202 def test_get_xdg_dir_1():
203 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
203 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
204 reload(path)
204 reload(path)
205 path.get_home_dir = lambda : HOME_TEST_DIR
205 path.get_home_dir = lambda : HOME_TEST_DIR
206 os.name = "posix"
206 os.name = "posix"
207 sys.platform = "linux2"
207 sys.platform = "linux2"
208 env.pop('IPYTHON_DIR', None)
208 env.pop('IPYTHON_DIR', None)
209 env.pop('IPYTHONDIR', None)
209 env.pop('IPYTHONDIR', None)
210 env.pop('XDG_CONFIG_HOME', None)
210 env.pop('XDG_CONFIG_HOME', None)
211 nt.assert_equal(path.get_xdg_dir(), None)
211 nt.assert_equal(path.get_xdg_dir(), None)
212
212
213 @with_environment
213 @with_environment
214 def test_get_xdg_dir_2():
214 def test_get_xdg_dir_2():
215 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
215 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
216 reload(path)
216 reload(path)
217 path.get_home_dir = lambda : HOME_TEST_DIR
217 path.get_home_dir = lambda : HOME_TEST_DIR
218 os.name = "posix"
218 os.name = "posix"
219 sys.platform = "linux2"
219 sys.platform = "linux2"
220 env.pop('IPYTHON_DIR', None)
220 env.pop('IPYTHON_DIR', None)
221 env.pop('IPYTHONDIR', None)
221 env.pop('IPYTHONDIR', None)
222 env.pop('XDG_CONFIG_HOME', None)
222 env.pop('XDG_CONFIG_HOME', None)
223 cfgdir=os.path.join(path.get_home_dir(), '.config')
223 cfgdir=os.path.join(path.get_home_dir(), '.config')
224 if not os.path.exists(cfgdir):
224 if not os.path.exists(cfgdir):
225 os.makedirs(cfgdir)
225 os.makedirs(cfgdir)
226
226
227 nt.assert_equal(path.get_xdg_dir(), cfgdir)
227 nt.assert_equal(path.get_xdg_dir(), cfgdir)
228
228
229 @with_environment
229 @with_environment
230 def test_get_xdg_dir_3():
230 def test_get_xdg_dir_3():
231 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
231 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
232 reload(path)
232 reload(path)
233 path.get_home_dir = lambda : HOME_TEST_DIR
233 path.get_home_dir = lambda : HOME_TEST_DIR
234 os.name = "posix"
234 os.name = "posix"
235 sys.platform = "darwin"
235 sys.platform = "darwin"
236 env.pop('IPYTHON_DIR', None)
236 env.pop('IPYTHON_DIR', None)
237 env.pop('IPYTHONDIR', None)
237 env.pop('IPYTHONDIR', None)
238 env.pop('XDG_CONFIG_HOME', None)
238 env.pop('XDG_CONFIG_HOME', None)
239 cfgdir=os.path.join(path.get_home_dir(), '.config')
239 cfgdir=os.path.join(path.get_home_dir(), '.config')
240 if not os.path.exists(cfgdir):
240 if not os.path.exists(cfgdir):
241 os.makedirs(cfgdir)
241 os.makedirs(cfgdir)
242
242
243 nt.assert_equal(path.get_xdg_dir(), None)
243 nt.assert_equal(path.get_xdg_dir(), None)
244
244
245 def test_filefind():
245 def test_filefind():
246 """Various tests for filefind"""
246 """Various tests for filefind"""
247 f = tempfile.NamedTemporaryFile()
247 f = tempfile.NamedTemporaryFile()
248 # print 'fname:',f.name
248 # print 'fname:',f.name
249 alt_dirs = paths.get_ipython_dir()
249 alt_dirs = paths.get_ipython_dir()
250 t = path.filefind(f.name, alt_dirs)
250 t = path.filefind(f.name, alt_dirs)
251 # print 'found:',t
251 # print 'found:',t
252
252
253
253
254 @dec.skip_if_not_win32
254 @dec.skip_if_not_win32
255 def test_get_long_path_name_win32():
255 def test_get_long_path_name_win32():
256 with TemporaryDirectory() as tmpdir:
256 with TemporaryDirectory() as tmpdir:
257
257
258 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
258 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
259 # path component, so ensure we include the long form of it
259 # path component, so ensure we include the long form of it
260 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
260 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
261 os.makedirs(long_path)
261 os.makedirs(long_path)
262
262
263 # Test to see if the short path evaluates correctly.
263 # Test to see if the short path evaluates correctly.
264 short_path = os.path.join(tmpdir, 'THISIS~1')
264 short_path = os.path.join(tmpdir, 'THISIS~1')
265 evaluated_path = path.get_long_path_name(short_path)
265 evaluated_path = path.get_long_path_name(short_path)
266 nt.assert_equal(evaluated_path.lower(), long_path.lower())
266 nt.assert_equal(evaluated_path.lower(), long_path.lower())
267
267
268
268
269 @dec.skip_win32
269 @dec.skip_win32
270 def test_get_long_path_name():
270 def test_get_long_path_name():
271 p = path.get_long_path_name('/usr/local')
271 p = path.get_long_path_name('/usr/local')
272 nt.assert_equal(p,'/usr/local')
272 nt.assert_equal(p,'/usr/local')
273
273
274
274
275 class TestRaiseDeprecation(unittest.TestCase):
275 class TestRaiseDeprecation(unittest.TestCase):
276
276
277 @dec.skip_win32 # can't create not-user-writable dir on win
277 @dec.skip_win32 # can't create not-user-writable dir on win
278 @with_environment
278 @with_environment
279 def test_not_writable_ipdir(self):
279 def test_not_writable_ipdir(self):
280 tmpdir = tempfile.mkdtemp()
280 tmpdir = tempfile.mkdtemp()
281 os.name = "posix"
281 os.name = "posix"
282 env.pop('IPYTHON_DIR', None)
282 env.pop('IPYTHON_DIR', None)
283 env.pop('IPYTHONDIR', None)
283 env.pop('IPYTHONDIR', None)
284 env.pop('XDG_CONFIG_HOME', None)
284 env.pop('XDG_CONFIG_HOME', None)
285 env['HOME'] = tmpdir
285 env['HOME'] = tmpdir
286 ipdir = os.path.join(tmpdir, '.ipython')
286 ipdir = os.path.join(tmpdir, '.ipython')
287 os.mkdir(ipdir, 0o555)
287 os.mkdir(ipdir, 0o555)
288 try:
288 try:
289 open(os.path.join(ipdir, "_foo_"), 'w').close()
289 open(os.path.join(ipdir, "_foo_"), 'w').close()
290 except IOError:
290 except IOError:
291 pass
291 pass
292 else:
292 else:
293 # I can still write to an unwritable dir,
293 # I can still write to an unwritable dir,
294 # assume I'm root and skip the test
294 # assume I'm root and skip the test
295 raise SkipTest("I can't create directories that I can't write to")
295 raise SkipTest("I can't create directories that I can't write to")
296 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
296 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
297 ipdir = paths.get_ipython_dir()
297 ipdir = paths.get_ipython_dir()
298 env.pop('IPYTHON_DIR', None)
298 env.pop('IPYTHON_DIR', None)
299
299
300 @with_environment
300 @with_environment
301 def test_get_py_filename():
301 def test_get_py_filename():
302 os.chdir(TMP_TEST_DIR)
302 os.chdir(TMP_TEST_DIR)
303 with make_tempfile('foo.py'):
303 with make_tempfile('foo.py'):
304 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
304 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
305 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
305 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
306 with make_tempfile('foo'):
306 with make_tempfile('foo'):
307 nt.assert_equal(path.get_py_filename('foo'), 'foo')
307 nt.assert_equal(path.get_py_filename('foo'), 'foo')
308 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
308 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
309 nt.assert_raises(IOError, path.get_py_filename, 'foo')
309 nt.assert_raises(IOError, path.get_py_filename, 'foo')
310 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
310 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
311 true_fn = 'foo with spaces.py'
311 true_fn = 'foo with spaces.py'
312 with make_tempfile(true_fn):
312 with make_tempfile(true_fn):
313 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
313 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
314 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
314 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
315 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
315 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
316 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
316 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
317
317
318 @onlyif_unicode_paths
318 @onlyif_unicode_paths
319 def test_unicode_in_filename():
319 def test_unicode_in_filename():
320 """When a file doesn't exist, the exception raised should be safe to call
320 """When a file doesn't exist, the exception raised should be safe to call
321 str() on - i.e. in Python 2 it must only have ASCII characters.
321 str() on - i.e. in Python 2 it must only have ASCII characters.
322
322
323 https://github.com/ipython/ipython/issues/875
323 https://github.com/ipython/ipython/issues/875
324 """
324 """
325 try:
325 try:
326 # these calls should not throw unicode encode exceptions
326 # these calls should not throw unicode encode exceptions
327 path.get_py_filename('fooéè.py')
327 path.get_py_filename('fooéè.py')
328 except IOError as ex:
328 except IOError as ex:
329 str(ex)
329 str(ex)
330
330
331
331
332 class TestShellGlob(unittest.TestCase):
332 class TestShellGlob(unittest.TestCase):
333
333
334 @classmethod
334 @classmethod
335 def setUpClass(cls):
335 def setUpClass(cls):
336 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
336 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
337 cls.filenames_end_with_b = ['0b', '1b', '2b']
337 cls.filenames_end_with_b = ['0b', '1b', '2b']
338 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
338 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
339 cls.tempdir = TemporaryDirectory()
339 cls.tempdir = TemporaryDirectory()
340 td = cls.tempdir.name
340 td = cls.tempdir.name
341
341
342 with cls.in_tempdir():
342 with cls.in_tempdir():
343 # Create empty files
343 # Create empty files
344 for fname in cls.filenames:
344 for fname in cls.filenames:
345 open(os.path.join(td, fname), 'w').close()
345 open(os.path.join(td, fname), 'w').close()
346
346
347 @classmethod
347 @classmethod
348 def tearDownClass(cls):
348 def tearDownClass(cls):
349 cls.tempdir.cleanup()
349 cls.tempdir.cleanup()
350
350
351 @classmethod
351 @classmethod
352 @contextmanager
352 @contextmanager
353 def in_tempdir(cls):
353 def in_tempdir(cls):
354 save = os.getcwd()
354 save = os.getcwd()
355 try:
355 try:
356 os.chdir(cls.tempdir.name)
356 os.chdir(cls.tempdir.name)
357 yield
357 yield
358 finally:
358 finally:
359 os.chdir(save)
359 os.chdir(save)
360
360
361 def check_match(self, patterns, matches):
361 def check_match(self, patterns, matches):
362 with self.in_tempdir():
362 with self.in_tempdir():
363 # glob returns unordered list. that's why sorted is required.
363 # glob returns unordered list. that's why sorted is required.
364 nt.assert_equal(sorted(path.shellglob(patterns)),
364 nt.assert_equal(sorted(path.shellglob(patterns)),
365 sorted(matches))
365 sorted(matches))
366
366
367 def common_cases(self):
367 def common_cases(self):
368 return [
368 return [
369 (['*'], self.filenames),
369 (['*'], self.filenames),
370 (['a*'], self.filenames_start_with_a),
370 (['a*'], self.filenames_start_with_a),
371 (['*c'], ['*c']),
371 (['*c'], ['*c']),
372 (['*', 'a*', '*b', '*c'], self.filenames
372 (['*', 'a*', '*b', '*c'], self.filenames
373 + self.filenames_start_with_a
373 + self.filenames_start_with_a
374 + self.filenames_end_with_b
374 + self.filenames_end_with_b
375 + ['*c']),
375 + ['*c']),
376 (['a[012]'], self.filenames_start_with_a),
376 (['a[012]'], self.filenames_start_with_a),
377 ]
377 ]
378
378
379 @skip_win32
379 @skip_win32
380 def test_match_posix(self):
380 def test_match_posix(self):
381 for (patterns, matches) in self.common_cases() + [
381 for (patterns, matches) in self.common_cases() + [
382 ([r'\*'], ['*']),
382 ([r'\*'], ['*']),
383 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
383 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
384 ([r'a\[012]'], ['a[012]']),
384 ([r'a\[012]'], ['a[012]']),
385 ]:
385 ]:
386 yield (self.check_match, patterns, matches)
386 yield (self.check_match, patterns, matches)
387
387
388 @skip_if_not_win32
388 @skip_if_not_win32
389 def test_match_windows(self):
389 def test_match_windows(self):
390 for (patterns, matches) in self.common_cases() + [
390 for (patterns, matches) in self.common_cases() + [
391 # In windows, backslash is interpreted as path
391 # In windows, backslash is interpreted as path
392 # separator. Therefore, you can't escape glob
392 # separator. Therefore, you can't escape glob
393 # using it.
393 # using it.
394 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
394 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
395 ([r'a\[012]'], [r'a\[012]']),
395 ([r'a\[012]'], [r'a\[012]']),
396 ]:
396 ]:
397 yield (self.check_match, patterns, matches)
397 yield (self.check_match, patterns, matches)
398
398
399
399
400 def test_unescape_glob():
400 def test_unescape_glob():
401 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
401 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
402 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
402 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
403 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
403 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
404 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
404 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
405 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
405 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
406
406
407
407
408 @onlyif_unicode_paths
408 @onlyif_unicode_paths
409 def test_ensure_dir_exists():
409 def test_ensure_dir_exists():
410 with TemporaryDirectory() as td:
410 with TemporaryDirectory() as td:
411 d = os.path.join(td, 'βˆ‚ir')
411 d = os.path.join(td, 'βˆ‚ir')
412 path.ensure_dir_exists(d) # create it
412 path.ensure_dir_exists(d) # create it
413 assert os.path.isdir(d)
413 assert os.path.isdir(d)
414 path.ensure_dir_exists(d) # no-op
414 path.ensure_dir_exists(d) # no-op
415 f = os.path.join(td, 'Ζ’ile')
415 f = os.path.join(td, 'Ζ’ile')
416 open(f, 'w').close() # touch
416 open(f, 'w').close() # touch
417 with nt.assert_raises(IOError):
417 with nt.assert_raises(IOError):
418 path.ensure_dir_exists(f)
418 path.ensure_dir_exists(f)
419
419
420 class TestLinkOrCopy(unittest.TestCase):
420 class TestLinkOrCopy(unittest.TestCase):
421 def setUp(self):
421 def setUp(self):
422 self.tempdir = TemporaryDirectory()
422 self.tempdir = TemporaryDirectory()
423 self.src = self.dst("src")
423 self.src = self.dst("src")
424 with open(self.src, "w") as f:
424 with open(self.src, "w") as f:
425 f.write("Hello, world!")
425 f.write("Hello, world!")
426
426
427 def tearDown(self):
427 def tearDown(self):
428 self.tempdir.cleanup()
428 self.tempdir.cleanup()
429
429
430 def dst(self, *args):
430 def dst(self, *args):
431 return os.path.join(self.tempdir.name, *args)
431 return os.path.join(self.tempdir.name, *args)
432
432
433 def assert_inode_not_equal(self, a, b):
433 def assert_inode_not_equal(self, a, b):
434 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
434 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
435 "%r and %r do reference the same indoes" %(a, b))
435 "%r and %r do reference the same indoes" %(a, b))
436
436
437 def assert_inode_equal(self, a, b):
437 def assert_inode_equal(self, a, b):
438 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
438 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
439 "%r and %r do not reference the same indoes" %(a, b))
439 "%r and %r do not reference the same indoes" %(a, b))
440
440
441 def assert_content_equal(self, a, b):
441 def assert_content_equal(self, a, b):
442 with open(a) as a_f:
442 with open(a) as a_f:
443 with open(b) as b_f:
443 with open(b) as b_f:
444 nt.assert_equal(a_f.read(), b_f.read())
444 nt.assert_equal(a_f.read(), b_f.read())
445
445
446 @skip_win32
446 @skip_win32
447 def test_link_successful(self):
447 def test_link_successful(self):
448 dst = self.dst("target")
448 dst = self.dst("target")
449 path.link_or_copy(self.src, dst)
449 path.link_or_copy(self.src, dst)
450 self.assert_inode_equal(self.src, dst)
450 self.assert_inode_equal(self.src, dst)
451
451
452 @skip_win32
452 @skip_win32
453 def test_link_into_dir(self):
453 def test_link_into_dir(self):
454 dst = self.dst("some_dir")
454 dst = self.dst("some_dir")
455 os.mkdir(dst)
455 os.mkdir(dst)
456 path.link_or_copy(self.src, dst)
456 path.link_or_copy(self.src, dst)
457 expected_dst = self.dst("some_dir", os.path.basename(self.src))
457 expected_dst = self.dst("some_dir", os.path.basename(self.src))
458 self.assert_inode_equal(self.src, expected_dst)
458 self.assert_inode_equal(self.src, expected_dst)
459
459
460 @skip_win32
460 @skip_win32
461 def test_target_exists(self):
461 def test_target_exists(self):
462 dst = self.dst("target")
462 dst = self.dst("target")
463 open(dst, "w").close()
463 open(dst, "w").close()
464 path.link_or_copy(self.src, dst)
464 path.link_or_copy(self.src, dst)
465 self.assert_inode_equal(self.src, dst)
465 self.assert_inode_equal(self.src, dst)
466
466
467 @skip_win32
467 @skip_win32
468 def test_no_link(self):
468 def test_no_link(self):
469 real_link = os.link
469 real_link = os.link
470 try:
470 try:
471 del os.link
471 del os.link
472 dst = self.dst("target")
472 dst = self.dst("target")
473 path.link_or_copy(self.src, dst)
473 path.link_or_copy(self.src, dst)
474 self.assert_content_equal(self.src, dst)
474 self.assert_content_equal(self.src, dst)
475 self.assert_inode_not_equal(self.src, dst)
475 self.assert_inode_not_equal(self.src, dst)
476 finally:
476 finally:
477 os.link = real_link
477 os.link = real_link
478
478
479 @skip_if_not_win32
479 @skip_if_not_win32
480 def test_windows(self):
480 def test_windows(self):
481 dst = self.dst("target")
481 dst = self.dst("target")
482 path.link_or_copy(self.src, dst)
482 path.link_or_copy(self.src, dst)
483 self.assert_content_equal(self.src, dst)
483 self.assert_content_equal(self.src, dst)
484
484
485 def test_link_twice(self):
485 def test_link_twice(self):
486 # Linking the same file twice shouldn't leave duplicates around.
486 # Linking the same file twice shouldn't leave duplicates around.
487 # See https://github.com/ipython/ipython/issues/6450
487 # See https://github.com/ipython/ipython/issues/6450
488 dst = self.dst('target')
488 dst = self.dst('target')
489 path.link_or_copy(self.src, dst)
489 path.link_or_copy(self.src, dst)
490 path.link_or_copy(self.src, dst)
490 path.link_or_copy(self.src, dst)
491 self.assert_inode_equal(self.src, dst)
491 self.assert_inode_equal(self.src, dst)
492 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
492 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
@@ -1,17 +1,17 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test suite for our sysinfo utilities."""
2 """Test suite for our sysinfo utilities."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import json
7 import json
8 import nose.tools as nt
8 import nose.tools as nt
9
9
10 from IPython.utils import sysinfo
10 from IPython.utils import sysinfo
11
11
12
12
13 def test_json_getsysinfo():
13 def test_json_getsysinfo():
14 """
14 """
15 test that it is easily jsonable and don't return bytes somewhere.
15 test that it is easily jsonable and don't return bytes somewhere.
16 """
16 """
17 json.dumps(sysinfo.get_sys_info())
17 json.dumps(sysinfo.get_sys_info())
@@ -1,771 +1,761 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.utils.text
7 .. inheritance-diagram:: IPython.utils.text
8 :parts: 3
8 :parts: 3
9 """
9 """
10
10
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14 import textwrap
14 import textwrap
15 from string import Formatter
15 from string import Formatter
16 from pathlib import Path
16 from pathlib import Path
17
17
18
18
19 # datetime.strftime date format for ipython
19 # datetime.strftime date format for ipython
20 if sys.platform == 'win32':
20 if sys.platform == 'win32':
21 date_format = "%B %d, %Y"
21 date_format = "%B %d, %Y"
22 else:
22 else:
23 date_format = "%B %-d, %Y"
23 date_format = "%B %-d, %Y"
24
24
25 class LSString(str):
25 class LSString(str):
26 """String derivative with a special access attributes.
26 """String derivative with a special access attributes.
27
27
28 These are normal strings, but with the special attributes:
28 These are normal strings, but with the special attributes:
29
29
30 .l (or .list) : value as list (split on newlines).
30 .l (or .list) : value as list (split on newlines).
31 .n (or .nlstr): original value (the string itself).
31 .n (or .nlstr): original value (the string itself).
32 .s (or .spstr): value as whitespace-separated string.
32 .s (or .spstr): value as whitespace-separated string.
33 .p (or .paths): list of path objects (requires path.py package)
33 .p (or .paths): list of path objects (requires path.py package)
34
34
35 Any values which require transformations are computed only once and
35 Any values which require transformations are computed only once and
36 cached.
36 cached.
37
37
38 Such strings are very useful to efficiently interact with the shell, which
38 Such strings are very useful to efficiently interact with the shell, which
39 typically only understands whitespace-separated options for commands."""
39 typically only understands whitespace-separated options for commands."""
40
40
41 def get_list(self):
41 def get_list(self):
42 try:
42 try:
43 return self.__list
43 return self.__list
44 except AttributeError:
44 except AttributeError:
45 self.__list = self.split('\n')
45 self.__list = self.split('\n')
46 return self.__list
46 return self.__list
47
47
48 l = list = property(get_list)
48 l = list = property(get_list)
49
49
50 def get_spstr(self):
50 def get_spstr(self):
51 try:
51 try:
52 return self.__spstr
52 return self.__spstr
53 except AttributeError:
53 except AttributeError:
54 self.__spstr = self.replace('\n',' ')
54 self.__spstr = self.replace('\n',' ')
55 return self.__spstr
55 return self.__spstr
56
56
57 s = spstr = property(get_spstr)
57 s = spstr = property(get_spstr)
58
58
59 def get_nlstr(self):
59 def get_nlstr(self):
60 return self
60 return self
61
61
62 n = nlstr = property(get_nlstr)
62 n = nlstr = property(get_nlstr)
63
63
64 def get_paths(self):
64 def get_paths(self):
65 try:
65 try:
66 return self.__paths
66 return self.__paths
67 except AttributeError:
67 except AttributeError:
68 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
68 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
69 return self.__paths
69 return self.__paths
70
70
71 p = paths = property(get_paths)
71 p = paths = property(get_paths)
72
72
73 # FIXME: We need to reimplement type specific displayhook and then add this
73 # FIXME: We need to reimplement type specific displayhook and then add this
74 # back as a custom printer. This should also be moved outside utils into the
74 # back as a custom printer. This should also be moved outside utils into the
75 # core.
75 # core.
76
76
77 # def print_lsstring(arg):
77 # def print_lsstring(arg):
78 # """ Prettier (non-repr-like) and more informative printer for LSString """
78 # """ Prettier (non-repr-like) and more informative printer for LSString """
79 # print "LSString (.p, .n, .l, .s available). Value:"
79 # print "LSString (.p, .n, .l, .s available). Value:"
80 # print arg
80 # print arg
81 #
81 #
82 #
82 #
83 # print_lsstring = result_display.register(LSString)(print_lsstring)
83 # print_lsstring = result_display.register(LSString)(print_lsstring)
84
84
85
85
86 class SList(list):
86 class SList(list):
87 """List derivative with a special access attributes.
87 """List derivative with a special access attributes.
88
88
89 These are normal lists, but with the special attributes:
89 These are normal lists, but with the special attributes:
90
90
91 * .l (or .list) : value as list (the list itself).
91 * .l (or .list) : value as list (the list itself).
92 * .n (or .nlstr): value as a string, joined on newlines.
92 * .n (or .nlstr): value as a string, joined on newlines.
93 * .s (or .spstr): value as a string, joined on spaces.
93 * .s (or .spstr): value as a string, joined on spaces.
94 * .p (or .paths): list of path objects (requires path.py package)
94 * .p (or .paths): list of path objects (requires path.py package)
95
95
96 Any values which require transformations are computed only once and
96 Any values which require transformations are computed only once and
97 cached."""
97 cached."""
98
98
99 def get_list(self):
99 def get_list(self):
100 return self
100 return self
101
101
102 l = list = property(get_list)
102 l = list = property(get_list)
103
103
104 def get_spstr(self):
104 def get_spstr(self):
105 try:
105 try:
106 return self.__spstr
106 return self.__spstr
107 except AttributeError:
107 except AttributeError:
108 self.__spstr = ' '.join(self)
108 self.__spstr = ' '.join(self)
109 return self.__spstr
109 return self.__spstr
110
110
111 s = spstr = property(get_spstr)
111 s = spstr = property(get_spstr)
112
112
113 def get_nlstr(self):
113 def get_nlstr(self):
114 try:
114 try:
115 return self.__nlstr
115 return self.__nlstr
116 except AttributeError:
116 except AttributeError:
117 self.__nlstr = '\n'.join(self)
117 self.__nlstr = '\n'.join(self)
118 return self.__nlstr
118 return self.__nlstr
119
119
120 n = nlstr = property(get_nlstr)
120 n = nlstr = property(get_nlstr)
121
121
122 def get_paths(self):
122 def get_paths(self):
123 try:
123 try:
124 return self.__paths
124 return self.__paths
125 except AttributeError:
125 except AttributeError:
126 self.__paths = [Path(p) for p in self if os.path.exists(p)]
126 self.__paths = [Path(p) for p in self if os.path.exists(p)]
127 return self.__paths
127 return self.__paths
128
128
129 p = paths = property(get_paths)
129 p = paths = property(get_paths)
130
130
131 def grep(self, pattern, prune = False, field = None):
131 def grep(self, pattern, prune = False, field = None):
132 """ Return all strings matching 'pattern' (a regex or callable)
132 """ Return all strings matching 'pattern' (a regex or callable)
133
133
134 This is case-insensitive. If prune is true, return all items
134 This is case-insensitive. If prune is true, return all items
135 NOT matching the pattern.
135 NOT matching the pattern.
136
136
137 If field is specified, the match must occur in the specified
137 If field is specified, the match must occur in the specified
138 whitespace-separated field.
138 whitespace-separated field.
139
139
140 Examples::
140 Examples::
141
141
142 a.grep( lambda x: x.startswith('C') )
142 a.grep( lambda x: x.startswith('C') )
143 a.grep('Cha.*log', prune=1)
143 a.grep('Cha.*log', prune=1)
144 a.grep('chm', field=-1)
144 a.grep('chm', field=-1)
145 """
145 """
146
146
147 def match_target(s):
147 def match_target(s):
148 if field is None:
148 if field is None:
149 return s
149 return s
150 parts = s.split()
150 parts = s.split()
151 try:
151 try:
152 tgt = parts[field]
152 tgt = parts[field]
153 return tgt
153 return tgt
154 except IndexError:
154 except IndexError:
155 return ""
155 return ""
156
156
157 if isinstance(pattern, str):
157 if isinstance(pattern, str):
158 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
158 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
159 else:
159 else:
160 pred = pattern
160 pred = pattern
161 if not prune:
161 if not prune:
162 return SList([el for el in self if pred(match_target(el))])
162 return SList([el for el in self if pred(match_target(el))])
163 else:
163 else:
164 return SList([el for el in self if not pred(match_target(el))])
164 return SList([el for el in self if not pred(match_target(el))])
165
165
166 def fields(self, *fields):
166 def fields(self, *fields):
167 """ Collect whitespace-separated fields from string list
167 """ Collect whitespace-separated fields from string list
168
168
169 Allows quick awk-like usage of string lists.
169 Allows quick awk-like usage of string lists.
170
170
171 Example data (in var a, created by 'a = !ls -l')::
171 Example data (in var a, created by 'a = !ls -l')::
172
172
173 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
173 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
174 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
174 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
175
175
176 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
176 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
177 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
177 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
178 (note the joining by space).
178 (note the joining by space).
179 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
179 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
180
180
181 IndexErrors are ignored.
181 IndexErrors are ignored.
182
182
183 Without args, fields() just split()'s the strings.
183 Without args, fields() just split()'s the strings.
184 """
184 """
185 if len(fields) == 0:
185 if len(fields) == 0:
186 return [el.split() for el in self]
186 return [el.split() for el in self]
187
187
188 res = SList()
188 res = SList()
189 for el in [f.split() for f in self]:
189 for el in [f.split() for f in self]:
190 lineparts = []
190 lineparts = []
191
191
192 for fd in fields:
192 for fd in fields:
193 try:
193 try:
194 lineparts.append(el[fd])
194 lineparts.append(el[fd])
195 except IndexError:
195 except IndexError:
196 pass
196 pass
197 if lineparts:
197 if lineparts:
198 res.append(" ".join(lineparts))
198 res.append(" ".join(lineparts))
199
199
200 return res
200 return res
201
201
202 def sort(self,field= None, nums = False):
202 def sort(self,field= None, nums = False):
203 """ sort by specified fields (see fields())
203 """ sort by specified fields (see fields())
204
204
205 Example::
205 Example::
206
206
207 a.sort(1, nums = True)
207 a.sort(1, nums = True)
208
208
209 Sorts a by second field, in numerical order (so that 21 > 3)
209 Sorts a by second field, in numerical order (so that 21 > 3)
210
210
211 """
211 """
212
212
213 #decorate, sort, undecorate
213 #decorate, sort, undecorate
214 if field is not None:
214 if field is not None:
215 dsu = [[SList([line]).fields(field), line] for line in self]
215 dsu = [[SList([line]).fields(field), line] for line in self]
216 else:
216 else:
217 dsu = [[line, line] for line in self]
217 dsu = [[line, line] for line in self]
218 if nums:
218 if nums:
219 for i in range(len(dsu)):
219 for i in range(len(dsu)):
220 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
220 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
221 try:
221 try:
222 n = int(numstr)
222 n = int(numstr)
223 except ValueError:
223 except ValueError:
224 n = 0
224 n = 0
225 dsu[i][0] = n
225 dsu[i][0] = n
226
226
227
227
228 dsu.sort()
228 dsu.sort()
229 return SList([t[1] for t in dsu])
229 return SList([t[1] for t in dsu])
230
230
231
231
232 # FIXME: We need to reimplement type specific displayhook and then add this
232 # FIXME: We need to reimplement type specific displayhook and then add this
233 # back as a custom printer. This should also be moved outside utils into the
233 # back as a custom printer. This should also be moved outside utils into the
234 # core.
234 # core.
235
235
236 # def print_slist(arg):
236 # def print_slist(arg):
237 # """ Prettier (non-repr-like) and more informative printer for SList """
237 # """ Prettier (non-repr-like) and more informative printer for SList """
238 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
238 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
239 # if hasattr(arg, 'hideonce') and arg.hideonce:
239 # if hasattr(arg, 'hideonce') and arg.hideonce:
240 # arg.hideonce = False
240 # arg.hideonce = False
241 # return
241 # return
242 #
242 #
243 # nlprint(arg) # This was a nested list printer, now removed.
243 # nlprint(arg) # This was a nested list printer, now removed.
244 #
244 #
245 # print_slist = result_display.register(SList)(print_slist)
245 # print_slist = result_display.register(SList)(print_slist)
246
246
247
247
248 def indent(instr,nspaces=4, ntabs=0, flatten=False):
248 def indent(instr,nspaces=4, ntabs=0, flatten=False):
249 """Indent a string a given number of spaces or tabstops.
249 """Indent a string a given number of spaces or tabstops.
250
250
251 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
251 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
252
252
253 Parameters
253 Parameters
254 ----------
254 ----------
255
256 instr : basestring
255 instr : basestring
257 The string to be indented.
256 The string to be indented.
258 nspaces : int (default: 4)
257 nspaces : int (default: 4)
259 The number of spaces to be indented.
258 The number of spaces to be indented.
260 ntabs : int (default: 0)
259 ntabs : int (default: 0)
261 The number of tabs to be indented.
260 The number of tabs to be indented.
262 flatten : bool (default: False)
261 flatten : bool (default: False)
263 Whether to scrub existing indentation. If True, all lines will be
262 Whether to scrub existing indentation. If True, all lines will be
264 aligned to the same indentation. If False, existing indentation will
263 aligned to the same indentation. If False, existing indentation will
265 be strictly increased.
264 be strictly increased.
266
265
267 Returns
266 Returns
268 -------
267 -------
269
270 str|unicode : string indented by ntabs and nspaces.
268 str|unicode : string indented by ntabs and nspaces.
271
269
272 """
270 """
273 if instr is None:
271 if instr is None:
274 return
272 return
275 ind = '\t'*ntabs+' '*nspaces
273 ind = '\t'*ntabs+' '*nspaces
276 if flatten:
274 if flatten:
277 pat = re.compile(r'^\s*', re.MULTILINE)
275 pat = re.compile(r'^\s*', re.MULTILINE)
278 else:
276 else:
279 pat = re.compile(r'^', re.MULTILINE)
277 pat = re.compile(r'^', re.MULTILINE)
280 outstr = re.sub(pat, ind, instr)
278 outstr = re.sub(pat, ind, instr)
281 if outstr.endswith(os.linesep+ind):
279 if outstr.endswith(os.linesep+ind):
282 return outstr[:-len(ind)]
280 return outstr[:-len(ind)]
283 else:
281 else:
284 return outstr
282 return outstr
285
283
286
284
287 def list_strings(arg):
285 def list_strings(arg):
288 """Always return a list of strings, given a string or list of strings
286 """Always return a list of strings, given a string or list of strings
289 as input.
287 as input.
290
288
291 Examples
289 Examples
292 --------
290 --------
293 ::
291 ::
294
292
295 In [7]: list_strings('A single string')
293 In [7]: list_strings('A single string')
296 Out[7]: ['A single string']
294 Out[7]: ['A single string']
297
295
298 In [8]: list_strings(['A single string in a list'])
296 In [8]: list_strings(['A single string in a list'])
299 Out[8]: ['A single string in a list']
297 Out[8]: ['A single string in a list']
300
298
301 In [9]: list_strings(['A','list','of','strings'])
299 In [9]: list_strings(['A','list','of','strings'])
302 Out[9]: ['A', 'list', 'of', 'strings']
300 Out[9]: ['A', 'list', 'of', 'strings']
303 """
301 """
304
302
305 if isinstance(arg, str):
303 if isinstance(arg, str):
306 return [arg]
304 return [arg]
307 else:
305 else:
308 return arg
306 return arg
309
307
310
308
311 def marquee(txt='',width=78,mark='*'):
309 def marquee(txt='',width=78,mark='*'):
312 """Return the input string centered in a 'marquee'.
310 """Return the input string centered in a 'marquee'.
313
311
314 Examples
312 Examples
315 --------
313 --------
316 ::
314 ::
317
315
318 In [16]: marquee('A test',40)
316 In [16]: marquee('A test',40)
319 Out[16]: '**************** A test ****************'
317 Out[16]: '**************** A test ****************'
320
318
321 In [17]: marquee('A test',40,'-')
319 In [17]: marquee('A test',40,'-')
322 Out[17]: '---------------- A test ----------------'
320 Out[17]: '---------------- A test ----------------'
323
321
324 In [18]: marquee('A test',40,' ')
322 In [18]: marquee('A test',40,' ')
325 Out[18]: ' A test '
323 Out[18]: ' A test '
326
324
327 """
325 """
328 if not txt:
326 if not txt:
329 return (mark*width)[:width]
327 return (mark*width)[:width]
330 nmark = (width-len(txt)-2)//len(mark)//2
328 nmark = (width-len(txt)-2)//len(mark)//2
331 if nmark < 0: nmark =0
329 if nmark < 0: nmark =0
332 marks = mark*nmark
330 marks = mark*nmark
333 return '%s %s %s' % (marks,txt,marks)
331 return '%s %s %s' % (marks,txt,marks)
334
332
335
333
336 ini_spaces_re = re.compile(r'^(\s+)')
334 ini_spaces_re = re.compile(r'^(\s+)')
337
335
338 def num_ini_spaces(strng):
336 def num_ini_spaces(strng):
339 """Return the number of initial spaces in a string"""
337 """Return the number of initial spaces in a string"""
340
338
341 ini_spaces = ini_spaces_re.match(strng)
339 ini_spaces = ini_spaces_re.match(strng)
342 if ini_spaces:
340 if ini_spaces:
343 return ini_spaces.end()
341 return ini_spaces.end()
344 else:
342 else:
345 return 0
343 return 0
346
344
347
345
348 def format_screen(strng):
346 def format_screen(strng):
349 """Format a string for screen printing.
347 """Format a string for screen printing.
350
348
351 This removes some latex-type format codes."""
349 This removes some latex-type format codes."""
352 # Paragraph continue
350 # Paragraph continue
353 par_re = re.compile(r'\\$',re.MULTILINE)
351 par_re = re.compile(r'\\$',re.MULTILINE)
354 strng = par_re.sub('',strng)
352 strng = par_re.sub('',strng)
355 return strng
353 return strng
356
354
357
355
358 def dedent(text):
356 def dedent(text):
359 """Equivalent of textwrap.dedent that ignores unindented first line.
357 """Equivalent of textwrap.dedent that ignores unindented first line.
360
358
361 This means it will still dedent strings like:
359 This means it will still dedent strings like:
362 '''foo
360 '''foo
363 is a bar
361 is a bar
364 '''
362 '''
365
363
366 For use in wrap_paragraphs.
364 For use in wrap_paragraphs.
367 """
365 """
368
366
369 if text.startswith('\n'):
367 if text.startswith('\n'):
370 # text starts with blank line, don't ignore the first line
368 # text starts with blank line, don't ignore the first line
371 return textwrap.dedent(text)
369 return textwrap.dedent(text)
372
370
373 # split first line
371 # split first line
374 splits = text.split('\n',1)
372 splits = text.split('\n',1)
375 if len(splits) == 1:
373 if len(splits) == 1:
376 # only one line
374 # only one line
377 return textwrap.dedent(text)
375 return textwrap.dedent(text)
378
376
379 first, rest = splits
377 first, rest = splits
380 # dedent everything but the first line
378 # dedent everything but the first line
381 rest = textwrap.dedent(rest)
379 rest = textwrap.dedent(rest)
382 return '\n'.join([first, rest])
380 return '\n'.join([first, rest])
383
381
384
382
385 def wrap_paragraphs(text, ncols=80):
383 def wrap_paragraphs(text, ncols=80):
386 """Wrap multiple paragraphs to fit a specified width.
384 """Wrap multiple paragraphs to fit a specified width.
387
385
388 This is equivalent to textwrap.wrap, but with support for multiple
386 This is equivalent to textwrap.wrap, but with support for multiple
389 paragraphs, as separated by empty lines.
387 paragraphs, as separated by empty lines.
390
388
391 Returns
389 Returns
392 -------
390 -------
393
394 list of complete paragraphs, wrapped to fill `ncols` columns.
391 list of complete paragraphs, wrapped to fill `ncols` columns.
395 """
392 """
396 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
393 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
397 text = dedent(text).strip()
394 text = dedent(text).strip()
398 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
395 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
399 out_ps = []
396 out_ps = []
400 indent_re = re.compile(r'\n\s+', re.MULTILINE)
397 indent_re = re.compile(r'\n\s+', re.MULTILINE)
401 for p in paragraphs:
398 for p in paragraphs:
402 # presume indentation that survives dedent is meaningful formatting,
399 # presume indentation that survives dedent is meaningful formatting,
403 # so don't fill unless text is flush.
400 # so don't fill unless text is flush.
404 if indent_re.search(p) is None:
401 if indent_re.search(p) is None:
405 # wrap paragraph
402 # wrap paragraph
406 p = textwrap.fill(p, ncols)
403 p = textwrap.fill(p, ncols)
407 out_ps.append(p)
404 out_ps.append(p)
408 return out_ps
405 return out_ps
409
406
410
407
411 def long_substr(data):
408 def long_substr(data):
412 """Return the longest common substring in a list of strings.
409 """Return the longest common substring in a list of strings.
413
410
414 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
411 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
415 """
412 """
416 substr = ''
413 substr = ''
417 if len(data) > 1 and len(data[0]) > 0:
414 if len(data) > 1 and len(data[0]) > 0:
418 for i in range(len(data[0])):
415 for i in range(len(data[0])):
419 for j in range(len(data[0])-i+1):
416 for j in range(len(data[0])-i+1):
420 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
417 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
421 substr = data[0][i:i+j]
418 substr = data[0][i:i+j]
422 elif len(data) == 1:
419 elif len(data) == 1:
423 substr = data[0]
420 substr = data[0]
424 return substr
421 return substr
425
422
426
423
427 def strip_email_quotes(text):
424 def strip_email_quotes(text):
428 """Strip leading email quotation characters ('>').
425 """Strip leading email quotation characters ('>').
429
426
430 Removes any combination of leading '>' interspersed with whitespace that
427 Removes any combination of leading '>' interspersed with whitespace that
431 appears *identically* in all lines of the input text.
428 appears *identically* in all lines of the input text.
432
429
433 Parameters
430 Parameters
434 ----------
431 ----------
435 text : str
432 text : str
436
433
437 Examples
434 Examples
438 --------
435 --------
439
436
440 Simple uses::
437 Simple uses::
441
438
442 In [2]: strip_email_quotes('> > text')
439 In [2]: strip_email_quotes('> > text')
443 Out[2]: 'text'
440 Out[2]: 'text'
444
441
445 In [3]: strip_email_quotes('> > text\\n> > more')
442 In [3]: strip_email_quotes('> > text\\n> > more')
446 Out[3]: 'text\\nmore'
443 Out[3]: 'text\\nmore'
447
444
448 Note how only the common prefix that appears in all lines is stripped::
445 Note how only the common prefix that appears in all lines is stripped::
449
446
450 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
447 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
451 Out[4]: '> text\\n> more\\nmore...'
448 Out[4]: '> text\\n> more\\nmore...'
452
449
453 So if any line has no quote marks ('>') , then none are stripped from any
450 So if any line has no quote marks ('>') , then none are stripped from any
454 of them ::
451 of them ::
455
452
456 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
453 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
457 Out[5]: '> > text\\n> > more\\nlast different'
454 Out[5]: '> > text\\n> > more\\nlast different'
458 """
455 """
459 lines = text.splitlines()
456 lines = text.splitlines()
460 matches = set()
457 matches = set()
461 for line in lines:
458 for line in lines:
462 prefix = re.match(r'^(\s*>[ >]*)', line)
459 prefix = re.match(r'^(\s*>[ >]*)', line)
463 if prefix:
460 if prefix:
464 matches.add(prefix.group(1))
461 matches.add(prefix.group(1))
465 else:
462 else:
466 break
463 break
467 else:
464 else:
468 prefix = long_substr(list(matches))
465 prefix = long_substr(list(matches))
469 if prefix:
466 if prefix:
470 strip = len(prefix)
467 strip = len(prefix)
471 text = '\n'.join([ ln[strip:] for ln in lines])
468 text = '\n'.join([ ln[strip:] for ln in lines])
472 return text
469 return text
473
470
474 def strip_ansi(source):
471 def strip_ansi(source):
475 """
472 """
476 Remove ansi escape codes from text.
473 Remove ansi escape codes from text.
477
474
478 Parameters
475 Parameters
479 ----------
476 ----------
480 source : str
477 source : str
481 Source to remove the ansi from
478 Source to remove the ansi from
482 """
479 """
483 return re.sub(r'\033\[(\d|;)+?m', '', source)
480 return re.sub(r'\033\[(\d|;)+?m', '', source)
484
481
485
482
486 class EvalFormatter(Formatter):
483 class EvalFormatter(Formatter):
487 """A String Formatter that allows evaluation of simple expressions.
484 """A String Formatter that allows evaluation of simple expressions.
488
485
489 Note that this version interprets a : as specifying a format string (as per
486 Note that this version interprets a : as specifying a format string (as per
490 standard string formatting), so if slicing is required, you must explicitly
487 standard string formatting), so if slicing is required, you must explicitly
491 create a slice.
488 create a slice.
492
489
493 This is to be used in templating cases, such as the parallel batch
490 This is to be used in templating cases, such as the parallel batch
494 script templates, where simple arithmetic on arguments is useful.
491 script templates, where simple arithmetic on arguments is useful.
495
492
496 Examples
493 Examples
497 --------
494 --------
498 ::
495 ::
499
496
500 In [1]: f = EvalFormatter()
497 In [1]: f = EvalFormatter()
501 In [2]: f.format('{n//4}', n=8)
498 In [2]: f.format('{n//4}', n=8)
502 Out[2]: '2'
499 Out[2]: '2'
503
500
504 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
501 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
505 Out[3]: 'll'
502 Out[3]: 'll'
506 """
503 """
507 def get_field(self, name, args, kwargs):
504 def get_field(self, name, args, kwargs):
508 v = eval(name, kwargs)
505 v = eval(name, kwargs)
509 return v, name
506 return v, name
510
507
511 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
508 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
512 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
509 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
513 # above, it should be possible to remove FullEvalFormatter.
510 # above, it should be possible to remove FullEvalFormatter.
514
511
515 class FullEvalFormatter(Formatter):
512 class FullEvalFormatter(Formatter):
516 """A String Formatter that allows evaluation of simple expressions.
513 """A String Formatter that allows evaluation of simple expressions.
517
514
518 Any time a format key is not found in the kwargs,
515 Any time a format key is not found in the kwargs,
519 it will be tried as an expression in the kwargs namespace.
516 it will be tried as an expression in the kwargs namespace.
520
517
521 Note that this version allows slicing using [1:2], so you cannot specify
518 Note that this version allows slicing using [1:2], so you cannot specify
522 a format string. Use :class:`EvalFormatter` to permit format strings.
519 a format string. Use :class:`EvalFormatter` to permit format strings.
523
520
524 Examples
521 Examples
525 --------
522 --------
526 ::
523 ::
527
524
528 In [1]: f = FullEvalFormatter()
525 In [1]: f = FullEvalFormatter()
529 In [2]: f.format('{n//4}', n=8)
526 In [2]: f.format('{n//4}', n=8)
530 Out[2]: '2'
527 Out[2]: '2'
531
528
532 In [3]: f.format('{list(range(5))[2:4]}')
529 In [3]: f.format('{list(range(5))[2:4]}')
533 Out[3]: '[2, 3]'
530 Out[3]: '[2, 3]'
534
531
535 In [4]: f.format('{3*2}')
532 In [4]: f.format('{3*2}')
536 Out[4]: '6'
533 Out[4]: '6'
537 """
534 """
538 # copied from Formatter._vformat with minor changes to allow eval
535 # copied from Formatter._vformat with minor changes to allow eval
539 # and replace the format_spec code with slicing
536 # and replace the format_spec code with slicing
540 def vformat(self, format_string:str, args, kwargs)->str:
537 def vformat(self, format_string:str, args, kwargs)->str:
541 result = []
538 result = []
542 for literal_text, field_name, format_spec, conversion in \
539 for literal_text, field_name, format_spec, conversion in \
543 self.parse(format_string):
540 self.parse(format_string):
544
541
545 # output the literal text
542 # output the literal text
546 if literal_text:
543 if literal_text:
547 result.append(literal_text)
544 result.append(literal_text)
548
545
549 # if there's a field, output it
546 # if there's a field, output it
550 if field_name is not None:
547 if field_name is not None:
551 # this is some markup, find the object and do
548 # this is some markup, find the object and do
552 # the formatting
549 # the formatting
553
550
554 if format_spec:
551 if format_spec:
555 # override format spec, to allow slicing:
552 # override format spec, to allow slicing:
556 field_name = ':'.join([field_name, format_spec])
553 field_name = ':'.join([field_name, format_spec])
557
554
558 # eval the contents of the field for the object
555 # eval the contents of the field for the object
559 # to be formatted
556 # to be formatted
560 obj = eval(field_name, kwargs)
557 obj = eval(field_name, kwargs)
561
558
562 # do any conversion on the resulting object
559 # do any conversion on the resulting object
563 obj = self.convert_field(obj, conversion)
560 obj = self.convert_field(obj, conversion)
564
561
565 # format the object and append to the result
562 # format the object and append to the result
566 result.append(self.format_field(obj, ''))
563 result.append(self.format_field(obj, ''))
567
564
568 return ''.join(result)
565 return ''.join(result)
569
566
570
567
571 class DollarFormatter(FullEvalFormatter):
568 class DollarFormatter(FullEvalFormatter):
572 """Formatter allowing Itpl style $foo replacement, for names and attribute
569 """Formatter allowing Itpl style $foo replacement, for names and attribute
573 access only. Standard {foo} replacement also works, and allows full
570 access only. Standard {foo} replacement also works, and allows full
574 evaluation of its arguments.
571 evaluation of its arguments.
575
572
576 Examples
573 Examples
577 --------
574 --------
578 ::
575 ::
579
576
580 In [1]: f = DollarFormatter()
577 In [1]: f = DollarFormatter()
581 In [2]: f.format('{n//4}', n=8)
578 In [2]: f.format('{n//4}', n=8)
582 Out[2]: '2'
579 Out[2]: '2'
583
580
584 In [3]: f.format('23 * 76 is $result', result=23*76)
581 In [3]: f.format('23 * 76 is $result', result=23*76)
585 Out[3]: '23 * 76 is 1748'
582 Out[3]: '23 * 76 is 1748'
586
583
587 In [4]: f.format('$a or {b}', a=1, b=2)
584 In [4]: f.format('$a or {b}', a=1, b=2)
588 Out[4]: '1 or 2'
585 Out[4]: '1 or 2'
589 """
586 """
590 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
587 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
591 def parse(self, fmt_string):
588 def parse(self, fmt_string):
592 for literal_txt, field_name, format_spec, conversion \
589 for literal_txt, field_name, format_spec, conversion \
593 in Formatter.parse(self, fmt_string):
590 in Formatter.parse(self, fmt_string):
594
591
595 # Find $foo patterns in the literal text.
592 # Find $foo patterns in the literal text.
596 continue_from = 0
593 continue_from = 0
597 txt = ""
594 txt = ""
598 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
595 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
599 new_txt, new_field = m.group(1,2)
596 new_txt, new_field = m.group(1,2)
600 # $$foo --> $foo
597 # $$foo --> $foo
601 if new_field.startswith("$"):
598 if new_field.startswith("$"):
602 txt += new_txt + new_field
599 txt += new_txt + new_field
603 else:
600 else:
604 yield (txt + new_txt, new_field, "", None)
601 yield (txt + new_txt, new_field, "", None)
605 txt = ""
602 txt = ""
606 continue_from = m.end()
603 continue_from = m.end()
607
604
608 # Re-yield the {foo} style pattern
605 # Re-yield the {foo} style pattern
609 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
606 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
610
607
611 #-----------------------------------------------------------------------------
608 #-----------------------------------------------------------------------------
612 # Utils to columnize a list of string
609 # Utils to columnize a list of string
613 #-----------------------------------------------------------------------------
610 #-----------------------------------------------------------------------------
614
611
615 def _col_chunks(l, max_rows, row_first=False):
612 def _col_chunks(l, max_rows, row_first=False):
616 """Yield successive max_rows-sized column chunks from l."""
613 """Yield successive max_rows-sized column chunks from l."""
617 if row_first:
614 if row_first:
618 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
615 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
619 for i in range(ncols):
616 for i in range(ncols):
620 yield [l[j] for j in range(i, len(l), ncols)]
617 yield [l[j] for j in range(i, len(l), ncols)]
621 else:
618 else:
622 for i in range(0, len(l), max_rows):
619 for i in range(0, len(l), max_rows):
623 yield l[i:(i + max_rows)]
620 yield l[i:(i + max_rows)]
624
621
625
622
626 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
623 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
627 """Calculate optimal info to columnize a list of string"""
624 """Calculate optimal info to columnize a list of string"""
628 for max_rows in range(1, len(rlist) + 1):
625 for max_rows in range(1, len(rlist) + 1):
629 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
626 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
630 sumlength = sum(col_widths)
627 sumlength = sum(col_widths)
631 ncols = len(col_widths)
628 ncols = len(col_widths)
632 if sumlength + separator_size * (ncols - 1) <= displaywidth:
629 if sumlength + separator_size * (ncols - 1) <= displaywidth:
633 break
630 break
634 return {'num_columns': ncols,
631 return {'num_columns': ncols,
635 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
632 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
636 'max_rows': max_rows,
633 'max_rows': max_rows,
637 'column_widths': col_widths
634 'column_widths': col_widths
638 }
635 }
639
636
640
637
641 def _get_or_default(mylist, i, default=None):
638 def _get_or_default(mylist, i, default=None):
642 """return list item number, or default if don't exist"""
639 """return list item number, or default if don't exist"""
643 if i >= len(mylist):
640 if i >= len(mylist):
644 return default
641 return default
645 else :
642 else :
646 return mylist[i]
643 return mylist[i]
647
644
648
645
649 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
646 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
650 """Returns a nested list, and info to columnize items
647 """Returns a nested list, and info to columnize items
651
648
652 Parameters
649 Parameters
653 ----------
650 ----------
654
655 items
651 items
656 list of strings to columize
652 list of strings to columize
657 row_first : (default False)
653 row_first : (default False)
658 Whether to compute columns for a row-first matrix instead of
654 Whether to compute columns for a row-first matrix instead of
659 column-first (default).
655 column-first (default).
660 empty : (default None)
656 empty : (default None)
661 default value to fill list if needed
657 default value to fill list if needed
662 separator_size : int (default=2)
658 separator_size : int (default=2)
663 How much characters will be used as a separation between each columns.
659 How much characters will be used as a separation between each columns.
664 displaywidth : int (default=80)
660 displaywidth : int (default=80)
665 The width of the area onto which the columns should enter
661 The width of the area onto which the columns should enter
666
662
667 Returns
663 Returns
668 -------
664 -------
669
670 strings_matrix
665 strings_matrix
671
672 nested list of string, the outer most list contains as many list as
666 nested list of string, the outer most list contains as many list as
673 rows, the innermost lists have each as many element as columns. If the
667 rows, the innermost lists have each as many element as columns. If the
674 total number of elements in `items` does not equal the product of
668 total number of elements in `items` does not equal the product of
675 rows*columns, the last element of some lists are filled with `None`.
669 rows*columns, the last element of some lists are filled with `None`.
676
677 dict_info
670 dict_info
678 some info to make columnize easier:
671 some info to make columnize easier:
679
672
680 num_columns
673 num_columns
681 number of columns
674 number of columns
682 max_rows
675 max_rows
683 maximum number of rows (final number may be less)
676 maximum number of rows (final number may be less)
684 column_widths
677 column_widths
685 list of with of each columns
678 list of with of each columns
686 optimal_separator_width
679 optimal_separator_width
687 best separator width between columns
680 best separator width between columns
688
681
689 Examples
682 Examples
690 --------
683 --------
691 ::
684 ::
692
685
693 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
686 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
694 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
687 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
695 In [3]: list
688 In [3]: list
696 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
689 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
697 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
690 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
698 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
691 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
699 Out[5]: True
692 Out[5]: True
700 """
693 """
701 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
694 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
702 nrow, ncol = info['max_rows'], info['num_columns']
695 nrow, ncol = info['max_rows'], info['num_columns']
703 if row_first:
696 if row_first:
704 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
697 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
705 else:
698 else:
706 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
699 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
707
700
708
701
709 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
702 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
710 """ Transform a list of strings into a single string with columns.
703 """ Transform a list of strings into a single string with columns.
711
704
712 Parameters
705 Parameters
713 ----------
706 ----------
714 items : sequence of strings
707 items : sequence of strings
715 The strings to process.
708 The strings to process.
716
717 row_first : (default False)
709 row_first : (default False)
718 Whether to compute columns for a row-first matrix instead of
710 Whether to compute columns for a row-first matrix instead of
719 column-first (default).
711 column-first (default).
720
721 separator : str, optional [default is two spaces]
712 separator : str, optional [default is two spaces]
722 The string that separates columns.
713 The string that separates columns.
723
724 displaywidth : int, optional [default is 80]
714 displaywidth : int, optional [default is 80]
725 Width of the display in number of characters.
715 Width of the display in number of characters.
726
716
727 Returns
717 Returns
728 -------
718 -------
729 The formatted string.
719 The formatted string.
730 """
720 """
731 if not items:
721 if not items:
732 return '\n'
722 return '\n'
733 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
723 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
734 if spread:
724 if spread:
735 separator = separator.ljust(int(info['optimal_separator_width']))
725 separator = separator.ljust(int(info['optimal_separator_width']))
736 fmatrix = [filter(None, x) for x in matrix]
726 fmatrix = [filter(None, x) for x in matrix]
737 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
727 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
738 return '\n'.join(map(sjoin, fmatrix))+'\n'
728 return '\n'.join(map(sjoin, fmatrix))+'\n'
739
729
740
730
741 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
731 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
742 """
732 """
743 Return a string with a natural enumeration of items
733 Return a string with a natural enumeration of items
744
734
745 >>> get_text_list(['a', 'b', 'c', 'd'])
735 >>> get_text_list(['a', 'b', 'c', 'd'])
746 'a, b, c and d'
736 'a, b, c and d'
747 >>> get_text_list(['a', 'b', 'c'], ' or ')
737 >>> get_text_list(['a', 'b', 'c'], ' or ')
748 'a, b or c'
738 'a, b or c'
749 >>> get_text_list(['a', 'b', 'c'], ', ')
739 >>> get_text_list(['a', 'b', 'c'], ', ')
750 'a, b, c'
740 'a, b, c'
751 >>> get_text_list(['a', 'b'], ' or ')
741 >>> get_text_list(['a', 'b'], ' or ')
752 'a or b'
742 'a or b'
753 >>> get_text_list(['a'])
743 >>> get_text_list(['a'])
754 'a'
744 'a'
755 >>> get_text_list([])
745 >>> get_text_list([])
756 ''
746 ''
757 >>> get_text_list(['a', 'b'], wrap_item_with="`")
747 >>> get_text_list(['a', 'b'], wrap_item_with="`")
758 '`a` and `b`'
748 '`a` and `b`'
759 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
749 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
760 'a + b + c = d'
750 'a + b + c = d'
761 """
751 """
762 if len(list_) == 0:
752 if len(list_) == 0:
763 return ''
753 return ''
764 if wrap_item_with:
754 if wrap_item_with:
765 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
755 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
766 item in list_]
756 item in list_]
767 if len(list_) == 1:
757 if len(list_) == 1:
768 return list_[0]
758 return list_[0]
769 return '%s%s%s' % (
759 return '%s%s%s' % (
770 sep.join(i for i in list_[:-1]),
760 sep.join(i for i in list_[:-1]),
771 last_sep, list_[-1])
761 last_sep, list_[-1])
@@ -1,130 +1,127 b''
1 """Token-related utilities"""
1 """Token-related utilities"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from collections import namedtuple
6 from collections import namedtuple
7 from io import StringIO
7 from io import StringIO
8 from keyword import iskeyword
8 from keyword import iskeyword
9
9
10 import tokenize
10 import tokenize
11
11
12
12
13 Token = namedtuple('Token', ['token', 'text', 'start', 'end', 'line'])
13 Token = namedtuple('Token', ['token', 'text', 'start', 'end', 'line'])
14
14
15 def generate_tokens(readline):
15 def generate_tokens(readline):
16 """wrap generate_tokens to catch EOF errors"""
16 """wrap generate_tokens to catch EOF errors"""
17 try:
17 try:
18 for token in tokenize.generate_tokens(readline):
18 for token in tokenize.generate_tokens(readline):
19 yield token
19 yield token
20 except tokenize.TokenError:
20 except tokenize.TokenError:
21 # catch EOF error
21 # catch EOF error
22 return
22 return
23
23
24 def line_at_cursor(cell, cursor_pos=0):
24 def line_at_cursor(cell, cursor_pos=0):
25 """Return the line in a cell at a given cursor position
25 """Return the line in a cell at a given cursor position
26
26
27 Used for calling line-based APIs that don't support multi-line input, yet.
27 Used for calling line-based APIs that don't support multi-line input, yet.
28
28
29 Parameters
29 Parameters
30 ----------
30 ----------
31
31 cell : str
32 cell: str
33 multiline block of text
32 multiline block of text
34 cursor_pos: integer
33 cursor_pos : integer
35 the cursor position
34 the cursor position
36
35
37 Returns
36 Returns
38 -------
37 -------
39
40 (line, offset): (string, integer)
38 (line, offset): (string, integer)
41 The line with the current cursor, and the character offset of the start of the line.
39 The line with the current cursor, and the character offset of the start of the line.
42 """
40 """
43 offset = 0
41 offset = 0
44 lines = cell.splitlines(True)
42 lines = cell.splitlines(True)
45 for line in lines:
43 for line in lines:
46 next_offset = offset + len(line)
44 next_offset = offset + len(line)
47 if not line.endswith('\n'):
45 if not line.endswith('\n'):
48 # If the last line doesn't have a trailing newline, treat it as if
46 # If the last line doesn't have a trailing newline, treat it as if
49 # it does so that the cursor at the end of the line still counts
47 # it does so that the cursor at the end of the line still counts
50 # as being on that line.
48 # as being on that line.
51 next_offset += 1
49 next_offset += 1
52 if next_offset > cursor_pos:
50 if next_offset > cursor_pos:
53 break
51 break
54 offset = next_offset
52 offset = next_offset
55 else:
53 else:
56 line = ""
54 line = ""
57 return (line, offset)
55 return (line, offset)
58
56
59 def token_at_cursor(cell, cursor_pos=0):
57 def token_at_cursor(cell, cursor_pos=0):
60 """Get the token at a given cursor
58 """Get the token at a given cursor
61
59
62 Used for introspection.
60 Used for introspection.
63
61
64 Function calls are prioritized, so the token for the callable will be returned
62 Function calls are prioritized, so the token for the callable will be returned
65 if the cursor is anywhere inside the call.
63 if the cursor is anywhere inside the call.
66
64
67 Parameters
65 Parameters
68 ----------
66 ----------
69
70 cell : unicode
67 cell : unicode
71 A block of Python code
68 A block of Python code
72 cursor_pos : int
69 cursor_pos : int
73 The location of the cursor in the block where the token should be found
70 The location of the cursor in the block where the token should be found
74 """
71 """
75 names = []
72 names = []
76 tokens = []
73 tokens = []
77 call_names = []
74 call_names = []
78
75
79 offsets = {1: 0} # lines start at 1
76 offsets = {1: 0} # lines start at 1
80 for tup in generate_tokens(StringIO(cell).readline):
77 for tup in generate_tokens(StringIO(cell).readline):
81
78
82 tok = Token(*tup)
79 tok = Token(*tup)
83
80
84 # token, text, start, end, line = tup
81 # token, text, start, end, line = tup
85 start_line, start_col = tok.start
82 start_line, start_col = tok.start
86 end_line, end_col = tok.end
83 end_line, end_col = tok.end
87 if end_line + 1 not in offsets:
84 if end_line + 1 not in offsets:
88 # keep track of offsets for each line
85 # keep track of offsets for each line
89 lines = tok.line.splitlines(True)
86 lines = tok.line.splitlines(True)
90 for lineno, line in enumerate(lines, start_line + 1):
87 for lineno, line in enumerate(lines, start_line + 1):
91 if lineno not in offsets:
88 if lineno not in offsets:
92 offsets[lineno] = offsets[lineno-1] + len(line)
89 offsets[lineno] = offsets[lineno-1] + len(line)
93
90
94 offset = offsets[start_line]
91 offset = offsets[start_line]
95 # allow '|foo' to find 'foo' at the beginning of a line
92 # allow '|foo' to find 'foo' at the beginning of a line
96 boundary = cursor_pos + 1 if start_col == 0 else cursor_pos
93 boundary = cursor_pos + 1 if start_col == 0 else cursor_pos
97 if offset + start_col >= boundary:
94 if offset + start_col >= boundary:
98 # current token starts after the cursor,
95 # current token starts after the cursor,
99 # don't consume it
96 # don't consume it
100 break
97 break
101
98
102 if tok.token == tokenize.NAME and not iskeyword(tok.text):
99 if tok.token == tokenize.NAME and not iskeyword(tok.text):
103 if names and tokens and tokens[-1].token == tokenize.OP and tokens[-1].text == '.':
100 if names and tokens and tokens[-1].token == tokenize.OP and tokens[-1].text == '.':
104 names[-1] = "%s.%s" % (names[-1], tok.text)
101 names[-1] = "%s.%s" % (names[-1], tok.text)
105 else:
102 else:
106 names.append(tok.text)
103 names.append(tok.text)
107 elif tok.token == tokenize.OP:
104 elif tok.token == tokenize.OP:
108 if tok.text == '=' and names:
105 if tok.text == '=' and names:
109 # don't inspect the lhs of an assignment
106 # don't inspect the lhs of an assignment
110 names.pop(-1)
107 names.pop(-1)
111 if tok.text == '(' and names:
108 if tok.text == '(' and names:
112 # if we are inside a function call, inspect the function
109 # if we are inside a function call, inspect the function
113 call_names.append(names[-1])
110 call_names.append(names[-1])
114 elif tok.text == ')' and call_names:
111 elif tok.text == ')' and call_names:
115 call_names.pop(-1)
112 call_names.pop(-1)
116
113
117 tokens.append(tok)
114 tokens.append(tok)
118
115
119 if offsets[end_line] + end_col > cursor_pos:
116 if offsets[end_line] + end_col > cursor_pos:
120 # we found the cursor, stop reading
117 # we found the cursor, stop reading
121 break
118 break
122
119
123 if call_names:
120 if call_names:
124 return call_names[-1]
121 return call_names[-1]
125 elif names:
122 elif names:
126 return names[-1]
123 return names[-1]
127 else:
124 else:
128 return ''
125 return ''
129
126
130
127
General Comments 0
You need to be logged in to leave comments. Login now