##// END OF EJS Templates
Python 3 compatibility for os.getcwdu()
Thomas Kluyver -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,381 +1,382 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating configurables.
6 handling configuration and creating configurables.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15 * Min RK
15 * Min RK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008 The IPython Development Team
20 # Copyright (C) 2008 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 import atexit
30 import atexit
31 import errno
31 import errno
32 import glob
32 import glob
33 import logging
33 import logging
34 import os
34 import os
35 import shutil
35 import shutil
36 import sys
36 import sys
37
37
38 from IPython.config.application import Application, catch_config_error
38 from IPython.config.application import Application, catch_config_error
39 from IPython.config.loader import ConfigFileNotFound
39 from IPython.config.loader import ConfigFileNotFound
40 from IPython.core import release, crashhandler
40 from IPython.core import release, crashhandler
41 from IPython.core.profiledir import ProfileDir, ProfileDirError
41 from IPython.core.profiledir import ProfileDir, ProfileDirError
42 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
42 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
43 from IPython.utils import py3compat
43 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
44 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
44
45
45 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
46 # Classes and functions
47 # Classes and functions
47 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
48
49
49
50
50 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
51 # Base Application Class
52 # Base Application Class
52 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
53
54
54 # aliases and flags
55 # aliases and flags
55
56
56 base_aliases = {
57 base_aliases = {
57 'profile-dir' : 'ProfileDir.location',
58 'profile-dir' : 'ProfileDir.location',
58 'profile' : 'BaseIPythonApplication.profile',
59 'profile' : 'BaseIPythonApplication.profile',
59 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
60 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
60 'log-level' : 'Application.log_level',
61 'log-level' : 'Application.log_level',
61 'config' : 'BaseIPythonApplication.extra_config_file',
62 'config' : 'BaseIPythonApplication.extra_config_file',
62 }
63 }
63
64
64 base_flags = dict(
65 base_flags = dict(
65 debug = ({'Application' : {'log_level' : logging.DEBUG}},
66 debug = ({'Application' : {'log_level' : logging.DEBUG}},
66 "set log level to logging.DEBUG (maximize logging output)"),
67 "set log level to logging.DEBUG (maximize logging output)"),
67 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
68 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
68 "set log level to logging.CRITICAL (minimize logging output)"),
69 "set log level to logging.CRITICAL (minimize logging output)"),
69 init = ({'BaseIPythonApplication' : {
70 init = ({'BaseIPythonApplication' : {
70 'copy_config_files' : True,
71 'copy_config_files' : True,
71 'auto_create' : True}
72 'auto_create' : True}
72 }, """Initialize profile with default config files. This is equivalent
73 }, """Initialize profile with default config files. This is equivalent
73 to running `ipython profile create <profile>` prior to startup.
74 to running `ipython profile create <profile>` prior to startup.
74 """)
75 """)
75 )
76 )
76
77
77
78
78 class BaseIPythonApplication(Application):
79 class BaseIPythonApplication(Application):
79
80
80 name = Unicode(u'ipython')
81 name = Unicode(u'ipython')
81 description = Unicode(u'IPython: an enhanced interactive Python shell.')
82 description = Unicode(u'IPython: an enhanced interactive Python shell.')
82 version = Unicode(release.version)
83 version = Unicode(release.version)
83
84
84 aliases = Dict(base_aliases)
85 aliases = Dict(base_aliases)
85 flags = Dict(base_flags)
86 flags = Dict(base_flags)
86 classes = List([ProfileDir])
87 classes = List([ProfileDir])
87
88
88 # Track whether the config_file has changed,
89 # Track whether the config_file has changed,
89 # because some logic happens only if we aren't using the default.
90 # because some logic happens only if we aren't using the default.
90 config_file_specified = Set()
91 config_file_specified = Set()
91
92
92 config_file_name = Unicode()
93 config_file_name = Unicode()
93 def _config_file_name_default(self):
94 def _config_file_name_default(self):
94 return self.name.replace('-','_') + u'_config.py'
95 return self.name.replace('-','_') + u'_config.py'
95 def _config_file_name_changed(self, name, old, new):
96 def _config_file_name_changed(self, name, old, new):
96 if new != old:
97 if new != old:
97 self.config_file_specified.add(new)
98 self.config_file_specified.add(new)
98
99
99 # The directory that contains IPython's builtin profiles.
100 # The directory that contains IPython's builtin profiles.
100 builtin_profile_dir = Unicode(
101 builtin_profile_dir = Unicode(
101 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
102 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
102 )
103 )
103
104
104 config_file_paths = List(Unicode)
105 config_file_paths = List(Unicode)
105 def _config_file_paths_default(self):
106 def _config_file_paths_default(self):
106 return [os.getcwdu()]
107 return [py3compat.getcwd()]
107
108
108 extra_config_file = Unicode(config=True,
109 extra_config_file = Unicode(config=True,
109 help="""Path to an extra config file to load.
110 help="""Path to an extra config file to load.
110
111
111 If specified, load this config file in addition to any other IPython config.
112 If specified, load this config file in addition to any other IPython config.
112 """)
113 """)
113 def _extra_config_file_changed(self, name, old, new):
114 def _extra_config_file_changed(self, name, old, new):
114 try:
115 try:
115 self.config_files.remove(old)
116 self.config_files.remove(old)
116 except ValueError:
117 except ValueError:
117 pass
118 pass
118 self.config_file_specified.add(new)
119 self.config_file_specified.add(new)
119 self.config_files.append(new)
120 self.config_files.append(new)
120
121
121 profile = Unicode(u'default', config=True,
122 profile = Unicode(u'default', config=True,
122 help="""The IPython profile to use."""
123 help="""The IPython profile to use."""
123 )
124 )
124
125
125 def _profile_changed(self, name, old, new):
126 def _profile_changed(self, name, old, new):
126 self.builtin_profile_dir = os.path.join(
127 self.builtin_profile_dir = os.path.join(
127 get_ipython_package_dir(), u'config', u'profile', new
128 get_ipython_package_dir(), u'config', u'profile', new
128 )
129 )
129
130
130 ipython_dir = Unicode(config=True,
131 ipython_dir = Unicode(config=True,
131 help="""
132 help="""
132 The name of the IPython directory. This directory is used for logging
133 The name of the IPython directory. This directory is used for logging
133 configuration (through profiles), history storage, etc. The default
134 configuration (through profiles), history storage, etc. The default
134 is usually $HOME/.ipython. This options can also be specified through
135 is usually $HOME/.ipython. This options can also be specified through
135 the environment variable IPYTHONDIR.
136 the environment variable IPYTHONDIR.
136 """
137 """
137 )
138 )
138 def _ipython_dir_default(self):
139 def _ipython_dir_default(self):
139 d = get_ipython_dir()
140 d = get_ipython_dir()
140 self._ipython_dir_changed('ipython_dir', d, d)
141 self._ipython_dir_changed('ipython_dir', d, d)
141 return d
142 return d
142
143
143 _in_init_profile_dir = False
144 _in_init_profile_dir = False
144 profile_dir = Instance(ProfileDir)
145 profile_dir = Instance(ProfileDir)
145 def _profile_dir_default(self):
146 def _profile_dir_default(self):
146 # avoid recursion
147 # avoid recursion
147 if self._in_init_profile_dir:
148 if self._in_init_profile_dir:
148 return
149 return
149 # profile_dir requested early, force initialization
150 # profile_dir requested early, force initialization
150 self.init_profile_dir()
151 self.init_profile_dir()
151 return self.profile_dir
152 return self.profile_dir
152
153
153 overwrite = Bool(False, config=True,
154 overwrite = Bool(False, config=True,
154 help="""Whether to overwrite existing config files when copying""")
155 help="""Whether to overwrite existing config files when copying""")
155 auto_create = Bool(False, config=True,
156 auto_create = Bool(False, config=True,
156 help="""Whether to create profile dir if it doesn't exist""")
157 help="""Whether to create profile dir if it doesn't exist""")
157
158
158 config_files = List(Unicode)
159 config_files = List(Unicode)
159 def _config_files_default(self):
160 def _config_files_default(self):
160 return [self.config_file_name]
161 return [self.config_file_name]
161
162
162 copy_config_files = Bool(False, config=True,
163 copy_config_files = Bool(False, config=True,
163 help="""Whether to install the default config files into the profile dir.
164 help="""Whether to install the default config files into the profile dir.
164 If a new profile is being created, and IPython contains config files for that
165 If a new profile is being created, and IPython contains config files for that
165 profile, then they will be staged into the new directory. Otherwise,
166 profile, then they will be staged into the new directory. Otherwise,
166 default config files will be automatically generated.
167 default config files will be automatically generated.
167 """)
168 """)
168
169
169 verbose_crash = Bool(False, config=True,
170 verbose_crash = Bool(False, config=True,
170 help="""Create a massive crash report when IPython encounters what may be an
171 help="""Create a massive crash report when IPython encounters what may be an
171 internal error. The default is to append a short message to the
172 internal error. The default is to append a short message to the
172 usual traceback""")
173 usual traceback""")
173
174
174 # The class to use as the crash handler.
175 # The class to use as the crash handler.
175 crash_handler_class = Type(crashhandler.CrashHandler)
176 crash_handler_class = Type(crashhandler.CrashHandler)
176
177
177 @catch_config_error
178 @catch_config_error
178 def __init__(self, **kwargs):
179 def __init__(self, **kwargs):
179 super(BaseIPythonApplication, self).__init__(**kwargs)
180 super(BaseIPythonApplication, self).__init__(**kwargs)
180 # ensure current working directory exists
181 # ensure current working directory exists
181 try:
182 try:
182 directory = os.getcwdu()
183 directory = py3compat.getcwd()
183 except:
184 except:
184 # raise exception
185 # raise exception
185 self.log.error("Current working directory doesn't exist.")
186 self.log.error("Current working directory doesn't exist.")
186 raise
187 raise
187
188
188 #-------------------------------------------------------------------------
189 #-------------------------------------------------------------------------
189 # Various stages of Application creation
190 # Various stages of Application creation
190 #-------------------------------------------------------------------------
191 #-------------------------------------------------------------------------
191
192
192 def init_crash_handler(self):
193 def init_crash_handler(self):
193 """Create a crash handler, typically setting sys.excepthook to it."""
194 """Create a crash handler, typically setting sys.excepthook to it."""
194 self.crash_handler = self.crash_handler_class(self)
195 self.crash_handler = self.crash_handler_class(self)
195 sys.excepthook = self.excepthook
196 sys.excepthook = self.excepthook
196 def unset_crashhandler():
197 def unset_crashhandler():
197 sys.excepthook = sys.__excepthook__
198 sys.excepthook = sys.__excepthook__
198 atexit.register(unset_crashhandler)
199 atexit.register(unset_crashhandler)
199
200
200 def excepthook(self, etype, evalue, tb):
201 def excepthook(self, etype, evalue, tb):
201 """this is sys.excepthook after init_crashhandler
202 """this is sys.excepthook after init_crashhandler
202
203
203 set self.verbose_crash=True to use our full crashhandler, instead of
204 set self.verbose_crash=True to use our full crashhandler, instead of
204 a regular traceback with a short message (crash_handler_lite)
205 a regular traceback with a short message (crash_handler_lite)
205 """
206 """
206
207
207 if self.verbose_crash:
208 if self.verbose_crash:
208 return self.crash_handler(etype, evalue, tb)
209 return self.crash_handler(etype, evalue, tb)
209 else:
210 else:
210 return crashhandler.crash_handler_lite(etype, evalue, tb)
211 return crashhandler.crash_handler_lite(etype, evalue, tb)
211
212
212 def _ipython_dir_changed(self, name, old, new):
213 def _ipython_dir_changed(self, name, old, new):
213 if old in sys.path:
214 if old in sys.path:
214 sys.path.remove(old)
215 sys.path.remove(old)
215 sys.path.append(os.path.abspath(new))
216 sys.path.append(os.path.abspath(new))
216 if not os.path.isdir(new):
217 if not os.path.isdir(new):
217 os.makedirs(new, mode=0o777)
218 os.makedirs(new, mode=0o777)
218 readme = os.path.join(new, 'README')
219 readme = os.path.join(new, 'README')
219 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
220 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
220 if not os.path.exists(readme) and os.path.exists(readme_src):
221 if not os.path.exists(readme) and os.path.exists(readme_src):
221 shutil.copy(readme_src, readme)
222 shutil.copy(readme_src, readme)
222 for d in ('extensions', 'nbextensions'):
223 for d in ('extensions', 'nbextensions'):
223 path = os.path.join(new, d)
224 path = os.path.join(new, d)
224 if not os.path.exists(path):
225 if not os.path.exists(path):
225 try:
226 try:
226 os.mkdir(path)
227 os.mkdir(path)
227 except OSError as e:
228 except OSError as e:
228 if e.errno != errno.EEXIST:
229 if e.errno != errno.EEXIST:
229 self.log.error("couldn't create path %s: %s", path, e)
230 self.log.error("couldn't create path %s: %s", path, e)
230 self.log.debug("IPYTHONDIR set to: %s" % new)
231 self.log.debug("IPYTHONDIR set to: %s" % new)
231
232
232 def load_config_file(self, suppress_errors=True):
233 def load_config_file(self, suppress_errors=True):
233 """Load the config file.
234 """Load the config file.
234
235
235 By default, errors in loading config are handled, and a warning
236 By default, errors in loading config are handled, and a warning
236 printed on screen. For testing, the suppress_errors option is set
237 printed on screen. For testing, the suppress_errors option is set
237 to False, so errors will make tests fail.
238 to False, so errors will make tests fail.
238 """
239 """
239 self.log.debug("Searching path %s for config files", self.config_file_paths)
240 self.log.debug("Searching path %s for config files", self.config_file_paths)
240 base_config = 'ipython_config.py'
241 base_config = 'ipython_config.py'
241 self.log.debug("Attempting to load config file: %s" %
242 self.log.debug("Attempting to load config file: %s" %
242 base_config)
243 base_config)
243 try:
244 try:
244 Application.load_config_file(
245 Application.load_config_file(
245 self,
246 self,
246 base_config,
247 base_config,
247 path=self.config_file_paths
248 path=self.config_file_paths
248 )
249 )
249 except ConfigFileNotFound:
250 except ConfigFileNotFound:
250 # ignore errors loading parent
251 # ignore errors loading parent
251 self.log.debug("Config file %s not found", base_config)
252 self.log.debug("Config file %s not found", base_config)
252 pass
253 pass
253
254
254 for config_file_name in self.config_files:
255 for config_file_name in self.config_files:
255 if not config_file_name or config_file_name == base_config:
256 if not config_file_name or config_file_name == base_config:
256 continue
257 continue
257 self.log.debug("Attempting to load config file: %s" %
258 self.log.debug("Attempting to load config file: %s" %
258 self.config_file_name)
259 self.config_file_name)
259 try:
260 try:
260 Application.load_config_file(
261 Application.load_config_file(
261 self,
262 self,
262 config_file_name,
263 config_file_name,
263 path=self.config_file_paths
264 path=self.config_file_paths
264 )
265 )
265 except ConfigFileNotFound:
266 except ConfigFileNotFound:
266 # Only warn if the default config file was NOT being used.
267 # Only warn if the default config file was NOT being used.
267 if config_file_name in self.config_file_specified:
268 if config_file_name in self.config_file_specified:
268 msg = self.log.warn
269 msg = self.log.warn
269 else:
270 else:
270 msg = self.log.debug
271 msg = self.log.debug
271 msg("Config file not found, skipping: %s", config_file_name)
272 msg("Config file not found, skipping: %s", config_file_name)
272 except:
273 except:
273 # For testing purposes.
274 # For testing purposes.
274 if not suppress_errors:
275 if not suppress_errors:
275 raise
276 raise
276 self.log.warn("Error loading config file: %s" %
277 self.log.warn("Error loading config file: %s" %
277 self.config_file_name, exc_info=True)
278 self.config_file_name, exc_info=True)
278
279
279 def init_profile_dir(self):
280 def init_profile_dir(self):
280 """initialize the profile dir"""
281 """initialize the profile dir"""
281 self._in_init_profile_dir = True
282 self._in_init_profile_dir = True
282 if self.profile_dir is not None:
283 if self.profile_dir is not None:
283 # already ran
284 # already ran
284 return
285 return
285 if 'ProfileDir.location' not in self.config:
286 if 'ProfileDir.location' not in self.config:
286 # location not specified, find by profile name
287 # location not specified, find by profile name
287 try:
288 try:
288 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
289 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
289 except ProfileDirError:
290 except ProfileDirError:
290 # not found, maybe create it (always create default profile)
291 # not found, maybe create it (always create default profile)
291 if self.auto_create or self.profile == 'default':
292 if self.auto_create or self.profile == 'default':
292 try:
293 try:
293 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
294 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
294 except ProfileDirError:
295 except ProfileDirError:
295 self.log.fatal("Could not create profile: %r"%self.profile)
296 self.log.fatal("Could not create profile: %r"%self.profile)
296 self.exit(1)
297 self.exit(1)
297 else:
298 else:
298 self.log.info("Created profile dir: %r"%p.location)
299 self.log.info("Created profile dir: %r"%p.location)
299 else:
300 else:
300 self.log.fatal("Profile %r not found."%self.profile)
301 self.log.fatal("Profile %r not found."%self.profile)
301 self.exit(1)
302 self.exit(1)
302 else:
303 else:
303 self.log.info("Using existing profile dir: %r"%p.location)
304 self.log.info("Using existing profile dir: %r"%p.location)
304 else:
305 else:
305 location = self.config.ProfileDir.location
306 location = self.config.ProfileDir.location
306 # location is fully specified
307 # location is fully specified
307 try:
308 try:
308 p = ProfileDir.find_profile_dir(location, self.config)
309 p = ProfileDir.find_profile_dir(location, self.config)
309 except ProfileDirError:
310 except ProfileDirError:
310 # not found, maybe create it
311 # not found, maybe create it
311 if self.auto_create:
312 if self.auto_create:
312 try:
313 try:
313 p = ProfileDir.create_profile_dir(location, self.config)
314 p = ProfileDir.create_profile_dir(location, self.config)
314 except ProfileDirError:
315 except ProfileDirError:
315 self.log.fatal("Could not create profile directory: %r"%location)
316 self.log.fatal("Could not create profile directory: %r"%location)
316 self.exit(1)
317 self.exit(1)
317 else:
318 else:
318 self.log.info("Creating new profile dir: %r"%location)
319 self.log.info("Creating new profile dir: %r"%location)
319 else:
320 else:
320 self.log.fatal("Profile directory %r not found."%location)
321 self.log.fatal("Profile directory %r not found."%location)
321 self.exit(1)
322 self.exit(1)
322 else:
323 else:
323 self.log.info("Using existing profile dir: %r"%location)
324 self.log.info("Using existing profile dir: %r"%location)
324
325
325 self.profile_dir = p
326 self.profile_dir = p
326 self.config_file_paths.append(p.location)
327 self.config_file_paths.append(p.location)
327 self._in_init_profile_dir = False
328 self._in_init_profile_dir = False
328
329
329 def init_config_files(self):
330 def init_config_files(self):
330 """[optionally] copy default config files into profile dir."""
331 """[optionally] copy default config files into profile dir."""
331 # copy config files
332 # copy config files
332 path = self.builtin_profile_dir
333 path = self.builtin_profile_dir
333 if self.copy_config_files:
334 if self.copy_config_files:
334 src = self.profile
335 src = self.profile
335
336
336 cfg = self.config_file_name
337 cfg = self.config_file_name
337 if path and os.path.exists(os.path.join(path, cfg)):
338 if path and os.path.exists(os.path.join(path, cfg)):
338 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
339 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
339 cfg, src, self.profile_dir.location, self.overwrite)
340 cfg, src, self.profile_dir.location, self.overwrite)
340 )
341 )
341 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
342 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
342 else:
343 else:
343 self.stage_default_config_file()
344 self.stage_default_config_file()
344 else:
345 else:
345 # Still stage *bundled* config files, but not generated ones
346 # Still stage *bundled* config files, but not generated ones
346 # This is necessary for `ipython profile=sympy` to load the profile
347 # This is necessary for `ipython profile=sympy` to load the profile
347 # on the first go
348 # on the first go
348 files = glob.glob(os.path.join(path, '*.py'))
349 files = glob.glob(os.path.join(path, '*.py'))
349 for fullpath in files:
350 for fullpath in files:
350 cfg = os.path.basename(fullpath)
351 cfg = os.path.basename(fullpath)
351 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
352 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
352 # file was copied
353 # file was copied
353 self.log.warn("Staging bundled %s from %s into %r"%(
354 self.log.warn("Staging bundled %s from %s into %r"%(
354 cfg, self.profile, self.profile_dir.location)
355 cfg, self.profile, self.profile_dir.location)
355 )
356 )
356
357
357
358
358 def stage_default_config_file(self):
359 def stage_default_config_file(self):
359 """auto generate default config file, and stage it into the profile."""
360 """auto generate default config file, and stage it into the profile."""
360 s = self.generate_config_file()
361 s = self.generate_config_file()
361 fname = os.path.join(self.profile_dir.location, self.config_file_name)
362 fname = os.path.join(self.profile_dir.location, self.config_file_name)
362 if self.overwrite or not os.path.exists(fname):
363 if self.overwrite or not os.path.exists(fname):
363 self.log.warn("Generating default config file: %r"%(fname))
364 self.log.warn("Generating default config file: %r"%(fname))
364 with open(fname, 'w') as f:
365 with open(fname, 'w') as f:
365 f.write(s)
366 f.write(s)
366
367
367 @catch_config_error
368 @catch_config_error
368 def initialize(self, argv=None):
369 def initialize(self, argv=None):
369 # don't hook up crash handler before parsing command-line
370 # don't hook up crash handler before parsing command-line
370 self.parse_command_line(argv)
371 self.parse_command_line(argv)
371 self.init_crash_handler()
372 self.init_crash_handler()
372 if self.subapp is not None:
373 if self.subapp is not None:
373 # stop here if subapp is taking over
374 # stop here if subapp is taking over
374 return
375 return
375 cl_config = self.config
376 cl_config = self.config
376 self.init_profile_dir()
377 self.init_profile_dir()
377 self.init_config_files()
378 self.init_config_files()
378 self.load_config_file()
379 self.load_config_file()
379 # enforce cl-opts override configfile opts:
380 # enforce cl-opts override configfile opts:
380 self.update_config(cl_config)
381 self.update_config(cl_config)
381
382
@@ -1,216 +1,216 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian E. Granger
7 * Brian E. Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 from __future__ import print_function
21 from __future__ import print_function
22
22
23 import os
23 import os
24 import sys
24 import sys
25 import traceback
25 import traceback
26 from pprint import pformat
26 from pprint import pformat
27
27
28 from IPython.core import ultratb
28 from IPython.core import ultratb
29 from IPython.core.release import author_email
29 from IPython.core.release import author_email
30 from IPython.utils.sysinfo import sys_info
30 from IPython.utils.sysinfo import sys_info
31 from IPython.utils.py3compat import input
31 from IPython.utils.py3compat import input, getcwd
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Code
34 # Code
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 # Template for the user message.
37 # Template for the user message.
38 _default_message_template = """\
38 _default_message_template = """\
39 Oops, {app_name} crashed. We do our best to make it stable, but...
39 Oops, {app_name} crashed. We do our best to make it stable, but...
40
40
41 A crash report was automatically generated with the following information:
41 A crash report was automatically generated with the following information:
42 - A verbatim copy of the crash traceback.
42 - A verbatim copy of the crash traceback.
43 - A copy of your input history during this session.
43 - A copy of your input history during this session.
44 - Data on your current {app_name} configuration.
44 - Data on your current {app_name} configuration.
45
45
46 It was left in the file named:
46 It was left in the file named:
47 \t'{crash_report_fname}'
47 \t'{crash_report_fname}'
48 If you can email this file to the developers, the information in it will help
48 If you can email this file to the developers, the information in it will help
49 them in understanding and correcting the problem.
49 them in understanding and correcting the problem.
50
50
51 You can mail it to: {contact_name} at {contact_email}
51 You can mail it to: {contact_name} at {contact_email}
52 with the subject '{app_name} Crash Report'.
52 with the subject '{app_name} Crash Report'.
53
53
54 If you want to do it now, the following command will work (under Unix):
54 If you want to do it now, the following command will work (under Unix):
55 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
55 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
56
56
57 To ensure accurate tracking of this issue, please file a report about it at:
57 To ensure accurate tracking of this issue, please file a report about it at:
58 {bug_tracker}
58 {bug_tracker}
59 """
59 """
60
60
61 _lite_message_template = """
61 _lite_message_template = """
62 If you suspect this is an IPython bug, please report it at:
62 If you suspect this is an IPython bug, please report it at:
63 https://github.com/ipython/ipython/issues
63 https://github.com/ipython/ipython/issues
64 or send an email to the mailing list at {email}
64 or send an email to the mailing list at {email}
65
65
66 You can print a more detailed traceback right now with "%tb", or use "%debug"
66 You can print a more detailed traceback right now with "%tb", or use "%debug"
67 to interactively debug it.
67 to interactively debug it.
68
68
69 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
69 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
70 {config}Application.verbose_crash=True
70 {config}Application.verbose_crash=True
71 """
71 """
72
72
73
73
74 class CrashHandler(object):
74 class CrashHandler(object):
75 """Customizable crash handlers for IPython applications.
75 """Customizable crash handlers for IPython applications.
76
76
77 Instances of this class provide a :meth:`__call__` method which can be
77 Instances of this class provide a :meth:`__call__` method which can be
78 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
78 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
79
79
80 def __call__(self, etype, evalue, etb)
80 def __call__(self, etype, evalue, etb)
81 """
81 """
82
82
83 message_template = _default_message_template
83 message_template = _default_message_template
84 section_sep = '\n\n'+'*'*75+'\n\n'
84 section_sep = '\n\n'+'*'*75+'\n\n'
85
85
86 def __init__(self, app, contact_name=None, contact_email=None,
86 def __init__(self, app, contact_name=None, contact_email=None,
87 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
87 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
88 """Create a new crash handler
88 """Create a new crash handler
89
89
90 Parameters
90 Parameters
91 ----------
91 ----------
92 app : Application
92 app : Application
93 A running :class:`Application` instance, which will be queried at
93 A running :class:`Application` instance, which will be queried at
94 crash time for internal information.
94 crash time for internal information.
95
95
96 contact_name : str
96 contact_name : str
97 A string with the name of the person to contact.
97 A string with the name of the person to contact.
98
98
99 contact_email : str
99 contact_email : str
100 A string with the email address of the contact.
100 A string with the email address of the contact.
101
101
102 bug_tracker : str
102 bug_tracker : str
103 A string with the URL for your project's bug tracker.
103 A string with the URL for your project's bug tracker.
104
104
105 show_crash_traceback : bool
105 show_crash_traceback : bool
106 If false, don't print the crash traceback on stderr, only generate
106 If false, don't print the crash traceback on stderr, only generate
107 the on-disk report
107 the on-disk report
108
108
109 Non-argument instance attributes:
109 Non-argument instance attributes:
110
110
111 These instances contain some non-argument attributes which allow for
111 These instances contain some non-argument attributes which allow for
112 further customization of the crash handler's behavior. Please see the
112 further customization of the crash handler's behavior. Please see the
113 source for further details.
113 source for further details.
114 """
114 """
115 self.crash_report_fname = "Crash_report_%s.txt" % app.name
115 self.crash_report_fname = "Crash_report_%s.txt" % app.name
116 self.app = app
116 self.app = app
117 self.call_pdb = call_pdb
117 self.call_pdb = call_pdb
118 #self.call_pdb = True # dbg
118 #self.call_pdb = True # dbg
119 self.show_crash_traceback = show_crash_traceback
119 self.show_crash_traceback = show_crash_traceback
120 self.info = dict(app_name = app.name,
120 self.info = dict(app_name = app.name,
121 contact_name = contact_name,
121 contact_name = contact_name,
122 contact_email = contact_email,
122 contact_email = contact_email,
123 bug_tracker = bug_tracker,
123 bug_tracker = bug_tracker,
124 crash_report_fname = self.crash_report_fname)
124 crash_report_fname = self.crash_report_fname)
125
125
126
126
127 def __call__(self, etype, evalue, etb):
127 def __call__(self, etype, evalue, etb):
128 """Handle an exception, call for compatible with sys.excepthook"""
128 """Handle an exception, call for compatible with sys.excepthook"""
129
129
130 # do not allow the crash handler to be called twice without reinstalling it
130 # do not allow the crash handler to be called twice without reinstalling it
131 # this prevents unlikely errors in the crash handling from entering an
131 # this prevents unlikely errors in the crash handling from entering an
132 # infinite loop.
132 # infinite loop.
133 sys.excepthook = sys.__excepthook__
133 sys.excepthook = sys.__excepthook__
134
134
135 # Report tracebacks shouldn't use color in general (safer for users)
135 # Report tracebacks shouldn't use color in general (safer for users)
136 color_scheme = 'NoColor'
136 color_scheme = 'NoColor'
137
137
138 # Use this ONLY for developer debugging (keep commented out for release)
138 # Use this ONLY for developer debugging (keep commented out for release)
139 #color_scheme = 'Linux' # dbg
139 #color_scheme = 'Linux' # dbg
140 try:
140 try:
141 rptdir = self.app.ipython_dir
141 rptdir = self.app.ipython_dir
142 except:
142 except:
143 rptdir = os.getcwdu()
143 rptdir = getcwd()
144 if rptdir is None or not os.path.isdir(rptdir):
144 if rptdir is None or not os.path.isdir(rptdir):
145 rptdir = os.getcwdu()
145 rptdir = getcwd()
146 report_name = os.path.join(rptdir,self.crash_report_fname)
146 report_name = os.path.join(rptdir,self.crash_report_fname)
147 # write the report filename into the instance dict so it can get
147 # write the report filename into the instance dict so it can get
148 # properly expanded out in the user message template
148 # properly expanded out in the user message template
149 self.crash_report_fname = report_name
149 self.crash_report_fname = report_name
150 self.info['crash_report_fname'] = report_name
150 self.info['crash_report_fname'] = report_name
151 TBhandler = ultratb.VerboseTB(
151 TBhandler = ultratb.VerboseTB(
152 color_scheme=color_scheme,
152 color_scheme=color_scheme,
153 long_header=1,
153 long_header=1,
154 call_pdb=self.call_pdb,
154 call_pdb=self.call_pdb,
155 )
155 )
156 if self.call_pdb:
156 if self.call_pdb:
157 TBhandler(etype,evalue,etb)
157 TBhandler(etype,evalue,etb)
158 return
158 return
159 else:
159 else:
160 traceback = TBhandler.text(etype,evalue,etb,context=31)
160 traceback = TBhandler.text(etype,evalue,etb,context=31)
161
161
162 # print traceback to screen
162 # print traceback to screen
163 if self.show_crash_traceback:
163 if self.show_crash_traceback:
164 print(traceback, file=sys.stderr)
164 print(traceback, file=sys.stderr)
165
165
166 # and generate a complete report on disk
166 # and generate a complete report on disk
167 try:
167 try:
168 report = open(report_name,'w')
168 report = open(report_name,'w')
169 except:
169 except:
170 print('Could not create crash report on disk.', file=sys.stderr)
170 print('Could not create crash report on disk.', file=sys.stderr)
171 return
171 return
172
172
173 # Inform user on stderr of what happened
173 # Inform user on stderr of what happened
174 print('\n'+'*'*70+'\n', file=sys.stderr)
174 print('\n'+'*'*70+'\n', file=sys.stderr)
175 print(self.message_template.format(**self.info), file=sys.stderr)
175 print(self.message_template.format(**self.info), file=sys.stderr)
176
176
177 # Construct report on disk
177 # Construct report on disk
178 report.write(self.make_report(traceback))
178 report.write(self.make_report(traceback))
179 report.close()
179 report.close()
180 input("Hit <Enter> to quit (your terminal may close):")
180 input("Hit <Enter> to quit (your terminal may close):")
181
181
182 def make_report(self,traceback):
182 def make_report(self,traceback):
183 """Return a string containing a crash report."""
183 """Return a string containing a crash report."""
184
184
185 sec_sep = self.section_sep
185 sec_sep = self.section_sep
186
186
187 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
187 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
188 rpt_add = report.append
188 rpt_add = report.append
189 rpt_add(sys_info())
189 rpt_add(sys_info())
190
190
191 try:
191 try:
192 config = pformat(self.app.config)
192 config = pformat(self.app.config)
193 rpt_add(sec_sep)
193 rpt_add(sec_sep)
194 rpt_add('Application name: %s\n\n' % self.app_name)
194 rpt_add('Application name: %s\n\n' % self.app_name)
195 rpt_add('Current user configuration structure:\n\n')
195 rpt_add('Current user configuration structure:\n\n')
196 rpt_add(config)
196 rpt_add(config)
197 except:
197 except:
198 pass
198 pass
199 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
199 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
200
200
201 return ''.join(report)
201 return ''.join(report)
202
202
203
203
204 def crash_handler_lite(etype, evalue, tb):
204 def crash_handler_lite(etype, evalue, tb):
205 """a light excepthook, adding a small message to the usual traceback"""
205 """a light excepthook, adding a small message to the usual traceback"""
206 traceback.print_exception(etype, evalue, tb)
206 traceback.print_exception(etype, evalue, tb)
207
207
208 from IPython.core.interactiveshell import InteractiveShell
208 from IPython.core.interactiveshell import InteractiveShell
209 if InteractiveShell.initialized():
209 if InteractiveShell.initialized():
210 # we are in a Shell environment, give %magic example
210 # we are in a Shell environment, give %magic example
211 config = "%config "
211 config = "%config "
212 else:
212 else:
213 # we are not in a shell, show generic config
213 # we are not in a shell, show generic config
214 config = "c."
214 config = "c."
215 print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr)
215 print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr)
216
216
@@ -1,807 +1,808 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010-2011 The IPython Development Team.
3 # Copyright (C) 2010-2011 The IPython Development Team.
4 #
4 #
5 # Distributed under the terms of the BSD License.
5 # Distributed under the terms of the BSD License.
6 #
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import atexit
16 import atexit
17 import datetime
17 import datetime
18 import os
18 import os
19 import re
19 import re
20 try:
20 try:
21 import sqlite3
21 import sqlite3
22 except ImportError:
22 except ImportError:
23 try:
23 try:
24 from pysqlite2 import dbapi2 as sqlite3
24 from pysqlite2 import dbapi2 as sqlite3
25 except ImportError:
25 except ImportError:
26 sqlite3 = None
26 sqlite3 = None
27 import threading
27 import threading
28
28
29 # Our own packages
29 # Our own packages
30 from IPython.config.configurable import Configurable
30 from IPython.config.configurable import Configurable
31 from IPython.external.decorator import decorator
31 from IPython.external.decorator import decorator
32 from IPython.utils.path import locate_profile
32 from IPython.utils.path import locate_profile
33 from IPython.utils import py3compat
33 from IPython.utils.traitlets import (
34 from IPython.utils.traitlets import (
34 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
35 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
35 )
36 )
36 from IPython.utils.warn import warn
37 from IPython.utils.warn import warn
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # Classes and functions
40 # Classes and functions
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41
42
42 class DummyDB(object):
43 class DummyDB(object):
43 """Dummy DB that will act as a black hole for history.
44 """Dummy DB that will act as a black hole for history.
44
45
45 Only used in the absence of sqlite"""
46 Only used in the absence of sqlite"""
46 def execute(*args, **kwargs):
47 def execute(*args, **kwargs):
47 return []
48 return []
48
49
49 def commit(self, *args, **kwargs):
50 def commit(self, *args, **kwargs):
50 pass
51 pass
51
52
52 def __enter__(self, *args, **kwargs):
53 def __enter__(self, *args, **kwargs):
53 pass
54 pass
54
55
55 def __exit__(self, *args, **kwargs):
56 def __exit__(self, *args, **kwargs):
56 pass
57 pass
57
58
58
59
59 @decorator
60 @decorator
60 def needs_sqlite(f, self, *a, **kw):
61 def needs_sqlite(f, self, *a, **kw):
61 """return an empty list in the absence of sqlite"""
62 """return an empty list in the absence of sqlite"""
62 if sqlite3 is None or not self.enabled:
63 if sqlite3 is None or not self.enabled:
63 return []
64 return []
64 else:
65 else:
65 return f(self, *a, **kw)
66 return f(self, *a, **kw)
66
67
67
68
68 if sqlite3 is not None:
69 if sqlite3 is not None:
69 DatabaseError = sqlite3.DatabaseError
70 DatabaseError = sqlite3.DatabaseError
70 else:
71 else:
71 class DatabaseError(Exception):
72 class DatabaseError(Exception):
72 "Dummy exception when sqlite could not be imported. Should never occur."
73 "Dummy exception when sqlite could not be imported. Should never occur."
73
74
74 @decorator
75 @decorator
75 def catch_corrupt_db(f, self, *a, **kw):
76 def catch_corrupt_db(f, self, *a, **kw):
76 """A decorator which wraps HistoryAccessor method calls to catch errors from
77 """A decorator which wraps HistoryAccessor method calls to catch errors from
77 a corrupt SQLite database, move the old database out of the way, and create
78 a corrupt SQLite database, move the old database out of the way, and create
78 a new one.
79 a new one.
79 """
80 """
80 try:
81 try:
81 return f(self, *a, **kw)
82 return f(self, *a, **kw)
82 except DatabaseError:
83 except DatabaseError:
83 if os.path.isfile(self.hist_file):
84 if os.path.isfile(self.hist_file):
84 # Try to move the file out of the way
85 # Try to move the file out of the way
85 base,ext = os.path.splitext(self.hist_file)
86 base,ext = os.path.splitext(self.hist_file)
86 newpath = base + '-corrupt' + ext
87 newpath = base + '-corrupt' + ext
87 os.rename(self.hist_file, newpath)
88 os.rename(self.hist_file, newpath)
88 self.init_db()
89 self.init_db()
89 print("ERROR! History file wasn't a valid SQLite database.",
90 print("ERROR! History file wasn't a valid SQLite database.",
90 "It was moved to %s" % newpath, "and a new file created.")
91 "It was moved to %s" % newpath, "and a new file created.")
91 return []
92 return []
92
93
93 else:
94 else:
94 # The hist_file is probably :memory: or something else.
95 # The hist_file is probably :memory: or something else.
95 raise
96 raise
96
97
97
98
98
99
99 class HistoryAccessor(Configurable):
100 class HistoryAccessor(Configurable):
100 """Access the history database without adding to it.
101 """Access the history database without adding to it.
101
102
102 This is intended for use by standalone history tools. IPython shells use
103 This is intended for use by standalone history tools. IPython shells use
103 HistoryManager, below, which is a subclass of this."""
104 HistoryManager, below, which is a subclass of this."""
104
105
105 # String holding the path to the history file
106 # String holding the path to the history file
106 hist_file = Unicode(config=True,
107 hist_file = Unicode(config=True,
107 help="""Path to file to use for SQLite history database.
108 help="""Path to file to use for SQLite history database.
108
109
109 By default, IPython will put the history database in the IPython
110 By default, IPython will put the history database in the IPython
110 profile directory. If you would rather share one history among
111 profile directory. If you would rather share one history among
111 profiles, you can set this value in each, so that they are consistent.
112 profiles, you can set this value in each, so that they are consistent.
112
113
113 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
114 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
114 mounts. If you see IPython hanging, try setting this to something on a
115 mounts. If you see IPython hanging, try setting this to something on a
115 local disk, e.g::
116 local disk, e.g::
116
117
117 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
118 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
118
119
119 """)
120 """)
120
121
121 enabled = Bool(True, config=True,
122 enabled = Bool(True, config=True,
122 help="""enable the SQLite history
123 help="""enable the SQLite history
123
124
124 set enabled=False to disable the SQLite history,
125 set enabled=False to disable the SQLite history,
125 in which case there will be no stored history, no SQLite connection,
126 in which case there will be no stored history, no SQLite connection,
126 and no background saving thread. This may be necessary in some
127 and no background saving thread. This may be necessary in some
127 threaded environments where IPython is embedded.
128 threaded environments where IPython is embedded.
128 """
129 """
129 )
130 )
130
131
131 connection_options = Dict(config=True,
132 connection_options = Dict(config=True,
132 help="""Options for configuring the SQLite connection
133 help="""Options for configuring the SQLite connection
133
134
134 These options are passed as keyword args to sqlite3.connect
135 These options are passed as keyword args to sqlite3.connect
135 when establishing database conenctions.
136 when establishing database conenctions.
136 """
137 """
137 )
138 )
138
139
139 # The SQLite database
140 # The SQLite database
140 db = Any()
141 db = Any()
141 def _db_changed(self, name, old, new):
142 def _db_changed(self, name, old, new):
142 """validate the db, since it can be an Instance of two different types"""
143 """validate the db, since it can be an Instance of two different types"""
143 connection_types = (DummyDB,)
144 connection_types = (DummyDB,)
144 if sqlite3 is not None:
145 if sqlite3 is not None:
145 connection_types = (DummyDB, sqlite3.Connection)
146 connection_types = (DummyDB, sqlite3.Connection)
146 if not isinstance(new, connection_types):
147 if not isinstance(new, connection_types):
147 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
148 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
148 (self.__class__.__name__, new)
149 (self.__class__.__name__, new)
149 raise TraitError(msg)
150 raise TraitError(msg)
150
151
151 def __init__(self, profile='default', hist_file=u'', **traits):
152 def __init__(self, profile='default', hist_file=u'', **traits):
152 """Create a new history accessor.
153 """Create a new history accessor.
153
154
154 Parameters
155 Parameters
155 ----------
156 ----------
156 profile : str
157 profile : str
157 The name of the profile from which to open history.
158 The name of the profile from which to open history.
158 hist_file : str
159 hist_file : str
159 Path to an SQLite history database stored by IPython. If specified,
160 Path to an SQLite history database stored by IPython. If specified,
160 hist_file overrides profile.
161 hist_file overrides profile.
161 config :
162 config :
162 Config object. hist_file can also be set through this.
163 Config object. hist_file can also be set through this.
163 """
164 """
164 # We need a pointer back to the shell for various tasks.
165 # We need a pointer back to the shell for various tasks.
165 super(HistoryAccessor, self).__init__(**traits)
166 super(HistoryAccessor, self).__init__(**traits)
166 # defer setting hist_file from kwarg until after init,
167 # defer setting hist_file from kwarg until after init,
167 # otherwise the default kwarg value would clobber any value
168 # otherwise the default kwarg value would clobber any value
168 # set by config
169 # set by config
169 if hist_file:
170 if hist_file:
170 self.hist_file = hist_file
171 self.hist_file = hist_file
171
172
172 if self.hist_file == u'':
173 if self.hist_file == u'':
173 # No one has set the hist_file, yet.
174 # No one has set the hist_file, yet.
174 self.hist_file = self._get_hist_file_name(profile)
175 self.hist_file = self._get_hist_file_name(profile)
175
176
176 if sqlite3 is None and self.enabled:
177 if sqlite3 is None and self.enabled:
177 warn("IPython History requires SQLite, your history will not be saved")
178 warn("IPython History requires SQLite, your history will not be saved")
178 self.enabled = False
179 self.enabled = False
179
180
180 self.init_db()
181 self.init_db()
181
182
182 def _get_hist_file_name(self, profile='default'):
183 def _get_hist_file_name(self, profile='default'):
183 """Find the history file for the given profile name.
184 """Find the history file for the given profile name.
184
185
185 This is overridden by the HistoryManager subclass, to use the shell's
186 This is overridden by the HistoryManager subclass, to use the shell's
186 active profile.
187 active profile.
187
188
188 Parameters
189 Parameters
189 ----------
190 ----------
190 profile : str
191 profile : str
191 The name of a profile which has a history file.
192 The name of a profile which has a history file.
192 """
193 """
193 return os.path.join(locate_profile(profile), 'history.sqlite')
194 return os.path.join(locate_profile(profile), 'history.sqlite')
194
195
195 @catch_corrupt_db
196 @catch_corrupt_db
196 def init_db(self):
197 def init_db(self):
197 """Connect to the database, and create tables if necessary."""
198 """Connect to the database, and create tables if necessary."""
198 if not self.enabled:
199 if not self.enabled:
199 self.db = DummyDB()
200 self.db = DummyDB()
200 return
201 return
201
202
202 # use detect_types so that timestamps return datetime objects
203 # use detect_types so that timestamps return datetime objects
203 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
204 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
204 kwargs.update(self.connection_options)
205 kwargs.update(self.connection_options)
205 self.db = sqlite3.connect(self.hist_file, **kwargs)
206 self.db = sqlite3.connect(self.hist_file, **kwargs)
206 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
207 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
207 primary key autoincrement, start timestamp,
208 primary key autoincrement, start timestamp,
208 end timestamp, num_cmds integer, remark text)""")
209 end timestamp, num_cmds integer, remark text)""")
209 self.db.execute("""CREATE TABLE IF NOT EXISTS history
210 self.db.execute("""CREATE TABLE IF NOT EXISTS history
210 (session integer, line integer, source text, source_raw text,
211 (session integer, line integer, source text, source_raw text,
211 PRIMARY KEY (session, line))""")
212 PRIMARY KEY (session, line))""")
212 # Output history is optional, but ensure the table's there so it can be
213 # Output history is optional, but ensure the table's there so it can be
213 # enabled later.
214 # enabled later.
214 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
215 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
215 (session integer, line integer, output text,
216 (session integer, line integer, output text,
216 PRIMARY KEY (session, line))""")
217 PRIMARY KEY (session, line))""")
217 self.db.commit()
218 self.db.commit()
218
219
219 def writeout_cache(self):
220 def writeout_cache(self):
220 """Overridden by HistoryManager to dump the cache before certain
221 """Overridden by HistoryManager to dump the cache before certain
221 database lookups."""
222 database lookups."""
222 pass
223 pass
223
224
224 ## -------------------------------
225 ## -------------------------------
225 ## Methods for retrieving history:
226 ## Methods for retrieving history:
226 ## -------------------------------
227 ## -------------------------------
227 def _run_sql(self, sql, params, raw=True, output=False):
228 def _run_sql(self, sql, params, raw=True, output=False):
228 """Prepares and runs an SQL query for the history database.
229 """Prepares and runs an SQL query for the history database.
229
230
230 Parameters
231 Parameters
231 ----------
232 ----------
232 sql : str
233 sql : str
233 Any filtering expressions to go after SELECT ... FROM ...
234 Any filtering expressions to go after SELECT ... FROM ...
234 params : tuple
235 params : tuple
235 Parameters passed to the SQL query (to replace "?")
236 Parameters passed to the SQL query (to replace "?")
236 raw, output : bool
237 raw, output : bool
237 See :meth:`get_range`
238 See :meth:`get_range`
238
239
239 Returns
240 Returns
240 -------
241 -------
241 Tuples as :meth:`get_range`
242 Tuples as :meth:`get_range`
242 """
243 """
243 toget = 'source_raw' if raw else 'source'
244 toget = 'source_raw' if raw else 'source'
244 sqlfrom = "history"
245 sqlfrom = "history"
245 if output:
246 if output:
246 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
247 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
247 toget = "history.%s, output_history.output" % toget
248 toget = "history.%s, output_history.output" % toget
248 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
249 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
249 (toget, sqlfrom) + sql, params)
250 (toget, sqlfrom) + sql, params)
250 if output: # Regroup into 3-tuples, and parse JSON
251 if output: # Regroup into 3-tuples, and parse JSON
251 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
252 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
252 return cur
253 return cur
253
254
254 @needs_sqlite
255 @needs_sqlite
255 @catch_corrupt_db
256 @catch_corrupt_db
256 def get_session_info(self, session=0):
257 def get_session_info(self, session=0):
257 """get info about a session
258 """get info about a session
258
259
259 Parameters
260 Parameters
260 ----------
261 ----------
261
262
262 session : int
263 session : int
263 Session number to retrieve. The current session is 0, and negative
264 Session number to retrieve. The current session is 0, and negative
264 numbers count back from current session, so -1 is previous session.
265 numbers count back from current session, so -1 is previous session.
265
266
266 Returns
267 Returns
267 -------
268 -------
268
269
269 (session_id [int], start [datetime], end [datetime], num_cmds [int],
270 (session_id [int], start [datetime], end [datetime], num_cmds [int],
270 remark [unicode])
271 remark [unicode])
271
272
272 Sessions that are running or did not exit cleanly will have `end=None`
273 Sessions that are running or did not exit cleanly will have `end=None`
273 and `num_cmds=None`.
274 and `num_cmds=None`.
274
275
275 """
276 """
276
277
277 if session <= 0:
278 if session <= 0:
278 session += self.session_number
279 session += self.session_number
279
280
280 query = "SELECT * from sessions where session == ?"
281 query = "SELECT * from sessions where session == ?"
281 return self.db.execute(query, (session,)).fetchone()
282 return self.db.execute(query, (session,)).fetchone()
282
283
283 @catch_corrupt_db
284 @catch_corrupt_db
284 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
285 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
285 """Get the last n lines from the history database.
286 """Get the last n lines from the history database.
286
287
287 Parameters
288 Parameters
288 ----------
289 ----------
289 n : int
290 n : int
290 The number of lines to get
291 The number of lines to get
291 raw, output : bool
292 raw, output : bool
292 See :meth:`get_range`
293 See :meth:`get_range`
293 include_latest : bool
294 include_latest : bool
294 If False (default), n+1 lines are fetched, and the latest one
295 If False (default), n+1 lines are fetched, and the latest one
295 is discarded. This is intended to be used where the function
296 is discarded. This is intended to be used where the function
296 is called by a user command, which it should not return.
297 is called by a user command, which it should not return.
297
298
298 Returns
299 Returns
299 -------
300 -------
300 Tuples as :meth:`get_range`
301 Tuples as :meth:`get_range`
301 """
302 """
302 self.writeout_cache()
303 self.writeout_cache()
303 if not include_latest:
304 if not include_latest:
304 n += 1
305 n += 1
305 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
306 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
306 (n,), raw=raw, output=output)
307 (n,), raw=raw, output=output)
307 if not include_latest:
308 if not include_latest:
308 return reversed(list(cur)[1:])
309 return reversed(list(cur)[1:])
309 return reversed(list(cur))
310 return reversed(list(cur))
310
311
311 @catch_corrupt_db
312 @catch_corrupt_db
312 def search(self, pattern="*", raw=True, search_raw=True,
313 def search(self, pattern="*", raw=True, search_raw=True,
313 output=False, n=None, unique=False):
314 output=False, n=None, unique=False):
314 """Search the database using unix glob-style matching (wildcards
315 """Search the database using unix glob-style matching (wildcards
315 * and ?).
316 * and ?).
316
317
317 Parameters
318 Parameters
318 ----------
319 ----------
319 pattern : str
320 pattern : str
320 The wildcarded pattern to match when searching
321 The wildcarded pattern to match when searching
321 search_raw : bool
322 search_raw : bool
322 If True, search the raw input, otherwise, the parsed input
323 If True, search the raw input, otherwise, the parsed input
323 raw, output : bool
324 raw, output : bool
324 See :meth:`get_range`
325 See :meth:`get_range`
325 n : None or int
326 n : None or int
326 If an integer is given, it defines the limit of
327 If an integer is given, it defines the limit of
327 returned entries.
328 returned entries.
328 unique : bool
329 unique : bool
329 When it is true, return only unique entries.
330 When it is true, return only unique entries.
330
331
331 Returns
332 Returns
332 -------
333 -------
333 Tuples as :meth:`get_range`
334 Tuples as :meth:`get_range`
334 """
335 """
335 tosearch = "source_raw" if search_raw else "source"
336 tosearch = "source_raw" if search_raw else "source"
336 if output:
337 if output:
337 tosearch = "history." + tosearch
338 tosearch = "history." + tosearch
338 self.writeout_cache()
339 self.writeout_cache()
339 sqlform = "WHERE %s GLOB ?" % tosearch
340 sqlform = "WHERE %s GLOB ?" % tosearch
340 params = (pattern,)
341 params = (pattern,)
341 if unique:
342 if unique:
342 sqlform += ' GROUP BY {0}'.format(tosearch)
343 sqlform += ' GROUP BY {0}'.format(tosearch)
343 if n is not None:
344 if n is not None:
344 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
345 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
345 params += (n,)
346 params += (n,)
346 elif unique:
347 elif unique:
347 sqlform += " ORDER BY session, line"
348 sqlform += " ORDER BY session, line"
348 cur = self._run_sql(sqlform, params, raw=raw, output=output)
349 cur = self._run_sql(sqlform, params, raw=raw, output=output)
349 if n is not None:
350 if n is not None:
350 return reversed(list(cur))
351 return reversed(list(cur))
351 return cur
352 return cur
352
353
353 @catch_corrupt_db
354 @catch_corrupt_db
354 def get_range(self, session, start=1, stop=None, raw=True,output=False):
355 def get_range(self, session, start=1, stop=None, raw=True,output=False):
355 """Retrieve input by session.
356 """Retrieve input by session.
356
357
357 Parameters
358 Parameters
358 ----------
359 ----------
359 session : int
360 session : int
360 Session number to retrieve.
361 Session number to retrieve.
361 start : int
362 start : int
362 First line to retrieve.
363 First line to retrieve.
363 stop : int
364 stop : int
364 End of line range (excluded from output itself). If None, retrieve
365 End of line range (excluded from output itself). If None, retrieve
365 to the end of the session.
366 to the end of the session.
366 raw : bool
367 raw : bool
367 If True, return untranslated input
368 If True, return untranslated input
368 output : bool
369 output : bool
369 If True, attempt to include output. This will be 'real' Python
370 If True, attempt to include output. This will be 'real' Python
370 objects for the current session, or text reprs from previous
371 objects for the current session, or text reprs from previous
371 sessions if db_log_output was enabled at the time. Where no output
372 sessions if db_log_output was enabled at the time. Where no output
372 is found, None is used.
373 is found, None is used.
373
374
374 Returns
375 Returns
375 -------
376 -------
376 An iterator over the desired lines. Each line is a 3-tuple, either
377 An iterator over the desired lines. Each line is a 3-tuple, either
377 (session, line, input) if output is False, or
378 (session, line, input) if output is False, or
378 (session, line, (input, output)) if output is True.
379 (session, line, (input, output)) if output is True.
379 """
380 """
380 if stop:
381 if stop:
381 lineclause = "line >= ? AND line < ?"
382 lineclause = "line >= ? AND line < ?"
382 params = (session, start, stop)
383 params = (session, start, stop)
383 else:
384 else:
384 lineclause = "line>=?"
385 lineclause = "line>=?"
385 params = (session, start)
386 params = (session, start)
386
387
387 return self._run_sql("WHERE session==? AND %s" % lineclause,
388 return self._run_sql("WHERE session==? AND %s" % lineclause,
388 params, raw=raw, output=output)
389 params, raw=raw, output=output)
389
390
390 def get_range_by_str(self, rangestr, raw=True, output=False):
391 def get_range_by_str(self, rangestr, raw=True, output=False):
391 """Get lines of history from a string of ranges, as used by magic
392 """Get lines of history from a string of ranges, as used by magic
392 commands %hist, %save, %macro, etc.
393 commands %hist, %save, %macro, etc.
393
394
394 Parameters
395 Parameters
395 ----------
396 ----------
396 rangestr : str
397 rangestr : str
397 A string specifying ranges, e.g. "5 ~2/1-4". See
398 A string specifying ranges, e.g. "5 ~2/1-4". See
398 :func:`magic_history` for full details.
399 :func:`magic_history` for full details.
399 raw, output : bool
400 raw, output : bool
400 As :meth:`get_range`
401 As :meth:`get_range`
401
402
402 Returns
403 Returns
403 -------
404 -------
404 Tuples as :meth:`get_range`
405 Tuples as :meth:`get_range`
405 """
406 """
406 for sess, s, e in extract_hist_ranges(rangestr):
407 for sess, s, e in extract_hist_ranges(rangestr):
407 for line in self.get_range(sess, s, e, raw=raw, output=output):
408 for line in self.get_range(sess, s, e, raw=raw, output=output):
408 yield line
409 yield line
409
410
410
411
411 class HistoryManager(HistoryAccessor):
412 class HistoryManager(HistoryAccessor):
412 """A class to organize all history-related functionality in one place.
413 """A class to organize all history-related functionality in one place.
413 """
414 """
414 # Public interface
415 # Public interface
415
416
416 # An instance of the IPython shell we are attached to
417 # An instance of the IPython shell we are attached to
417 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
418 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
418 # Lists to hold processed and raw history. These start with a blank entry
419 # Lists to hold processed and raw history. These start with a blank entry
419 # so that we can index them starting from 1
420 # so that we can index them starting from 1
420 input_hist_parsed = List([""])
421 input_hist_parsed = List([""])
421 input_hist_raw = List([""])
422 input_hist_raw = List([""])
422 # A list of directories visited during session
423 # A list of directories visited during session
423 dir_hist = List()
424 dir_hist = List()
424 def _dir_hist_default(self):
425 def _dir_hist_default(self):
425 try:
426 try:
426 return [os.getcwdu()]
427 return [py3compat.getcwd()]
427 except OSError:
428 except OSError:
428 return []
429 return []
429
430
430 # A dict of output history, keyed with ints from the shell's
431 # A dict of output history, keyed with ints from the shell's
431 # execution count.
432 # execution count.
432 output_hist = Dict()
433 output_hist = Dict()
433 # The text/plain repr of outputs.
434 # The text/plain repr of outputs.
434 output_hist_reprs = Dict()
435 output_hist_reprs = Dict()
435
436
436 # The number of the current session in the history database
437 # The number of the current session in the history database
437 session_number = Integer()
438 session_number = Integer()
438 # Should we log output to the database? (default no)
439 # Should we log output to the database? (default no)
439 db_log_output = Bool(False, config=True)
440 db_log_output = Bool(False, config=True)
440 # Write to database every x commands (higher values save disk access & power)
441 # Write to database every x commands (higher values save disk access & power)
441 # Values of 1 or less effectively disable caching.
442 # Values of 1 or less effectively disable caching.
442 db_cache_size = Integer(0, config=True)
443 db_cache_size = Integer(0, config=True)
443 # The input and output caches
444 # The input and output caches
444 db_input_cache = List()
445 db_input_cache = List()
445 db_output_cache = List()
446 db_output_cache = List()
446
447
447 # History saving in separate thread
448 # History saving in separate thread
448 save_thread = Instance('IPython.core.history.HistorySavingThread')
449 save_thread = Instance('IPython.core.history.HistorySavingThread')
449 try: # Event is a function returning an instance of _Event...
450 try: # Event is a function returning an instance of _Event...
450 save_flag = Instance(threading._Event)
451 save_flag = Instance(threading._Event)
451 except AttributeError: # ...until Python 3.3, when it's a class.
452 except AttributeError: # ...until Python 3.3, when it's a class.
452 save_flag = Instance(threading.Event)
453 save_flag = Instance(threading.Event)
453
454
454 # Private interface
455 # Private interface
455 # Variables used to store the three last inputs from the user. On each new
456 # Variables used to store the three last inputs from the user. On each new
456 # history update, we populate the user's namespace with these, shifted as
457 # history update, we populate the user's namespace with these, shifted as
457 # necessary.
458 # necessary.
458 _i00 = Unicode(u'')
459 _i00 = Unicode(u'')
459 _i = Unicode(u'')
460 _i = Unicode(u'')
460 _ii = Unicode(u'')
461 _ii = Unicode(u'')
461 _iii = Unicode(u'')
462 _iii = Unicode(u'')
462
463
463 # A regex matching all forms of the exit command, so that we don't store
464 # A regex matching all forms of the exit command, so that we don't store
464 # them in the history (it's annoying to rewind the first entry and land on
465 # them in the history (it's annoying to rewind the first entry and land on
465 # an exit call).
466 # an exit call).
466 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
467 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
467
468
468 def __init__(self, shell=None, config=None, **traits):
469 def __init__(self, shell=None, config=None, **traits):
469 """Create a new history manager associated with a shell instance.
470 """Create a new history manager associated with a shell instance.
470 """
471 """
471 # We need a pointer back to the shell for various tasks.
472 # We need a pointer back to the shell for various tasks.
472 super(HistoryManager, self).__init__(shell=shell, config=config,
473 super(HistoryManager, self).__init__(shell=shell, config=config,
473 **traits)
474 **traits)
474 self.save_flag = threading.Event()
475 self.save_flag = threading.Event()
475 self.db_input_cache_lock = threading.Lock()
476 self.db_input_cache_lock = threading.Lock()
476 self.db_output_cache_lock = threading.Lock()
477 self.db_output_cache_lock = threading.Lock()
477 if self.enabled and self.hist_file != ':memory:':
478 if self.enabled and self.hist_file != ':memory:':
478 self.save_thread = HistorySavingThread(self)
479 self.save_thread = HistorySavingThread(self)
479 self.save_thread.start()
480 self.save_thread.start()
480
481
481 self.new_session()
482 self.new_session()
482
483
483 def _get_hist_file_name(self, profile=None):
484 def _get_hist_file_name(self, profile=None):
484 """Get default history file name based on the Shell's profile.
485 """Get default history file name based on the Shell's profile.
485
486
486 The profile parameter is ignored, but must exist for compatibility with
487 The profile parameter is ignored, but must exist for compatibility with
487 the parent class."""
488 the parent class."""
488 profile_dir = self.shell.profile_dir.location
489 profile_dir = self.shell.profile_dir.location
489 return os.path.join(profile_dir, 'history.sqlite')
490 return os.path.join(profile_dir, 'history.sqlite')
490
491
491 @needs_sqlite
492 @needs_sqlite
492 def new_session(self, conn=None):
493 def new_session(self, conn=None):
493 """Get a new session number."""
494 """Get a new session number."""
494 if conn is None:
495 if conn is None:
495 conn = self.db
496 conn = self.db
496
497
497 with conn:
498 with conn:
498 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
499 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
499 NULL, "") """, (datetime.datetime.now(),))
500 NULL, "") """, (datetime.datetime.now(),))
500 self.session_number = cur.lastrowid
501 self.session_number = cur.lastrowid
501
502
502 def end_session(self):
503 def end_session(self):
503 """Close the database session, filling in the end time and line count."""
504 """Close the database session, filling in the end time and line count."""
504 self.writeout_cache()
505 self.writeout_cache()
505 with self.db:
506 with self.db:
506 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
507 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
507 session==?""", (datetime.datetime.now(),
508 session==?""", (datetime.datetime.now(),
508 len(self.input_hist_parsed)-1, self.session_number))
509 len(self.input_hist_parsed)-1, self.session_number))
509 self.session_number = 0
510 self.session_number = 0
510
511
511 def name_session(self, name):
512 def name_session(self, name):
512 """Give the current session a name in the history database."""
513 """Give the current session a name in the history database."""
513 with self.db:
514 with self.db:
514 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
515 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
515 (name, self.session_number))
516 (name, self.session_number))
516
517
517 def reset(self, new_session=True):
518 def reset(self, new_session=True):
518 """Clear the session history, releasing all object references, and
519 """Clear the session history, releasing all object references, and
519 optionally open a new session."""
520 optionally open a new session."""
520 self.output_hist.clear()
521 self.output_hist.clear()
521 # The directory history can't be completely empty
522 # The directory history can't be completely empty
522 self.dir_hist[:] = [os.getcwdu()]
523 self.dir_hist[:] = [py3compat.getcwd()]
523
524
524 if new_session:
525 if new_session:
525 if self.session_number:
526 if self.session_number:
526 self.end_session()
527 self.end_session()
527 self.input_hist_parsed[:] = [""]
528 self.input_hist_parsed[:] = [""]
528 self.input_hist_raw[:] = [""]
529 self.input_hist_raw[:] = [""]
529 self.new_session()
530 self.new_session()
530
531
531 # ------------------------------
532 # ------------------------------
532 # Methods for retrieving history
533 # Methods for retrieving history
533 # ------------------------------
534 # ------------------------------
534 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
535 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
535 """Get input and output history from the current session. Called by
536 """Get input and output history from the current session. Called by
536 get_range, and takes similar parameters."""
537 get_range, and takes similar parameters."""
537 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
538 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
538
539
539 n = len(input_hist)
540 n = len(input_hist)
540 if start < 0:
541 if start < 0:
541 start += n
542 start += n
542 if not stop or (stop > n):
543 if not stop or (stop > n):
543 stop = n
544 stop = n
544 elif stop < 0:
545 elif stop < 0:
545 stop += n
546 stop += n
546
547
547 for i in range(start, stop):
548 for i in range(start, stop):
548 if output:
549 if output:
549 line = (input_hist[i], self.output_hist_reprs.get(i))
550 line = (input_hist[i], self.output_hist_reprs.get(i))
550 else:
551 else:
551 line = input_hist[i]
552 line = input_hist[i]
552 yield (0, i, line)
553 yield (0, i, line)
553
554
554 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
555 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
555 """Retrieve input by session.
556 """Retrieve input by session.
556
557
557 Parameters
558 Parameters
558 ----------
559 ----------
559 session : int
560 session : int
560 Session number to retrieve. The current session is 0, and negative
561 Session number to retrieve. The current session is 0, and negative
561 numbers count back from current session, so -1 is previous session.
562 numbers count back from current session, so -1 is previous session.
562 start : int
563 start : int
563 First line to retrieve.
564 First line to retrieve.
564 stop : int
565 stop : int
565 End of line range (excluded from output itself). If None, retrieve
566 End of line range (excluded from output itself). If None, retrieve
566 to the end of the session.
567 to the end of the session.
567 raw : bool
568 raw : bool
568 If True, return untranslated input
569 If True, return untranslated input
569 output : bool
570 output : bool
570 If True, attempt to include output. This will be 'real' Python
571 If True, attempt to include output. This will be 'real' Python
571 objects for the current session, or text reprs from previous
572 objects for the current session, or text reprs from previous
572 sessions if db_log_output was enabled at the time. Where no output
573 sessions if db_log_output was enabled at the time. Where no output
573 is found, None is used.
574 is found, None is used.
574
575
575 Returns
576 Returns
576 -------
577 -------
577 An iterator over the desired lines. Each line is a 3-tuple, either
578 An iterator over the desired lines. Each line is a 3-tuple, either
578 (session, line, input) if output is False, or
579 (session, line, input) if output is False, or
579 (session, line, (input, output)) if output is True.
580 (session, line, (input, output)) if output is True.
580 """
581 """
581 if session <= 0:
582 if session <= 0:
582 session += self.session_number
583 session += self.session_number
583 if session==self.session_number: # Current session
584 if session==self.session_number: # Current session
584 return self._get_range_session(start, stop, raw, output)
585 return self._get_range_session(start, stop, raw, output)
585 return super(HistoryManager, self).get_range(session, start, stop, raw,
586 return super(HistoryManager, self).get_range(session, start, stop, raw,
586 output)
587 output)
587
588
588 ## ----------------------------
589 ## ----------------------------
589 ## Methods for storing history:
590 ## Methods for storing history:
590 ## ----------------------------
591 ## ----------------------------
591 def store_inputs(self, line_num, source, source_raw=None):
592 def store_inputs(self, line_num, source, source_raw=None):
592 """Store source and raw input in history and create input cache
593 """Store source and raw input in history and create input cache
593 variables _i*.
594 variables _i*.
594
595
595 Parameters
596 Parameters
596 ----------
597 ----------
597 line_num : int
598 line_num : int
598 The prompt number of this input.
599 The prompt number of this input.
599
600
600 source : str
601 source : str
601 Python input.
602 Python input.
602
603
603 source_raw : str, optional
604 source_raw : str, optional
604 If given, this is the raw input without any IPython transformations
605 If given, this is the raw input without any IPython transformations
605 applied to it. If not given, ``source`` is used.
606 applied to it. If not given, ``source`` is used.
606 """
607 """
607 if source_raw is None:
608 if source_raw is None:
608 source_raw = source
609 source_raw = source
609 source = source.rstrip('\n')
610 source = source.rstrip('\n')
610 source_raw = source_raw.rstrip('\n')
611 source_raw = source_raw.rstrip('\n')
611
612
612 # do not store exit/quit commands
613 # do not store exit/quit commands
613 if self._exit_re.match(source_raw.strip()):
614 if self._exit_re.match(source_raw.strip()):
614 return
615 return
615
616
616 self.input_hist_parsed.append(source)
617 self.input_hist_parsed.append(source)
617 self.input_hist_raw.append(source_raw)
618 self.input_hist_raw.append(source_raw)
618
619
619 with self.db_input_cache_lock:
620 with self.db_input_cache_lock:
620 self.db_input_cache.append((line_num, source, source_raw))
621 self.db_input_cache.append((line_num, source, source_raw))
621 # Trigger to flush cache and write to DB.
622 # Trigger to flush cache and write to DB.
622 if len(self.db_input_cache) >= self.db_cache_size:
623 if len(self.db_input_cache) >= self.db_cache_size:
623 self.save_flag.set()
624 self.save_flag.set()
624
625
625 # update the auto _i variables
626 # update the auto _i variables
626 self._iii = self._ii
627 self._iii = self._ii
627 self._ii = self._i
628 self._ii = self._i
628 self._i = self._i00
629 self._i = self._i00
629 self._i00 = source_raw
630 self._i00 = source_raw
630
631
631 # hackish access to user namespace to create _i1,_i2... dynamically
632 # hackish access to user namespace to create _i1,_i2... dynamically
632 new_i = '_i%s' % line_num
633 new_i = '_i%s' % line_num
633 to_main = {'_i': self._i,
634 to_main = {'_i': self._i,
634 '_ii': self._ii,
635 '_ii': self._ii,
635 '_iii': self._iii,
636 '_iii': self._iii,
636 new_i : self._i00 }
637 new_i : self._i00 }
637
638
638 if self.shell is not None:
639 if self.shell is not None:
639 self.shell.push(to_main, interactive=False)
640 self.shell.push(to_main, interactive=False)
640
641
641 def store_output(self, line_num):
642 def store_output(self, line_num):
642 """If database output logging is enabled, this saves all the
643 """If database output logging is enabled, this saves all the
643 outputs from the indicated prompt number to the database. It's
644 outputs from the indicated prompt number to the database. It's
644 called by run_cell after code has been executed.
645 called by run_cell after code has been executed.
645
646
646 Parameters
647 Parameters
647 ----------
648 ----------
648 line_num : int
649 line_num : int
649 The line number from which to save outputs
650 The line number from which to save outputs
650 """
651 """
651 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
652 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
652 return
653 return
653 output = self.output_hist_reprs[line_num]
654 output = self.output_hist_reprs[line_num]
654
655
655 with self.db_output_cache_lock:
656 with self.db_output_cache_lock:
656 self.db_output_cache.append((line_num, output))
657 self.db_output_cache.append((line_num, output))
657 if self.db_cache_size <= 1:
658 if self.db_cache_size <= 1:
658 self.save_flag.set()
659 self.save_flag.set()
659
660
660 def _writeout_input_cache(self, conn):
661 def _writeout_input_cache(self, conn):
661 with conn:
662 with conn:
662 for line in self.db_input_cache:
663 for line in self.db_input_cache:
663 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
664 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
664 (self.session_number,)+line)
665 (self.session_number,)+line)
665
666
666 def _writeout_output_cache(self, conn):
667 def _writeout_output_cache(self, conn):
667 with conn:
668 with conn:
668 for line in self.db_output_cache:
669 for line in self.db_output_cache:
669 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
670 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
670 (self.session_number,)+line)
671 (self.session_number,)+line)
671
672
672 @needs_sqlite
673 @needs_sqlite
673 def writeout_cache(self, conn=None):
674 def writeout_cache(self, conn=None):
674 """Write any entries in the cache to the database."""
675 """Write any entries in the cache to the database."""
675 if conn is None:
676 if conn is None:
676 conn = self.db
677 conn = self.db
677
678
678 with self.db_input_cache_lock:
679 with self.db_input_cache_lock:
679 try:
680 try:
680 self._writeout_input_cache(conn)
681 self._writeout_input_cache(conn)
681 except sqlite3.IntegrityError:
682 except sqlite3.IntegrityError:
682 self.new_session(conn)
683 self.new_session(conn)
683 print("ERROR! Session/line number was not unique in",
684 print("ERROR! Session/line number was not unique in",
684 "database. History logging moved to new session",
685 "database. History logging moved to new session",
685 self.session_number)
686 self.session_number)
686 try:
687 try:
687 # Try writing to the new session. If this fails, don't
688 # Try writing to the new session. If this fails, don't
688 # recurse
689 # recurse
689 self._writeout_input_cache(conn)
690 self._writeout_input_cache(conn)
690 except sqlite3.IntegrityError:
691 except sqlite3.IntegrityError:
691 pass
692 pass
692 finally:
693 finally:
693 self.db_input_cache = []
694 self.db_input_cache = []
694
695
695 with self.db_output_cache_lock:
696 with self.db_output_cache_lock:
696 try:
697 try:
697 self._writeout_output_cache(conn)
698 self._writeout_output_cache(conn)
698 except sqlite3.IntegrityError:
699 except sqlite3.IntegrityError:
699 print("!! Session/line number for output was not unique",
700 print("!! Session/line number for output was not unique",
700 "in database. Output will not be stored.")
701 "in database. Output will not be stored.")
701 finally:
702 finally:
702 self.db_output_cache = []
703 self.db_output_cache = []
703
704
704
705
705 class HistorySavingThread(threading.Thread):
706 class HistorySavingThread(threading.Thread):
706 """This thread takes care of writing history to the database, so that
707 """This thread takes care of writing history to the database, so that
707 the UI isn't held up while that happens.
708 the UI isn't held up while that happens.
708
709
709 It waits for the HistoryManager's save_flag to be set, then writes out
710 It waits for the HistoryManager's save_flag to be set, then writes out
710 the history cache. The main thread is responsible for setting the flag when
711 the history cache. The main thread is responsible for setting the flag when
711 the cache size reaches a defined threshold."""
712 the cache size reaches a defined threshold."""
712 daemon = True
713 daemon = True
713 stop_now = False
714 stop_now = False
714 enabled = True
715 enabled = True
715 def __init__(self, history_manager):
716 def __init__(self, history_manager):
716 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
717 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
717 self.history_manager = history_manager
718 self.history_manager = history_manager
718 self.enabled = history_manager.enabled
719 self.enabled = history_manager.enabled
719 atexit.register(self.stop)
720 atexit.register(self.stop)
720
721
721 @needs_sqlite
722 @needs_sqlite
722 def run(self):
723 def run(self):
723 # We need a separate db connection per thread:
724 # We need a separate db connection per thread:
724 try:
725 try:
725 self.db = sqlite3.connect(self.history_manager.hist_file,
726 self.db = sqlite3.connect(self.history_manager.hist_file,
726 **self.history_manager.connection_options
727 **self.history_manager.connection_options
727 )
728 )
728 while True:
729 while True:
729 self.history_manager.save_flag.wait()
730 self.history_manager.save_flag.wait()
730 if self.stop_now:
731 if self.stop_now:
731 return
732 return
732 self.history_manager.save_flag.clear()
733 self.history_manager.save_flag.clear()
733 self.history_manager.writeout_cache(self.db)
734 self.history_manager.writeout_cache(self.db)
734 except Exception as e:
735 except Exception as e:
735 print(("The history saving thread hit an unexpected error (%s)."
736 print(("The history saving thread hit an unexpected error (%s)."
736 "History will not be written to the database.") % repr(e))
737 "History will not be written to the database.") % repr(e))
737
738
738 def stop(self):
739 def stop(self):
739 """This can be called from the main thread to safely stop this thread.
740 """This can be called from the main thread to safely stop this thread.
740
741
741 Note that it does not attempt to write out remaining history before
742 Note that it does not attempt to write out remaining history before
742 exiting. That should be done by calling the HistoryManager's
743 exiting. That should be done by calling the HistoryManager's
743 end_session method."""
744 end_session method."""
744 self.stop_now = True
745 self.stop_now = True
745 self.history_manager.save_flag.set()
746 self.history_manager.save_flag.set()
746 self.join()
747 self.join()
747
748
748
749
749 # To match, e.g. ~5/8-~2/3
750 # To match, e.g. ~5/8-~2/3
750 range_re = re.compile(r"""
751 range_re = re.compile(r"""
751 ((?P<startsess>~?\d+)/)?
752 ((?P<startsess>~?\d+)/)?
752 (?P<start>\d+)?
753 (?P<start>\d+)?
753 ((?P<sep>[\-:])
754 ((?P<sep>[\-:])
754 ((?P<endsess>~?\d+)/)?
755 ((?P<endsess>~?\d+)/)?
755 (?P<end>\d+))?
756 (?P<end>\d+))?
756 $""", re.VERBOSE)
757 $""", re.VERBOSE)
757
758
758
759
759 def extract_hist_ranges(ranges_str):
760 def extract_hist_ranges(ranges_str):
760 """Turn a string of history ranges into 3-tuples of (session, start, stop).
761 """Turn a string of history ranges into 3-tuples of (session, start, stop).
761
762
762 Examples
763 Examples
763 --------
764 --------
764 list(extract_input_ranges("~8/5-~7/4 2"))
765 list(extract_input_ranges("~8/5-~7/4 2"))
765 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
766 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
766 """
767 """
767 for range_str in ranges_str.split():
768 for range_str in ranges_str.split():
768 rmatch = range_re.match(range_str)
769 rmatch = range_re.match(range_str)
769 if not rmatch:
770 if not rmatch:
770 continue
771 continue
771 start = rmatch.group("start")
772 start = rmatch.group("start")
772 if start:
773 if start:
773 start = int(start)
774 start = int(start)
774 end = rmatch.group("end")
775 end = rmatch.group("end")
775 # If no end specified, get (a, a + 1)
776 # If no end specified, get (a, a + 1)
776 end = int(end) if end else start + 1
777 end = int(end) if end else start + 1
777 else: # start not specified
778 else: # start not specified
778 if not rmatch.group('startsess'): # no startsess
779 if not rmatch.group('startsess'): # no startsess
779 continue
780 continue
780 start = 1
781 start = 1
781 end = None # provide the entire session hist
782 end = None # provide the entire session hist
782
783
783 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
784 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
784 end += 1
785 end += 1
785 startsess = rmatch.group("startsess") or "0"
786 startsess = rmatch.group("startsess") or "0"
786 endsess = rmatch.group("endsess") or startsess
787 endsess = rmatch.group("endsess") or startsess
787 startsess = int(startsess.replace("~","-"))
788 startsess = int(startsess.replace("~","-"))
788 endsess = int(endsess.replace("~","-"))
789 endsess = int(endsess.replace("~","-"))
789 assert endsess >= startsess, "start session must be earlier than end session"
790 assert endsess >= startsess, "start session must be earlier than end session"
790
791
791 if endsess == startsess:
792 if endsess == startsess:
792 yield (startsess, start, end)
793 yield (startsess, start, end)
793 continue
794 continue
794 # Multiple sessions in one range:
795 # Multiple sessions in one range:
795 yield (startsess, start, None)
796 yield (startsess, start, None)
796 for sess in range(startsess+1, endsess):
797 for sess in range(startsess+1, endsess):
797 yield (sess, 1, None)
798 yield (sess, 1, None)
798 yield (endsess, 1, end)
799 yield (endsess, 1, end)
799
800
800
801
801 def _format_lineno(session, line):
802 def _format_lineno(session, line):
802 """Helper function to format line numbers properly."""
803 """Helper function to format line numbers properly."""
803 if session == 0:
804 if session == 0:
804 return str(line)
805 return str(line)
805 return "%s#%s" % (session, line)
806 return "%s#%s" % (session, line)
806
807
807
808
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,740 +1,741 b''
1 """Implementation of magic functions for interaction with the OS.
1 """Implementation of magic functions for interaction with the OS.
2
2
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
4 builtin.
4 builtin.
5 """
5 """
6 from __future__ import print_function
6 from __future__ import print_function
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2012 The IPython Development Team.
8 # Copyright (c) 2012 The IPython Development Team.
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # Stdlib
19 # Stdlib
20 import io
20 import io
21 import os
21 import os
22 import re
22 import re
23 import sys
23 import sys
24 from pprint import pformat
24 from pprint import pformat
25
25
26 # Our own packages
26 # Our own packages
27 from IPython.core import magic_arguments
27 from IPython.core import magic_arguments
28 from IPython.core import oinspect
28 from IPython.core import oinspect
29 from IPython.core import page
29 from IPython.core import page
30 from IPython.core.alias import AliasError, Alias
30 from IPython.core.alias import AliasError, Alias
31 from IPython.core.error import UsageError
31 from IPython.core.error import UsageError
32 from IPython.core.magic import (
32 from IPython.core.magic import (
33 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
33 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
34 )
34 )
35 from IPython.testing.skipdoctest import skip_doctest
35 from IPython.testing.skipdoctest import skip_doctest
36 from IPython.utils.openpy import source_to_unicode
36 from IPython.utils.openpy import source_to_unicode
37 from IPython.utils.path import unquote_filename
37 from IPython.utils.path import unquote_filename
38 from IPython.utils.process import abbrev_cwd
38 from IPython.utils.process import abbrev_cwd
39 from IPython.utils import py3compat
39 from IPython.utils.py3compat import unicode_type
40 from IPython.utils.py3compat import unicode_type
40 from IPython.utils.terminal import set_term_title
41 from IPython.utils.terminal import set_term_title
41
42
42 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
43 # Magic implementation classes
44 # Magic implementation classes
44 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
45 @magics_class
46 @magics_class
46 class OSMagics(Magics):
47 class OSMagics(Magics):
47 """Magics to interact with the underlying OS (shell-type functionality).
48 """Magics to interact with the underlying OS (shell-type functionality).
48 """
49 """
49
50
50 @skip_doctest
51 @skip_doctest
51 @line_magic
52 @line_magic
52 def alias(self, parameter_s=''):
53 def alias(self, parameter_s=''):
53 """Define an alias for a system command.
54 """Define an alias for a system command.
54
55
55 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
56 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
56
57
57 Then, typing 'alias_name params' will execute the system command 'cmd
58 Then, typing 'alias_name params' will execute the system command 'cmd
58 params' (from your underlying operating system).
59 params' (from your underlying operating system).
59
60
60 Aliases have lower precedence than magic functions and Python normal
61 Aliases have lower precedence than magic functions and Python normal
61 variables, so if 'foo' is both a Python variable and an alias, the
62 variables, so if 'foo' is both a Python variable and an alias, the
62 alias can not be executed until 'del foo' removes the Python variable.
63 alias can not be executed until 'del foo' removes the Python variable.
63
64
64 You can use the %l specifier in an alias definition to represent the
65 You can use the %l specifier in an alias definition to represent the
65 whole line when the alias is called. For example::
66 whole line when the alias is called. For example::
66
67
67 In [2]: alias bracket echo "Input in brackets: <%l>"
68 In [2]: alias bracket echo "Input in brackets: <%l>"
68 In [3]: bracket hello world
69 In [3]: bracket hello world
69 Input in brackets: <hello world>
70 Input in brackets: <hello world>
70
71
71 You can also define aliases with parameters using %s specifiers (one
72 You can also define aliases with parameters using %s specifiers (one
72 per parameter)::
73 per parameter)::
73
74
74 In [1]: alias parts echo first %s second %s
75 In [1]: alias parts echo first %s second %s
75 In [2]: %parts A B
76 In [2]: %parts A B
76 first A second B
77 first A second B
77 In [3]: %parts A
78 In [3]: %parts A
78 Incorrect number of arguments: 2 expected.
79 Incorrect number of arguments: 2 expected.
79 parts is an alias to: 'echo first %s second %s'
80 parts is an alias to: 'echo first %s second %s'
80
81
81 Note that %l and %s are mutually exclusive. You can only use one or
82 Note that %l and %s are mutually exclusive. You can only use one or
82 the other in your aliases.
83 the other in your aliases.
83
84
84 Aliases expand Python variables just like system calls using ! or !!
85 Aliases expand Python variables just like system calls using ! or !!
85 do: all expressions prefixed with '$' get expanded. For details of
86 do: all expressions prefixed with '$' get expanded. For details of
86 the semantic rules, see PEP-215:
87 the semantic rules, see PEP-215:
87 http://www.python.org/peps/pep-0215.html. This is the library used by
88 http://www.python.org/peps/pep-0215.html. This is the library used by
88 IPython for variable expansion. If you want to access a true shell
89 IPython for variable expansion. If you want to access a true shell
89 variable, an extra $ is necessary to prevent its expansion by
90 variable, an extra $ is necessary to prevent its expansion by
90 IPython::
91 IPython::
91
92
92 In [6]: alias show echo
93 In [6]: alias show echo
93 In [7]: PATH='A Python string'
94 In [7]: PATH='A Python string'
94 In [8]: show $PATH
95 In [8]: show $PATH
95 A Python string
96 A Python string
96 In [9]: show $$PATH
97 In [9]: show $$PATH
97 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
98 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
98
99
99 You can use the alias facility to acess all of $PATH. See the %rehash
100 You can use the alias facility to acess all of $PATH. See the %rehash
100 and %rehashx functions, which automatically create aliases for the
101 and %rehashx functions, which automatically create aliases for the
101 contents of your $PATH.
102 contents of your $PATH.
102
103
103 If called with no parameters, %alias prints the current alias table."""
104 If called with no parameters, %alias prints the current alias table."""
104
105
105 par = parameter_s.strip()
106 par = parameter_s.strip()
106 if not par:
107 if not par:
107 aliases = sorted(self.shell.alias_manager.aliases)
108 aliases = sorted(self.shell.alias_manager.aliases)
108 # stored = self.shell.db.get('stored_aliases', {} )
109 # stored = self.shell.db.get('stored_aliases', {} )
109 # for k, v in stored:
110 # for k, v in stored:
110 # atab.append(k, v[0])
111 # atab.append(k, v[0])
111
112
112 print("Total number of aliases:", len(aliases))
113 print("Total number of aliases:", len(aliases))
113 sys.stdout.flush()
114 sys.stdout.flush()
114 return aliases
115 return aliases
115
116
116 # Now try to define a new one
117 # Now try to define a new one
117 try:
118 try:
118 alias,cmd = par.split(None, 1)
119 alias,cmd = par.split(None, 1)
119 except TypeError:
120 except TypeError:
120 print(oinspect.getdoc(self.alias))
121 print(oinspect.getdoc(self.alias))
121 return
122 return
122
123
123 try:
124 try:
124 self.shell.alias_manager.define_alias(alias, cmd)
125 self.shell.alias_manager.define_alias(alias, cmd)
125 except AliasError as e:
126 except AliasError as e:
126 print(e)
127 print(e)
127 # end magic_alias
128 # end magic_alias
128
129
129 @line_magic
130 @line_magic
130 def unalias(self, parameter_s=''):
131 def unalias(self, parameter_s=''):
131 """Remove an alias"""
132 """Remove an alias"""
132
133
133 aname = parameter_s.strip()
134 aname = parameter_s.strip()
134 try:
135 try:
135 self.shell.alias_manager.undefine_alias(aname)
136 self.shell.alias_manager.undefine_alias(aname)
136 except ValueError as e:
137 except ValueError as e:
137 print(e)
138 print(e)
138 return
139 return
139
140
140 stored = self.shell.db.get('stored_aliases', {} )
141 stored = self.shell.db.get('stored_aliases', {} )
141 if aname in stored:
142 if aname in stored:
142 print("Removing %stored alias",aname)
143 print("Removing %stored alias",aname)
143 del stored[aname]
144 del stored[aname]
144 self.shell.db['stored_aliases'] = stored
145 self.shell.db['stored_aliases'] = stored
145
146
146 @line_magic
147 @line_magic
147 def rehashx(self, parameter_s=''):
148 def rehashx(self, parameter_s=''):
148 """Update the alias table with all executable files in $PATH.
149 """Update the alias table with all executable files in $PATH.
149
150
150 This version explicitly checks that every entry in $PATH is a file
151 This version explicitly checks that every entry in $PATH is a file
151 with execute access (os.X_OK), so it is much slower than %rehash.
152 with execute access (os.X_OK), so it is much slower than %rehash.
152
153
153 Under Windows, it checks executability as a match against a
154 Under Windows, it checks executability as a match against a
154 '|'-separated string of extensions, stored in the IPython config
155 '|'-separated string of extensions, stored in the IPython config
155 variable win_exec_ext. This defaults to 'exe|com|bat'.
156 variable win_exec_ext. This defaults to 'exe|com|bat'.
156
157
157 This function also resets the root module cache of module completer,
158 This function also resets the root module cache of module completer,
158 used on slow filesystems.
159 used on slow filesystems.
159 """
160 """
160 from IPython.core.alias import InvalidAliasError
161 from IPython.core.alias import InvalidAliasError
161
162
162 # for the benefit of module completer in ipy_completers.py
163 # for the benefit of module completer in ipy_completers.py
163 del self.shell.db['rootmodules_cache']
164 del self.shell.db['rootmodules_cache']
164
165
165 path = [os.path.abspath(os.path.expanduser(p)) for p in
166 path = [os.path.abspath(os.path.expanduser(p)) for p in
166 os.environ.get('PATH','').split(os.pathsep)]
167 os.environ.get('PATH','').split(os.pathsep)]
167 path = filter(os.path.isdir,path)
168 path = filter(os.path.isdir,path)
168
169
169 syscmdlist = []
170 syscmdlist = []
170 # Now define isexec in a cross platform manner.
171 # Now define isexec in a cross platform manner.
171 if os.name == 'posix':
172 if os.name == 'posix':
172 isexec = lambda fname:os.path.isfile(fname) and \
173 isexec = lambda fname:os.path.isfile(fname) and \
173 os.access(fname,os.X_OK)
174 os.access(fname,os.X_OK)
174 else:
175 else:
175 try:
176 try:
176 winext = os.environ['pathext'].replace(';','|').replace('.','')
177 winext = os.environ['pathext'].replace(';','|').replace('.','')
177 except KeyError:
178 except KeyError:
178 winext = 'exe|com|bat|py'
179 winext = 'exe|com|bat|py'
179 if 'py' not in winext:
180 if 'py' not in winext:
180 winext += '|py'
181 winext += '|py'
181 execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
182 execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
182 isexec = lambda fname:os.path.isfile(fname) and execre.match(fname)
183 isexec = lambda fname:os.path.isfile(fname) and execre.match(fname)
183 savedir = os.getcwdu()
184 savedir = py3compat.getcwd()
184
185
185 # Now walk the paths looking for executables to alias.
186 # Now walk the paths looking for executables to alias.
186 try:
187 try:
187 # write the whole loop for posix/Windows so we don't have an if in
188 # write the whole loop for posix/Windows so we don't have an if in
188 # the innermost part
189 # the innermost part
189 if os.name == 'posix':
190 if os.name == 'posix':
190 for pdir in path:
191 for pdir in path:
191 os.chdir(pdir)
192 os.chdir(pdir)
192 for ff in os.listdir(pdir):
193 for ff in os.listdir(pdir):
193 if isexec(ff):
194 if isexec(ff):
194 try:
195 try:
195 # Removes dots from the name since ipython
196 # Removes dots from the name since ipython
196 # will assume names with dots to be python.
197 # will assume names with dots to be python.
197 if not self.shell.alias_manager.is_alias(ff):
198 if not self.shell.alias_manager.is_alias(ff):
198 self.shell.alias_manager.define_alias(
199 self.shell.alias_manager.define_alias(
199 ff.replace('.',''), ff)
200 ff.replace('.',''), ff)
200 except InvalidAliasError:
201 except InvalidAliasError:
201 pass
202 pass
202 else:
203 else:
203 syscmdlist.append(ff)
204 syscmdlist.append(ff)
204 else:
205 else:
205 no_alias = Alias.blacklist
206 no_alias = Alias.blacklist
206 for pdir in path:
207 for pdir in path:
207 os.chdir(pdir)
208 os.chdir(pdir)
208 for ff in os.listdir(pdir):
209 for ff in os.listdir(pdir):
209 base, ext = os.path.splitext(ff)
210 base, ext = os.path.splitext(ff)
210 if isexec(ff) and base.lower() not in no_alias:
211 if isexec(ff) and base.lower() not in no_alias:
211 if ext.lower() == '.exe':
212 if ext.lower() == '.exe':
212 ff = base
213 ff = base
213 try:
214 try:
214 # Removes dots from the name since ipython
215 # Removes dots from the name since ipython
215 # will assume names with dots to be python.
216 # will assume names with dots to be python.
216 self.shell.alias_manager.define_alias(
217 self.shell.alias_manager.define_alias(
217 base.lower().replace('.',''), ff)
218 base.lower().replace('.',''), ff)
218 except InvalidAliasError:
219 except InvalidAliasError:
219 pass
220 pass
220 syscmdlist.append(ff)
221 syscmdlist.append(ff)
221 self.shell.db['syscmdlist'] = syscmdlist
222 self.shell.db['syscmdlist'] = syscmdlist
222 finally:
223 finally:
223 os.chdir(savedir)
224 os.chdir(savedir)
224
225
225 @skip_doctest
226 @skip_doctest
226 @line_magic
227 @line_magic
227 def pwd(self, parameter_s=''):
228 def pwd(self, parameter_s=''):
228 """Return the current working directory path.
229 """Return the current working directory path.
229
230
230 Examples
231 Examples
231 --------
232 --------
232 ::
233 ::
233
234
234 In [9]: pwd
235 In [9]: pwd
235 Out[9]: '/home/tsuser/sprint/ipython'
236 Out[9]: '/home/tsuser/sprint/ipython'
236 """
237 """
237 return os.getcwdu()
238 return py3compat.getcwd()
238
239
239 @skip_doctest
240 @skip_doctest
240 @line_magic
241 @line_magic
241 def cd(self, parameter_s=''):
242 def cd(self, parameter_s=''):
242 """Change the current working directory.
243 """Change the current working directory.
243
244
244 This command automatically maintains an internal list of directories
245 This command automatically maintains an internal list of directories
245 you visit during your IPython session, in the variable _dh. The
246 you visit during your IPython session, in the variable _dh. The
246 command %dhist shows this history nicely formatted. You can also
247 command %dhist shows this history nicely formatted. You can also
247 do 'cd -<tab>' to see directory history conveniently.
248 do 'cd -<tab>' to see directory history conveniently.
248
249
249 Usage:
250 Usage:
250
251
251 cd 'dir': changes to directory 'dir'.
252 cd 'dir': changes to directory 'dir'.
252
253
253 cd -: changes to the last visited directory.
254 cd -: changes to the last visited directory.
254
255
255 cd -<n>: changes to the n-th directory in the directory history.
256 cd -<n>: changes to the n-th directory in the directory history.
256
257
257 cd --foo: change to directory that matches 'foo' in history
258 cd --foo: change to directory that matches 'foo' in history
258
259
259 cd -b <bookmark_name>: jump to a bookmark set by %bookmark
260 cd -b <bookmark_name>: jump to a bookmark set by %bookmark
260 (note: cd <bookmark_name> is enough if there is no
261 (note: cd <bookmark_name> is enough if there is no
261 directory <bookmark_name>, but a bookmark with the name exists.)
262 directory <bookmark_name>, but a bookmark with the name exists.)
262 'cd -b <tab>' allows you to tab-complete bookmark names.
263 'cd -b <tab>' allows you to tab-complete bookmark names.
263
264
264 Options:
265 Options:
265
266
266 -q: quiet. Do not print the working directory after the cd command is
267 -q: quiet. Do not print the working directory after the cd command is
267 executed. By default IPython's cd command does print this directory,
268 executed. By default IPython's cd command does print this directory,
268 since the default prompts do not display path information.
269 since the default prompts do not display path information.
269
270
270 Note that !cd doesn't work for this purpose because the shell where
271 Note that !cd doesn't work for this purpose because the shell where
271 !command runs is immediately discarded after executing 'command'.
272 !command runs is immediately discarded after executing 'command'.
272
273
273 Examples
274 Examples
274 --------
275 --------
275 ::
276 ::
276
277
277 In [10]: cd parent/child
278 In [10]: cd parent/child
278 /home/tsuser/parent/child
279 /home/tsuser/parent/child
279 """
280 """
280
281
281 oldcwd = os.getcwdu()
282 oldcwd = py3compat.getcwd()
282 numcd = re.match(r'(-)(\d+)$',parameter_s)
283 numcd = re.match(r'(-)(\d+)$',parameter_s)
283 # jump in directory history by number
284 # jump in directory history by number
284 if numcd:
285 if numcd:
285 nn = int(numcd.group(2))
286 nn = int(numcd.group(2))
286 try:
287 try:
287 ps = self.shell.user_ns['_dh'][nn]
288 ps = self.shell.user_ns['_dh'][nn]
288 except IndexError:
289 except IndexError:
289 print('The requested directory does not exist in history.')
290 print('The requested directory does not exist in history.')
290 return
291 return
291 else:
292 else:
292 opts = {}
293 opts = {}
293 elif parameter_s.startswith('--'):
294 elif parameter_s.startswith('--'):
294 ps = None
295 ps = None
295 fallback = None
296 fallback = None
296 pat = parameter_s[2:]
297 pat = parameter_s[2:]
297 dh = self.shell.user_ns['_dh']
298 dh = self.shell.user_ns['_dh']
298 # first search only by basename (last component)
299 # first search only by basename (last component)
299 for ent in reversed(dh):
300 for ent in reversed(dh):
300 if pat in os.path.basename(ent) and os.path.isdir(ent):
301 if pat in os.path.basename(ent) and os.path.isdir(ent):
301 ps = ent
302 ps = ent
302 break
303 break
303
304
304 if fallback is None and pat in ent and os.path.isdir(ent):
305 if fallback is None and pat in ent and os.path.isdir(ent):
305 fallback = ent
306 fallback = ent
306
307
307 # if we have no last part match, pick the first full path match
308 # if we have no last part match, pick the first full path match
308 if ps is None:
309 if ps is None:
309 ps = fallback
310 ps = fallback
310
311
311 if ps is None:
312 if ps is None:
312 print("No matching entry in directory history")
313 print("No matching entry in directory history")
313 return
314 return
314 else:
315 else:
315 opts = {}
316 opts = {}
316
317
317
318
318 else:
319 else:
319 #turn all non-space-escaping backslashes to slashes,
320 #turn all non-space-escaping backslashes to slashes,
320 # for c:\windows\directory\names\
321 # for c:\windows\directory\names\
321 parameter_s = re.sub(r'\\(?! )','/', parameter_s)
322 parameter_s = re.sub(r'\\(?! )','/', parameter_s)
322 opts,ps = self.parse_options(parameter_s,'qb',mode='string')
323 opts,ps = self.parse_options(parameter_s,'qb',mode='string')
323 # jump to previous
324 # jump to previous
324 if ps == '-':
325 if ps == '-':
325 try:
326 try:
326 ps = self.shell.user_ns['_dh'][-2]
327 ps = self.shell.user_ns['_dh'][-2]
327 except IndexError:
328 except IndexError:
328 raise UsageError('%cd -: No previous directory to change to.')
329 raise UsageError('%cd -: No previous directory to change to.')
329 # jump to bookmark if needed
330 # jump to bookmark if needed
330 else:
331 else:
331 if not os.path.isdir(ps) or 'b' in opts:
332 if not os.path.isdir(ps) or 'b' in opts:
332 bkms = self.shell.db.get('bookmarks', {})
333 bkms = self.shell.db.get('bookmarks', {})
333
334
334 if ps in bkms:
335 if ps in bkms:
335 target = bkms[ps]
336 target = bkms[ps]
336 print('(bookmark:%s) -> %s' % (ps, target))
337 print('(bookmark:%s) -> %s' % (ps, target))
337 ps = target
338 ps = target
338 else:
339 else:
339 if 'b' in opts:
340 if 'b' in opts:
340 raise UsageError("Bookmark '%s' not found. "
341 raise UsageError("Bookmark '%s' not found. "
341 "Use '%%bookmark -l' to see your bookmarks." % ps)
342 "Use '%%bookmark -l' to see your bookmarks." % ps)
342
343
343 # strip extra quotes on Windows, because os.chdir doesn't like them
344 # strip extra quotes on Windows, because os.chdir doesn't like them
344 ps = unquote_filename(ps)
345 ps = unquote_filename(ps)
345 # at this point ps should point to the target dir
346 # at this point ps should point to the target dir
346 if ps:
347 if ps:
347 try:
348 try:
348 os.chdir(os.path.expanduser(ps))
349 os.chdir(os.path.expanduser(ps))
349 if hasattr(self.shell, 'term_title') and self.shell.term_title:
350 if hasattr(self.shell, 'term_title') and self.shell.term_title:
350 set_term_title('IPython: ' + abbrev_cwd())
351 set_term_title('IPython: ' + abbrev_cwd())
351 except OSError:
352 except OSError:
352 print(sys.exc_info()[1])
353 print(sys.exc_info()[1])
353 else:
354 else:
354 cwd = os.getcwdu()
355 cwd = py3compat.getcwd()
355 dhist = self.shell.user_ns['_dh']
356 dhist = self.shell.user_ns['_dh']
356 if oldcwd != cwd:
357 if oldcwd != cwd:
357 dhist.append(cwd)
358 dhist.append(cwd)
358 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
359 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
359
360
360 else:
361 else:
361 os.chdir(self.shell.home_dir)
362 os.chdir(self.shell.home_dir)
362 if hasattr(self.shell, 'term_title') and self.shell.term_title:
363 if hasattr(self.shell, 'term_title') and self.shell.term_title:
363 set_term_title('IPython: ' + '~')
364 set_term_title('IPython: ' + '~')
364 cwd = os.getcwdu()
365 cwd = py3compat.getcwd()
365 dhist = self.shell.user_ns['_dh']
366 dhist = self.shell.user_ns['_dh']
366
367
367 if oldcwd != cwd:
368 if oldcwd != cwd:
368 dhist.append(cwd)
369 dhist.append(cwd)
369 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
370 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
370 if not 'q' in opts and self.shell.user_ns['_dh']:
371 if not 'q' in opts and self.shell.user_ns['_dh']:
371 print(self.shell.user_ns['_dh'][-1])
372 print(self.shell.user_ns['_dh'][-1])
372
373
373
374
374 @line_magic
375 @line_magic
375 def env(self, parameter_s=''):
376 def env(self, parameter_s=''):
376 """List environment variables."""
377 """List environment variables."""
377
378
378 return dict(os.environ)
379 return dict(os.environ)
379
380
380 @line_magic
381 @line_magic
381 def pushd(self, parameter_s=''):
382 def pushd(self, parameter_s=''):
382 """Place the current dir on stack and change directory.
383 """Place the current dir on stack and change directory.
383
384
384 Usage:\\
385 Usage:\\
385 %pushd ['dirname']
386 %pushd ['dirname']
386 """
387 """
387
388
388 dir_s = self.shell.dir_stack
389 dir_s = self.shell.dir_stack
389 tgt = os.path.expanduser(unquote_filename(parameter_s))
390 tgt = os.path.expanduser(unquote_filename(parameter_s))
390 cwd = os.getcwdu().replace(self.shell.home_dir,'~')
391 cwd = py3compat.getcwd().replace(self.shell.home_dir,'~')
391 if tgt:
392 if tgt:
392 self.cd(parameter_s)
393 self.cd(parameter_s)
393 dir_s.insert(0,cwd)
394 dir_s.insert(0,cwd)
394 return self.shell.magic('dirs')
395 return self.shell.magic('dirs')
395
396
396 @line_magic
397 @line_magic
397 def popd(self, parameter_s=''):
398 def popd(self, parameter_s=''):
398 """Change to directory popped off the top of the stack.
399 """Change to directory popped off the top of the stack.
399 """
400 """
400 if not self.shell.dir_stack:
401 if not self.shell.dir_stack:
401 raise UsageError("%popd on empty stack")
402 raise UsageError("%popd on empty stack")
402 top = self.shell.dir_stack.pop(0)
403 top = self.shell.dir_stack.pop(0)
403 self.cd(top)
404 self.cd(top)
404 print("popd ->",top)
405 print("popd ->",top)
405
406
406 @line_magic
407 @line_magic
407 def dirs(self, parameter_s=''):
408 def dirs(self, parameter_s=''):
408 """Return the current directory stack."""
409 """Return the current directory stack."""
409
410
410 return self.shell.dir_stack
411 return self.shell.dir_stack
411
412
412 @line_magic
413 @line_magic
413 def dhist(self, parameter_s=''):
414 def dhist(self, parameter_s=''):
414 """Print your history of visited directories.
415 """Print your history of visited directories.
415
416
416 %dhist -> print full history\\
417 %dhist -> print full history\\
417 %dhist n -> print last n entries only\\
418 %dhist n -> print last n entries only\\
418 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
419 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
419
420
420 This history is automatically maintained by the %cd command, and
421 This history is automatically maintained by the %cd command, and
421 always available as the global list variable _dh. You can use %cd -<n>
422 always available as the global list variable _dh. You can use %cd -<n>
422 to go to directory number <n>.
423 to go to directory number <n>.
423
424
424 Note that most of time, you should view directory history by entering
425 Note that most of time, you should view directory history by entering
425 cd -<TAB>.
426 cd -<TAB>.
426
427
427 """
428 """
428
429
429 dh = self.shell.user_ns['_dh']
430 dh = self.shell.user_ns['_dh']
430 if parameter_s:
431 if parameter_s:
431 try:
432 try:
432 args = map(int,parameter_s.split())
433 args = map(int,parameter_s.split())
433 except:
434 except:
434 self.arg_err(self.dhist)
435 self.arg_err(self.dhist)
435 return
436 return
436 if len(args) == 1:
437 if len(args) == 1:
437 ini,fin = max(len(dh)-(args[0]),0),len(dh)
438 ini,fin = max(len(dh)-(args[0]),0),len(dh)
438 elif len(args) == 2:
439 elif len(args) == 2:
439 ini,fin = args
440 ini,fin = args
440 fin = min(fin, len(dh))
441 fin = min(fin, len(dh))
441 else:
442 else:
442 self.arg_err(self.dhist)
443 self.arg_err(self.dhist)
443 return
444 return
444 else:
445 else:
445 ini,fin = 0,len(dh)
446 ini,fin = 0,len(dh)
446 print('Directory history (kept in _dh)')
447 print('Directory history (kept in _dh)')
447 for i in range(ini, fin):
448 for i in range(ini, fin):
448 print("%d: %s" % (i, dh[i]))
449 print("%d: %s" % (i, dh[i]))
449
450
450 @skip_doctest
451 @skip_doctest
451 @line_magic
452 @line_magic
452 def sc(self, parameter_s=''):
453 def sc(self, parameter_s=''):
453 """Shell capture - run shell command and capture output (DEPRECATED use !).
454 """Shell capture - run shell command and capture output (DEPRECATED use !).
454
455
455 DEPRECATED. Suboptimal, retained for backwards compatibility.
456 DEPRECATED. Suboptimal, retained for backwards compatibility.
456
457
457 You should use the form 'var = !command' instead. Example:
458 You should use the form 'var = !command' instead. Example:
458
459
459 "%sc -l myfiles = ls ~" should now be written as
460 "%sc -l myfiles = ls ~" should now be written as
460
461
461 "myfiles = !ls ~"
462 "myfiles = !ls ~"
462
463
463 myfiles.s, myfiles.l and myfiles.n still apply as documented
464 myfiles.s, myfiles.l and myfiles.n still apply as documented
464 below.
465 below.
465
466
466 --
467 --
467 %sc [options] varname=command
468 %sc [options] varname=command
468
469
469 IPython will run the given command using commands.getoutput(), and
470 IPython will run the given command using commands.getoutput(), and
470 will then update the user's interactive namespace with a variable
471 will then update the user's interactive namespace with a variable
471 called varname, containing the value of the call. Your command can
472 called varname, containing the value of the call. Your command can
472 contain shell wildcards, pipes, etc.
473 contain shell wildcards, pipes, etc.
473
474
474 The '=' sign in the syntax is mandatory, and the variable name you
475 The '=' sign in the syntax is mandatory, and the variable name you
475 supply must follow Python's standard conventions for valid names.
476 supply must follow Python's standard conventions for valid names.
476
477
477 (A special format without variable name exists for internal use)
478 (A special format without variable name exists for internal use)
478
479
479 Options:
480 Options:
480
481
481 -l: list output. Split the output on newlines into a list before
482 -l: list output. Split the output on newlines into a list before
482 assigning it to the given variable. By default the output is stored
483 assigning it to the given variable. By default the output is stored
483 as a single string.
484 as a single string.
484
485
485 -v: verbose. Print the contents of the variable.
486 -v: verbose. Print the contents of the variable.
486
487
487 In most cases you should not need to split as a list, because the
488 In most cases you should not need to split as a list, because the
488 returned value is a special type of string which can automatically
489 returned value is a special type of string which can automatically
489 provide its contents either as a list (split on newlines) or as a
490 provide its contents either as a list (split on newlines) or as a
490 space-separated string. These are convenient, respectively, either
491 space-separated string. These are convenient, respectively, either
491 for sequential processing or to be passed to a shell command.
492 for sequential processing or to be passed to a shell command.
492
493
493 For example::
494 For example::
494
495
495 # Capture into variable a
496 # Capture into variable a
496 In [1]: sc a=ls *py
497 In [1]: sc a=ls *py
497
498
498 # a is a string with embedded newlines
499 # a is a string with embedded newlines
499 In [2]: a
500 In [2]: a
500 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
501 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
501
502
502 # which can be seen as a list:
503 # which can be seen as a list:
503 In [3]: a.l
504 In [3]: a.l
504 Out[3]: ['setup.py', 'win32_manual_post_install.py']
505 Out[3]: ['setup.py', 'win32_manual_post_install.py']
505
506
506 # or as a whitespace-separated string:
507 # or as a whitespace-separated string:
507 In [4]: a.s
508 In [4]: a.s
508 Out[4]: 'setup.py win32_manual_post_install.py'
509 Out[4]: 'setup.py win32_manual_post_install.py'
509
510
510 # a.s is useful to pass as a single command line:
511 # a.s is useful to pass as a single command line:
511 In [5]: !wc -l $a.s
512 In [5]: !wc -l $a.s
512 146 setup.py
513 146 setup.py
513 130 win32_manual_post_install.py
514 130 win32_manual_post_install.py
514 276 total
515 276 total
515
516
516 # while the list form is useful to loop over:
517 # while the list form is useful to loop over:
517 In [6]: for f in a.l:
518 In [6]: for f in a.l:
518 ...: !wc -l $f
519 ...: !wc -l $f
519 ...:
520 ...:
520 146 setup.py
521 146 setup.py
521 130 win32_manual_post_install.py
522 130 win32_manual_post_install.py
522
523
523 Similarly, the lists returned by the -l option are also special, in
524 Similarly, the lists returned by the -l option are also special, in
524 the sense that you can equally invoke the .s attribute on them to
525 the sense that you can equally invoke the .s attribute on them to
525 automatically get a whitespace-separated string from their contents::
526 automatically get a whitespace-separated string from their contents::
526
527
527 In [7]: sc -l b=ls *py
528 In [7]: sc -l b=ls *py
528
529
529 In [8]: b
530 In [8]: b
530 Out[8]: ['setup.py', 'win32_manual_post_install.py']
531 Out[8]: ['setup.py', 'win32_manual_post_install.py']
531
532
532 In [9]: b.s
533 In [9]: b.s
533 Out[9]: 'setup.py win32_manual_post_install.py'
534 Out[9]: 'setup.py win32_manual_post_install.py'
534
535
535 In summary, both the lists and strings used for output capture have
536 In summary, both the lists and strings used for output capture have
536 the following special attributes::
537 the following special attributes::
537
538
538 .l (or .list) : value as list.
539 .l (or .list) : value as list.
539 .n (or .nlstr): value as newline-separated string.
540 .n (or .nlstr): value as newline-separated string.
540 .s (or .spstr): value as space-separated string.
541 .s (or .spstr): value as space-separated string.
541 """
542 """
542
543
543 opts,args = self.parse_options(parameter_s, 'lv')
544 opts,args = self.parse_options(parameter_s, 'lv')
544 # Try to get a variable name and command to run
545 # Try to get a variable name and command to run
545 try:
546 try:
546 # the variable name must be obtained from the parse_options
547 # the variable name must be obtained from the parse_options
547 # output, which uses shlex.split to strip options out.
548 # output, which uses shlex.split to strip options out.
548 var,_ = args.split('=', 1)
549 var,_ = args.split('=', 1)
549 var = var.strip()
550 var = var.strip()
550 # But the command has to be extracted from the original input
551 # But the command has to be extracted from the original input
551 # parameter_s, not on what parse_options returns, to avoid the
552 # parameter_s, not on what parse_options returns, to avoid the
552 # quote stripping which shlex.split performs on it.
553 # quote stripping which shlex.split performs on it.
553 _,cmd = parameter_s.split('=', 1)
554 _,cmd = parameter_s.split('=', 1)
554 except ValueError:
555 except ValueError:
555 var,cmd = '',''
556 var,cmd = '',''
556 # If all looks ok, proceed
557 # If all looks ok, proceed
557 split = 'l' in opts
558 split = 'l' in opts
558 out = self.shell.getoutput(cmd, split=split)
559 out = self.shell.getoutput(cmd, split=split)
559 if 'v' in opts:
560 if 'v' in opts:
560 print('%s ==\n%s' % (var, pformat(out)))
561 print('%s ==\n%s' % (var, pformat(out)))
561 if var:
562 if var:
562 self.shell.user_ns.update({var:out})
563 self.shell.user_ns.update({var:out})
563 else:
564 else:
564 return out
565 return out
565
566
566 @line_cell_magic
567 @line_cell_magic
567 def sx(self, line='', cell=None):
568 def sx(self, line='', cell=None):
568 """Shell execute - run shell command and capture output (!! is short-hand).
569 """Shell execute - run shell command and capture output (!! is short-hand).
569
570
570 %sx command
571 %sx command
571
572
572 IPython will run the given command using commands.getoutput(), and
573 IPython will run the given command using commands.getoutput(), and
573 return the result formatted as a list (split on '\\n'). Since the
574 return the result formatted as a list (split on '\\n'). Since the
574 output is _returned_, it will be stored in ipython's regular output
575 output is _returned_, it will be stored in ipython's regular output
575 cache Out[N] and in the '_N' automatic variables.
576 cache Out[N] and in the '_N' automatic variables.
576
577
577 Notes:
578 Notes:
578
579
579 1) If an input line begins with '!!', then %sx is automatically
580 1) If an input line begins with '!!', then %sx is automatically
580 invoked. That is, while::
581 invoked. That is, while::
581
582
582 !ls
583 !ls
583
584
584 causes ipython to simply issue system('ls'), typing::
585 causes ipython to simply issue system('ls'), typing::
585
586
586 !!ls
587 !!ls
587
588
588 is a shorthand equivalent to::
589 is a shorthand equivalent to::
589
590
590 %sx ls
591 %sx ls
591
592
592 2) %sx differs from %sc in that %sx automatically splits into a list,
593 2) %sx differs from %sc in that %sx automatically splits into a list,
593 like '%sc -l'. The reason for this is to make it as easy as possible
594 like '%sc -l'. The reason for this is to make it as easy as possible
594 to process line-oriented shell output via further python commands.
595 to process line-oriented shell output via further python commands.
595 %sc is meant to provide much finer control, but requires more
596 %sc is meant to provide much finer control, but requires more
596 typing.
597 typing.
597
598
598 3) Just like %sc -l, this is a list with special attributes:
599 3) Just like %sc -l, this is a list with special attributes:
599 ::
600 ::
600
601
601 .l (or .list) : value as list.
602 .l (or .list) : value as list.
602 .n (or .nlstr): value as newline-separated string.
603 .n (or .nlstr): value as newline-separated string.
603 .s (or .spstr): value as whitespace-separated string.
604 .s (or .spstr): value as whitespace-separated string.
604
605
605 This is very useful when trying to use such lists as arguments to
606 This is very useful when trying to use such lists as arguments to
606 system commands."""
607 system commands."""
607
608
608 if cell is None:
609 if cell is None:
609 # line magic
610 # line magic
610 return self.shell.getoutput(line)
611 return self.shell.getoutput(line)
611 else:
612 else:
612 opts,args = self.parse_options(line, '', 'out=')
613 opts,args = self.parse_options(line, '', 'out=')
613 output = self.shell.getoutput(cell)
614 output = self.shell.getoutput(cell)
614 out_name = opts.get('out', opts.get('o'))
615 out_name = opts.get('out', opts.get('o'))
615 if out_name:
616 if out_name:
616 self.shell.user_ns[out_name] = output
617 self.shell.user_ns[out_name] = output
617 else:
618 else:
618 return output
619 return output
619
620
620 system = line_cell_magic('system')(sx)
621 system = line_cell_magic('system')(sx)
621 bang = cell_magic('!')(sx)
622 bang = cell_magic('!')(sx)
622
623
623 @line_magic
624 @line_magic
624 def bookmark(self, parameter_s=''):
625 def bookmark(self, parameter_s=''):
625 """Manage IPython's bookmark system.
626 """Manage IPython's bookmark system.
626
627
627 %bookmark <name> - set bookmark to current dir
628 %bookmark <name> - set bookmark to current dir
628 %bookmark <name> <dir> - set bookmark to <dir>
629 %bookmark <name> <dir> - set bookmark to <dir>
629 %bookmark -l - list all bookmarks
630 %bookmark -l - list all bookmarks
630 %bookmark -d <name> - remove bookmark
631 %bookmark -d <name> - remove bookmark
631 %bookmark -r - remove all bookmarks
632 %bookmark -r - remove all bookmarks
632
633
633 You can later on access a bookmarked folder with::
634 You can later on access a bookmarked folder with::
634
635
635 %cd -b <name>
636 %cd -b <name>
636
637
637 or simply '%cd <name>' if there is no directory called <name> AND
638 or simply '%cd <name>' if there is no directory called <name> AND
638 there is such a bookmark defined.
639 there is such a bookmark defined.
639
640
640 Your bookmarks persist through IPython sessions, but they are
641 Your bookmarks persist through IPython sessions, but they are
641 associated with each profile."""
642 associated with each profile."""
642
643
643 opts,args = self.parse_options(parameter_s,'drl',mode='list')
644 opts,args = self.parse_options(parameter_s,'drl',mode='list')
644 if len(args) > 2:
645 if len(args) > 2:
645 raise UsageError("%bookmark: too many arguments")
646 raise UsageError("%bookmark: too many arguments")
646
647
647 bkms = self.shell.db.get('bookmarks',{})
648 bkms = self.shell.db.get('bookmarks',{})
648
649
649 if 'd' in opts:
650 if 'd' in opts:
650 try:
651 try:
651 todel = args[0]
652 todel = args[0]
652 except IndexError:
653 except IndexError:
653 raise UsageError(
654 raise UsageError(
654 "%bookmark -d: must provide a bookmark to delete")
655 "%bookmark -d: must provide a bookmark to delete")
655 else:
656 else:
656 try:
657 try:
657 del bkms[todel]
658 del bkms[todel]
658 except KeyError:
659 except KeyError:
659 raise UsageError(
660 raise UsageError(
660 "%%bookmark -d: Can't delete bookmark '%s'" % todel)
661 "%%bookmark -d: Can't delete bookmark '%s'" % todel)
661
662
662 elif 'r' in opts:
663 elif 'r' in opts:
663 bkms = {}
664 bkms = {}
664 elif 'l' in opts:
665 elif 'l' in opts:
665 bks = bkms.keys()
666 bks = bkms.keys()
666 bks.sort()
667 bks.sort()
667 if bks:
668 if bks:
668 size = max(map(len, bks))
669 size = max(map(len, bks))
669 else:
670 else:
670 size = 0
671 size = 0
671 fmt = '%-'+str(size)+'s -> %s'
672 fmt = '%-'+str(size)+'s -> %s'
672 print('Current bookmarks:')
673 print('Current bookmarks:')
673 for bk in bks:
674 for bk in bks:
674 print(fmt % (bk, bkms[bk]))
675 print(fmt % (bk, bkms[bk]))
675 else:
676 else:
676 if not args:
677 if not args:
677 raise UsageError("%bookmark: You must specify the bookmark name")
678 raise UsageError("%bookmark: You must specify the bookmark name")
678 elif len(args)==1:
679 elif len(args)==1:
679 bkms[args[0]] = os.getcwdu()
680 bkms[args[0]] = py3compat.getcwd()
680 elif len(args)==2:
681 elif len(args)==2:
681 bkms[args[0]] = args[1]
682 bkms[args[0]] = args[1]
682 self.shell.db['bookmarks'] = bkms
683 self.shell.db['bookmarks'] = bkms
683
684
684 @line_magic
685 @line_magic
685 def pycat(self, parameter_s=''):
686 def pycat(self, parameter_s=''):
686 """Show a syntax-highlighted file through a pager.
687 """Show a syntax-highlighted file through a pager.
687
688
688 This magic is similar to the cat utility, but it will assume the file
689 This magic is similar to the cat utility, but it will assume the file
689 to be Python source and will show it with syntax highlighting.
690 to be Python source and will show it with syntax highlighting.
690
691
691 This magic command can either take a local filename, an url,
692 This magic command can either take a local filename, an url,
692 an history range (see %history) or a macro as argument ::
693 an history range (see %history) or a macro as argument ::
693
694
694 %pycat myscript.py
695 %pycat myscript.py
695 %pycat 7-27
696 %pycat 7-27
696 %pycat myMacro
697 %pycat myMacro
697 %pycat http://www.example.com/myscript.py
698 %pycat http://www.example.com/myscript.py
698 """
699 """
699 if not parameter_s:
700 if not parameter_s:
700 raise UsageError('Missing filename, URL, input history range, '
701 raise UsageError('Missing filename, URL, input history range, '
701 'or macro.')
702 'or macro.')
702
703
703 try :
704 try :
704 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
705 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
705 except (ValueError, IOError):
706 except (ValueError, IOError):
706 print("Error: no such file, variable, URL, history range or macro")
707 print("Error: no such file, variable, URL, history range or macro")
707 return
708 return
708
709
709 page.page(self.shell.pycolorize(source_to_unicode(cont)))
710 page.page(self.shell.pycolorize(source_to_unicode(cont)))
710
711
711 @magic_arguments.magic_arguments()
712 @magic_arguments.magic_arguments()
712 @magic_arguments.argument(
713 @magic_arguments.argument(
713 '-a', '--append', action='store_true', default=False,
714 '-a', '--append', action='store_true', default=False,
714 help='Append contents of the cell to an existing file. '
715 help='Append contents of the cell to an existing file. '
715 'The file will be created if it does not exist.'
716 'The file will be created if it does not exist.'
716 )
717 )
717 @magic_arguments.argument(
718 @magic_arguments.argument(
718 'filename', type=unicode_type,
719 'filename', type=unicode_type,
719 help='file to write'
720 help='file to write'
720 )
721 )
721 @cell_magic
722 @cell_magic
722 def writefile(self, line, cell):
723 def writefile(self, line, cell):
723 """Write the contents of the cell to a file.
724 """Write the contents of the cell to a file.
724
725
725 The file will be overwritten unless the -a (--append) flag is specified.
726 The file will be overwritten unless the -a (--append) flag is specified.
726 """
727 """
727 args = magic_arguments.parse_argstring(self.writefile, line)
728 args = magic_arguments.parse_argstring(self.writefile, line)
728 filename = os.path.expanduser(unquote_filename(args.filename))
729 filename = os.path.expanduser(unquote_filename(args.filename))
729
730
730 if os.path.exists(filename):
731 if os.path.exists(filename):
731 if args.append:
732 if args.append:
732 print("Appending to %s" % filename)
733 print("Appending to %s" % filename)
733 else:
734 else:
734 print("Overwriting %s" % filename)
735 print("Overwriting %s" % filename)
735 else:
736 else:
736 print("Writing %s" % filename)
737 print("Writing %s" % filename)
737
738
738 mode = 'a' if args.append else 'w'
739 mode = 'a' if args.append else 'w'
739 with io.open(filename, mode, encoding='utf-8') as f:
740 with io.open(filename, mode, encoding='utf-8') as f:
740 f.write(cell)
741 f.write(cell)
@@ -1,314 +1,315 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for managing IPython profiles.
3 An application for managing IPython profiles.
4
4
5 To be invoked as the `ipython profile` subcommand.
5 To be invoked as the `ipython profile` subcommand.
6
6
7 Authors:
7 Authors:
8
8
9 * Min RK
9 * Min RK
10
10
11 """
11 """
12 from __future__ import print_function
12 from __future__ import print_function
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 import os
25 import os
26
26
27 from IPython.config.application import Application
27 from IPython.config.application import Application
28 from IPython.core.application import (
28 from IPython.core.application import (
29 BaseIPythonApplication, base_flags
29 BaseIPythonApplication, base_flags
30 )
30 )
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.importstring import import_item
32 from IPython.utils.importstring import import_item
33 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
33 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
34 from IPython.utils import py3compat
34 from IPython.utils.traitlets import Unicode, Bool, Dict
35 from IPython.utils.traitlets import Unicode, Bool, Dict
35
36
36 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
37 # Constants
38 # Constants
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39
40
40 create_help = """Create an IPython profile by name
41 create_help = """Create an IPython profile by name
41
42
42 Create an ipython profile directory by its name or
43 Create an ipython profile directory by its name or
43 profile directory path. Profile directories contain
44 profile directory path. Profile directories contain
44 configuration, log and security related files and are named
45 configuration, log and security related files and are named
45 using the convention 'profile_<name>'. By default they are
46 using the convention 'profile_<name>'. By default they are
46 located in your ipython directory. Once created, you will
47 located in your ipython directory. Once created, you will
47 can edit the configuration files in the profile
48 can edit the configuration files in the profile
48 directory to configure IPython. Most users will create a
49 directory to configure IPython. Most users will create a
49 profile directory by name,
50 profile directory by name,
50 `ipython profile create myprofile`, which will put the directory
51 `ipython profile create myprofile`, which will put the directory
51 in `<ipython_dir>/profile_myprofile`.
52 in `<ipython_dir>/profile_myprofile`.
52 """
53 """
53 list_help = """List available IPython profiles
54 list_help = """List available IPython profiles
54
55
55 List all available profiles, by profile location, that can
56 List all available profiles, by profile location, that can
56 be found in the current working directly or in the ipython
57 be found in the current working directly or in the ipython
57 directory. Profile directories are named using the convention
58 directory. Profile directories are named using the convention
58 'profile_<profile>'.
59 'profile_<profile>'.
59 """
60 """
60 profile_help = """Manage IPython profiles
61 profile_help = """Manage IPython profiles
61
62
62 Profile directories contain
63 Profile directories contain
63 configuration, log and security related files and are named
64 configuration, log and security related files and are named
64 using the convention 'profile_<name>'. By default they are
65 using the convention 'profile_<name>'. By default they are
65 located in your ipython directory. You can create profiles
66 located in your ipython directory. You can create profiles
66 with `ipython profile create <name>`, or see the profiles you
67 with `ipython profile create <name>`, or see the profiles you
67 already have with `ipython profile list`
68 already have with `ipython profile list`
68
69
69 To get started configuring IPython, simply do:
70 To get started configuring IPython, simply do:
70
71
71 $> ipython profile create
72 $> ipython profile create
72
73
73 and IPython will create the default profile in <ipython_dir>/profile_default,
74 and IPython will create the default profile in <ipython_dir>/profile_default,
74 where you can edit ipython_config.py to start configuring IPython.
75 where you can edit ipython_config.py to start configuring IPython.
75
76
76 """
77 """
77
78
78 _list_examples = "ipython profile list # list all profiles"
79 _list_examples = "ipython profile list # list all profiles"
79
80
80 _create_examples = """
81 _create_examples = """
81 ipython profile create foo # create profile foo w/ default config files
82 ipython profile create foo # create profile foo w/ default config files
82 ipython profile create foo --reset # restage default config files over current
83 ipython profile create foo --reset # restage default config files over current
83 ipython profile create foo --parallel # also stage parallel config files
84 ipython profile create foo --parallel # also stage parallel config files
84 """
85 """
85
86
86 _main_examples = """
87 _main_examples = """
87 ipython profile create -h # show the help string for the create subcommand
88 ipython profile create -h # show the help string for the create subcommand
88 ipython profile list -h # show the help string for the list subcommand
89 ipython profile list -h # show the help string for the list subcommand
89
90
90 ipython locate profile foo # print the path to the directory for profile 'foo'
91 ipython locate profile foo # print the path to the directory for profile 'foo'
91 """
92 """
92
93
93 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
94 # Profile Application Class (for `ipython profile` subcommand)
95 # Profile Application Class (for `ipython profile` subcommand)
95 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
96
97
97
98
98 def list_profiles_in(path):
99 def list_profiles_in(path):
99 """list profiles in a given root directory"""
100 """list profiles in a given root directory"""
100 files = os.listdir(path)
101 files = os.listdir(path)
101 profiles = []
102 profiles = []
102 for f in files:
103 for f in files:
103 try:
104 try:
104 full_path = os.path.join(path, f)
105 full_path = os.path.join(path, f)
105 except UnicodeError:
106 except UnicodeError:
106 continue
107 continue
107 if os.path.isdir(full_path) and f.startswith('profile_'):
108 if os.path.isdir(full_path) and f.startswith('profile_'):
108 profiles.append(f.split('_',1)[-1])
109 profiles.append(f.split('_',1)[-1])
109 return profiles
110 return profiles
110
111
111
112
112 def list_bundled_profiles():
113 def list_bundled_profiles():
113 """list profiles that are bundled with IPython."""
114 """list profiles that are bundled with IPython."""
114 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
115 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
115 files = os.listdir(path)
116 files = os.listdir(path)
116 profiles = []
117 profiles = []
117 for profile in files:
118 for profile in files:
118 full_path = os.path.join(path, profile)
119 full_path = os.path.join(path, profile)
119 if os.path.isdir(full_path) and profile != "__pycache__":
120 if os.path.isdir(full_path) and profile != "__pycache__":
120 profiles.append(profile)
121 profiles.append(profile)
121 return profiles
122 return profiles
122
123
123
124
124 class ProfileLocate(BaseIPythonApplication):
125 class ProfileLocate(BaseIPythonApplication):
125 description = """print the path to an IPython profile dir"""
126 description = """print the path to an IPython profile dir"""
126
127
127 def parse_command_line(self, argv=None):
128 def parse_command_line(self, argv=None):
128 super(ProfileLocate, self).parse_command_line(argv)
129 super(ProfileLocate, self).parse_command_line(argv)
129 if self.extra_args:
130 if self.extra_args:
130 self.profile = self.extra_args[0]
131 self.profile = self.extra_args[0]
131
132
132 def start(self):
133 def start(self):
133 print(self.profile_dir.location)
134 print(self.profile_dir.location)
134
135
135
136
136 class ProfileList(Application):
137 class ProfileList(Application):
137 name = u'ipython-profile'
138 name = u'ipython-profile'
138 description = list_help
139 description = list_help
139 examples = _list_examples
140 examples = _list_examples
140
141
141 aliases = Dict({
142 aliases = Dict({
142 'ipython-dir' : 'ProfileList.ipython_dir',
143 'ipython-dir' : 'ProfileList.ipython_dir',
143 'log-level' : 'Application.log_level',
144 'log-level' : 'Application.log_level',
144 })
145 })
145 flags = Dict(dict(
146 flags = Dict(dict(
146 debug = ({'Application' : {'log_level' : 0}},
147 debug = ({'Application' : {'log_level' : 0}},
147 "Set Application.log_level to 0, maximizing log output."
148 "Set Application.log_level to 0, maximizing log output."
148 )
149 )
149 ))
150 ))
150
151
151 ipython_dir = Unicode(get_ipython_dir(), config=True,
152 ipython_dir = Unicode(get_ipython_dir(), config=True,
152 help="""
153 help="""
153 The name of the IPython directory. This directory is used for logging
154 The name of the IPython directory. This directory is used for logging
154 configuration (through profiles), history storage, etc. The default
155 configuration (through profiles), history storage, etc. The default
155 is usually $HOME/.ipython. This options can also be specified through
156 is usually $HOME/.ipython. This options can also be specified through
156 the environment variable IPYTHONDIR.
157 the environment variable IPYTHONDIR.
157 """
158 """
158 )
159 )
159
160
160
161
161 def _print_profiles(self, profiles):
162 def _print_profiles(self, profiles):
162 """print list of profiles, indented."""
163 """print list of profiles, indented."""
163 for profile in profiles:
164 for profile in profiles:
164 print(' %s' % profile)
165 print(' %s' % profile)
165
166
166 def list_profile_dirs(self):
167 def list_profile_dirs(self):
167 profiles = list_bundled_profiles()
168 profiles = list_bundled_profiles()
168 if profiles:
169 if profiles:
169 print()
170 print()
170 print("Available profiles in IPython:")
171 print("Available profiles in IPython:")
171 self._print_profiles(profiles)
172 self._print_profiles(profiles)
172 print()
173 print()
173 print(" The first request for a bundled profile will copy it")
174 print(" The first request for a bundled profile will copy it")
174 print(" into your IPython directory (%s)," % self.ipython_dir)
175 print(" into your IPython directory (%s)," % self.ipython_dir)
175 print(" where you can customize it.")
176 print(" where you can customize it.")
176
177
177 profiles = list_profiles_in(self.ipython_dir)
178 profiles = list_profiles_in(self.ipython_dir)
178 if profiles:
179 if profiles:
179 print()
180 print()
180 print("Available profiles in %s:" % self.ipython_dir)
181 print("Available profiles in %s:" % self.ipython_dir)
181 self._print_profiles(profiles)
182 self._print_profiles(profiles)
182
183
183 profiles = list_profiles_in(os.getcwdu())
184 profiles = list_profiles_in(py3compat.getcwd())
184 if profiles:
185 if profiles:
185 print()
186 print()
186 print("Available profiles in current directory (%s):" % os.getcwdu())
187 print("Available profiles in current directory (%s):" % py3compat.getcwd())
187 self._print_profiles(profiles)
188 self._print_profiles(profiles)
188
189
189 print()
190 print()
190 print("To use any of the above profiles, start IPython with:")
191 print("To use any of the above profiles, start IPython with:")
191 print(" ipython --profile=<name>")
192 print(" ipython --profile=<name>")
192 print()
193 print()
193
194
194 def start(self):
195 def start(self):
195 self.list_profile_dirs()
196 self.list_profile_dirs()
196
197
197
198
198 create_flags = {}
199 create_flags = {}
199 create_flags.update(base_flags)
200 create_flags.update(base_flags)
200 # don't include '--init' flag, which implies running profile create in other apps
201 # don't include '--init' flag, which implies running profile create in other apps
201 create_flags.pop('init')
202 create_flags.pop('init')
202 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
203 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
203 "reset config files in this profile to the defaults.")
204 "reset config files in this profile to the defaults.")
204 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
205 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
205 "Include the config files for parallel "
206 "Include the config files for parallel "
206 "computing apps (ipengine, ipcontroller, etc.)")
207 "computing apps (ipengine, ipcontroller, etc.)")
207
208
208
209
209 class ProfileCreate(BaseIPythonApplication):
210 class ProfileCreate(BaseIPythonApplication):
210 name = u'ipython-profile'
211 name = u'ipython-profile'
211 description = create_help
212 description = create_help
212 examples = _create_examples
213 examples = _create_examples
213 auto_create = Bool(True, config=False)
214 auto_create = Bool(True, config=False)
214 def _log_format_default(self):
215 def _log_format_default(self):
215 return "[%(name)s] %(message)s"
216 return "[%(name)s] %(message)s"
216
217
217 def _copy_config_files_default(self):
218 def _copy_config_files_default(self):
218 return True
219 return True
219
220
220 parallel = Bool(False, config=True,
221 parallel = Bool(False, config=True,
221 help="whether to include parallel computing config files")
222 help="whether to include parallel computing config files")
222 def _parallel_changed(self, name, old, new):
223 def _parallel_changed(self, name, old, new):
223 parallel_files = [ 'ipcontroller_config.py',
224 parallel_files = [ 'ipcontroller_config.py',
224 'ipengine_config.py',
225 'ipengine_config.py',
225 'ipcluster_config.py'
226 'ipcluster_config.py'
226 ]
227 ]
227 if new:
228 if new:
228 for cf in parallel_files:
229 for cf in parallel_files:
229 self.config_files.append(cf)
230 self.config_files.append(cf)
230 else:
231 else:
231 for cf in parallel_files:
232 for cf in parallel_files:
232 if cf in self.config_files:
233 if cf in self.config_files:
233 self.config_files.remove(cf)
234 self.config_files.remove(cf)
234
235
235 def parse_command_line(self, argv):
236 def parse_command_line(self, argv):
236 super(ProfileCreate, self).parse_command_line(argv)
237 super(ProfileCreate, self).parse_command_line(argv)
237 # accept positional arg as profile name
238 # accept positional arg as profile name
238 if self.extra_args:
239 if self.extra_args:
239 self.profile = self.extra_args[0]
240 self.profile = self.extra_args[0]
240
241
241 flags = Dict(create_flags)
242 flags = Dict(create_flags)
242
243
243 classes = [ProfileDir]
244 classes = [ProfileDir]
244
245
245 def _import_app(self, app_path):
246 def _import_app(self, app_path):
246 """import an app class"""
247 """import an app class"""
247 app = None
248 app = None
248 name = app_path.rsplit('.', 1)[-1]
249 name = app_path.rsplit('.', 1)[-1]
249 try:
250 try:
250 app = import_item(app_path)
251 app = import_item(app_path)
251 except ImportError as e:
252 except ImportError:
252 self.log.info("Couldn't import %s, config file will be excluded", name)
253 self.log.info("Couldn't import %s, config file will be excluded", name)
253 except Exception:
254 except Exception:
254 self.log.warn('Unexpected error importing %s', name, exc_info=True)
255 self.log.warn('Unexpected error importing %s', name, exc_info=True)
255 return app
256 return app
256
257
257 def init_config_files(self):
258 def init_config_files(self):
258 super(ProfileCreate, self).init_config_files()
259 super(ProfileCreate, self).init_config_files()
259 # use local imports, since these classes may import from here
260 # use local imports, since these classes may import from here
260 from IPython.terminal.ipapp import TerminalIPythonApp
261 from IPython.terminal.ipapp import TerminalIPythonApp
261 apps = [TerminalIPythonApp]
262 apps = [TerminalIPythonApp]
262 for app_path in (
263 for app_path in (
263 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
264 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
264 'IPython.html.notebookapp.NotebookApp',
265 'IPython.html.notebookapp.NotebookApp',
265 'IPython.nbconvert.nbconvertapp.NbConvertApp',
266 'IPython.nbconvert.nbconvertapp.NbConvertApp',
266 ):
267 ):
267 app = self._import_app(app_path)
268 app = self._import_app(app_path)
268 if app is not None:
269 if app is not None:
269 apps.append(app)
270 apps.append(app)
270 if self.parallel:
271 if self.parallel:
271 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
272 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
272 from IPython.parallel.apps.ipengineapp import IPEngineApp
273 from IPython.parallel.apps.ipengineapp import IPEngineApp
273 from IPython.parallel.apps.ipclusterapp import IPClusterStart
274 from IPython.parallel.apps.ipclusterapp import IPClusterStart
274 from IPython.parallel.apps.iploggerapp import IPLoggerApp
275 from IPython.parallel.apps.iploggerapp import IPLoggerApp
275 apps.extend([
276 apps.extend([
276 IPControllerApp,
277 IPControllerApp,
277 IPEngineApp,
278 IPEngineApp,
278 IPClusterStart,
279 IPClusterStart,
279 IPLoggerApp,
280 IPLoggerApp,
280 ])
281 ])
281 for App in apps:
282 for App in apps:
282 app = App()
283 app = App()
283 app.config.update(self.config)
284 app.config.update(self.config)
284 app.log = self.log
285 app.log = self.log
285 app.overwrite = self.overwrite
286 app.overwrite = self.overwrite
286 app.copy_config_files=True
287 app.copy_config_files=True
287 app.profile = self.profile
288 app.profile = self.profile
288 app.init_profile_dir()
289 app.init_profile_dir()
289 app.init_config_files()
290 app.init_config_files()
290
291
291 def stage_default_config_file(self):
292 def stage_default_config_file(self):
292 pass
293 pass
293
294
294
295
295 class ProfileApp(Application):
296 class ProfileApp(Application):
296 name = u'ipython-profile'
297 name = u'ipython-profile'
297 description = profile_help
298 description = profile_help
298 examples = _main_examples
299 examples = _main_examples
299
300
300 subcommands = Dict(dict(
301 subcommands = Dict(dict(
301 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
302 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
302 list = (ProfileList, ProfileList.description.splitlines()[0]),
303 list = (ProfileList, ProfileList.description.splitlines()[0]),
303 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
304 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
304 ))
305 ))
305
306
306 def start(self):
307 def start(self):
307 if self.subapp is None:
308 if self.subapp is None:
308 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
309 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
309 print()
310 print()
310 self.print_description()
311 self.print_description()
311 self.print_subcommands()
312 self.print_subcommands()
312 self.exit(1)
313 self.exit(1)
313 else:
314 else:
314 return self.subapp.start()
315 return self.subapp.start()
@@ -1,273 +1,274 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An object for managing IPython profile directories.
3 An object for managing IPython profile directories.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez
8 * Fernando Perez
9 * Min RK
9 * Min RK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2011 The IPython Development Team
14 # Copyright (C) 2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import os
24 import os
25 import shutil
25 import shutil
26 import errno
26 import errno
27
27
28 from IPython.config.configurable import LoggingConfigurable
28 from IPython.config.configurable import LoggingConfigurable
29 from IPython.utils.path import get_ipython_package_dir, expand_path
29 from IPython.utils.path import get_ipython_package_dir, expand_path
30 from IPython.utils import py3compat
30 from IPython.utils.traitlets import Unicode, Bool
31 from IPython.utils.traitlets import Unicode, Bool
31
32
32 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
33 # Classes and functions
34 # Classes and functions
34 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
35
36
36
37
37 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
38 # Module errors
39 # Module errors
39 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
40
41
41 class ProfileDirError(Exception):
42 class ProfileDirError(Exception):
42 pass
43 pass
43
44
44
45
45 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
46 # Class for managing profile directories
47 # Class for managing profile directories
47 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
48
49
49 class ProfileDir(LoggingConfigurable):
50 class ProfileDir(LoggingConfigurable):
50 """An object to manage the profile directory and its resources.
51 """An object to manage the profile directory and its resources.
51
52
52 The profile directory is used by all IPython applications, to manage
53 The profile directory is used by all IPython applications, to manage
53 configuration, logging and security.
54 configuration, logging and security.
54
55
55 This object knows how to find, create and manage these directories. This
56 This object knows how to find, create and manage these directories. This
56 should be used by any code that wants to handle profiles.
57 should be used by any code that wants to handle profiles.
57 """
58 """
58
59
59 security_dir_name = Unicode('security')
60 security_dir_name = Unicode('security')
60 log_dir_name = Unicode('log')
61 log_dir_name = Unicode('log')
61 startup_dir_name = Unicode('startup')
62 startup_dir_name = Unicode('startup')
62 pid_dir_name = Unicode('pid')
63 pid_dir_name = Unicode('pid')
63 static_dir_name = Unicode('static')
64 static_dir_name = Unicode('static')
64 security_dir = Unicode(u'')
65 security_dir = Unicode(u'')
65 log_dir = Unicode(u'')
66 log_dir = Unicode(u'')
66 startup_dir = Unicode(u'')
67 startup_dir = Unicode(u'')
67 pid_dir = Unicode(u'')
68 pid_dir = Unicode(u'')
68 static_dir = Unicode(u'')
69 static_dir = Unicode(u'')
69
70
70 location = Unicode(u'', config=True,
71 location = Unicode(u'', config=True,
71 help="""Set the profile location directly. This overrides the logic used by the
72 help="""Set the profile location directly. This overrides the logic used by the
72 `profile` option.""",
73 `profile` option.""",
73 )
74 )
74
75
75 _location_isset = Bool(False) # flag for detecting multiply set location
76 _location_isset = Bool(False) # flag for detecting multiply set location
76
77
77 def _location_changed(self, name, old, new):
78 def _location_changed(self, name, old, new):
78 if self._location_isset:
79 if self._location_isset:
79 raise RuntimeError("Cannot set profile location more than once.")
80 raise RuntimeError("Cannot set profile location more than once.")
80 self._location_isset = True
81 self._location_isset = True
81 if not os.path.isdir(new):
82 if not os.path.isdir(new):
82 os.makedirs(new)
83 os.makedirs(new)
83
84
84 # ensure config files exist:
85 # ensure config files exist:
85 self.security_dir = os.path.join(new, self.security_dir_name)
86 self.security_dir = os.path.join(new, self.security_dir_name)
86 self.log_dir = os.path.join(new, self.log_dir_name)
87 self.log_dir = os.path.join(new, self.log_dir_name)
87 self.startup_dir = os.path.join(new, self.startup_dir_name)
88 self.startup_dir = os.path.join(new, self.startup_dir_name)
88 self.pid_dir = os.path.join(new, self.pid_dir_name)
89 self.pid_dir = os.path.join(new, self.pid_dir_name)
89 self.static_dir = os.path.join(new, self.static_dir_name)
90 self.static_dir = os.path.join(new, self.static_dir_name)
90 self.check_dirs()
91 self.check_dirs()
91
92
92 def _log_dir_changed(self, name, old, new):
93 def _log_dir_changed(self, name, old, new):
93 self.check_log_dir()
94 self.check_log_dir()
94
95
95 def _mkdir(self, path, mode=None):
96 def _mkdir(self, path, mode=None):
96 """ensure a directory exists at a given path
97 """ensure a directory exists at a given path
97
98
98 This is a version of os.mkdir, with the following differences:
99 This is a version of os.mkdir, with the following differences:
99
100
100 - returns True if it created the directory, False otherwise
101 - returns True if it created the directory, False otherwise
101 - ignores EEXIST, protecting against race conditions where
102 - ignores EEXIST, protecting against race conditions where
102 the dir may have been created in between the check and
103 the dir may have been created in between the check and
103 the creation
104 the creation
104 - sets permissions if requested and the dir already exists
105 - sets permissions if requested and the dir already exists
105 """
106 """
106 if os.path.exists(path):
107 if os.path.exists(path):
107 if mode and os.stat(path).st_mode != mode:
108 if mode and os.stat(path).st_mode != mode:
108 try:
109 try:
109 os.chmod(path, mode)
110 os.chmod(path, mode)
110 except OSError:
111 except OSError:
111 self.log.warn(
112 self.log.warn(
112 "Could not set permissions on %s",
113 "Could not set permissions on %s",
113 path
114 path
114 )
115 )
115 return False
116 return False
116 try:
117 try:
117 if mode:
118 if mode:
118 os.mkdir(path, mode)
119 os.mkdir(path, mode)
119 else:
120 else:
120 os.mkdir(path)
121 os.mkdir(path)
121 except OSError as e:
122 except OSError as e:
122 if e.errno == errno.EEXIST:
123 if e.errno == errno.EEXIST:
123 return False
124 return False
124 else:
125 else:
125 raise
126 raise
126
127
127 return True
128 return True
128
129
129 def check_log_dir(self):
130 def check_log_dir(self):
130 self._mkdir(self.log_dir)
131 self._mkdir(self.log_dir)
131
132
132 def _startup_dir_changed(self, name, old, new):
133 def _startup_dir_changed(self, name, old, new):
133 self.check_startup_dir()
134 self.check_startup_dir()
134
135
135 def check_startup_dir(self):
136 def check_startup_dir(self):
136 self._mkdir(self.startup_dir)
137 self._mkdir(self.startup_dir)
137
138
138 readme = os.path.join(self.startup_dir, 'README')
139 readme = os.path.join(self.startup_dir, 'README')
139 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
140 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
140
141
141 if not os.path.exists(src):
142 if not os.path.exists(src):
142 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
143 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
143
144
144 if os.path.exists(src) and not os.path.exists(readme):
145 if os.path.exists(src) and not os.path.exists(readme):
145 shutil.copy(src, readme)
146 shutil.copy(src, readme)
146
147
147 def _security_dir_changed(self, name, old, new):
148 def _security_dir_changed(self, name, old, new):
148 self.check_security_dir()
149 self.check_security_dir()
149
150
150 def check_security_dir(self):
151 def check_security_dir(self):
151 self._mkdir(self.security_dir, 0o40700)
152 self._mkdir(self.security_dir, 0o40700)
152
153
153 def _pid_dir_changed(self, name, old, new):
154 def _pid_dir_changed(self, name, old, new):
154 self.check_pid_dir()
155 self.check_pid_dir()
155
156
156 def check_pid_dir(self):
157 def check_pid_dir(self):
157 self._mkdir(self.pid_dir, 0o40700)
158 self._mkdir(self.pid_dir, 0o40700)
158
159
159 def _static_dir_changed(self, name, old, new):
160 def _static_dir_changed(self, name, old, new):
160 self.check_startup_dir()
161 self.check_startup_dir()
161
162
162 def check_static_dir(self):
163 def check_static_dir(self):
163 self._mkdir(self.static_dir)
164 self._mkdir(self.static_dir)
164 custom = os.path.join(self.static_dir, 'custom')
165 custom = os.path.join(self.static_dir, 'custom')
165 self._mkdir(custom)
166 self._mkdir(custom)
166 from IPython.html import DEFAULT_STATIC_FILES_PATH
167 from IPython.html import DEFAULT_STATIC_FILES_PATH
167 for fname in ('custom.js', 'custom.css'):
168 for fname in ('custom.js', 'custom.css'):
168 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
169 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
169 dest = os.path.join(custom, fname)
170 dest = os.path.join(custom, fname)
170 if not os.path.exists(src):
171 if not os.path.exists(src):
171 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
172 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
172 continue
173 continue
173 if not os.path.exists(dest):
174 if not os.path.exists(dest):
174 shutil.copy(src, dest)
175 shutil.copy(src, dest)
175
176
176 def check_dirs(self):
177 def check_dirs(self):
177 self.check_security_dir()
178 self.check_security_dir()
178 self.check_log_dir()
179 self.check_log_dir()
179 self.check_pid_dir()
180 self.check_pid_dir()
180 self.check_startup_dir()
181 self.check_startup_dir()
181 self.check_static_dir()
182 self.check_static_dir()
182
183
183 def copy_config_file(self, config_file, path=None, overwrite=False):
184 def copy_config_file(self, config_file, path=None, overwrite=False):
184 """Copy a default config file into the active profile directory.
185 """Copy a default config file into the active profile directory.
185
186
186 Default configuration files are kept in :mod:`IPython.config.default`.
187 Default configuration files are kept in :mod:`IPython.config.default`.
187 This function moves these from that location to the working profile
188 This function moves these from that location to the working profile
188 directory.
189 directory.
189 """
190 """
190 dst = os.path.join(self.location, config_file)
191 dst = os.path.join(self.location, config_file)
191 if os.path.isfile(dst) and not overwrite:
192 if os.path.isfile(dst) and not overwrite:
192 return False
193 return False
193 if path is None:
194 if path is None:
194 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
195 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
195 src = os.path.join(path, config_file)
196 src = os.path.join(path, config_file)
196 shutil.copy(src, dst)
197 shutil.copy(src, dst)
197 return True
198 return True
198
199
199 @classmethod
200 @classmethod
200 def create_profile_dir(cls, profile_dir, config=None):
201 def create_profile_dir(cls, profile_dir, config=None):
201 """Create a new profile directory given a full path.
202 """Create a new profile directory given a full path.
202
203
203 Parameters
204 Parameters
204 ----------
205 ----------
205 profile_dir : str
206 profile_dir : str
206 The full path to the profile directory. If it does exist, it will
207 The full path to the profile directory. If it does exist, it will
207 be used. If not, it will be created.
208 be used. If not, it will be created.
208 """
209 """
209 return cls(location=profile_dir, config=config)
210 return cls(location=profile_dir, config=config)
210
211
211 @classmethod
212 @classmethod
212 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
213 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
213 """Create a profile dir by profile name and path.
214 """Create a profile dir by profile name and path.
214
215
215 Parameters
216 Parameters
216 ----------
217 ----------
217 path : unicode
218 path : unicode
218 The path (directory) to put the profile directory in.
219 The path (directory) to put the profile directory in.
219 name : unicode
220 name : unicode
220 The name of the profile. The name of the profile directory will
221 The name of the profile. The name of the profile directory will
221 be "profile_<profile>".
222 be "profile_<profile>".
222 """
223 """
223 if not os.path.isdir(path):
224 if not os.path.isdir(path):
224 raise ProfileDirError('Directory not found: %s' % path)
225 raise ProfileDirError('Directory not found: %s' % path)
225 profile_dir = os.path.join(path, u'profile_' + name)
226 profile_dir = os.path.join(path, u'profile_' + name)
226 return cls(location=profile_dir, config=config)
227 return cls(location=profile_dir, config=config)
227
228
228 @classmethod
229 @classmethod
229 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
230 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
230 """Find an existing profile dir by profile name, return its ProfileDir.
231 """Find an existing profile dir by profile name, return its ProfileDir.
231
232
232 This searches through a sequence of paths for a profile dir. If it
233 This searches through a sequence of paths for a profile dir. If it
233 is not found, a :class:`ProfileDirError` exception will be raised.
234 is not found, a :class:`ProfileDirError` exception will be raised.
234
235
235 The search path algorithm is:
236 The search path algorithm is:
236 1. ``os.getcwdu()``
237 1. ``py3compat.getcwd()``
237 2. ``ipython_dir``
238 2. ``ipython_dir``
238
239
239 Parameters
240 Parameters
240 ----------
241 ----------
241 ipython_dir : unicode or str
242 ipython_dir : unicode or str
242 The IPython directory to use.
243 The IPython directory to use.
243 name : unicode or str
244 name : unicode or str
244 The name of the profile. The name of the profile directory
245 The name of the profile. The name of the profile directory
245 will be "profile_<profile>".
246 will be "profile_<profile>".
246 """
247 """
247 dirname = u'profile_' + name
248 dirname = u'profile_' + name
248 paths = [os.getcwdu(), ipython_dir]
249 paths = [py3compat.getcwd(), ipython_dir]
249 for p in paths:
250 for p in paths:
250 profile_dir = os.path.join(p, dirname)
251 profile_dir = os.path.join(p, dirname)
251 if os.path.isdir(profile_dir):
252 if os.path.isdir(profile_dir):
252 return cls(location=profile_dir, config=config)
253 return cls(location=profile_dir, config=config)
253 else:
254 else:
254 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
255 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
255
256
256 @classmethod
257 @classmethod
257 def find_profile_dir(cls, profile_dir, config=None):
258 def find_profile_dir(cls, profile_dir, config=None):
258 """Find/create a profile dir and return its ProfileDir.
259 """Find/create a profile dir and return its ProfileDir.
259
260
260 This will create the profile directory if it doesn't exist.
261 This will create the profile directory if it doesn't exist.
261
262
262 Parameters
263 Parameters
263 ----------
264 ----------
264 profile_dir : unicode or str
265 profile_dir : unicode or str
265 The path of the profile directory. This is expanded using
266 The path of the profile directory. This is expanded using
266 :func:`IPython.utils.genutils.expand_path`.
267 :func:`IPython.utils.genutils.expand_path`.
267 """
268 """
268 profile_dir = expand_path(profile_dir)
269 profile_dir = expand_path(profile_dir)
269 if not os.path.isdir(profile_dir):
270 if not os.path.isdir(profile_dir):
270 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
271 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
271 return cls(location=profile_dir, config=config)
272 return cls(location=profile_dir, config=config)
272
273
273
274
@@ -1,439 +1,439 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Classes for handling input/output prompts.
2 """Classes for handling input/output prompts.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian Granger
7 * Brian Granger
8 * Thomas Kluyver
8 * Thomas Kluyver
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import re
24 import re
25 import socket
25 import socket
26 import sys
26 import sys
27 import time
27 import time
28
28
29 from string import Formatter
29 from string import Formatter
30
30
31 from IPython.config.configurable import Configurable
31 from IPython.config.configurable import Configurable
32 from IPython.core import release
32 from IPython.core import release
33 from IPython.utils import coloransi, py3compat
33 from IPython.utils import coloransi, py3compat
34 from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
34 from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Color schemes for prompts
37 # Color schemes for prompts
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 InputColors = coloransi.InputTermColors # just a shorthand
40 InputColors = coloransi.InputTermColors # just a shorthand
41 Colors = coloransi.TermColors # just a shorthand
41 Colors = coloransi.TermColors # just a shorthand
42
42
43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
44
44
45 PColNoColors = coloransi.ColorScheme(
45 PColNoColors = coloransi.ColorScheme(
46 'NoColor',
46 'NoColor',
47 in_prompt = InputColors.NoColor, # Input prompt
47 in_prompt = InputColors.NoColor, # Input prompt
48 in_number = InputColors.NoColor, # Input prompt number
48 in_number = InputColors.NoColor, # Input prompt number
49 in_prompt2 = InputColors.NoColor, # Continuation prompt
49 in_prompt2 = InputColors.NoColor, # Continuation prompt
50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
51
51
52 out_prompt = Colors.NoColor, # Output prompt
52 out_prompt = Colors.NoColor, # Output prompt
53 out_number = Colors.NoColor, # Output prompt number
53 out_number = Colors.NoColor, # Output prompt number
54
54
55 normal = Colors.NoColor # color off (usu. Colors.Normal)
55 normal = Colors.NoColor # color off (usu. Colors.Normal)
56 )
56 )
57
57
58 # make some schemes as instances so we can copy them for modification easily:
58 # make some schemes as instances so we can copy them for modification easily:
59 PColLinux = coloransi.ColorScheme(
59 PColLinux = coloransi.ColorScheme(
60 'Linux',
60 'Linux',
61 in_prompt = InputColors.Green,
61 in_prompt = InputColors.Green,
62 in_number = InputColors.LightGreen,
62 in_number = InputColors.LightGreen,
63 in_prompt2 = InputColors.Green,
63 in_prompt2 = InputColors.Green,
64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
65
65
66 out_prompt = Colors.Red,
66 out_prompt = Colors.Red,
67 out_number = Colors.LightRed,
67 out_number = Colors.LightRed,
68
68
69 normal = Colors.Normal
69 normal = Colors.Normal
70 )
70 )
71
71
72 # Slightly modified Linux for light backgrounds
72 # Slightly modified Linux for light backgrounds
73 PColLightBG = PColLinux.copy('LightBG')
73 PColLightBG = PColLinux.copy('LightBG')
74
74
75 PColLightBG.colors.update(
75 PColLightBG.colors.update(
76 in_prompt = InputColors.Blue,
76 in_prompt = InputColors.Blue,
77 in_number = InputColors.LightBlue,
77 in_number = InputColors.LightBlue,
78 in_prompt2 = InputColors.Blue
78 in_prompt2 = InputColors.Blue
79 )
79 )
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Utilities
82 # Utilities
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85 class LazyEvaluate(object):
85 class LazyEvaluate(object):
86 """This is used for formatting strings with values that need to be updated
86 """This is used for formatting strings with values that need to be updated
87 at that time, such as the current time or working directory."""
87 at that time, such as the current time or working directory."""
88 def __init__(self, func, *args, **kwargs):
88 def __init__(self, func, *args, **kwargs):
89 self.func = func
89 self.func = func
90 self.args = args
90 self.args = args
91 self.kwargs = kwargs
91 self.kwargs = kwargs
92
92
93 def __call__(self, **kwargs):
93 def __call__(self, **kwargs):
94 self.kwargs.update(kwargs)
94 self.kwargs.update(kwargs)
95 return self.func(*self.args, **self.kwargs)
95 return self.func(*self.args, **self.kwargs)
96
96
97 def __str__(self):
97 def __str__(self):
98 return str(self())
98 return str(self())
99
99
100 def __unicode__(self):
100 def __unicode__(self):
101 return py3compat.unicode_type(self())
101 return py3compat.unicode_type(self())
102
102
103 def __format__(self, format_spec):
103 def __format__(self, format_spec):
104 return format(self(), format_spec)
104 return format(self(), format_spec)
105
105
106 def multiple_replace(dict, text):
106 def multiple_replace(dict, text):
107 """ Replace in 'text' all occurences of any key in the given
107 """ Replace in 'text' all occurences of any key in the given
108 dictionary by its corresponding value. Returns the new string."""
108 dictionary by its corresponding value. Returns the new string."""
109
109
110 # Function by Xavier Defrang, originally found at:
110 # Function by Xavier Defrang, originally found at:
111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
112
112
113 # Create a regular expression from the dictionary keys
113 # Create a regular expression from the dictionary keys
114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
115 # For each match, look-up corresponding value in dictionary
115 # For each match, look-up corresponding value in dictionary
116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
117
117
118 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
119 # Special characters that can be used in prompt templates, mainly bash-like
119 # Special characters that can be used in prompt templates, mainly bash-like
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121
121
122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
123 # never be expanded out into '~'. Basically anything which can never be a
123 # never be expanded out into '~'. Basically anything which can never be a
124 # reasonable directory name will do, we just want the $HOME -> '~' operation
124 # reasonable directory name will do, we just want the $HOME -> '~' operation
125 # to become a no-op. We pre-compute $HOME here so it's not done on every
125 # to become a no-op. We pre-compute $HOME here so it's not done on every
126 # prompt call.
126 # prompt call.
127
127
128 # FIXME:
128 # FIXME:
129
129
130 # - This should be turned into a class which does proper namespace management,
130 # - This should be turned into a class which does proper namespace management,
131 # since the prompt specials need to be evaluated in a certain namespace.
131 # since the prompt specials need to be evaluated in a certain namespace.
132 # Currently it's just globals, which need to be managed manually by code
132 # Currently it's just globals, which need to be managed manually by code
133 # below.
133 # below.
134
134
135 # - I also need to split up the color schemes from the prompt specials
135 # - I also need to split up the color schemes from the prompt specials
136 # somehow. I don't have a clean design for that quite yet.
136 # somehow. I don't have a clean design for that quite yet.
137
137
138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
139
139
140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
141 # /usr/home, but retain the $HOME variable as pointing to /home
141 # /usr/home, but retain the $HOME variable as pointing to /home
142 HOME = os.path.realpath(HOME)
142 HOME = os.path.realpath(HOME)
143
143
144 # We precompute a few more strings here for the prompt_specials, which are
144 # We precompute a few more strings here for the prompt_specials, which are
145 # fixed once ipython starts. This reduces the runtime overhead of computing
145 # fixed once ipython starts. This reduces the runtime overhead of computing
146 # prompt strings.
146 # prompt strings.
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
150 ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$"
150 ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$"
151
151
152 prompt_abbreviations = {
152 prompt_abbreviations = {
153 # Prompt/history count
153 # Prompt/history count
154 '%n' : '{color.number}' '{count}' '{color.prompt}',
154 '%n' : '{color.number}' '{count}' '{color.prompt}',
155 r'\#': '{color.number}' '{count}' '{color.prompt}',
155 r'\#': '{color.number}' '{count}' '{color.prompt}',
156 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
156 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
157 # can get numbers displayed in whatever color they want.
157 # can get numbers displayed in whatever color they want.
158 r'\N': '{count}',
158 r'\N': '{count}',
159
159
160 # Prompt/history count, with the actual digits replaced by dots. Used
160 # Prompt/history count, with the actual digits replaced by dots. Used
161 # mainly in continuation prompts (prompt_in2)
161 # mainly in continuation prompts (prompt_in2)
162 r'\D': '{dots}',
162 r'\D': '{dots}',
163
163
164 # Current time
164 # Current time
165 r'\T' : '{time}',
165 r'\T' : '{time}',
166 # Current working directory
166 # Current working directory
167 r'\w': '{cwd}',
167 r'\w': '{cwd}',
168 # Basename of current working directory.
168 # Basename of current working directory.
169 # (use os.sep to make this portable across OSes)
169 # (use os.sep to make this portable across OSes)
170 r'\W' : '{cwd_last}',
170 r'\W' : '{cwd_last}',
171 # These X<N> are an extension to the normal bash prompts. They return
171 # These X<N> are an extension to the normal bash prompts. They return
172 # N terms of the path, after replacing $HOME with '~'
172 # N terms of the path, after replacing $HOME with '~'
173 r'\X0': '{cwd_x[0]}',
173 r'\X0': '{cwd_x[0]}',
174 r'\X1': '{cwd_x[1]}',
174 r'\X1': '{cwd_x[1]}',
175 r'\X2': '{cwd_x[2]}',
175 r'\X2': '{cwd_x[2]}',
176 r'\X3': '{cwd_x[3]}',
176 r'\X3': '{cwd_x[3]}',
177 r'\X4': '{cwd_x[4]}',
177 r'\X4': '{cwd_x[4]}',
178 r'\X5': '{cwd_x[5]}',
178 r'\X5': '{cwd_x[5]}',
179 # Y<N> are similar to X<N>, but they show '~' if it's the directory
179 # Y<N> are similar to X<N>, but they show '~' if it's the directory
180 # N+1 in the list. Somewhat like %cN in tcsh.
180 # N+1 in the list. Somewhat like %cN in tcsh.
181 r'\Y0': '{cwd_y[0]}',
181 r'\Y0': '{cwd_y[0]}',
182 r'\Y1': '{cwd_y[1]}',
182 r'\Y1': '{cwd_y[1]}',
183 r'\Y2': '{cwd_y[2]}',
183 r'\Y2': '{cwd_y[2]}',
184 r'\Y3': '{cwd_y[3]}',
184 r'\Y3': '{cwd_y[3]}',
185 r'\Y4': '{cwd_y[4]}',
185 r'\Y4': '{cwd_y[4]}',
186 r'\Y5': '{cwd_y[5]}',
186 r'\Y5': '{cwd_y[5]}',
187 # Hostname up to first .
187 # Hostname up to first .
188 r'\h': HOSTNAME_SHORT,
188 r'\h': HOSTNAME_SHORT,
189 # Full hostname
189 # Full hostname
190 r'\H': HOSTNAME,
190 r'\H': HOSTNAME,
191 # Username of current user
191 # Username of current user
192 r'\u': USER,
192 r'\u': USER,
193 # Escaped '\'
193 # Escaped '\'
194 '\\\\': '\\',
194 '\\\\': '\\',
195 # Newline
195 # Newline
196 r'\n': '\n',
196 r'\n': '\n',
197 # Carriage return
197 # Carriage return
198 r'\r': '\r',
198 r'\r': '\r',
199 # Release version
199 # Release version
200 r'\v': release.version,
200 r'\v': release.version,
201 # Root symbol ($ or #)
201 # Root symbol ($ or #)
202 r'\$': ROOT_SYMBOL,
202 r'\$': ROOT_SYMBOL,
203 }
203 }
204
204
205 #-----------------------------------------------------------------------------
205 #-----------------------------------------------------------------------------
206 # More utilities
206 # More utilities
207 #-----------------------------------------------------------------------------
207 #-----------------------------------------------------------------------------
208
208
209 def cwd_filt(depth):
209 def cwd_filt(depth):
210 """Return the last depth elements of the current working directory.
210 """Return the last depth elements of the current working directory.
211
211
212 $HOME is always replaced with '~'.
212 $HOME is always replaced with '~'.
213 If depth==0, the full path is returned."""
213 If depth==0, the full path is returned."""
214
214
215 cwd = os.getcwdu().replace(HOME,"~")
215 cwd = py3compat.getcwd().replace(HOME,"~")
216 out = os.sep.join(cwd.split(os.sep)[-depth:])
216 out = os.sep.join(cwd.split(os.sep)[-depth:])
217 return out or os.sep
217 return out or os.sep
218
218
219 def cwd_filt2(depth):
219 def cwd_filt2(depth):
220 """Return the last depth elements of the current working directory.
220 """Return the last depth elements of the current working directory.
221
221
222 $HOME is always replaced with '~'.
222 $HOME is always replaced with '~'.
223 If depth==0, the full path is returned."""
223 If depth==0, the full path is returned."""
224
224
225 full_cwd = os.getcwdu()
225 full_cwd = py3compat.getcwd()
226 cwd = full_cwd.replace(HOME,"~").split(os.sep)
226 cwd = full_cwd.replace(HOME,"~").split(os.sep)
227 if '~' in cwd and len(cwd) == depth+1:
227 if '~' in cwd and len(cwd) == depth+1:
228 depth += 1
228 depth += 1
229 drivepart = ''
229 drivepart = ''
230 if sys.platform == 'win32' and len(cwd) > depth:
230 if sys.platform == 'win32' and len(cwd) > depth:
231 drivepart = os.path.splitdrive(full_cwd)[0]
231 drivepart = os.path.splitdrive(full_cwd)[0]
232 out = drivepart + '/'.join(cwd[-depth:])
232 out = drivepart + '/'.join(cwd[-depth:])
233
233
234 return out or os.sep
234 return out or os.sep
235
235
236 #-----------------------------------------------------------------------------
236 #-----------------------------------------------------------------------------
237 # Prompt classes
237 # Prompt classes
238 #-----------------------------------------------------------------------------
238 #-----------------------------------------------------------------------------
239
239
240 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
240 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
241 'cwd': LazyEvaluate(os.getcwdu),
241 'cwd': LazyEvaluate(py3compat.getcwd),
242 'cwd_last': LazyEvaluate(lambda: os.getcwdu().split(os.sep)[-1]),
242 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
243 'cwd_x': [LazyEvaluate(lambda: os.getcwdu().replace(HOME,"~"))] +\
243 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
244 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
244 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
245 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
245 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
246 }
246 }
247
247
248 def _lenlastline(s):
248 def _lenlastline(s):
249 """Get the length of the last line. More intelligent than
249 """Get the length of the last line. More intelligent than
250 len(s.splitlines()[-1]).
250 len(s.splitlines()[-1]).
251 """
251 """
252 if not s or s.endswith(('\n', '\r')):
252 if not s or s.endswith(('\n', '\r')):
253 return 0
253 return 0
254 return len(s.splitlines()[-1])
254 return len(s.splitlines()[-1])
255
255
256
256
257 class UserNSFormatter(Formatter):
257 class UserNSFormatter(Formatter):
258 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
258 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
259 def __init__(self, shell):
259 def __init__(self, shell):
260 self.shell = shell
260 self.shell = shell
261
261
262 def get_value(self, key, args, kwargs):
262 def get_value(self, key, args, kwargs):
263 # try regular formatting first:
263 # try regular formatting first:
264 try:
264 try:
265 return Formatter.get_value(self, key, args, kwargs)
265 return Formatter.get_value(self, key, args, kwargs)
266 except Exception:
266 except Exception:
267 pass
267 pass
268 # next, look in user_ns and builtins:
268 # next, look in user_ns and builtins:
269 for container in (self.shell.user_ns, __builtins__):
269 for container in (self.shell.user_ns, __builtins__):
270 if key in container:
270 if key in container:
271 return container[key]
271 return container[key]
272 # nothing found, put error message in its place
272 # nothing found, put error message in its place
273 return "<ERROR: '%s' not found>" % key
273 return "<ERROR: '%s' not found>" % key
274
274
275
275
276 class PromptManager(Configurable):
276 class PromptManager(Configurable):
277 """This is the primary interface for producing IPython's prompts."""
277 """This is the primary interface for producing IPython's prompts."""
278 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
278 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
279
279
280 color_scheme_table = Instance(coloransi.ColorSchemeTable)
280 color_scheme_table = Instance(coloransi.ColorSchemeTable)
281 color_scheme = Unicode('Linux', config=True)
281 color_scheme = Unicode('Linux', config=True)
282 def _color_scheme_changed(self, name, new_value):
282 def _color_scheme_changed(self, name, new_value):
283 self.color_scheme_table.set_active_scheme(new_value)
283 self.color_scheme_table.set_active_scheme(new_value)
284 for pname in ['in', 'in2', 'out', 'rewrite']:
284 for pname in ['in', 'in2', 'out', 'rewrite']:
285 # We need to recalculate the number of invisible characters
285 # We need to recalculate the number of invisible characters
286 self.update_prompt(pname)
286 self.update_prompt(pname)
287
287
288 lazy_evaluate_fields = Dict(help="""
288 lazy_evaluate_fields = Dict(help="""
289 This maps field names used in the prompt templates to functions which
289 This maps field names used in the prompt templates to functions which
290 will be called when the prompt is rendered. This allows us to include
290 will be called when the prompt is rendered. This allows us to include
291 things like the current time in the prompts. Functions are only called
291 things like the current time in the prompts. Functions are only called
292 if they are used in the prompt.
292 if they are used in the prompt.
293 """)
293 """)
294 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
294 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
295
295
296 in_template = Unicode('In [\\#]: ', config=True,
296 in_template = Unicode('In [\\#]: ', config=True,
297 help="Input prompt. '\\#' will be transformed to the prompt number")
297 help="Input prompt. '\\#' will be transformed to the prompt number")
298 in2_template = Unicode(' .\\D.: ', config=True,
298 in2_template = Unicode(' .\\D.: ', config=True,
299 help="Continuation prompt.")
299 help="Continuation prompt.")
300 out_template = Unicode('Out[\\#]: ', config=True,
300 out_template = Unicode('Out[\\#]: ', config=True,
301 help="Output prompt. '\\#' will be transformed to the prompt number")
301 help="Output prompt. '\\#' will be transformed to the prompt number")
302
302
303 justify = Bool(True, config=True, help="""
303 justify = Bool(True, config=True, help="""
304 If True (default), each prompt will be right-aligned with the
304 If True (default), each prompt will be right-aligned with the
305 preceding one.
305 preceding one.
306 """)
306 """)
307
307
308 # We actually store the expanded templates here:
308 # We actually store the expanded templates here:
309 templates = Dict()
309 templates = Dict()
310
310
311 # The number of characters in the last prompt rendered, not including
311 # The number of characters in the last prompt rendered, not including
312 # colour characters.
312 # colour characters.
313 width = Int()
313 width = Int()
314 txtwidth = Int() # Not including right-justification
314 txtwidth = Int() # Not including right-justification
315
315
316 # The number of characters in each prompt which don't contribute to width
316 # The number of characters in each prompt which don't contribute to width
317 invisible_chars = Dict()
317 invisible_chars = Dict()
318 def _invisible_chars_default(self):
318 def _invisible_chars_default(self):
319 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
319 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
320
320
321 def __init__(self, shell, **kwargs):
321 def __init__(self, shell, **kwargs):
322 super(PromptManager, self).__init__(shell=shell, **kwargs)
322 super(PromptManager, self).__init__(shell=shell, **kwargs)
323
323
324 # Prepare colour scheme table
324 # Prepare colour scheme table
325 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
325 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
326 PColLinux, PColLightBG], self.color_scheme)
326 PColLinux, PColLightBG], self.color_scheme)
327
327
328 self._formatter = UserNSFormatter(shell)
328 self._formatter = UserNSFormatter(shell)
329 # Prepare templates & numbers of invisible characters
329 # Prepare templates & numbers of invisible characters
330 self.update_prompt('in', self.in_template)
330 self.update_prompt('in', self.in_template)
331 self.update_prompt('in2', self.in2_template)
331 self.update_prompt('in2', self.in2_template)
332 self.update_prompt('out', self.out_template)
332 self.update_prompt('out', self.out_template)
333 self.update_prompt('rewrite')
333 self.update_prompt('rewrite')
334 self.on_trait_change(self._update_prompt_trait, ['in_template',
334 self.on_trait_change(self._update_prompt_trait, ['in_template',
335 'in2_template', 'out_template'])
335 'in2_template', 'out_template'])
336
336
337 def update_prompt(self, name, new_template=None):
337 def update_prompt(self, name, new_template=None):
338 """This is called when a prompt template is updated. It processes
338 """This is called when a prompt template is updated. It processes
339 abbreviations used in the prompt template (like \#) and calculates how
339 abbreviations used in the prompt template (like \#) and calculates how
340 many invisible characters (ANSI colour escapes) the resulting prompt
340 many invisible characters (ANSI colour escapes) the resulting prompt
341 contains.
341 contains.
342
342
343 It is also called for each prompt on changing the colour scheme. In both
343 It is also called for each prompt on changing the colour scheme. In both
344 cases, traitlets should take care of calling this automatically.
344 cases, traitlets should take care of calling this automatically.
345 """
345 """
346 if new_template is not None:
346 if new_template is not None:
347 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
347 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
348 # We count invisible characters (colour escapes) on the last line of the
348 # We count invisible characters (colour escapes) on the last line of the
349 # prompt, to calculate the width for lining up subsequent prompts.
349 # prompt, to calculate the width for lining up subsequent prompts.
350 invis_chars = _lenlastline(self._render(name, color=True)) - \
350 invis_chars = _lenlastline(self._render(name, color=True)) - \
351 _lenlastline(self._render(name, color=False))
351 _lenlastline(self._render(name, color=False))
352 self.invisible_chars[name] = invis_chars
352 self.invisible_chars[name] = invis_chars
353
353
354 def _update_prompt_trait(self, traitname, new_template):
354 def _update_prompt_trait(self, traitname, new_template):
355 name = traitname[:-9] # Cut off '_template'
355 name = traitname[:-9] # Cut off '_template'
356 self.update_prompt(name, new_template)
356 self.update_prompt(name, new_template)
357
357
358 def _render(self, name, color=True, **kwargs):
358 def _render(self, name, color=True, **kwargs):
359 """Render but don't justify, or update the width or txtwidth attributes.
359 """Render but don't justify, or update the width or txtwidth attributes.
360 """
360 """
361 if name == 'rewrite':
361 if name == 'rewrite':
362 return self._render_rewrite(color=color)
362 return self._render_rewrite(color=color)
363
363
364 if color:
364 if color:
365 scheme = self.color_scheme_table.active_colors
365 scheme = self.color_scheme_table.active_colors
366 if name=='out':
366 if name=='out':
367 colors = color_lists['normal']
367 colors = color_lists['normal']
368 colors.number, colors.prompt, colors.normal = \
368 colors.number, colors.prompt, colors.normal = \
369 scheme.out_number, scheme.out_prompt, scheme.normal
369 scheme.out_number, scheme.out_prompt, scheme.normal
370 else:
370 else:
371 colors = color_lists['inp']
371 colors = color_lists['inp']
372 colors.number, colors.prompt, colors.normal = \
372 colors.number, colors.prompt, colors.normal = \
373 scheme.in_number, scheme.in_prompt, scheme.in_normal
373 scheme.in_number, scheme.in_prompt, scheme.in_normal
374 if name=='in2':
374 if name=='in2':
375 colors.prompt = scheme.in_prompt2
375 colors.prompt = scheme.in_prompt2
376 else:
376 else:
377 # No color
377 # No color
378 colors = color_lists['nocolor']
378 colors = color_lists['nocolor']
379 colors.number, colors.prompt, colors.normal = '', '', ''
379 colors.number, colors.prompt, colors.normal = '', '', ''
380
380
381 count = self.shell.execution_count # Shorthand
381 count = self.shell.execution_count # Shorthand
382 # Build the dictionary to be passed to string formatting
382 # Build the dictionary to be passed to string formatting
383 fmtargs = dict(color=colors, count=count,
383 fmtargs = dict(color=colors, count=count,
384 dots="."*len(str(count)),
384 dots="."*len(str(count)),
385 width=self.width, txtwidth=self.txtwidth )
385 width=self.width, txtwidth=self.txtwidth )
386 fmtargs.update(self.lazy_evaluate_fields)
386 fmtargs.update(self.lazy_evaluate_fields)
387 fmtargs.update(kwargs)
387 fmtargs.update(kwargs)
388
388
389 # Prepare the prompt
389 # Prepare the prompt
390 prompt = colors.prompt + self.templates[name] + colors.normal
390 prompt = colors.prompt + self.templates[name] + colors.normal
391
391
392 # Fill in required fields
392 # Fill in required fields
393 return self._formatter.format(prompt, **fmtargs)
393 return self._formatter.format(prompt, **fmtargs)
394
394
395 def _render_rewrite(self, color=True):
395 def _render_rewrite(self, color=True):
396 """Render the ---> rewrite prompt."""
396 """Render the ---> rewrite prompt."""
397 if color:
397 if color:
398 scheme = self.color_scheme_table.active_colors
398 scheme = self.color_scheme_table.active_colors
399 # We need a non-input version of these escapes
399 # We need a non-input version of these escapes
400 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
400 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
401 color_normal = scheme.normal
401 color_normal = scheme.normal
402 else:
402 else:
403 color_prompt, color_normal = '', ''
403 color_prompt, color_normal = '', ''
404
404
405 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
405 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
406
406
407 def render(self, name, color=True, just=None, **kwargs):
407 def render(self, name, color=True, just=None, **kwargs):
408 """
408 """
409 Render the selected prompt.
409 Render the selected prompt.
410
410
411 Parameters
411 Parameters
412 ----------
412 ----------
413 name : str
413 name : str
414 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
414 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
415 color : bool
415 color : bool
416 If True (default), include ANSI escape sequences for a coloured prompt.
416 If True (default), include ANSI escape sequences for a coloured prompt.
417 just : bool
417 just : bool
418 If True, justify the prompt to the width of the last prompt. The
418 If True, justify the prompt to the width of the last prompt. The
419 default is stored in self.justify.
419 default is stored in self.justify.
420 **kwargs :
420 **kwargs :
421 Additional arguments will be passed to the string formatting operation,
421 Additional arguments will be passed to the string formatting operation,
422 so they can override the values that would otherwise fill in the
422 so they can override the values that would otherwise fill in the
423 template.
423 template.
424
424
425 Returns
425 Returns
426 -------
426 -------
427 A string containing the rendered prompt.
427 A string containing the rendered prompt.
428 """
428 """
429 res = self._render(name, color=color, **kwargs)
429 res = self._render(name, color=color, **kwargs)
430
430
431 # Handle justification of prompt
431 # Handle justification of prompt
432 invis_chars = self.invisible_chars[name] if color else 0
432 invis_chars = self.invisible_chars[name] if color else 0
433 self.txtwidth = _lenlastline(res) - invis_chars
433 self.txtwidth = _lenlastline(res) - invis_chars
434 just = self.justify if (just is None) else just
434 just = self.justify if (just is None) else just
435 # If the prompt spans more than one line, don't try to justify it:
435 # If the prompt spans more than one line, don't try to justify it:
436 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
436 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
437 res = res.rjust(self.width + invis_chars)
437 res = res.rjust(self.width + invis_chars)
438 self.width = _lenlastline(res) - invis_chars
438 self.width = _lenlastline(res) - invis_chars
439 return res
439 return res
@@ -1,50 +1,50 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for IPython.core.application"""
2 """Tests for IPython.core.application"""
3
3
4 import os
4 import os
5 import tempfile
5 import tempfile
6
6
7 from IPython.core.application import BaseIPythonApplication
7 from IPython.core.application import BaseIPythonApplication
8 from IPython.testing import decorators as dec
8 from IPython.testing import decorators as dec
9 from IPython.utils import py3compat
9 from IPython.utils import py3compat
10
10
11 @dec.onlyif_unicode_paths
11 @dec.onlyif_unicode_paths
12 def test_unicode_cwd():
12 def test_unicode_cwd():
13 """Check that IPython starts with non-ascii characters in the path."""
13 """Check that IPython starts with non-ascii characters in the path."""
14 wd = tempfile.mkdtemp(suffix=u"€")
14 wd = tempfile.mkdtemp(suffix=u"€")
15
15
16 old_wd = os.getcwdu()
16 old_wd = py3compat.getcwd()
17 os.chdir(wd)
17 os.chdir(wd)
18 #raise Exception(repr(os.getcwdu()))
18 #raise Exception(repr(py3compat.getcwd()))
19 try:
19 try:
20 app = BaseIPythonApplication()
20 app = BaseIPythonApplication()
21 # The lines below are copied from Application.initialize()
21 # The lines below are copied from Application.initialize()
22 app.init_profile_dir()
22 app.init_profile_dir()
23 app.init_config_files()
23 app.init_config_files()
24 app.load_config_file(suppress_errors=False)
24 app.load_config_file(suppress_errors=False)
25 finally:
25 finally:
26 os.chdir(old_wd)
26 os.chdir(old_wd)
27
27
28 @dec.onlyif_unicode_paths
28 @dec.onlyif_unicode_paths
29 def test_unicode_ipdir():
29 def test_unicode_ipdir():
30 """Check that IPython starts with non-ascii characters in the IP dir."""
30 """Check that IPython starts with non-ascii characters in the IP dir."""
31 ipdir = tempfile.mkdtemp(suffix=u"€")
31 ipdir = tempfile.mkdtemp(suffix=u"€")
32
32
33 # Create the config file, so it tries to load it.
33 # Create the config file, so it tries to load it.
34 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
34 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
35 pass
35 pass
36
36
37 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
37 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
38 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
38 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
39 os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8")
39 os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8")
40 try:
40 try:
41 app = BaseIPythonApplication()
41 app = BaseIPythonApplication()
42 # The lines below are copied from Application.initialize()
42 # The lines below are copied from Application.initialize()
43 app.init_profile_dir()
43 app.init_profile_dir()
44 app.init_config_files()
44 app.init_config_files()
45 app.load_config_file(suppress_errors=False)
45 app.load_config_file(suppress_errors=False)
46 finally:
46 finally:
47 if old_ipdir1:
47 if old_ipdir1:
48 os.environ["IPYTHONDIR"] = old_ipdir1
48 os.environ["IPYTHONDIR"] = old_ipdir1
49 if old_ipdir2:
49 if old_ipdir2:
50 os.environ["IPYTHONDIR"] = old_ipdir2
50 os.environ["IPYTHONDIR"] = old_ipdir2
@@ -1,393 +1,394 b''
1 """Tests for the IPython tab-completion machinery.
1 """Tests for the IPython tab-completion machinery.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Module imports
4 # Module imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # stdlib
7 # stdlib
8 import os
8 import os
9 import sys
9 import sys
10 import unittest
10 import unittest
11
11
12 # third party
12 # third party
13 import nose.tools as nt
13 import nose.tools as nt
14
14
15 # our own packages
15 # our own packages
16 from IPython.config.loader import Config
16 from IPython.config.loader import Config
17 from IPython.core import completer
17 from IPython.core import completer
18 from IPython.external.decorators import knownfailureif
18 from IPython.external.decorators import knownfailureif
19 from IPython.utils.tempdir import TemporaryDirectory
19 from IPython.utils.tempdir import TemporaryDirectory
20 from IPython.utils.generics import complete_object
20 from IPython.utils.generics import complete_object
21 from IPython.utils import py3compat
21 from IPython.utils.py3compat import string_types, unicode_type
22 from IPython.utils.py3compat import string_types, unicode_type
22
23
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
24 # Test functions
25 # Test functions
25 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
26 def test_protect_filename():
27 def test_protect_filename():
27 pairs = [ ('abc','abc'),
28 pairs = [ ('abc','abc'),
28 (' abc',r'\ abc'),
29 (' abc',r'\ abc'),
29 ('a bc',r'a\ bc'),
30 ('a bc',r'a\ bc'),
30 ('a bc',r'a\ \ bc'),
31 ('a bc',r'a\ \ bc'),
31 (' bc',r'\ \ bc'),
32 (' bc',r'\ \ bc'),
32 ]
33 ]
33 # On posix, we also protect parens and other special characters
34 # On posix, we also protect parens and other special characters
34 if sys.platform != 'win32':
35 if sys.platform != 'win32':
35 pairs.extend( [('a(bc',r'a\(bc'),
36 pairs.extend( [('a(bc',r'a\(bc'),
36 ('a)bc',r'a\)bc'),
37 ('a)bc',r'a\)bc'),
37 ('a( )bc',r'a\(\ \)bc'),
38 ('a( )bc',r'a\(\ \)bc'),
38 ('a[1]bc', r'a\[1\]bc'),
39 ('a[1]bc', r'a\[1\]bc'),
39 ('a{1}bc', r'a\{1\}bc'),
40 ('a{1}bc', r'a\{1\}bc'),
40 ('a#bc', r'a\#bc'),
41 ('a#bc', r'a\#bc'),
41 ('a?bc', r'a\?bc'),
42 ('a?bc', r'a\?bc'),
42 ('a=bc', r'a\=bc'),
43 ('a=bc', r'a\=bc'),
43 ('a\\bc', r'a\\bc'),
44 ('a\\bc', r'a\\bc'),
44 ('a|bc', r'a\|bc'),
45 ('a|bc', r'a\|bc'),
45 ('a;bc', r'a\;bc'),
46 ('a;bc', r'a\;bc'),
46 ('a:bc', r'a\:bc'),
47 ('a:bc', r'a\:bc'),
47 ("a'bc", r"a\'bc"),
48 ("a'bc", r"a\'bc"),
48 ('a*bc', r'a\*bc'),
49 ('a*bc', r'a\*bc'),
49 ('a"bc', r'a\"bc'),
50 ('a"bc', r'a\"bc'),
50 ('a^bc', r'a\^bc'),
51 ('a^bc', r'a\^bc'),
51 ('a&bc', r'a\&bc'),
52 ('a&bc', r'a\&bc'),
52 ] )
53 ] )
53 # run the actual tests
54 # run the actual tests
54 for s1, s2 in pairs:
55 for s1, s2 in pairs:
55 s1p = completer.protect_filename(s1)
56 s1p = completer.protect_filename(s1)
56 nt.assert_equal(s1p, s2)
57 nt.assert_equal(s1p, s2)
57
58
58
59
59 def check_line_split(splitter, test_specs):
60 def check_line_split(splitter, test_specs):
60 for part1, part2, split in test_specs:
61 for part1, part2, split in test_specs:
61 cursor_pos = len(part1)
62 cursor_pos = len(part1)
62 line = part1+part2
63 line = part1+part2
63 out = splitter.split_line(line, cursor_pos)
64 out = splitter.split_line(line, cursor_pos)
64 nt.assert_equal(out, split)
65 nt.assert_equal(out, split)
65
66
66
67
67 def test_line_split():
68 def test_line_split():
68 """Basic line splitter test with default specs."""
69 """Basic line splitter test with default specs."""
69 sp = completer.CompletionSplitter()
70 sp = completer.CompletionSplitter()
70 # The format of the test specs is: part1, part2, expected answer. Parts 1
71 # The format of the test specs is: part1, part2, expected answer. Parts 1
71 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
72 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
72 # was at the end of part1. So an empty part2 represents someone hitting
73 # was at the end of part1. So an empty part2 represents someone hitting
73 # tab at the end of the line, the most common case.
74 # tab at the end of the line, the most common case.
74 t = [('run some/scrip', '', 'some/scrip'),
75 t = [('run some/scrip', '', 'some/scrip'),
75 ('run scripts/er', 'ror.py foo', 'scripts/er'),
76 ('run scripts/er', 'ror.py foo', 'scripts/er'),
76 ('echo $HOM', '', 'HOM'),
77 ('echo $HOM', '', 'HOM'),
77 ('print sys.pa', '', 'sys.pa'),
78 ('print sys.pa', '', 'sys.pa'),
78 ('print(sys.pa', '', 'sys.pa'),
79 ('print(sys.pa', '', 'sys.pa'),
79 ("execfile('scripts/er", '', 'scripts/er'),
80 ("execfile('scripts/er", '', 'scripts/er'),
80 ('a[x.', '', 'x.'),
81 ('a[x.', '', 'x.'),
81 ('a[x.', 'y', 'x.'),
82 ('a[x.', 'y', 'x.'),
82 ('cd "some_file/', '', 'some_file/'),
83 ('cd "some_file/', '', 'some_file/'),
83 ]
84 ]
84 check_line_split(sp, t)
85 check_line_split(sp, t)
85 # Ensure splitting works OK with unicode by re-running the tests with
86 # Ensure splitting works OK with unicode by re-running the tests with
86 # all inputs turned into unicode
87 # all inputs turned into unicode
87 check_line_split(sp, [ map(unicode_type, p) for p in t] )
88 check_line_split(sp, [ map(unicode_type, p) for p in t] )
88
89
89
90
90 def test_custom_completion_error():
91 def test_custom_completion_error():
91 """Test that errors from custom attribute completers are silenced."""
92 """Test that errors from custom attribute completers are silenced."""
92 ip = get_ipython()
93 ip = get_ipython()
93 class A(object): pass
94 class A(object): pass
94 ip.user_ns['a'] = A()
95 ip.user_ns['a'] = A()
95
96
96 @complete_object.when_type(A)
97 @complete_object.when_type(A)
97 def complete_A(a, existing_completions):
98 def complete_A(a, existing_completions):
98 raise TypeError("this should be silenced")
99 raise TypeError("this should be silenced")
99
100
100 ip.complete("a.")
101 ip.complete("a.")
101
102
102
103
103 def test_unicode_completions():
104 def test_unicode_completions():
104 ip = get_ipython()
105 ip = get_ipython()
105 # Some strings that trigger different types of completion. Check them both
106 # Some strings that trigger different types of completion. Check them both
106 # in str and unicode forms
107 # in str and unicode forms
107 s = ['ru', '%ru', 'cd /', 'floa', 'float(x)/']
108 s = ['ru', '%ru', 'cd /', 'floa', 'float(x)/']
108 for t in s + list(map(unicode_type, s)):
109 for t in s + list(map(unicode_type, s)):
109 # We don't need to check exact completion values (they may change
110 # We don't need to check exact completion values (they may change
110 # depending on the state of the namespace, but at least no exceptions
111 # depending on the state of the namespace, but at least no exceptions
111 # should be thrown and the return value should be a pair of text, list
112 # should be thrown and the return value should be a pair of text, list
112 # values.
113 # values.
113 text, matches = ip.complete(t)
114 text, matches = ip.complete(t)
114 nt.assert_true(isinstance(text, string_types))
115 nt.assert_true(isinstance(text, string_types))
115 nt.assert_true(isinstance(matches, list))
116 nt.assert_true(isinstance(matches, list))
116
117
117
118
118 class CompletionSplitterTestCase(unittest.TestCase):
119 class CompletionSplitterTestCase(unittest.TestCase):
119 def setUp(self):
120 def setUp(self):
120 self.sp = completer.CompletionSplitter()
121 self.sp = completer.CompletionSplitter()
121
122
122 def test_delim_setting(self):
123 def test_delim_setting(self):
123 self.sp.delims = ' '
124 self.sp.delims = ' '
124 nt.assert_equal(self.sp.delims, ' ')
125 nt.assert_equal(self.sp.delims, ' ')
125 nt.assert_equal(self.sp._delim_expr, '[\ ]')
126 nt.assert_equal(self.sp._delim_expr, '[\ ]')
126
127
127 def test_spaces(self):
128 def test_spaces(self):
128 """Test with only spaces as split chars."""
129 """Test with only spaces as split chars."""
129 self.sp.delims = ' '
130 self.sp.delims = ' '
130 t = [('foo', '', 'foo'),
131 t = [('foo', '', 'foo'),
131 ('run foo', '', 'foo'),
132 ('run foo', '', 'foo'),
132 ('run foo', 'bar', 'foo'),
133 ('run foo', 'bar', 'foo'),
133 ]
134 ]
134 check_line_split(self.sp, t)
135 check_line_split(self.sp, t)
135
136
136
137
137 def test_has_open_quotes1():
138 def test_has_open_quotes1():
138 for s in ["'", "'''", "'hi' '"]:
139 for s in ["'", "'''", "'hi' '"]:
139 nt.assert_equal(completer.has_open_quotes(s), "'")
140 nt.assert_equal(completer.has_open_quotes(s), "'")
140
141
141
142
142 def test_has_open_quotes2():
143 def test_has_open_quotes2():
143 for s in ['"', '"""', '"hi" "']:
144 for s in ['"', '"""', '"hi" "']:
144 nt.assert_equal(completer.has_open_quotes(s), '"')
145 nt.assert_equal(completer.has_open_quotes(s), '"')
145
146
146
147
147 def test_has_open_quotes3():
148 def test_has_open_quotes3():
148 for s in ["''", "''' '''", "'hi' 'ipython'"]:
149 for s in ["''", "''' '''", "'hi' 'ipython'"]:
149 nt.assert_false(completer.has_open_quotes(s))
150 nt.assert_false(completer.has_open_quotes(s))
150
151
151
152
152 def test_has_open_quotes4():
153 def test_has_open_quotes4():
153 for s in ['""', '""" """', '"hi" "ipython"']:
154 for s in ['""', '""" """', '"hi" "ipython"']:
154 nt.assert_false(completer.has_open_quotes(s))
155 nt.assert_false(completer.has_open_quotes(s))
155
156
156
157
157 @knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows")
158 @knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows")
158 def test_abspath_file_completions():
159 def test_abspath_file_completions():
159 ip = get_ipython()
160 ip = get_ipython()
160 with TemporaryDirectory() as tmpdir:
161 with TemporaryDirectory() as tmpdir:
161 prefix = os.path.join(tmpdir, 'foo')
162 prefix = os.path.join(tmpdir, 'foo')
162 suffixes = ['1', '2']
163 suffixes = ['1', '2']
163 names = [prefix+s for s in suffixes]
164 names = [prefix+s for s in suffixes]
164 for n in names:
165 for n in names:
165 open(n, 'w').close()
166 open(n, 'w').close()
166
167
167 # Check simple completion
168 # Check simple completion
168 c = ip.complete(prefix)[1]
169 c = ip.complete(prefix)[1]
169 nt.assert_equal(c, names)
170 nt.assert_equal(c, names)
170
171
171 # Now check with a function call
172 # Now check with a function call
172 cmd = 'a = f("%s' % prefix
173 cmd = 'a = f("%s' % prefix
173 c = ip.complete(prefix, cmd)[1]
174 c = ip.complete(prefix, cmd)[1]
174 comp = [prefix+s for s in suffixes]
175 comp = [prefix+s for s in suffixes]
175 nt.assert_equal(c, comp)
176 nt.assert_equal(c, comp)
176
177
177
178
178 def test_local_file_completions():
179 def test_local_file_completions():
179 ip = get_ipython()
180 ip = get_ipython()
180 cwd = os.getcwdu()
181 cwd = py3compat.getcwd()
181 try:
182 try:
182 with TemporaryDirectory() as tmpdir:
183 with TemporaryDirectory() as tmpdir:
183 os.chdir(tmpdir)
184 os.chdir(tmpdir)
184 prefix = './foo'
185 prefix = './foo'
185 suffixes = ['1', '2']
186 suffixes = ['1', '2']
186 names = [prefix+s for s in suffixes]
187 names = [prefix+s for s in suffixes]
187 for n in names:
188 for n in names:
188 open(n, 'w').close()
189 open(n, 'w').close()
189
190
190 # Check simple completion
191 # Check simple completion
191 c = ip.complete(prefix)[1]
192 c = ip.complete(prefix)[1]
192 nt.assert_equal(c, names)
193 nt.assert_equal(c, names)
193
194
194 # Now check with a function call
195 # Now check with a function call
195 cmd = 'a = f("%s' % prefix
196 cmd = 'a = f("%s' % prefix
196 c = ip.complete(prefix, cmd)[1]
197 c = ip.complete(prefix, cmd)[1]
197 comp = [prefix+s for s in suffixes]
198 comp = [prefix+s for s in suffixes]
198 nt.assert_equal(c, comp)
199 nt.assert_equal(c, comp)
199 finally:
200 finally:
200 # prevent failures from making chdir stick
201 # prevent failures from making chdir stick
201 os.chdir(cwd)
202 os.chdir(cwd)
202
203
203
204
204 def test_greedy_completions():
205 def test_greedy_completions():
205 ip = get_ipython()
206 ip = get_ipython()
206 greedy_original = ip.Completer.greedy
207 greedy_original = ip.Completer.greedy
207 try:
208 try:
208 ip.Completer.greedy = False
209 ip.Completer.greedy = False
209 ip.ex('a=list(range(5))')
210 ip.ex('a=list(range(5))')
210 _,c = ip.complete('.',line='a[0].')
211 _,c = ip.complete('.',line='a[0].')
211 nt.assert_false('a[0].real' in c,
212 nt.assert_false('a[0].real' in c,
212 "Shouldn't have completed on a[0]: %s"%c)
213 "Shouldn't have completed on a[0]: %s"%c)
213 ip.Completer.greedy = True
214 ip.Completer.greedy = True
214 _,c = ip.complete('.',line='a[0].')
215 _,c = ip.complete('.',line='a[0].')
215 nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c)
216 nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c)
216 finally:
217 finally:
217 ip.Completer.greedy = greedy_original
218 ip.Completer.greedy = greedy_original
218
219
219
220
220 def test_omit__names():
221 def test_omit__names():
221 # also happens to test IPCompleter as a configurable
222 # also happens to test IPCompleter as a configurable
222 ip = get_ipython()
223 ip = get_ipython()
223 ip._hidden_attr = 1
224 ip._hidden_attr = 1
224 c = ip.Completer
225 c = ip.Completer
225 ip.ex('ip=get_ipython()')
226 ip.ex('ip=get_ipython()')
226 cfg = Config()
227 cfg = Config()
227 cfg.IPCompleter.omit__names = 0
228 cfg.IPCompleter.omit__names = 0
228 c.update_config(cfg)
229 c.update_config(cfg)
229 s,matches = c.complete('ip.')
230 s,matches = c.complete('ip.')
230 nt.assert_in('ip.__str__', matches)
231 nt.assert_in('ip.__str__', matches)
231 nt.assert_in('ip._hidden_attr', matches)
232 nt.assert_in('ip._hidden_attr', matches)
232 cfg.IPCompleter.omit__names = 1
233 cfg.IPCompleter.omit__names = 1
233 c.update_config(cfg)
234 c.update_config(cfg)
234 s,matches = c.complete('ip.')
235 s,matches = c.complete('ip.')
235 nt.assert_not_in('ip.__str__', matches)
236 nt.assert_not_in('ip.__str__', matches)
236 nt.assert_in('ip._hidden_attr', matches)
237 nt.assert_in('ip._hidden_attr', matches)
237 cfg.IPCompleter.omit__names = 2
238 cfg.IPCompleter.omit__names = 2
238 c.update_config(cfg)
239 c.update_config(cfg)
239 s,matches = c.complete('ip.')
240 s,matches = c.complete('ip.')
240 nt.assert_not_in('ip.__str__', matches)
241 nt.assert_not_in('ip.__str__', matches)
241 nt.assert_not_in('ip._hidden_attr', matches)
242 nt.assert_not_in('ip._hidden_attr', matches)
242 del ip._hidden_attr
243 del ip._hidden_attr
243
244
244
245
245 def test_limit_to__all__False_ok():
246 def test_limit_to__all__False_ok():
246 ip = get_ipython()
247 ip = get_ipython()
247 c = ip.Completer
248 c = ip.Completer
248 ip.ex('class D: x=24')
249 ip.ex('class D: x=24')
249 ip.ex('d=D()')
250 ip.ex('d=D()')
250 cfg = Config()
251 cfg = Config()
251 cfg.IPCompleter.limit_to__all__ = False
252 cfg.IPCompleter.limit_to__all__ = False
252 c.update_config(cfg)
253 c.update_config(cfg)
253 s, matches = c.complete('d.')
254 s, matches = c.complete('d.')
254 nt.assert_in('d.x', matches)
255 nt.assert_in('d.x', matches)
255
256
256
257
257 def test_limit_to__all__True_ok():
258 def test_limit_to__all__True_ok():
258 ip = get_ipython()
259 ip = get_ipython()
259 c = ip.Completer
260 c = ip.Completer
260 ip.ex('class D: x=24')
261 ip.ex('class D: x=24')
261 ip.ex('d=D()')
262 ip.ex('d=D()')
262 ip.ex("d.__all__=['z']")
263 ip.ex("d.__all__=['z']")
263 cfg = Config()
264 cfg = Config()
264 cfg.IPCompleter.limit_to__all__ = True
265 cfg.IPCompleter.limit_to__all__ = True
265 c.update_config(cfg)
266 c.update_config(cfg)
266 s, matches = c.complete('d.')
267 s, matches = c.complete('d.')
267 nt.assert_in('d.z', matches)
268 nt.assert_in('d.z', matches)
268 nt.assert_not_in('d.x', matches)
269 nt.assert_not_in('d.x', matches)
269
270
270
271
271 def test_get__all__entries_ok():
272 def test_get__all__entries_ok():
272 class A(object):
273 class A(object):
273 __all__ = ['x', 1]
274 __all__ = ['x', 1]
274 words = completer.get__all__entries(A())
275 words = completer.get__all__entries(A())
275 nt.assert_equal(words, ['x'])
276 nt.assert_equal(words, ['x'])
276
277
277
278
278 def test_get__all__entries_no__all__ok():
279 def test_get__all__entries_no__all__ok():
279 class A(object):
280 class A(object):
280 pass
281 pass
281 words = completer.get__all__entries(A())
282 words = completer.get__all__entries(A())
282 nt.assert_equal(words, [])
283 nt.assert_equal(words, [])
283
284
284
285
285 def test_func_kw_completions():
286 def test_func_kw_completions():
286 ip = get_ipython()
287 ip = get_ipython()
287 c = ip.Completer
288 c = ip.Completer
288 ip.ex('def myfunc(a=1,b=2): return a+b')
289 ip.ex('def myfunc(a=1,b=2): return a+b')
289 s, matches = c.complete(None, 'myfunc(1,b')
290 s, matches = c.complete(None, 'myfunc(1,b')
290 nt.assert_in('b=', matches)
291 nt.assert_in('b=', matches)
291 # Simulate completing with cursor right after b (pos==10):
292 # Simulate completing with cursor right after b (pos==10):
292 s, matches = c.complete(None, 'myfunc(1,b)', 10)
293 s, matches = c.complete(None, 'myfunc(1,b)', 10)
293 nt.assert_in('b=', matches)
294 nt.assert_in('b=', matches)
294 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
295 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
295 nt.assert_in('b=', matches)
296 nt.assert_in('b=', matches)
296 #builtin function
297 #builtin function
297 s, matches = c.complete(None, 'min(k, k')
298 s, matches = c.complete(None, 'min(k, k')
298 nt.assert_in('key=', matches)
299 nt.assert_in('key=', matches)
299
300
300
301
301 def test_default_arguments_from_docstring():
302 def test_default_arguments_from_docstring():
302 doc = min.__doc__
303 doc = min.__doc__
303 ip = get_ipython()
304 ip = get_ipython()
304 c = ip.Completer
305 c = ip.Completer
305 kwd = c._default_arguments_from_docstring(
306 kwd = c._default_arguments_from_docstring(
306 'min(iterable[, key=func]) -> value')
307 'min(iterable[, key=func]) -> value')
307 nt.assert_equal(kwd, ['key'])
308 nt.assert_equal(kwd, ['key'])
308 #with cython type etc
309 #with cython type etc
309 kwd = c._default_arguments_from_docstring(
310 kwd = c._default_arguments_from_docstring(
310 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n')
311 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n')
311 nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit'])
312 nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit'])
312 #white spaces
313 #white spaces
313 kwd = c._default_arguments_from_docstring(
314 kwd = c._default_arguments_from_docstring(
314 '\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n')
315 '\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n')
315 nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit'])
316 nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit'])
316
317
317 def test_line_magics():
318 def test_line_magics():
318 ip = get_ipython()
319 ip = get_ipython()
319 c = ip.Completer
320 c = ip.Completer
320 s, matches = c.complete(None, 'lsmag')
321 s, matches = c.complete(None, 'lsmag')
321 nt.assert_in('%lsmagic', matches)
322 nt.assert_in('%lsmagic', matches)
322 s, matches = c.complete(None, '%lsmag')
323 s, matches = c.complete(None, '%lsmag')
323 nt.assert_in('%lsmagic', matches)
324 nt.assert_in('%lsmagic', matches)
324
325
325
326
326 def test_cell_magics():
327 def test_cell_magics():
327 from IPython.core.magic import register_cell_magic
328 from IPython.core.magic import register_cell_magic
328
329
329 @register_cell_magic
330 @register_cell_magic
330 def _foo_cellm(line, cell):
331 def _foo_cellm(line, cell):
331 pass
332 pass
332
333
333 ip = get_ipython()
334 ip = get_ipython()
334 c = ip.Completer
335 c = ip.Completer
335
336
336 s, matches = c.complete(None, '_foo_ce')
337 s, matches = c.complete(None, '_foo_ce')
337 nt.assert_in('%%_foo_cellm', matches)
338 nt.assert_in('%%_foo_cellm', matches)
338 s, matches = c.complete(None, '%%_foo_ce')
339 s, matches = c.complete(None, '%%_foo_ce')
339 nt.assert_in('%%_foo_cellm', matches)
340 nt.assert_in('%%_foo_cellm', matches)
340
341
341
342
342 def test_line_cell_magics():
343 def test_line_cell_magics():
343 from IPython.core.magic import register_line_cell_magic
344 from IPython.core.magic import register_line_cell_magic
344
345
345 @register_line_cell_magic
346 @register_line_cell_magic
346 def _bar_cellm(line, cell):
347 def _bar_cellm(line, cell):
347 pass
348 pass
348
349
349 ip = get_ipython()
350 ip = get_ipython()
350 c = ip.Completer
351 c = ip.Completer
351
352
352 # The policy here is trickier, see comments in completion code. The
353 # The policy here is trickier, see comments in completion code. The
353 # returned values depend on whether the user passes %% or not explicitly,
354 # returned values depend on whether the user passes %% or not explicitly,
354 # and this will show a difference if the same name is both a line and cell
355 # and this will show a difference if the same name is both a line and cell
355 # magic.
356 # magic.
356 s, matches = c.complete(None, '_bar_ce')
357 s, matches = c.complete(None, '_bar_ce')
357 nt.assert_in('%_bar_cellm', matches)
358 nt.assert_in('%_bar_cellm', matches)
358 nt.assert_in('%%_bar_cellm', matches)
359 nt.assert_in('%%_bar_cellm', matches)
359 s, matches = c.complete(None, '%_bar_ce')
360 s, matches = c.complete(None, '%_bar_ce')
360 nt.assert_in('%_bar_cellm', matches)
361 nt.assert_in('%_bar_cellm', matches)
361 nt.assert_in('%%_bar_cellm', matches)
362 nt.assert_in('%%_bar_cellm', matches)
362 s, matches = c.complete(None, '%%_bar_ce')
363 s, matches = c.complete(None, '%%_bar_ce')
363 nt.assert_not_in('%_bar_cellm', matches)
364 nt.assert_not_in('%_bar_cellm', matches)
364 nt.assert_in('%%_bar_cellm', matches)
365 nt.assert_in('%%_bar_cellm', matches)
365
366
366
367
367 def test_magic_completion_order():
368 def test_magic_completion_order():
368
369
369 ip = get_ipython()
370 ip = get_ipython()
370 c = ip.Completer
371 c = ip.Completer
371
372
372 # Test ordering of magics and non-magics with the same name
373 # Test ordering of magics and non-magics with the same name
373 # We want the non-magic first
374 # We want the non-magic first
374
375
375 # Before importing matplotlib, there should only be one option:
376 # Before importing matplotlib, there should only be one option:
376
377
377 text, matches = c.complete('mat')
378 text, matches = c.complete('mat')
378 nt.assert_equal(matches, ["%matplotlib"])
379 nt.assert_equal(matches, ["%matplotlib"])
379
380
380
381
381 ip.run_cell("matplotlib = 1") # introduce name into namespace
382 ip.run_cell("matplotlib = 1") # introduce name into namespace
382
383
383 # After the import, there should be two options, ordered like this:
384 # After the import, there should be two options, ordered like this:
384 text, matches = c.complete('mat')
385 text, matches = c.complete('mat')
385 nt.assert_equal(matches, ["matplotlib", "%matplotlib"])
386 nt.assert_equal(matches, ["matplotlib", "%matplotlib"])
386
387
387
388
388 ip.run_cell("timeit = 1") # define a user variable called 'timeit'
389 ip.run_cell("timeit = 1") # define a user variable called 'timeit'
389
390
390 # Order of user variable and line and cell magics with same name:
391 # Order of user variable and line and cell magics with same name:
391 text, matches = c.complete('timeit')
392 text, matches = c.complete('timeit')
392 nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"])
393 nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"])
393
394
@@ -1,120 +1,121 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for completerlib.
2 """Tests for completerlib.
3
3
4 """
4 """
5 from __future__ import absolute_import
5 from __future__ import absolute_import
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Imports
8 # Imports
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 import os
11 import os
12 import shutil
12 import shutil
13 import sys
13 import sys
14 import tempfile
14 import tempfile
15 import unittest
15 import unittest
16 from os.path import join
16 from os.path import join
17
17
18 from IPython.core.completerlib import magic_run_completer, module_completion
18 from IPython.core.completerlib import magic_run_completer, module_completion
19 from IPython.utils import py3compat
19 from IPython.utils.tempdir import TemporaryDirectory
20 from IPython.utils.tempdir import TemporaryDirectory
20 from IPython.testing.decorators import onlyif_unicode_paths
21 from IPython.testing.decorators import onlyif_unicode_paths
21
22
22
23
23 class MockEvent(object):
24 class MockEvent(object):
24 def __init__(self, line):
25 def __init__(self, line):
25 self.line = line
26 self.line = line
26
27
27 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
28 # Test functions begin
29 # Test functions begin
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30 class Test_magic_run_completer(unittest.TestCase):
31 class Test_magic_run_completer(unittest.TestCase):
31 def setUp(self):
32 def setUp(self):
32 self.BASETESTDIR = tempfile.mkdtemp()
33 self.BASETESTDIR = tempfile.mkdtemp()
33 for fil in [u"aao.py", u"a.py", u"b.py"]:
34 for fil in [u"aao.py", u"a.py", u"b.py"]:
34 with open(join(self.BASETESTDIR, fil), "w") as sfile:
35 with open(join(self.BASETESTDIR, fil), "w") as sfile:
35 sfile.write("pass\n")
36 sfile.write("pass\n")
36 self.oldpath = os.getcwdu()
37 self.oldpath = py3compat.getcwd()
37 os.chdir(self.BASETESTDIR)
38 os.chdir(self.BASETESTDIR)
38
39
39 def tearDown(self):
40 def tearDown(self):
40 os.chdir(self.oldpath)
41 os.chdir(self.oldpath)
41 shutil.rmtree(self.BASETESTDIR)
42 shutil.rmtree(self.BASETESTDIR)
42
43
43 def test_1(self):
44 def test_1(self):
44 """Test magic_run_completer, should match two alterntives
45 """Test magic_run_completer, should match two alterntives
45 """
46 """
46 event = MockEvent(u"%run a")
47 event = MockEvent(u"%run a")
47 mockself = None
48 mockself = None
48 match = set(magic_run_completer(mockself, event))
49 match = set(magic_run_completer(mockself, event))
49 self.assertEqual(match, set([u"a.py", u"aao.py"]))
50 self.assertEqual(match, set([u"a.py", u"aao.py"]))
50
51
51 def test_2(self):
52 def test_2(self):
52 """Test magic_run_completer, should match one alterntive
53 """Test magic_run_completer, should match one alterntive
53 """
54 """
54 event = MockEvent(u"%run aa")
55 event = MockEvent(u"%run aa")
55 mockself = None
56 mockself = None
56 match = set(magic_run_completer(mockself, event))
57 match = set(magic_run_completer(mockself, event))
57 self.assertEqual(match, set([u"aao.py"]))
58 self.assertEqual(match, set([u"aao.py"]))
58
59
59 def test_3(self):
60 def test_3(self):
60 """Test magic_run_completer with unterminated " """
61 """Test magic_run_completer with unterminated " """
61 event = MockEvent(u'%run "a')
62 event = MockEvent(u'%run "a')
62 mockself = None
63 mockself = None
63 match = set(magic_run_completer(mockself, event))
64 match = set(magic_run_completer(mockself, event))
64 self.assertEqual(match, set([u"a.py", u"aao.py"]))
65 self.assertEqual(match, set([u"a.py", u"aao.py"]))
65
66
66 def test_import_invalid_module(self):
67 def test_import_invalid_module(self):
67 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
68 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
68 invalid_module_names = set(['foo-bar', 'foo:bar', '10foo'])
69 invalid_module_names = set(['foo-bar', 'foo:bar', '10foo'])
69 valid_module_names = set(['foobar'])
70 valid_module_names = set(['foobar'])
70 with TemporaryDirectory() as tmpdir:
71 with TemporaryDirectory() as tmpdir:
71 sys.path.insert( 0, tmpdir )
72 sys.path.insert( 0, tmpdir )
72 for name in invalid_module_names | valid_module_names:
73 for name in invalid_module_names | valid_module_names:
73 filename = os.path.join(tmpdir, name + '.py')
74 filename = os.path.join(tmpdir, name + '.py')
74 open(filename, 'w').close()
75 open(filename, 'w').close()
75
76
76 s = set( module_completion('import foo') )
77 s = set( module_completion('import foo') )
77 intersection = s.intersection(invalid_module_names)
78 intersection = s.intersection(invalid_module_names)
78 self.assertFalse(intersection, intersection)
79 self.assertFalse(intersection, intersection)
79
80
80 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
81 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
81
82
82 class Test_magic_run_completer_nonascii(unittest.TestCase):
83 class Test_magic_run_completer_nonascii(unittest.TestCase):
83 @onlyif_unicode_paths
84 @onlyif_unicode_paths
84 def setUp(self):
85 def setUp(self):
85 self.BASETESTDIR = tempfile.mkdtemp()
86 self.BASETESTDIR = tempfile.mkdtemp()
86 for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]:
87 for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]:
87 with open(join(self.BASETESTDIR, fil), "w") as sfile:
88 with open(join(self.BASETESTDIR, fil), "w") as sfile:
88 sfile.write("pass\n")
89 sfile.write("pass\n")
89 self.oldpath = os.getcwdu()
90 self.oldpath = py3compat.getcwd()
90 os.chdir(self.BASETESTDIR)
91 os.chdir(self.BASETESTDIR)
91
92
92 def tearDown(self):
93 def tearDown(self):
93 os.chdir(self.oldpath)
94 os.chdir(self.oldpath)
94 shutil.rmtree(self.BASETESTDIR)
95 shutil.rmtree(self.BASETESTDIR)
95
96
96 @onlyif_unicode_paths
97 @onlyif_unicode_paths
97 def test_1(self):
98 def test_1(self):
98 """Test magic_run_completer, should match two alterntives
99 """Test magic_run_completer, should match two alterntives
99 """
100 """
100 event = MockEvent(u"%run a")
101 event = MockEvent(u"%run a")
101 mockself = None
102 mockself = None
102 match = set(magic_run_completer(mockself, event))
103 match = set(magic_run_completer(mockself, event))
103 self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"]))
104 self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"]))
104
105
105 @onlyif_unicode_paths
106 @onlyif_unicode_paths
106 def test_2(self):
107 def test_2(self):
107 """Test magic_run_completer, should match one alterntive
108 """Test magic_run_completer, should match one alterntive
108 """
109 """
109 event = MockEvent(u"%run aa")
110 event = MockEvent(u"%run aa")
110 mockself = None
111 mockself = None
111 match = set(magic_run_completer(mockself, event))
112 match = set(magic_run_completer(mockself, event))
112 self.assertEqual(match, set([u"aaΓΈ.py"]))
113 self.assertEqual(match, set([u"aaΓΈ.py"]))
113
114
114 @onlyif_unicode_paths
115 @onlyif_unicode_paths
115 def test_3(self):
116 def test_3(self):
116 """Test magic_run_completer with unterminated " """
117 """Test magic_run_completer with unterminated " """
117 event = MockEvent(u'%run "a')
118 event = MockEvent(u'%run "a')
118 mockself = None
119 mockself = None
119 match = set(magic_run_completer(mockself, event))
120 match = set(magic_run_completer(mockself, event))
120 self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"]))
121 self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"]))
@@ -1,676 +1,677 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7
7
8 Authors
8 Authors
9 -------
9 -------
10 * Fernando Perez
10 * Fernando Perez
11 """
11 """
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2011 The IPython Development Team
13 # Copyright (C) 2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # stdlib
22 # stdlib
23 import ast
23 import ast
24 import os
24 import os
25 import signal
25 import signal
26 import shutil
26 import shutil
27 import sys
27 import sys
28 import tempfile
28 import tempfile
29 import unittest
29 import unittest
30 from os.path import join
30 from os.path import join
31
31
32 # third-party
32 # third-party
33 import nose.tools as nt
33 import nose.tools as nt
34
34
35 # Our own
35 # Our own
36 from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths
36 from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths
37 from IPython.testing import tools as tt
37 from IPython.testing import tools as tt
38 from IPython.utils import io
38 from IPython.utils import io
39 from IPython.utils import py3compat
39 from IPython.utils.py3compat import unicode_type, PY3
40 from IPython.utils.py3compat import unicode_type, PY3
40
41
41 if PY3:
42 if PY3:
42 from io import StringIO
43 from io import StringIO
43 else:
44 else:
44 from StringIO import StringIO
45 from StringIO import StringIO
45
46
46 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
47 # Globals
48 # Globals
48 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
49 # This is used by every single test, no point repeating it ad nauseam
50 # This is used by every single test, no point repeating it ad nauseam
50 ip = get_ipython()
51 ip = get_ipython()
51
52
52 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
53 # Tests
54 # Tests
54 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
55
56
56 class InteractiveShellTestCase(unittest.TestCase):
57 class InteractiveShellTestCase(unittest.TestCase):
57 def test_naked_string_cells(self):
58 def test_naked_string_cells(self):
58 """Test that cells with only naked strings are fully executed"""
59 """Test that cells with only naked strings are fully executed"""
59 # First, single-line inputs
60 # First, single-line inputs
60 ip.run_cell('"a"\n')
61 ip.run_cell('"a"\n')
61 self.assertEqual(ip.user_ns['_'], 'a')
62 self.assertEqual(ip.user_ns['_'], 'a')
62 # And also multi-line cells
63 # And also multi-line cells
63 ip.run_cell('"""a\nb"""\n')
64 ip.run_cell('"""a\nb"""\n')
64 self.assertEqual(ip.user_ns['_'], 'a\nb')
65 self.assertEqual(ip.user_ns['_'], 'a\nb')
65
66
66 def test_run_empty_cell(self):
67 def test_run_empty_cell(self):
67 """Just make sure we don't get a horrible error with a blank
68 """Just make sure we don't get a horrible error with a blank
68 cell of input. Yes, I did overlook that."""
69 cell of input. Yes, I did overlook that."""
69 old_xc = ip.execution_count
70 old_xc = ip.execution_count
70 ip.run_cell('')
71 ip.run_cell('')
71 self.assertEqual(ip.execution_count, old_xc)
72 self.assertEqual(ip.execution_count, old_xc)
72
73
73 def test_run_cell_multiline(self):
74 def test_run_cell_multiline(self):
74 """Multi-block, multi-line cells must execute correctly.
75 """Multi-block, multi-line cells must execute correctly.
75 """
76 """
76 src = '\n'.join(["x=1",
77 src = '\n'.join(["x=1",
77 "y=2",
78 "y=2",
78 "if 1:",
79 "if 1:",
79 " x += 1",
80 " x += 1",
80 " y += 1",])
81 " y += 1",])
81 ip.run_cell(src)
82 ip.run_cell(src)
82 self.assertEqual(ip.user_ns['x'], 2)
83 self.assertEqual(ip.user_ns['x'], 2)
83 self.assertEqual(ip.user_ns['y'], 3)
84 self.assertEqual(ip.user_ns['y'], 3)
84
85
85 def test_multiline_string_cells(self):
86 def test_multiline_string_cells(self):
86 "Code sprinkled with multiline strings should execute (GH-306)"
87 "Code sprinkled with multiline strings should execute (GH-306)"
87 ip.run_cell('tmp=0')
88 ip.run_cell('tmp=0')
88 self.assertEqual(ip.user_ns['tmp'], 0)
89 self.assertEqual(ip.user_ns['tmp'], 0)
89 ip.run_cell('tmp=1;"""a\nb"""\n')
90 ip.run_cell('tmp=1;"""a\nb"""\n')
90 self.assertEqual(ip.user_ns['tmp'], 1)
91 self.assertEqual(ip.user_ns['tmp'], 1)
91
92
92 def test_dont_cache_with_semicolon(self):
93 def test_dont_cache_with_semicolon(self):
93 "Ending a line with semicolon should not cache the returned object (GH-307)"
94 "Ending a line with semicolon should not cache the returned object (GH-307)"
94 oldlen = len(ip.user_ns['Out'])
95 oldlen = len(ip.user_ns['Out'])
95 a = ip.run_cell('1;', store_history=True)
96 a = ip.run_cell('1;', store_history=True)
96 newlen = len(ip.user_ns['Out'])
97 newlen = len(ip.user_ns['Out'])
97 self.assertEqual(oldlen, newlen)
98 self.assertEqual(oldlen, newlen)
98 #also test the default caching behavior
99 #also test the default caching behavior
99 ip.run_cell('1', store_history=True)
100 ip.run_cell('1', store_history=True)
100 newlen = len(ip.user_ns['Out'])
101 newlen = len(ip.user_ns['Out'])
101 self.assertEqual(oldlen+1, newlen)
102 self.assertEqual(oldlen+1, newlen)
102
103
103 def test_In_variable(self):
104 def test_In_variable(self):
104 "Verify that In variable grows with user input (GH-284)"
105 "Verify that In variable grows with user input (GH-284)"
105 oldlen = len(ip.user_ns['In'])
106 oldlen = len(ip.user_ns['In'])
106 ip.run_cell('1;', store_history=True)
107 ip.run_cell('1;', store_history=True)
107 newlen = len(ip.user_ns['In'])
108 newlen = len(ip.user_ns['In'])
108 self.assertEqual(oldlen+1, newlen)
109 self.assertEqual(oldlen+1, newlen)
109 self.assertEqual(ip.user_ns['In'][-1],'1;')
110 self.assertEqual(ip.user_ns['In'][-1],'1;')
110
111
111 def test_magic_names_in_string(self):
112 def test_magic_names_in_string(self):
112 ip.run_cell('a = """\n%exit\n"""')
113 ip.run_cell('a = """\n%exit\n"""')
113 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
114 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
114
115
115 def test_trailing_newline(self):
116 def test_trailing_newline(self):
116 """test that running !(command) does not raise a SyntaxError"""
117 """test that running !(command) does not raise a SyntaxError"""
117 ip.run_cell('!(true)\n', False)
118 ip.run_cell('!(true)\n', False)
118 ip.run_cell('!(true)\n\n\n', False)
119 ip.run_cell('!(true)\n\n\n', False)
119
120
120 def test_gh_597(self):
121 def test_gh_597(self):
121 """Pretty-printing lists of objects with non-ascii reprs may cause
122 """Pretty-printing lists of objects with non-ascii reprs may cause
122 problems."""
123 problems."""
123 class Spam(object):
124 class Spam(object):
124 def __repr__(self):
125 def __repr__(self):
125 return "\xe9"*50
126 return "\xe9"*50
126 import IPython.core.formatters
127 import IPython.core.formatters
127 f = IPython.core.formatters.PlainTextFormatter()
128 f = IPython.core.formatters.PlainTextFormatter()
128 f([Spam(),Spam()])
129 f([Spam(),Spam()])
129
130
130
131
131 def test_future_flags(self):
132 def test_future_flags(self):
132 """Check that future flags are used for parsing code (gh-777)"""
133 """Check that future flags are used for parsing code (gh-777)"""
133 ip.run_cell('from __future__ import print_function')
134 ip.run_cell('from __future__ import print_function')
134 try:
135 try:
135 ip.run_cell('prfunc_return_val = print(1,2, sep=" ")')
136 ip.run_cell('prfunc_return_val = print(1,2, sep=" ")')
136 assert 'prfunc_return_val' in ip.user_ns
137 assert 'prfunc_return_val' in ip.user_ns
137 finally:
138 finally:
138 # Reset compiler flags so we don't mess up other tests.
139 # Reset compiler flags so we don't mess up other tests.
139 ip.compile.reset_compiler_flags()
140 ip.compile.reset_compiler_flags()
140
141
141 def test_future_unicode(self):
142 def test_future_unicode(self):
142 """Check that unicode_literals is imported from __future__ (gh #786)"""
143 """Check that unicode_literals is imported from __future__ (gh #786)"""
143 try:
144 try:
144 ip.run_cell(u'byte_str = "a"')
145 ip.run_cell(u'byte_str = "a"')
145 assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default
146 assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default
146 ip.run_cell('from __future__ import unicode_literals')
147 ip.run_cell('from __future__ import unicode_literals')
147 ip.run_cell(u'unicode_str = "a"')
148 ip.run_cell(u'unicode_str = "a"')
148 assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode
149 assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode
149 finally:
150 finally:
150 # Reset compiler flags so we don't mess up other tests.
151 # Reset compiler flags so we don't mess up other tests.
151 ip.compile.reset_compiler_flags()
152 ip.compile.reset_compiler_flags()
152
153
153 def test_can_pickle(self):
154 def test_can_pickle(self):
154 "Can we pickle objects defined interactively (GH-29)"
155 "Can we pickle objects defined interactively (GH-29)"
155 ip = get_ipython()
156 ip = get_ipython()
156 ip.reset()
157 ip.reset()
157 ip.run_cell(("class Mylist(list):\n"
158 ip.run_cell(("class Mylist(list):\n"
158 " def __init__(self,x=[]):\n"
159 " def __init__(self,x=[]):\n"
159 " list.__init__(self,x)"))
160 " list.__init__(self,x)"))
160 ip.run_cell("w=Mylist([1,2,3])")
161 ip.run_cell("w=Mylist([1,2,3])")
161
162
162 from pickle import dumps
163 from pickle import dumps
163
164
164 # We need to swap in our main module - this is only necessary
165 # We need to swap in our main module - this is only necessary
165 # inside the test framework, because IPython puts the interactive module
166 # inside the test framework, because IPython puts the interactive module
166 # in place (but the test framework undoes this).
167 # in place (but the test framework undoes this).
167 _main = sys.modules['__main__']
168 _main = sys.modules['__main__']
168 sys.modules['__main__'] = ip.user_module
169 sys.modules['__main__'] = ip.user_module
169 try:
170 try:
170 res = dumps(ip.user_ns["w"])
171 res = dumps(ip.user_ns["w"])
171 finally:
172 finally:
172 sys.modules['__main__'] = _main
173 sys.modules['__main__'] = _main
173 self.assertTrue(isinstance(res, bytes))
174 self.assertTrue(isinstance(res, bytes))
174
175
175 def test_global_ns(self):
176 def test_global_ns(self):
176 "Code in functions must be able to access variables outside them."
177 "Code in functions must be able to access variables outside them."
177 ip = get_ipython()
178 ip = get_ipython()
178 ip.run_cell("a = 10")
179 ip.run_cell("a = 10")
179 ip.run_cell(("def f(x):\n"
180 ip.run_cell(("def f(x):\n"
180 " return x + a"))
181 " return x + a"))
181 ip.run_cell("b = f(12)")
182 ip.run_cell("b = f(12)")
182 self.assertEqual(ip.user_ns["b"], 22)
183 self.assertEqual(ip.user_ns["b"], 22)
183
184
184 def test_bad_custom_tb(self):
185 def test_bad_custom_tb(self):
185 """Check that InteractiveShell is protected from bad custom exception handlers"""
186 """Check that InteractiveShell is protected from bad custom exception handlers"""
186 from IPython.utils import io
187 from IPython.utils import io
187 save_stderr = io.stderr
188 save_stderr = io.stderr
188 try:
189 try:
189 # capture stderr
190 # capture stderr
190 io.stderr = StringIO()
191 io.stderr = StringIO()
191 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
192 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
192 self.assertEqual(ip.custom_exceptions, (IOError,))
193 self.assertEqual(ip.custom_exceptions, (IOError,))
193 ip.run_cell(u'raise IOError("foo")')
194 ip.run_cell(u'raise IOError("foo")')
194 self.assertEqual(ip.custom_exceptions, ())
195 self.assertEqual(ip.custom_exceptions, ())
195 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
196 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
196 finally:
197 finally:
197 io.stderr = save_stderr
198 io.stderr = save_stderr
198
199
199 def test_bad_custom_tb_return(self):
200 def test_bad_custom_tb_return(self):
200 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
201 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
201 from IPython.utils import io
202 from IPython.utils import io
202 save_stderr = io.stderr
203 save_stderr = io.stderr
203 try:
204 try:
204 # capture stderr
205 # capture stderr
205 io.stderr = StringIO()
206 io.stderr = StringIO()
206 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
207 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
207 self.assertEqual(ip.custom_exceptions, (NameError,))
208 self.assertEqual(ip.custom_exceptions, (NameError,))
208 ip.run_cell(u'a=abracadabra')
209 ip.run_cell(u'a=abracadabra')
209 self.assertEqual(ip.custom_exceptions, ())
210 self.assertEqual(ip.custom_exceptions, ())
210 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
211 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
211 finally:
212 finally:
212 io.stderr = save_stderr
213 io.stderr = save_stderr
213
214
214 def test_drop_by_id(self):
215 def test_drop_by_id(self):
215 myvars = {"a":object(), "b":object(), "c": object()}
216 myvars = {"a":object(), "b":object(), "c": object()}
216 ip.push(myvars, interactive=False)
217 ip.push(myvars, interactive=False)
217 for name in myvars:
218 for name in myvars:
218 assert name in ip.user_ns, name
219 assert name in ip.user_ns, name
219 assert name in ip.user_ns_hidden, name
220 assert name in ip.user_ns_hidden, name
220 ip.user_ns['b'] = 12
221 ip.user_ns['b'] = 12
221 ip.drop_by_id(myvars)
222 ip.drop_by_id(myvars)
222 for name in ["a", "c"]:
223 for name in ["a", "c"]:
223 assert name not in ip.user_ns, name
224 assert name not in ip.user_ns, name
224 assert name not in ip.user_ns_hidden, name
225 assert name not in ip.user_ns_hidden, name
225 assert ip.user_ns['b'] == 12
226 assert ip.user_ns['b'] == 12
226 ip.reset()
227 ip.reset()
227
228
228 def test_var_expand(self):
229 def test_var_expand(self):
229 ip.user_ns['f'] = u'Ca\xf1o'
230 ip.user_ns['f'] = u'Ca\xf1o'
230 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
231 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
231 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
232 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
232 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
233 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
233 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
234 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
234
235
235 ip.user_ns['f'] = b'Ca\xc3\xb1o'
236 ip.user_ns['f'] = b'Ca\xc3\xb1o'
236 # This should not raise any exception:
237 # This should not raise any exception:
237 ip.var_expand(u'echo $f')
238 ip.var_expand(u'echo $f')
238
239
239 def test_var_expand_local(self):
240 def test_var_expand_local(self):
240 """Test local variable expansion in !system and %magic calls"""
241 """Test local variable expansion in !system and %magic calls"""
241 # !system
242 # !system
242 ip.run_cell('def test():\n'
243 ip.run_cell('def test():\n'
243 ' lvar = "ttt"\n'
244 ' lvar = "ttt"\n'
244 ' ret = !echo {lvar}\n'
245 ' ret = !echo {lvar}\n'
245 ' return ret[0]\n')
246 ' return ret[0]\n')
246 res = ip.user_ns['test']()
247 res = ip.user_ns['test']()
247 nt.assert_in('ttt', res)
248 nt.assert_in('ttt', res)
248
249
249 # %magic
250 # %magic
250 ip.run_cell('def makemacro():\n'
251 ip.run_cell('def makemacro():\n'
251 ' macroname = "macro_var_expand_locals"\n'
252 ' macroname = "macro_var_expand_locals"\n'
252 ' %macro {macroname} codestr\n')
253 ' %macro {macroname} codestr\n')
253 ip.user_ns['codestr'] = "str(12)"
254 ip.user_ns['codestr'] = "str(12)"
254 ip.run_cell('makemacro()')
255 ip.run_cell('makemacro()')
255 nt.assert_in('macro_var_expand_locals', ip.user_ns)
256 nt.assert_in('macro_var_expand_locals', ip.user_ns)
256
257
257 def test_var_expand_self(self):
258 def test_var_expand_self(self):
258 """Test variable expansion with the name 'self', which was failing.
259 """Test variable expansion with the name 'self', which was failing.
259
260
260 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
261 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
261 """
262 """
262 ip.run_cell('class cTest:\n'
263 ip.run_cell('class cTest:\n'
263 ' classvar="see me"\n'
264 ' classvar="see me"\n'
264 ' def test(self):\n'
265 ' def test(self):\n'
265 ' res = !echo Variable: {self.classvar}\n'
266 ' res = !echo Variable: {self.classvar}\n'
266 ' return res[0]\n')
267 ' return res[0]\n')
267 nt.assert_in('see me', ip.user_ns['cTest']().test())
268 nt.assert_in('see me', ip.user_ns['cTest']().test())
268
269
269 def test_bad_var_expand(self):
270 def test_bad_var_expand(self):
270 """var_expand on invalid formats shouldn't raise"""
271 """var_expand on invalid formats shouldn't raise"""
271 # SyntaxError
272 # SyntaxError
272 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
273 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
273 # NameError
274 # NameError
274 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
275 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
275 # ZeroDivisionError
276 # ZeroDivisionError
276 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
277 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
277
278
278 def test_silent_nopostexec(self):
279 def test_silent_nopostexec(self):
279 """run_cell(silent=True) doesn't invoke post-exec funcs"""
280 """run_cell(silent=True) doesn't invoke post-exec funcs"""
280 d = dict(called=False)
281 d = dict(called=False)
281 def set_called():
282 def set_called():
282 d['called'] = True
283 d['called'] = True
283
284
284 ip.register_post_execute(set_called)
285 ip.register_post_execute(set_called)
285 ip.run_cell("1", silent=True)
286 ip.run_cell("1", silent=True)
286 self.assertFalse(d['called'])
287 self.assertFalse(d['called'])
287 # double-check that non-silent exec did what we expected
288 # double-check that non-silent exec did what we expected
288 # silent to avoid
289 # silent to avoid
289 ip.run_cell("1")
290 ip.run_cell("1")
290 self.assertTrue(d['called'])
291 self.assertTrue(d['called'])
291 # remove post-exec
292 # remove post-exec
292 ip._post_execute.pop(set_called)
293 ip._post_execute.pop(set_called)
293
294
294 def test_silent_noadvance(self):
295 def test_silent_noadvance(self):
295 """run_cell(silent=True) doesn't advance execution_count"""
296 """run_cell(silent=True) doesn't advance execution_count"""
296 ec = ip.execution_count
297 ec = ip.execution_count
297 # silent should force store_history=False
298 # silent should force store_history=False
298 ip.run_cell("1", store_history=True, silent=True)
299 ip.run_cell("1", store_history=True, silent=True)
299
300
300 self.assertEqual(ec, ip.execution_count)
301 self.assertEqual(ec, ip.execution_count)
301 # double-check that non-silent exec did what we expected
302 # double-check that non-silent exec did what we expected
302 # silent to avoid
303 # silent to avoid
303 ip.run_cell("1", store_history=True)
304 ip.run_cell("1", store_history=True)
304 self.assertEqual(ec+1, ip.execution_count)
305 self.assertEqual(ec+1, ip.execution_count)
305
306
306 def test_silent_nodisplayhook(self):
307 def test_silent_nodisplayhook(self):
307 """run_cell(silent=True) doesn't trigger displayhook"""
308 """run_cell(silent=True) doesn't trigger displayhook"""
308 d = dict(called=False)
309 d = dict(called=False)
309
310
310 trap = ip.display_trap
311 trap = ip.display_trap
311 save_hook = trap.hook
312 save_hook = trap.hook
312
313
313 def failing_hook(*args, **kwargs):
314 def failing_hook(*args, **kwargs):
314 d['called'] = True
315 d['called'] = True
315
316
316 try:
317 try:
317 trap.hook = failing_hook
318 trap.hook = failing_hook
318 ip.run_cell("1", silent=True)
319 ip.run_cell("1", silent=True)
319 self.assertFalse(d['called'])
320 self.assertFalse(d['called'])
320 # double-check that non-silent exec did what we expected
321 # double-check that non-silent exec did what we expected
321 # silent to avoid
322 # silent to avoid
322 ip.run_cell("1")
323 ip.run_cell("1")
323 self.assertTrue(d['called'])
324 self.assertTrue(d['called'])
324 finally:
325 finally:
325 trap.hook = save_hook
326 trap.hook = save_hook
326
327
327 @skipif(sys.version_info[0] >= 3, "softspace removed in py3")
328 @skipif(sys.version_info[0] >= 3, "softspace removed in py3")
328 def test_print_softspace(self):
329 def test_print_softspace(self):
329 """Verify that softspace is handled correctly when executing multiple
330 """Verify that softspace is handled correctly when executing multiple
330 statements.
331 statements.
331
332
332 In [1]: print 1; print 2
333 In [1]: print 1; print 2
333 1
334 1
334 2
335 2
335
336
336 In [2]: print 1,; print 2
337 In [2]: print 1,; print 2
337 1 2
338 1 2
338 """
339 """
339
340
340 def test_ofind_line_magic(self):
341 def test_ofind_line_magic(self):
341 from IPython.core.magic import register_line_magic
342 from IPython.core.magic import register_line_magic
342
343
343 @register_line_magic
344 @register_line_magic
344 def lmagic(line):
345 def lmagic(line):
345 "A line magic"
346 "A line magic"
346
347
347 # Get info on line magic
348 # Get info on line magic
348 lfind = ip._ofind('lmagic')
349 lfind = ip._ofind('lmagic')
349 info = dict(found=True, isalias=False, ismagic=True,
350 info = dict(found=True, isalias=False, ismagic=True,
350 namespace = 'IPython internal', obj= lmagic.__wrapped__,
351 namespace = 'IPython internal', obj= lmagic.__wrapped__,
351 parent = None)
352 parent = None)
352 nt.assert_equal(lfind, info)
353 nt.assert_equal(lfind, info)
353
354
354 def test_ofind_cell_magic(self):
355 def test_ofind_cell_magic(self):
355 from IPython.core.magic import register_cell_magic
356 from IPython.core.magic import register_cell_magic
356
357
357 @register_cell_magic
358 @register_cell_magic
358 def cmagic(line, cell):
359 def cmagic(line, cell):
359 "A cell magic"
360 "A cell magic"
360
361
361 # Get info on cell magic
362 # Get info on cell magic
362 find = ip._ofind('cmagic')
363 find = ip._ofind('cmagic')
363 info = dict(found=True, isalias=False, ismagic=True,
364 info = dict(found=True, isalias=False, ismagic=True,
364 namespace = 'IPython internal', obj= cmagic.__wrapped__,
365 namespace = 'IPython internal', obj= cmagic.__wrapped__,
365 parent = None)
366 parent = None)
366 nt.assert_equal(find, info)
367 nt.assert_equal(find, info)
367
368
368 def test_custom_exception(self):
369 def test_custom_exception(self):
369 called = []
370 called = []
370 def my_handler(shell, etype, value, tb, tb_offset=None):
371 def my_handler(shell, etype, value, tb, tb_offset=None):
371 called.append(etype)
372 called.append(etype)
372 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
373 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
373
374
374 ip.set_custom_exc((ValueError,), my_handler)
375 ip.set_custom_exc((ValueError,), my_handler)
375 try:
376 try:
376 ip.run_cell("raise ValueError('test')")
377 ip.run_cell("raise ValueError('test')")
377 # Check that this was called, and only once.
378 # Check that this was called, and only once.
378 self.assertEqual(called, [ValueError])
379 self.assertEqual(called, [ValueError])
379 finally:
380 finally:
380 # Reset the custom exception hook
381 # Reset the custom exception hook
381 ip.set_custom_exc((), None)
382 ip.set_custom_exc((), None)
382
383
383 @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3")
384 @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3")
384 def test_future_environment(self):
385 def test_future_environment(self):
385 "Can we run code with & without the shell's __future__ imports?"
386 "Can we run code with & without the shell's __future__ imports?"
386 ip.run_cell("from __future__ import division")
387 ip.run_cell("from __future__ import division")
387 ip.run_cell("a = 1/2", shell_futures=True)
388 ip.run_cell("a = 1/2", shell_futures=True)
388 self.assertEqual(ip.user_ns['a'], 0.5)
389 self.assertEqual(ip.user_ns['a'], 0.5)
389 ip.run_cell("b = 1/2", shell_futures=False)
390 ip.run_cell("b = 1/2", shell_futures=False)
390 self.assertEqual(ip.user_ns['b'], 0)
391 self.assertEqual(ip.user_ns['b'], 0)
391
392
392 ip.compile.reset_compiler_flags()
393 ip.compile.reset_compiler_flags()
393 # This shouldn't leak to the shell's compiler
394 # This shouldn't leak to the shell's compiler
394 ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False)
395 ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False)
395 self.assertEqual(ip.user_ns['c'], 0.5)
396 self.assertEqual(ip.user_ns['c'], 0.5)
396 ip.run_cell("d = 1/2", shell_futures=True)
397 ip.run_cell("d = 1/2", shell_futures=True)
397 self.assertEqual(ip.user_ns['d'], 0)
398 self.assertEqual(ip.user_ns['d'], 0)
398
399
399
400
400 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
401 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
401
402
402 @onlyif_unicode_paths
403 @onlyif_unicode_paths
403 def setUp(self):
404 def setUp(self):
404 self.BASETESTDIR = tempfile.mkdtemp()
405 self.BASETESTDIR = tempfile.mkdtemp()
405 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
406 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
406 os.mkdir(self.TESTDIR)
407 os.mkdir(self.TESTDIR)
407 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
408 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
408 sfile.write("pass\n")
409 sfile.write("pass\n")
409 self.oldpath = os.getcwdu()
410 self.oldpath = py3compat.getcwd()
410 os.chdir(self.TESTDIR)
411 os.chdir(self.TESTDIR)
411 self.fname = u"Γ₯Àâtestscript.py"
412 self.fname = u"Γ₯Àâtestscript.py"
412
413
413 def tearDown(self):
414 def tearDown(self):
414 os.chdir(self.oldpath)
415 os.chdir(self.oldpath)
415 shutil.rmtree(self.BASETESTDIR)
416 shutil.rmtree(self.BASETESTDIR)
416
417
417 @onlyif_unicode_paths
418 @onlyif_unicode_paths
418 def test_1(self):
419 def test_1(self):
419 """Test safe_execfile with non-ascii path
420 """Test safe_execfile with non-ascii path
420 """
421 """
421 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
422 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
422
423
423 class ExitCodeChecks(tt.TempFileMixin):
424 class ExitCodeChecks(tt.TempFileMixin):
424 def test_exit_code_ok(self):
425 def test_exit_code_ok(self):
425 self.system('exit 0')
426 self.system('exit 0')
426 self.assertEqual(ip.user_ns['_exit_code'], 0)
427 self.assertEqual(ip.user_ns['_exit_code'], 0)
427
428
428 def test_exit_code_error(self):
429 def test_exit_code_error(self):
429 self.system('exit 1')
430 self.system('exit 1')
430 self.assertEqual(ip.user_ns['_exit_code'], 1)
431 self.assertEqual(ip.user_ns['_exit_code'], 1)
431
432
432 @skipif(not hasattr(signal, 'SIGALRM'))
433 @skipif(not hasattr(signal, 'SIGALRM'))
433 def test_exit_code_signal(self):
434 def test_exit_code_signal(self):
434 self.mktmp("import signal, time\n"
435 self.mktmp("import signal, time\n"
435 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
436 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
436 "time.sleep(1)\n")
437 "time.sleep(1)\n")
437 self.system("%s %s" % (sys.executable, self.fname))
438 self.system("%s %s" % (sys.executable, self.fname))
438 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
439 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
439
440
440 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
441 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
441 system = ip.system_raw
442 system = ip.system_raw
442
443
443 @onlyif_unicode_paths
444 @onlyif_unicode_paths
444 def test_1(self):
445 def test_1(self):
445 """Test system_raw with non-ascii cmd
446 """Test system_raw with non-ascii cmd
446 """
447 """
447 cmd = u'''python -c "'Γ₯Àâ'" '''
448 cmd = u'''python -c "'Γ₯Àâ'" '''
448 ip.system_raw(cmd)
449 ip.system_raw(cmd)
449
450
450 # TODO: Exit codes are currently ignored on Windows.
451 # TODO: Exit codes are currently ignored on Windows.
451 class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
452 class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
452 system = ip.system_piped
453 system = ip.system_piped
453
454
454 @skip_win32
455 @skip_win32
455 def test_exit_code_ok(self):
456 def test_exit_code_ok(self):
456 ExitCodeChecks.test_exit_code_ok(self)
457 ExitCodeChecks.test_exit_code_ok(self)
457
458
458 @skip_win32
459 @skip_win32
459 def test_exit_code_error(self):
460 def test_exit_code_error(self):
460 ExitCodeChecks.test_exit_code_error(self)
461 ExitCodeChecks.test_exit_code_error(self)
461
462
462 @skip_win32
463 @skip_win32
463 def test_exit_code_signal(self):
464 def test_exit_code_signal(self):
464 ExitCodeChecks.test_exit_code_signal(self)
465 ExitCodeChecks.test_exit_code_signal(self)
465
466
466 class TestModules(unittest.TestCase, tt.TempFileMixin):
467 class TestModules(unittest.TestCase, tt.TempFileMixin):
467 def test_extraneous_loads(self):
468 def test_extraneous_loads(self):
468 """Test we're not loading modules on startup that we shouldn't.
469 """Test we're not loading modules on startup that we shouldn't.
469 """
470 """
470 self.mktmp("import sys\n"
471 self.mktmp("import sys\n"
471 "print('numpy' in sys.modules)\n"
472 "print('numpy' in sys.modules)\n"
472 "print('IPython.parallel' in sys.modules)\n"
473 "print('IPython.parallel' in sys.modules)\n"
473 "print('IPython.kernel.zmq' in sys.modules)\n"
474 "print('IPython.kernel.zmq' in sys.modules)\n"
474 )
475 )
475 out = "False\nFalse\nFalse\n"
476 out = "False\nFalse\nFalse\n"
476 tt.ipexec_validate(self.fname, out)
477 tt.ipexec_validate(self.fname, out)
477
478
478 class Negator(ast.NodeTransformer):
479 class Negator(ast.NodeTransformer):
479 """Negates all number literals in an AST."""
480 """Negates all number literals in an AST."""
480 def visit_Num(self, node):
481 def visit_Num(self, node):
481 node.n = -node.n
482 node.n = -node.n
482 return node
483 return node
483
484
484 class TestAstTransform(unittest.TestCase):
485 class TestAstTransform(unittest.TestCase):
485 def setUp(self):
486 def setUp(self):
486 self.negator = Negator()
487 self.negator = Negator()
487 ip.ast_transformers.append(self.negator)
488 ip.ast_transformers.append(self.negator)
488
489
489 def tearDown(self):
490 def tearDown(self):
490 ip.ast_transformers.remove(self.negator)
491 ip.ast_transformers.remove(self.negator)
491
492
492 def test_run_cell(self):
493 def test_run_cell(self):
493 with tt.AssertPrints('-34'):
494 with tt.AssertPrints('-34'):
494 ip.run_cell('print (12 + 22)')
495 ip.run_cell('print (12 + 22)')
495
496
496 # A named reference to a number shouldn't be transformed.
497 # A named reference to a number shouldn't be transformed.
497 ip.user_ns['n'] = 55
498 ip.user_ns['n'] = 55
498 with tt.AssertNotPrints('-55'):
499 with tt.AssertNotPrints('-55'):
499 ip.run_cell('print (n)')
500 ip.run_cell('print (n)')
500
501
501 def test_timeit(self):
502 def test_timeit(self):
502 called = set()
503 called = set()
503 def f(x):
504 def f(x):
504 called.add(x)
505 called.add(x)
505 ip.push({'f':f})
506 ip.push({'f':f})
506
507
507 with tt.AssertPrints("best of "):
508 with tt.AssertPrints("best of "):
508 ip.run_line_magic("timeit", "-n1 f(1)")
509 ip.run_line_magic("timeit", "-n1 f(1)")
509 self.assertEqual(called, set([-1]))
510 self.assertEqual(called, set([-1]))
510 called.clear()
511 called.clear()
511
512
512 with tt.AssertPrints("best of "):
513 with tt.AssertPrints("best of "):
513 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
514 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
514 self.assertEqual(called, set([-2, -3]))
515 self.assertEqual(called, set([-2, -3]))
515
516
516 def test_time(self):
517 def test_time(self):
517 called = []
518 called = []
518 def f(x):
519 def f(x):
519 called.append(x)
520 called.append(x)
520 ip.push({'f':f})
521 ip.push({'f':f})
521
522
522 # Test with an expression
523 # Test with an expression
523 with tt.AssertPrints("Wall time: "):
524 with tt.AssertPrints("Wall time: "):
524 ip.run_line_magic("time", "f(5+9)")
525 ip.run_line_magic("time", "f(5+9)")
525 self.assertEqual(called, [-14])
526 self.assertEqual(called, [-14])
526 called[:] = []
527 called[:] = []
527
528
528 # Test with a statement (different code path)
529 # Test with a statement (different code path)
529 with tt.AssertPrints("Wall time: "):
530 with tt.AssertPrints("Wall time: "):
530 ip.run_line_magic("time", "a = f(-3 + -2)")
531 ip.run_line_magic("time", "a = f(-3 + -2)")
531 self.assertEqual(called, [5])
532 self.assertEqual(called, [5])
532
533
533 def test_macro(self):
534 def test_macro(self):
534 ip.push({'a':10})
535 ip.push({'a':10})
535 # The AST transformation makes this do a+=-1
536 # The AST transformation makes this do a+=-1
536 ip.define_macro("amacro", "a+=1\nprint(a)")
537 ip.define_macro("amacro", "a+=1\nprint(a)")
537
538
538 with tt.AssertPrints("9"):
539 with tt.AssertPrints("9"):
539 ip.run_cell("amacro")
540 ip.run_cell("amacro")
540 with tt.AssertPrints("8"):
541 with tt.AssertPrints("8"):
541 ip.run_cell("amacro")
542 ip.run_cell("amacro")
542
543
543 class IntegerWrapper(ast.NodeTransformer):
544 class IntegerWrapper(ast.NodeTransformer):
544 """Wraps all integers in a call to Integer()"""
545 """Wraps all integers in a call to Integer()"""
545 def visit_Num(self, node):
546 def visit_Num(self, node):
546 if isinstance(node.n, int):
547 if isinstance(node.n, int):
547 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
548 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
548 args=[node], keywords=[])
549 args=[node], keywords=[])
549 return node
550 return node
550
551
551 class TestAstTransform2(unittest.TestCase):
552 class TestAstTransform2(unittest.TestCase):
552 def setUp(self):
553 def setUp(self):
553 self.intwrapper = IntegerWrapper()
554 self.intwrapper = IntegerWrapper()
554 ip.ast_transformers.append(self.intwrapper)
555 ip.ast_transformers.append(self.intwrapper)
555
556
556 self.calls = []
557 self.calls = []
557 def Integer(*args):
558 def Integer(*args):
558 self.calls.append(args)
559 self.calls.append(args)
559 return args
560 return args
560 ip.push({"Integer": Integer})
561 ip.push({"Integer": Integer})
561
562
562 def tearDown(self):
563 def tearDown(self):
563 ip.ast_transformers.remove(self.intwrapper)
564 ip.ast_transformers.remove(self.intwrapper)
564 del ip.user_ns['Integer']
565 del ip.user_ns['Integer']
565
566
566 def test_run_cell(self):
567 def test_run_cell(self):
567 ip.run_cell("n = 2")
568 ip.run_cell("n = 2")
568 self.assertEqual(self.calls, [(2,)])
569 self.assertEqual(self.calls, [(2,)])
569
570
570 # This shouldn't throw an error
571 # This shouldn't throw an error
571 ip.run_cell("o = 2.0")
572 ip.run_cell("o = 2.0")
572 self.assertEqual(ip.user_ns['o'], 2.0)
573 self.assertEqual(ip.user_ns['o'], 2.0)
573
574
574 def test_timeit(self):
575 def test_timeit(self):
575 called = set()
576 called = set()
576 def f(x):
577 def f(x):
577 called.add(x)
578 called.add(x)
578 ip.push({'f':f})
579 ip.push({'f':f})
579
580
580 with tt.AssertPrints("best of "):
581 with tt.AssertPrints("best of "):
581 ip.run_line_magic("timeit", "-n1 f(1)")
582 ip.run_line_magic("timeit", "-n1 f(1)")
582 self.assertEqual(called, set([(1,)]))
583 self.assertEqual(called, set([(1,)]))
583 called.clear()
584 called.clear()
584
585
585 with tt.AssertPrints("best of "):
586 with tt.AssertPrints("best of "):
586 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
587 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
587 self.assertEqual(called, set([(2,), (3,)]))
588 self.assertEqual(called, set([(2,), (3,)]))
588
589
589 class ErrorTransformer(ast.NodeTransformer):
590 class ErrorTransformer(ast.NodeTransformer):
590 """Throws an error when it sees a number."""
591 """Throws an error when it sees a number."""
591 def visit_Num(self):
592 def visit_Num(self):
592 raise ValueError("test")
593 raise ValueError("test")
593
594
594 class TestAstTransformError(unittest.TestCase):
595 class TestAstTransformError(unittest.TestCase):
595 def test_unregistering(self):
596 def test_unregistering(self):
596 err_transformer = ErrorTransformer()
597 err_transformer = ErrorTransformer()
597 ip.ast_transformers.append(err_transformer)
598 ip.ast_transformers.append(err_transformer)
598
599
599 with tt.AssertPrints("unregister", channel='stderr'):
600 with tt.AssertPrints("unregister", channel='stderr'):
600 ip.run_cell("1 + 2")
601 ip.run_cell("1 + 2")
601
602
602 # This should have been removed.
603 # This should have been removed.
603 nt.assert_not_in(err_transformer, ip.ast_transformers)
604 nt.assert_not_in(err_transformer, ip.ast_transformers)
604
605
605 def test__IPYTHON__():
606 def test__IPYTHON__():
606 # This shouldn't raise a NameError, that's all
607 # This shouldn't raise a NameError, that's all
607 __IPYTHON__
608 __IPYTHON__
608
609
609
610
610 class DummyRepr(object):
611 class DummyRepr(object):
611 def __repr__(self):
612 def __repr__(self):
612 return "DummyRepr"
613 return "DummyRepr"
613
614
614 def _repr_html_(self):
615 def _repr_html_(self):
615 return "<b>dummy</b>"
616 return "<b>dummy</b>"
616
617
617 def _repr_javascript_(self):
618 def _repr_javascript_(self):
618 return "console.log('hi');", {'key': 'value'}
619 return "console.log('hi');", {'key': 'value'}
619
620
620
621
621 def test_user_variables():
622 def test_user_variables():
622 # enable all formatters
623 # enable all formatters
623 ip.display_formatter.active_types = ip.display_formatter.format_types
624 ip.display_formatter.active_types = ip.display_formatter.format_types
624
625
625 ip.user_ns['dummy'] = d = DummyRepr()
626 ip.user_ns['dummy'] = d = DummyRepr()
626 keys = set(['dummy', 'doesnotexist'])
627 keys = set(['dummy', 'doesnotexist'])
627 r = ip.user_variables(keys)
628 r = ip.user_variables(keys)
628
629
629 nt.assert_equal(keys, set(r.keys()))
630 nt.assert_equal(keys, set(r.keys()))
630 dummy = r['dummy']
631 dummy = r['dummy']
631 nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys()))
632 nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys()))
632 nt.assert_equal(dummy['status'], 'ok')
633 nt.assert_equal(dummy['status'], 'ok')
633 data = dummy['data']
634 data = dummy['data']
634 metadata = dummy['metadata']
635 metadata = dummy['metadata']
635 nt.assert_equal(data.get('text/html'), d._repr_html_())
636 nt.assert_equal(data.get('text/html'), d._repr_html_())
636 js, jsmd = d._repr_javascript_()
637 js, jsmd = d._repr_javascript_()
637 nt.assert_equal(data.get('application/javascript'), js)
638 nt.assert_equal(data.get('application/javascript'), js)
638 nt.assert_equal(metadata.get('application/javascript'), jsmd)
639 nt.assert_equal(metadata.get('application/javascript'), jsmd)
639
640
640 dne = r['doesnotexist']
641 dne = r['doesnotexist']
641 nt.assert_equal(dne['status'], 'error')
642 nt.assert_equal(dne['status'], 'error')
642 nt.assert_equal(dne['ename'], 'KeyError')
643 nt.assert_equal(dne['ename'], 'KeyError')
643
644
644 # back to text only
645 # back to text only
645 ip.display_formatter.active_types = ['text/plain']
646 ip.display_formatter.active_types = ['text/plain']
646
647
647 def test_user_expression():
648 def test_user_expression():
648 # enable all formatters
649 # enable all formatters
649 ip.display_formatter.active_types = ip.display_formatter.format_types
650 ip.display_formatter.active_types = ip.display_formatter.format_types
650 query = {
651 query = {
651 'a' : '1 + 2',
652 'a' : '1 + 2',
652 'b' : '1/0',
653 'b' : '1/0',
653 }
654 }
654 r = ip.user_expressions(query)
655 r = ip.user_expressions(query)
655 import pprint
656 import pprint
656 pprint.pprint(r)
657 pprint.pprint(r)
657 nt.assert_equal(r.keys(), query.keys())
658 nt.assert_equal(r.keys(), query.keys())
658 a = r['a']
659 a = r['a']
659 nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys()))
660 nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys()))
660 nt.assert_equal(a['status'], 'ok')
661 nt.assert_equal(a['status'], 'ok')
661 data = a['data']
662 data = a['data']
662 metadata = a['metadata']
663 metadata = a['metadata']
663 nt.assert_equal(data.get('text/plain'), '3')
664 nt.assert_equal(data.get('text/plain'), '3')
664
665
665 b = r['b']
666 b = r['b']
666 nt.assert_equal(b['status'], 'error')
667 nt.assert_equal(b['status'], 'error')
667 nt.assert_equal(b['ename'], 'ZeroDivisionError')
668 nt.assert_equal(b['ename'], 'ZeroDivisionError')
668
669
669 # back to text only
670 # back to text only
670 ip.display_formatter.active_types = ['text/plain']
671 ip.display_formatter.active_types = ['text/plain']
671
672
672
673
673
674
674
675
675
676
676
677
@@ -1,944 +1,944 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for various magic functions.
2 """Tests for various magic functions.
3
3
4 Needs to be run by nose (to make ipython session available).
4 Needs to be run by nose (to make ipython session available).
5 """
5 """
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Imports
9 # Imports
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 import io
12 import io
13 import os
13 import os
14 import sys
14 import sys
15 from unittest import TestCase
15 from unittest import TestCase
16
16
17 try:
17 try:
18 from importlib import invalidate_caches # Required from Python 3.3
18 from importlib import invalidate_caches # Required from Python 3.3
19 except ImportError:
19 except ImportError:
20 def invalidate_caches():
20 def invalidate_caches():
21 pass
21 pass
22
22
23 import nose.tools as nt
23 import nose.tools as nt
24
24
25 from IPython.core import magic
25 from IPython.core import magic
26 from IPython.core.magic import (Magics, magics_class, line_magic,
26 from IPython.core.magic import (Magics, magics_class, line_magic,
27 cell_magic, line_cell_magic,
27 cell_magic, line_cell_magic,
28 register_line_magic, register_cell_magic,
28 register_line_magic, register_cell_magic,
29 register_line_cell_magic)
29 register_line_cell_magic)
30 from IPython.core.magics import execution, script, code
30 from IPython.core.magics import execution, script, code
31 from IPython.nbformat.v3.tests.nbexamples import nb0
31 from IPython.nbformat.v3.tests.nbexamples import nb0
32 from IPython.nbformat import current
32 from IPython.nbformat import current
33 from IPython.testing import decorators as dec
33 from IPython.testing import decorators as dec
34 from IPython.testing import tools as tt
34 from IPython.testing import tools as tt
35 from IPython.utils import py3compat
35 from IPython.utils import py3compat
36 from IPython.utils.io import capture_output
36 from IPython.utils.io import capture_output
37 from IPython.utils.tempdir import TemporaryDirectory
37 from IPython.utils.tempdir import TemporaryDirectory
38 from IPython.utils.process import find_cmd
38 from IPython.utils.process import find_cmd
39
39
40 if py3compat.PY3:
40 if py3compat.PY3:
41 from io import StringIO
41 from io import StringIO
42 else:
42 else:
43 from StringIO import StringIO
43 from StringIO import StringIO
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Test functions begin
46 # Test functions begin
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 @magic.magics_class
49 @magic.magics_class
50 class DummyMagics(magic.Magics): pass
50 class DummyMagics(magic.Magics): pass
51
51
52 def test_extract_code_ranges():
52 def test_extract_code_ranges():
53 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
53 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
54 expected = [(0, 1),
54 expected = [(0, 1),
55 (2, 3),
55 (2, 3),
56 (4, 6),
56 (4, 6),
57 (6, 9),
57 (6, 9),
58 (9, 14),
58 (9, 14),
59 (16, None),
59 (16, None),
60 (None, 9),
60 (None, 9),
61 (9, None),
61 (9, None),
62 (None, 13),
62 (None, 13),
63 (None, None)]
63 (None, None)]
64 actual = list(code.extract_code_ranges(instr))
64 actual = list(code.extract_code_ranges(instr))
65 nt.assert_equal(actual, expected)
65 nt.assert_equal(actual, expected)
66
66
67 def test_extract_symbols():
67 def test_extract_symbols():
68 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
68 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
69 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
69 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
70 expected = [([], ['a']),
70 expected = [([], ['a']),
71 (["def b():\n return 42\n"], []),
71 (["def b():\n return 42\n"], []),
72 (["class A: pass\n"], []),
72 (["class A: pass\n"], []),
73 (["class A: pass\n", "def b():\n return 42\n"], []),
73 (["class A: pass\n", "def b():\n return 42\n"], []),
74 (["class A: pass\n"], ['a']),
74 (["class A: pass\n"], ['a']),
75 ([], ['z'])]
75 ([], ['z'])]
76 for symbols, exp in zip(symbols_args, expected):
76 for symbols, exp in zip(symbols_args, expected):
77 nt.assert_equal(code.extract_symbols(source, symbols), exp)
77 nt.assert_equal(code.extract_symbols(source, symbols), exp)
78
78
79
79
80 def test_extract_symbols_raises_exception_with_non_python_code():
80 def test_extract_symbols_raises_exception_with_non_python_code():
81 source = ("=begin A Ruby program :)=end\n"
81 source = ("=begin A Ruby program :)=end\n"
82 "def hello\n"
82 "def hello\n"
83 "puts 'Hello world'\n"
83 "puts 'Hello world'\n"
84 "end")
84 "end")
85 with nt.assert_raises(SyntaxError):
85 with nt.assert_raises(SyntaxError):
86 code.extract_symbols(source, "hello")
86 code.extract_symbols(source, "hello")
87
87
88 def test_config():
88 def test_config():
89 """ test that config magic does not raise
89 """ test that config magic does not raise
90 can happen if Configurable init is moved too early into
90 can happen if Configurable init is moved too early into
91 Magics.__init__ as then a Config object will be registerd as a
91 Magics.__init__ as then a Config object will be registerd as a
92 magic.
92 magic.
93 """
93 """
94 ## should not raise.
94 ## should not raise.
95 _ip.magic('config')
95 _ip.magic('config')
96
96
97 def test_rehashx():
97 def test_rehashx():
98 # clear up everything
98 # clear up everything
99 _ip = get_ipython()
99 _ip = get_ipython()
100 _ip.alias_manager.clear_aliases()
100 _ip.alias_manager.clear_aliases()
101 del _ip.db['syscmdlist']
101 del _ip.db['syscmdlist']
102
102
103 _ip.magic('rehashx')
103 _ip.magic('rehashx')
104 # Practically ALL ipython development systems will have more than 10 aliases
104 # Practically ALL ipython development systems will have more than 10 aliases
105
105
106 nt.assert_true(len(_ip.alias_manager.aliases) > 10)
106 nt.assert_true(len(_ip.alias_manager.aliases) > 10)
107 for name, cmd in _ip.alias_manager.aliases:
107 for name, cmd in _ip.alias_manager.aliases:
108 # we must strip dots from alias names
108 # we must strip dots from alias names
109 nt.assert_not_in('.', name)
109 nt.assert_not_in('.', name)
110
110
111 # rehashx must fill up syscmdlist
111 # rehashx must fill up syscmdlist
112 scoms = _ip.db['syscmdlist']
112 scoms = _ip.db['syscmdlist']
113 nt.assert_true(len(scoms) > 10)
113 nt.assert_true(len(scoms) > 10)
114
114
115
115
116 def test_magic_parse_options():
116 def test_magic_parse_options():
117 """Test that we don't mangle paths when parsing magic options."""
117 """Test that we don't mangle paths when parsing magic options."""
118 ip = get_ipython()
118 ip = get_ipython()
119 path = 'c:\\x'
119 path = 'c:\\x'
120 m = DummyMagics(ip)
120 m = DummyMagics(ip)
121 opts = m.parse_options('-f %s' % path,'f:')[0]
121 opts = m.parse_options('-f %s' % path,'f:')[0]
122 # argv splitting is os-dependent
122 # argv splitting is os-dependent
123 if os.name == 'posix':
123 if os.name == 'posix':
124 expected = 'c:x'
124 expected = 'c:x'
125 else:
125 else:
126 expected = path
126 expected = path
127 nt.assert_equal(opts['f'], expected)
127 nt.assert_equal(opts['f'], expected)
128
128
129 def test_magic_parse_long_options():
129 def test_magic_parse_long_options():
130 """Magic.parse_options can handle --foo=bar long options"""
130 """Magic.parse_options can handle --foo=bar long options"""
131 ip = get_ipython()
131 ip = get_ipython()
132 m = DummyMagics(ip)
132 m = DummyMagics(ip)
133 opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=')
133 opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=')
134 nt.assert_in('foo', opts)
134 nt.assert_in('foo', opts)
135 nt.assert_in('bar', opts)
135 nt.assert_in('bar', opts)
136 nt.assert_equal(opts['bar'], "bubble")
136 nt.assert_equal(opts['bar'], "bubble")
137
137
138
138
139 @dec.skip_without('sqlite3')
139 @dec.skip_without('sqlite3')
140 def doctest_hist_f():
140 def doctest_hist_f():
141 """Test %hist -f with temporary filename.
141 """Test %hist -f with temporary filename.
142
142
143 In [9]: import tempfile
143 In [9]: import tempfile
144
144
145 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
145 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
146
146
147 In [11]: %hist -nl -f $tfile 3
147 In [11]: %hist -nl -f $tfile 3
148
148
149 In [13]: import os; os.unlink(tfile)
149 In [13]: import os; os.unlink(tfile)
150 """
150 """
151
151
152
152
153 @dec.skip_without('sqlite3')
153 @dec.skip_without('sqlite3')
154 def doctest_hist_r():
154 def doctest_hist_r():
155 """Test %hist -r
155 """Test %hist -r
156
156
157 XXX - This test is not recording the output correctly. For some reason, in
157 XXX - This test is not recording the output correctly. For some reason, in
158 testing mode the raw history isn't getting populated. No idea why.
158 testing mode the raw history isn't getting populated. No idea why.
159 Disabling the output checking for now, though at least we do run it.
159 Disabling the output checking for now, though at least we do run it.
160
160
161 In [1]: 'hist' in _ip.lsmagic()
161 In [1]: 'hist' in _ip.lsmagic()
162 Out[1]: True
162 Out[1]: True
163
163
164 In [2]: x=1
164 In [2]: x=1
165
165
166 In [3]: %hist -rl 2
166 In [3]: %hist -rl 2
167 x=1 # random
167 x=1 # random
168 %hist -r 2
168 %hist -r 2
169 """
169 """
170
170
171
171
172 @dec.skip_without('sqlite3')
172 @dec.skip_without('sqlite3')
173 def doctest_hist_op():
173 def doctest_hist_op():
174 """Test %hist -op
174 """Test %hist -op
175
175
176 In [1]: class b(float):
176 In [1]: class b(float):
177 ...: pass
177 ...: pass
178 ...:
178 ...:
179
179
180 In [2]: class s(object):
180 In [2]: class s(object):
181 ...: def __str__(self):
181 ...: def __str__(self):
182 ...: return 's'
182 ...: return 's'
183 ...:
183 ...:
184
184
185 In [3]:
185 In [3]:
186
186
187 In [4]: class r(b):
187 In [4]: class r(b):
188 ...: def __repr__(self):
188 ...: def __repr__(self):
189 ...: return 'r'
189 ...: return 'r'
190 ...:
190 ...:
191
191
192 In [5]: class sr(s,r): pass
192 In [5]: class sr(s,r): pass
193 ...:
193 ...:
194
194
195 In [6]:
195 In [6]:
196
196
197 In [7]: bb=b()
197 In [7]: bb=b()
198
198
199 In [8]: ss=s()
199 In [8]: ss=s()
200
200
201 In [9]: rr=r()
201 In [9]: rr=r()
202
202
203 In [10]: ssrr=sr()
203 In [10]: ssrr=sr()
204
204
205 In [11]: 4.5
205 In [11]: 4.5
206 Out[11]: 4.5
206 Out[11]: 4.5
207
207
208 In [12]: str(ss)
208 In [12]: str(ss)
209 Out[12]: 's'
209 Out[12]: 's'
210
210
211 In [13]:
211 In [13]:
212
212
213 In [14]: %hist -op
213 In [14]: %hist -op
214 >>> class b:
214 >>> class b:
215 ... pass
215 ... pass
216 ...
216 ...
217 >>> class s(b):
217 >>> class s(b):
218 ... def __str__(self):
218 ... def __str__(self):
219 ... return 's'
219 ... return 's'
220 ...
220 ...
221 >>>
221 >>>
222 >>> class r(b):
222 >>> class r(b):
223 ... def __repr__(self):
223 ... def __repr__(self):
224 ... return 'r'
224 ... return 'r'
225 ...
225 ...
226 >>> class sr(s,r): pass
226 >>> class sr(s,r): pass
227 >>>
227 >>>
228 >>> bb=b()
228 >>> bb=b()
229 >>> ss=s()
229 >>> ss=s()
230 >>> rr=r()
230 >>> rr=r()
231 >>> ssrr=sr()
231 >>> ssrr=sr()
232 >>> 4.5
232 >>> 4.5
233 4.5
233 4.5
234 >>> str(ss)
234 >>> str(ss)
235 's'
235 's'
236 >>>
236 >>>
237 """
237 """
238
238
239
239
240 @dec.skip_without('sqlite3')
240 @dec.skip_without('sqlite3')
241 def test_macro():
241 def test_macro():
242 ip = get_ipython()
242 ip = get_ipython()
243 ip.history_manager.reset() # Clear any existing history.
243 ip.history_manager.reset() # Clear any existing history.
244 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
244 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
245 for i, cmd in enumerate(cmds, start=1):
245 for i, cmd in enumerate(cmds, start=1):
246 ip.history_manager.store_inputs(i, cmd)
246 ip.history_manager.store_inputs(i, cmd)
247 ip.magic("macro test 1-3")
247 ip.magic("macro test 1-3")
248 nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n")
248 nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n")
249
249
250 # List macros
250 # List macros
251 nt.assert_in("test", ip.magic("macro"))
251 nt.assert_in("test", ip.magic("macro"))
252
252
253
253
254 @dec.skip_without('sqlite3')
254 @dec.skip_without('sqlite3')
255 def test_macro_run():
255 def test_macro_run():
256 """Test that we can run a multi-line macro successfully."""
256 """Test that we can run a multi-line macro successfully."""
257 ip = get_ipython()
257 ip = get_ipython()
258 ip.history_manager.reset()
258 ip.history_manager.reset()
259 cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"),
259 cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"),
260 "%macro test 2-3"]
260 "%macro test 2-3"]
261 for cmd in cmds:
261 for cmd in cmds:
262 ip.run_cell(cmd, store_history=True)
262 ip.run_cell(cmd, store_history=True)
263 nt.assert_equal(ip.user_ns["test"].value,
263 nt.assert_equal(ip.user_ns["test"].value,
264 py3compat.doctest_refactor_print("a+=1\nprint a\n"))
264 py3compat.doctest_refactor_print("a+=1\nprint a\n"))
265 with tt.AssertPrints("12"):
265 with tt.AssertPrints("12"):
266 ip.run_cell("test")
266 ip.run_cell("test")
267 with tt.AssertPrints("13"):
267 with tt.AssertPrints("13"):
268 ip.run_cell("test")
268 ip.run_cell("test")
269
269
270
270
271 def test_magic_magic():
271 def test_magic_magic():
272 """Test %magic"""
272 """Test %magic"""
273 ip = get_ipython()
273 ip = get_ipython()
274 with capture_output() as captured:
274 with capture_output() as captured:
275 ip.magic("magic")
275 ip.magic("magic")
276
276
277 stdout = captured.stdout
277 stdout = captured.stdout
278 nt.assert_in('%magic', stdout)
278 nt.assert_in('%magic', stdout)
279 nt.assert_in('IPython', stdout)
279 nt.assert_in('IPython', stdout)
280 nt.assert_in('Available', stdout)
280 nt.assert_in('Available', stdout)
281
281
282
282
283 @dec.skipif_not_numpy
283 @dec.skipif_not_numpy
284 def test_numpy_reset_array_undec():
284 def test_numpy_reset_array_undec():
285 "Test '%reset array' functionality"
285 "Test '%reset array' functionality"
286 _ip.ex('import numpy as np')
286 _ip.ex('import numpy as np')
287 _ip.ex('a = np.empty(2)')
287 _ip.ex('a = np.empty(2)')
288 nt.assert_in('a', _ip.user_ns)
288 nt.assert_in('a', _ip.user_ns)
289 _ip.magic('reset -f array')
289 _ip.magic('reset -f array')
290 nt.assert_not_in('a', _ip.user_ns)
290 nt.assert_not_in('a', _ip.user_ns)
291
291
292 def test_reset_out():
292 def test_reset_out():
293 "Test '%reset out' magic"
293 "Test '%reset out' magic"
294 _ip.run_cell("parrot = 'dead'", store_history=True)
294 _ip.run_cell("parrot = 'dead'", store_history=True)
295 # test '%reset -f out', make an Out prompt
295 # test '%reset -f out', make an Out prompt
296 _ip.run_cell("parrot", store_history=True)
296 _ip.run_cell("parrot", store_history=True)
297 nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
297 nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
298 _ip.magic('reset -f out')
298 _ip.magic('reset -f out')
299 nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
299 nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
300 nt.assert_equal(len(_ip.user_ns['Out']), 0)
300 nt.assert_equal(len(_ip.user_ns['Out']), 0)
301
301
302 def test_reset_in():
302 def test_reset_in():
303 "Test '%reset in' magic"
303 "Test '%reset in' magic"
304 # test '%reset -f in'
304 # test '%reset -f in'
305 _ip.run_cell("parrot", store_history=True)
305 _ip.run_cell("parrot", store_history=True)
306 nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
306 nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
307 _ip.magic('%reset -f in')
307 _ip.magic('%reset -f in')
308 nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
308 nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
309 nt.assert_equal(len(set(_ip.user_ns['In'])), 1)
309 nt.assert_equal(len(set(_ip.user_ns['In'])), 1)
310
310
311 def test_reset_dhist():
311 def test_reset_dhist():
312 "Test '%reset dhist' magic"
312 "Test '%reset dhist' magic"
313 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
313 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
314 _ip.magic('cd ' + os.path.dirname(nt.__file__))
314 _ip.magic('cd ' + os.path.dirname(nt.__file__))
315 _ip.magic('cd -')
315 _ip.magic('cd -')
316 nt.assert_true(len(_ip.user_ns['_dh']) > 0)
316 nt.assert_true(len(_ip.user_ns['_dh']) > 0)
317 _ip.magic('reset -f dhist')
317 _ip.magic('reset -f dhist')
318 nt.assert_equal(len(_ip.user_ns['_dh']), 0)
318 nt.assert_equal(len(_ip.user_ns['_dh']), 0)
319 _ip.run_cell("_dh = [d for d in tmp]") #restore
319 _ip.run_cell("_dh = [d for d in tmp]") #restore
320
320
321 def test_reset_in_length():
321 def test_reset_in_length():
322 "Test that '%reset in' preserves In[] length"
322 "Test that '%reset in' preserves In[] length"
323 _ip.run_cell("print 'foo'")
323 _ip.run_cell("print 'foo'")
324 _ip.run_cell("reset -f in")
324 _ip.run_cell("reset -f in")
325 nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1)
325 nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1)
326
326
327 def test_tb_syntaxerror():
327 def test_tb_syntaxerror():
328 """test %tb after a SyntaxError"""
328 """test %tb after a SyntaxError"""
329 ip = get_ipython()
329 ip = get_ipython()
330 ip.run_cell("for")
330 ip.run_cell("for")
331
331
332 # trap and validate stdout
332 # trap and validate stdout
333 save_stdout = sys.stdout
333 save_stdout = sys.stdout
334 try:
334 try:
335 sys.stdout = StringIO()
335 sys.stdout = StringIO()
336 ip.run_cell("%tb")
336 ip.run_cell("%tb")
337 out = sys.stdout.getvalue()
337 out = sys.stdout.getvalue()
338 finally:
338 finally:
339 sys.stdout = save_stdout
339 sys.stdout = save_stdout
340 # trim output, and only check the last line
340 # trim output, and only check the last line
341 last_line = out.rstrip().splitlines()[-1].strip()
341 last_line = out.rstrip().splitlines()[-1].strip()
342 nt.assert_equal(last_line, "SyntaxError: invalid syntax")
342 nt.assert_equal(last_line, "SyntaxError: invalid syntax")
343
343
344
344
345 def test_time():
345 def test_time():
346 ip = get_ipython()
346 ip = get_ipython()
347
347
348 with tt.AssertPrints("Wall time: "):
348 with tt.AssertPrints("Wall time: "):
349 ip.run_cell("%time None")
349 ip.run_cell("%time None")
350
350
351 ip.run_cell("def f(kmjy):\n"
351 ip.run_cell("def f(kmjy):\n"
352 " %time print (2*kmjy)")
352 " %time print (2*kmjy)")
353
353
354 with tt.AssertPrints("Wall time: "):
354 with tt.AssertPrints("Wall time: "):
355 with tt.AssertPrints("hihi", suppress=False):
355 with tt.AssertPrints("hihi", suppress=False):
356 ip.run_cell("f('hi')")
356 ip.run_cell("f('hi')")
357
357
358
358
359 @dec.skip_win32
359 @dec.skip_win32
360 def test_time2():
360 def test_time2():
361 ip = get_ipython()
361 ip = get_ipython()
362
362
363 with tt.AssertPrints("CPU times: user "):
363 with tt.AssertPrints("CPU times: user "):
364 ip.run_cell("%time None")
364 ip.run_cell("%time None")
365
365
366 def test_time3():
366 def test_time3():
367 """Erroneous magic function calls, issue gh-3334"""
367 """Erroneous magic function calls, issue gh-3334"""
368 ip = get_ipython()
368 ip = get_ipython()
369 ip.user_ns.pop('run', None)
369 ip.user_ns.pop('run', None)
370
370
371 with tt.AssertNotPrints("not found", channel='stderr'):
371 with tt.AssertNotPrints("not found", channel='stderr'):
372 ip.run_cell("%%time\n"
372 ip.run_cell("%%time\n"
373 "run = 0\n"
373 "run = 0\n"
374 "run += 1")
374 "run += 1")
375
375
376 def test_doctest_mode():
376 def test_doctest_mode():
377 "Toggle doctest_mode twice, it should be a no-op and run without error"
377 "Toggle doctest_mode twice, it should be a no-op and run without error"
378 _ip.magic('doctest_mode')
378 _ip.magic('doctest_mode')
379 _ip.magic('doctest_mode')
379 _ip.magic('doctest_mode')
380
380
381
381
382 def test_parse_options():
382 def test_parse_options():
383 """Tests for basic options parsing in magics."""
383 """Tests for basic options parsing in magics."""
384 # These are only the most minimal of tests, more should be added later. At
384 # These are only the most minimal of tests, more should be added later. At
385 # the very least we check that basic text/unicode calls work OK.
385 # the very least we check that basic text/unicode calls work OK.
386 m = DummyMagics(_ip)
386 m = DummyMagics(_ip)
387 nt.assert_equal(m.parse_options('foo', '')[1], 'foo')
387 nt.assert_equal(m.parse_options('foo', '')[1], 'foo')
388 nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo')
388 nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo')
389
389
390
390
391 def test_dirops():
391 def test_dirops():
392 """Test various directory handling operations."""
392 """Test various directory handling operations."""
393 # curpath = lambda :os.path.splitdrive(os.getcwdu())[1].replace('\\','/')
393 # curpath = lambda :os.path.splitdrive(py3compat.getcwd())[1].replace('\\','/')
394 curpath = os.getcwdu
394 curpath = py3compat.getcwd
395 startdir = os.getcwdu()
395 startdir = py3compat.getcwd()
396 ipdir = os.path.realpath(_ip.ipython_dir)
396 ipdir = os.path.realpath(_ip.ipython_dir)
397 try:
397 try:
398 _ip.magic('cd "%s"' % ipdir)
398 _ip.magic('cd "%s"' % ipdir)
399 nt.assert_equal(curpath(), ipdir)
399 nt.assert_equal(curpath(), ipdir)
400 _ip.magic('cd -')
400 _ip.magic('cd -')
401 nt.assert_equal(curpath(), startdir)
401 nt.assert_equal(curpath(), startdir)
402 _ip.magic('pushd "%s"' % ipdir)
402 _ip.magic('pushd "%s"' % ipdir)
403 nt.assert_equal(curpath(), ipdir)
403 nt.assert_equal(curpath(), ipdir)
404 _ip.magic('popd')
404 _ip.magic('popd')
405 nt.assert_equal(curpath(), startdir)
405 nt.assert_equal(curpath(), startdir)
406 finally:
406 finally:
407 os.chdir(startdir)
407 os.chdir(startdir)
408
408
409
409
410 def test_xmode():
410 def test_xmode():
411 # Calling xmode three times should be a no-op
411 # Calling xmode three times should be a no-op
412 xmode = _ip.InteractiveTB.mode
412 xmode = _ip.InteractiveTB.mode
413 for i in range(3):
413 for i in range(3):
414 _ip.magic("xmode")
414 _ip.magic("xmode")
415 nt.assert_equal(_ip.InteractiveTB.mode, xmode)
415 nt.assert_equal(_ip.InteractiveTB.mode, xmode)
416
416
417 def test_reset_hard():
417 def test_reset_hard():
418 monitor = []
418 monitor = []
419 class A(object):
419 class A(object):
420 def __del__(self):
420 def __del__(self):
421 monitor.append(1)
421 monitor.append(1)
422 def __repr__(self):
422 def __repr__(self):
423 return "<A instance>"
423 return "<A instance>"
424
424
425 _ip.user_ns["a"] = A()
425 _ip.user_ns["a"] = A()
426 _ip.run_cell("a")
426 _ip.run_cell("a")
427
427
428 nt.assert_equal(monitor, [])
428 nt.assert_equal(monitor, [])
429 _ip.magic("reset -f")
429 _ip.magic("reset -f")
430 nt.assert_equal(monitor, [1])
430 nt.assert_equal(monitor, [1])
431
431
432 class TestXdel(tt.TempFileMixin):
432 class TestXdel(tt.TempFileMixin):
433 def test_xdel(self):
433 def test_xdel(self):
434 """Test that references from %run are cleared by xdel."""
434 """Test that references from %run are cleared by xdel."""
435 src = ("class A(object):\n"
435 src = ("class A(object):\n"
436 " monitor = []\n"
436 " monitor = []\n"
437 " def __del__(self):\n"
437 " def __del__(self):\n"
438 " self.monitor.append(1)\n"
438 " self.monitor.append(1)\n"
439 "a = A()\n")
439 "a = A()\n")
440 self.mktmp(src)
440 self.mktmp(src)
441 # %run creates some hidden references...
441 # %run creates some hidden references...
442 _ip.magic("run %s" % self.fname)
442 _ip.magic("run %s" % self.fname)
443 # ... as does the displayhook.
443 # ... as does the displayhook.
444 _ip.run_cell("a")
444 _ip.run_cell("a")
445
445
446 monitor = _ip.user_ns["A"].monitor
446 monitor = _ip.user_ns["A"].monitor
447 nt.assert_equal(monitor, [])
447 nt.assert_equal(monitor, [])
448
448
449 _ip.magic("xdel a")
449 _ip.magic("xdel a")
450
450
451 # Check that a's __del__ method has been called.
451 # Check that a's __del__ method has been called.
452 nt.assert_equal(monitor, [1])
452 nt.assert_equal(monitor, [1])
453
453
454 def doctest_who():
454 def doctest_who():
455 """doctest for %who
455 """doctest for %who
456
456
457 In [1]: %reset -f
457 In [1]: %reset -f
458
458
459 In [2]: alpha = 123
459 In [2]: alpha = 123
460
460
461 In [3]: beta = 'beta'
461 In [3]: beta = 'beta'
462
462
463 In [4]: %who int
463 In [4]: %who int
464 alpha
464 alpha
465
465
466 In [5]: %who str
466 In [5]: %who str
467 beta
467 beta
468
468
469 In [6]: %whos
469 In [6]: %whos
470 Variable Type Data/Info
470 Variable Type Data/Info
471 ----------------------------
471 ----------------------------
472 alpha int 123
472 alpha int 123
473 beta str beta
473 beta str beta
474
474
475 In [7]: %who_ls
475 In [7]: %who_ls
476 Out[7]: ['alpha', 'beta']
476 Out[7]: ['alpha', 'beta']
477 """
477 """
478
478
479 def test_whos():
479 def test_whos():
480 """Check that whos is protected against objects where repr() fails."""
480 """Check that whos is protected against objects where repr() fails."""
481 class A(object):
481 class A(object):
482 def __repr__(self):
482 def __repr__(self):
483 raise Exception()
483 raise Exception()
484 _ip.user_ns['a'] = A()
484 _ip.user_ns['a'] = A()
485 _ip.magic("whos")
485 _ip.magic("whos")
486
486
487 @py3compat.u_format
487 @py3compat.u_format
488 def doctest_precision():
488 def doctest_precision():
489 """doctest for %precision
489 """doctest for %precision
490
490
491 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
491 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
492
492
493 In [2]: %precision 5
493 In [2]: %precision 5
494 Out[2]: {u}'%.5f'
494 Out[2]: {u}'%.5f'
495
495
496 In [3]: f.float_format
496 In [3]: f.float_format
497 Out[3]: {u}'%.5f'
497 Out[3]: {u}'%.5f'
498
498
499 In [4]: %precision %e
499 In [4]: %precision %e
500 Out[4]: {u}'%e'
500 Out[4]: {u}'%e'
501
501
502 In [5]: f(3.1415927)
502 In [5]: f(3.1415927)
503 Out[5]: {u}'3.141593e+00'
503 Out[5]: {u}'3.141593e+00'
504 """
504 """
505
505
506 def test_psearch():
506 def test_psearch():
507 with tt.AssertPrints("dict.fromkeys"):
507 with tt.AssertPrints("dict.fromkeys"):
508 _ip.run_cell("dict.fr*?")
508 _ip.run_cell("dict.fr*?")
509
509
510 def test_timeit_shlex():
510 def test_timeit_shlex():
511 """test shlex issues with timeit (#1109)"""
511 """test shlex issues with timeit (#1109)"""
512 _ip.ex("def f(*a,**kw): pass")
512 _ip.ex("def f(*a,**kw): pass")
513 _ip.magic('timeit -n1 "this is a bug".count(" ")')
513 _ip.magic('timeit -n1 "this is a bug".count(" ")')
514 _ip.magic('timeit -r1 -n1 f(" ", 1)')
514 _ip.magic('timeit -r1 -n1 f(" ", 1)')
515 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
515 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
516 _ip.magic('timeit -r1 -n1 ("a " + "b")')
516 _ip.magic('timeit -r1 -n1 ("a " + "b")')
517 _ip.magic('timeit -r1 -n1 f("a " + "b")')
517 _ip.magic('timeit -r1 -n1 f("a " + "b")')
518 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
518 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
519
519
520
520
521 def test_timeit_arguments():
521 def test_timeit_arguments():
522 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
522 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
523 _ip.magic("timeit ('#')")
523 _ip.magic("timeit ('#')")
524
524
525
525
526 def test_timeit_special_syntax():
526 def test_timeit_special_syntax():
527 "Test %%timeit with IPython special syntax"
527 "Test %%timeit with IPython special syntax"
528 @register_line_magic
528 @register_line_magic
529 def lmagic(line):
529 def lmagic(line):
530 ip = get_ipython()
530 ip = get_ipython()
531 ip.user_ns['lmagic_out'] = line
531 ip.user_ns['lmagic_out'] = line
532
532
533 # line mode test
533 # line mode test
534 _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line')
534 _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line')
535 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
535 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
536 # cell mode test
536 # cell mode test
537 _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2')
537 _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2')
538 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
538 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
539
539
540 def test_timeit_return():
540 def test_timeit_return():
541 """
541 """
542 test wether timeit -o return object
542 test wether timeit -o return object
543 """
543 """
544
544
545 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
545 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
546 assert(res is not None)
546 assert(res is not None)
547
547
548 def test_timeit_quiet():
548 def test_timeit_quiet():
549 """
549 """
550 test quiet option of timeit magic
550 test quiet option of timeit magic
551 """
551 """
552 with tt.AssertNotPrints("loops"):
552 with tt.AssertNotPrints("loops"):
553 _ip.run_cell("%timeit -n1 -r1 -q 1")
553 _ip.run_cell("%timeit -n1 -r1 -q 1")
554
554
555 @dec.skipif(execution.profile is None)
555 @dec.skipif(execution.profile is None)
556 def test_prun_special_syntax():
556 def test_prun_special_syntax():
557 "Test %%prun with IPython special syntax"
557 "Test %%prun with IPython special syntax"
558 @register_line_magic
558 @register_line_magic
559 def lmagic(line):
559 def lmagic(line):
560 ip = get_ipython()
560 ip = get_ipython()
561 ip.user_ns['lmagic_out'] = line
561 ip.user_ns['lmagic_out'] = line
562
562
563 # line mode test
563 # line mode test
564 _ip.run_line_magic('prun', '-q %lmagic my line')
564 _ip.run_line_magic('prun', '-q %lmagic my line')
565 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
565 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
566 # cell mode test
566 # cell mode test
567 _ip.run_cell_magic('prun', '-q', '%lmagic my line2')
567 _ip.run_cell_magic('prun', '-q', '%lmagic my line2')
568 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
568 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
569
569
570 @dec.skipif(execution.profile is None)
570 @dec.skipif(execution.profile is None)
571 def test_prun_quotes():
571 def test_prun_quotes():
572 "Test that prun does not clobber string escapes (GH #1302)"
572 "Test that prun does not clobber string escapes (GH #1302)"
573 _ip.magic(r"prun -q x = '\t'")
573 _ip.magic(r"prun -q x = '\t'")
574 nt.assert_equal(_ip.user_ns['x'], '\t')
574 nt.assert_equal(_ip.user_ns['x'], '\t')
575
575
576 def test_extension():
576 def test_extension():
577 tmpdir = TemporaryDirectory()
577 tmpdir = TemporaryDirectory()
578 orig_ipython_dir = _ip.ipython_dir
578 orig_ipython_dir = _ip.ipython_dir
579 try:
579 try:
580 _ip.ipython_dir = tmpdir.name
580 _ip.ipython_dir = tmpdir.name
581 nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension")
581 nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension")
582 url = os.path.join(os.path.dirname(__file__), "daft_extension.py")
582 url = os.path.join(os.path.dirname(__file__), "daft_extension.py")
583 _ip.magic("install_ext %s" % url)
583 _ip.magic("install_ext %s" % url)
584 _ip.user_ns.pop('arq', None)
584 _ip.user_ns.pop('arq', None)
585 invalidate_caches() # Clear import caches
585 invalidate_caches() # Clear import caches
586 _ip.magic("load_ext daft_extension")
586 _ip.magic("load_ext daft_extension")
587 nt.assert_equal(_ip.user_ns['arq'], 185)
587 nt.assert_equal(_ip.user_ns['arq'], 185)
588 _ip.magic("unload_ext daft_extension")
588 _ip.magic("unload_ext daft_extension")
589 assert 'arq' not in _ip.user_ns
589 assert 'arq' not in _ip.user_ns
590 finally:
590 finally:
591 _ip.ipython_dir = orig_ipython_dir
591 _ip.ipython_dir = orig_ipython_dir
592 tmpdir.cleanup()
592 tmpdir.cleanup()
593
593
594 def test_notebook_export_json():
594 def test_notebook_export_json():
595 with TemporaryDirectory() as td:
595 with TemporaryDirectory() as td:
596 outfile = os.path.join(td, "nb.ipynb")
596 outfile = os.path.join(td, "nb.ipynb")
597 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
597 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
598 _ip.magic("notebook -e %s" % outfile)
598 _ip.magic("notebook -e %s" % outfile)
599
599
600 def test_notebook_export_py():
600 def test_notebook_export_py():
601 with TemporaryDirectory() as td:
601 with TemporaryDirectory() as td:
602 outfile = os.path.join(td, "nb.py")
602 outfile = os.path.join(td, "nb.py")
603 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
603 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
604 _ip.magic("notebook -e %s" % outfile)
604 _ip.magic("notebook -e %s" % outfile)
605
605
606 def test_notebook_reformat_py():
606 def test_notebook_reformat_py():
607 with TemporaryDirectory() as td:
607 with TemporaryDirectory() as td:
608 infile = os.path.join(td, "nb.ipynb")
608 infile = os.path.join(td, "nb.ipynb")
609 with io.open(infile, 'w', encoding='utf-8') as f:
609 with io.open(infile, 'w', encoding='utf-8') as f:
610 current.write(nb0, f, 'json')
610 current.write(nb0, f, 'json')
611
611
612 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
612 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
613 _ip.magic("notebook -f py %s" % infile)
613 _ip.magic("notebook -f py %s" % infile)
614
614
615 def test_notebook_reformat_json():
615 def test_notebook_reformat_json():
616 with TemporaryDirectory() as td:
616 with TemporaryDirectory() as td:
617 infile = os.path.join(td, "nb.py")
617 infile = os.path.join(td, "nb.py")
618 with io.open(infile, 'w', encoding='utf-8') as f:
618 with io.open(infile, 'w', encoding='utf-8') as f:
619 current.write(nb0, f, 'py')
619 current.write(nb0, f, 'py')
620
620
621 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
621 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
622 _ip.magic("notebook -f ipynb %s" % infile)
622 _ip.magic("notebook -f ipynb %s" % infile)
623 _ip.magic("notebook -f json %s" % infile)
623 _ip.magic("notebook -f json %s" % infile)
624
624
625 def test_env():
625 def test_env():
626 env = _ip.magic("env")
626 env = _ip.magic("env")
627 assert isinstance(env, dict), type(env)
627 assert isinstance(env, dict), type(env)
628
628
629
629
630 class CellMagicTestCase(TestCase):
630 class CellMagicTestCase(TestCase):
631
631
632 def check_ident(self, magic):
632 def check_ident(self, magic):
633 # Manually called, we get the result
633 # Manually called, we get the result
634 out = _ip.run_cell_magic(magic, 'a', 'b')
634 out = _ip.run_cell_magic(magic, 'a', 'b')
635 nt.assert_equal(out, ('a','b'))
635 nt.assert_equal(out, ('a','b'))
636 # Via run_cell, it goes into the user's namespace via displayhook
636 # Via run_cell, it goes into the user's namespace via displayhook
637 _ip.run_cell('%%' + magic +' c\nd')
637 _ip.run_cell('%%' + magic +' c\nd')
638 nt.assert_equal(_ip.user_ns['_'], ('c','d'))
638 nt.assert_equal(_ip.user_ns['_'], ('c','d'))
639
639
640 def test_cell_magic_func_deco(self):
640 def test_cell_magic_func_deco(self):
641 "Cell magic using simple decorator"
641 "Cell magic using simple decorator"
642 @register_cell_magic
642 @register_cell_magic
643 def cellm(line, cell):
643 def cellm(line, cell):
644 return line, cell
644 return line, cell
645
645
646 self.check_ident('cellm')
646 self.check_ident('cellm')
647
647
648 def test_cell_magic_reg(self):
648 def test_cell_magic_reg(self):
649 "Cell magic manually registered"
649 "Cell magic manually registered"
650 def cellm(line, cell):
650 def cellm(line, cell):
651 return line, cell
651 return line, cell
652
652
653 _ip.register_magic_function(cellm, 'cell', 'cellm2')
653 _ip.register_magic_function(cellm, 'cell', 'cellm2')
654 self.check_ident('cellm2')
654 self.check_ident('cellm2')
655
655
656 def test_cell_magic_class(self):
656 def test_cell_magic_class(self):
657 "Cell magics declared via a class"
657 "Cell magics declared via a class"
658 @magics_class
658 @magics_class
659 class MyMagics(Magics):
659 class MyMagics(Magics):
660
660
661 @cell_magic
661 @cell_magic
662 def cellm3(self, line, cell):
662 def cellm3(self, line, cell):
663 return line, cell
663 return line, cell
664
664
665 _ip.register_magics(MyMagics)
665 _ip.register_magics(MyMagics)
666 self.check_ident('cellm3')
666 self.check_ident('cellm3')
667
667
668 def test_cell_magic_class2(self):
668 def test_cell_magic_class2(self):
669 "Cell magics declared via a class, #2"
669 "Cell magics declared via a class, #2"
670 @magics_class
670 @magics_class
671 class MyMagics2(Magics):
671 class MyMagics2(Magics):
672
672
673 @cell_magic('cellm4')
673 @cell_magic('cellm4')
674 def cellm33(self, line, cell):
674 def cellm33(self, line, cell):
675 return line, cell
675 return line, cell
676
676
677 _ip.register_magics(MyMagics2)
677 _ip.register_magics(MyMagics2)
678 self.check_ident('cellm4')
678 self.check_ident('cellm4')
679 # Check that nothing is registered as 'cellm33'
679 # Check that nothing is registered as 'cellm33'
680 c33 = _ip.find_cell_magic('cellm33')
680 c33 = _ip.find_cell_magic('cellm33')
681 nt.assert_equal(c33, None)
681 nt.assert_equal(c33, None)
682
682
683 def test_file():
683 def test_file():
684 """Basic %%file"""
684 """Basic %%file"""
685 ip = get_ipython()
685 ip = get_ipython()
686 with TemporaryDirectory() as td:
686 with TemporaryDirectory() as td:
687 fname = os.path.join(td, 'file1')
687 fname = os.path.join(td, 'file1')
688 ip.run_cell_magic("file", fname, u'\n'.join([
688 ip.run_cell_magic("file", fname, u'\n'.join([
689 'line1',
689 'line1',
690 'line2',
690 'line2',
691 ]))
691 ]))
692 with open(fname) as f:
692 with open(fname) as f:
693 s = f.read()
693 s = f.read()
694 nt.assert_in('line1\n', s)
694 nt.assert_in('line1\n', s)
695 nt.assert_in('line2', s)
695 nt.assert_in('line2', s)
696
696
697 def test_file_var_expand():
697 def test_file_var_expand():
698 """%%file $filename"""
698 """%%file $filename"""
699 ip = get_ipython()
699 ip = get_ipython()
700 with TemporaryDirectory() as td:
700 with TemporaryDirectory() as td:
701 fname = os.path.join(td, 'file1')
701 fname = os.path.join(td, 'file1')
702 ip.user_ns['filename'] = fname
702 ip.user_ns['filename'] = fname
703 ip.run_cell_magic("file", '$filename', u'\n'.join([
703 ip.run_cell_magic("file", '$filename', u'\n'.join([
704 'line1',
704 'line1',
705 'line2',
705 'line2',
706 ]))
706 ]))
707 with open(fname) as f:
707 with open(fname) as f:
708 s = f.read()
708 s = f.read()
709 nt.assert_in('line1\n', s)
709 nt.assert_in('line1\n', s)
710 nt.assert_in('line2', s)
710 nt.assert_in('line2', s)
711
711
712 def test_file_unicode():
712 def test_file_unicode():
713 """%%file with unicode cell"""
713 """%%file with unicode cell"""
714 ip = get_ipython()
714 ip = get_ipython()
715 with TemporaryDirectory() as td:
715 with TemporaryDirectory() as td:
716 fname = os.path.join(td, 'file1')
716 fname = os.path.join(td, 'file1')
717 ip.run_cell_magic("file", fname, u'\n'.join([
717 ip.run_cell_magic("file", fname, u'\n'.join([
718 u'linΓ©1',
718 u'linΓ©1',
719 u'linΓ©2',
719 u'linΓ©2',
720 ]))
720 ]))
721 with io.open(fname, encoding='utf-8') as f:
721 with io.open(fname, encoding='utf-8') as f:
722 s = f.read()
722 s = f.read()
723 nt.assert_in(u'linΓ©1\n', s)
723 nt.assert_in(u'linΓ©1\n', s)
724 nt.assert_in(u'linΓ©2', s)
724 nt.assert_in(u'linΓ©2', s)
725
725
726 def test_file_amend():
726 def test_file_amend():
727 """%%file -a amends files"""
727 """%%file -a amends files"""
728 ip = get_ipython()
728 ip = get_ipython()
729 with TemporaryDirectory() as td:
729 with TemporaryDirectory() as td:
730 fname = os.path.join(td, 'file2')
730 fname = os.path.join(td, 'file2')
731 ip.run_cell_magic("file", fname, u'\n'.join([
731 ip.run_cell_magic("file", fname, u'\n'.join([
732 'line1',
732 'line1',
733 'line2',
733 'line2',
734 ]))
734 ]))
735 ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([
735 ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([
736 'line3',
736 'line3',
737 'line4',
737 'line4',
738 ]))
738 ]))
739 with open(fname) as f:
739 with open(fname) as f:
740 s = f.read()
740 s = f.read()
741 nt.assert_in('line1\n', s)
741 nt.assert_in('line1\n', s)
742 nt.assert_in('line3\n', s)
742 nt.assert_in('line3\n', s)
743
743
744
744
745 def test_script_config():
745 def test_script_config():
746 ip = get_ipython()
746 ip = get_ipython()
747 ip.config.ScriptMagics.script_magics = ['whoda']
747 ip.config.ScriptMagics.script_magics = ['whoda']
748 sm = script.ScriptMagics(shell=ip)
748 sm = script.ScriptMagics(shell=ip)
749 nt.assert_in('whoda', sm.magics['cell'])
749 nt.assert_in('whoda', sm.magics['cell'])
750
750
751 @dec.skip_win32
751 @dec.skip_win32
752 def test_script_out():
752 def test_script_out():
753 ip = get_ipython()
753 ip = get_ipython()
754 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
754 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
755 nt.assert_equal(ip.user_ns['output'], 'hi\n')
755 nt.assert_equal(ip.user_ns['output'], 'hi\n')
756
756
757 @dec.skip_win32
757 @dec.skip_win32
758 def test_script_err():
758 def test_script_err():
759 ip = get_ipython()
759 ip = get_ipython()
760 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
760 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
761 nt.assert_equal(ip.user_ns['error'], 'hello\n')
761 nt.assert_equal(ip.user_ns['error'], 'hello\n')
762
762
763 @dec.skip_win32
763 @dec.skip_win32
764 def test_script_out_err():
764 def test_script_out_err():
765 ip = get_ipython()
765 ip = get_ipython()
766 ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2")
766 ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2")
767 nt.assert_equal(ip.user_ns['output'], 'hi\n')
767 nt.assert_equal(ip.user_ns['output'], 'hi\n')
768 nt.assert_equal(ip.user_ns['error'], 'hello\n')
768 nt.assert_equal(ip.user_ns['error'], 'hello\n')
769
769
770 @dec.skip_win32
770 @dec.skip_win32
771 def test_script_bg_out():
771 def test_script_bg_out():
772 ip = get_ipython()
772 ip = get_ipython()
773 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
773 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
774 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
774 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
775
775
776 @dec.skip_win32
776 @dec.skip_win32
777 def test_script_bg_err():
777 def test_script_bg_err():
778 ip = get_ipython()
778 ip = get_ipython()
779 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
779 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
780 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
780 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
781
781
782 @dec.skip_win32
782 @dec.skip_win32
783 def test_script_bg_out_err():
783 def test_script_bg_out_err():
784 ip = get_ipython()
784 ip = get_ipython()
785 ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2")
785 ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2")
786 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
786 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
787 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
787 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
788
788
789 def test_script_defaults():
789 def test_script_defaults():
790 ip = get_ipython()
790 ip = get_ipython()
791 for cmd in ['sh', 'bash', 'perl', 'ruby']:
791 for cmd in ['sh', 'bash', 'perl', 'ruby']:
792 try:
792 try:
793 find_cmd(cmd)
793 find_cmd(cmd)
794 except Exception:
794 except Exception:
795 pass
795 pass
796 else:
796 else:
797 nt.assert_in(cmd, ip.magics_manager.magics['cell'])
797 nt.assert_in(cmd, ip.magics_manager.magics['cell'])
798
798
799
799
800 @magics_class
800 @magics_class
801 class FooFoo(Magics):
801 class FooFoo(Magics):
802 """class with both %foo and %%foo magics"""
802 """class with both %foo and %%foo magics"""
803 @line_magic('foo')
803 @line_magic('foo')
804 def line_foo(self, line):
804 def line_foo(self, line):
805 "I am line foo"
805 "I am line foo"
806 pass
806 pass
807
807
808 @cell_magic("foo")
808 @cell_magic("foo")
809 def cell_foo(self, line, cell):
809 def cell_foo(self, line, cell):
810 "I am cell foo, not line foo"
810 "I am cell foo, not line foo"
811 pass
811 pass
812
812
813 def test_line_cell_info():
813 def test_line_cell_info():
814 """%%foo and %foo magics are distinguishable to inspect"""
814 """%%foo and %foo magics are distinguishable to inspect"""
815 ip = get_ipython()
815 ip = get_ipython()
816 ip.magics_manager.register(FooFoo)
816 ip.magics_manager.register(FooFoo)
817 oinfo = ip.object_inspect('foo')
817 oinfo = ip.object_inspect('foo')
818 nt.assert_true(oinfo['found'])
818 nt.assert_true(oinfo['found'])
819 nt.assert_true(oinfo['ismagic'])
819 nt.assert_true(oinfo['ismagic'])
820
820
821 oinfo = ip.object_inspect('%%foo')
821 oinfo = ip.object_inspect('%%foo')
822 nt.assert_true(oinfo['found'])
822 nt.assert_true(oinfo['found'])
823 nt.assert_true(oinfo['ismagic'])
823 nt.assert_true(oinfo['ismagic'])
824 nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__)
824 nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__)
825
825
826 oinfo = ip.object_inspect('%foo')
826 oinfo = ip.object_inspect('%foo')
827 nt.assert_true(oinfo['found'])
827 nt.assert_true(oinfo['found'])
828 nt.assert_true(oinfo['ismagic'])
828 nt.assert_true(oinfo['ismagic'])
829 nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__)
829 nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__)
830
830
831 def test_multiple_magics():
831 def test_multiple_magics():
832 ip = get_ipython()
832 ip = get_ipython()
833 foo1 = FooFoo(ip)
833 foo1 = FooFoo(ip)
834 foo2 = FooFoo(ip)
834 foo2 = FooFoo(ip)
835 mm = ip.magics_manager
835 mm = ip.magics_manager
836 mm.register(foo1)
836 mm.register(foo1)
837 nt.assert_true(mm.magics['line']['foo'].__self__ is foo1)
837 nt.assert_true(mm.magics['line']['foo'].__self__ is foo1)
838 mm.register(foo2)
838 mm.register(foo2)
839 nt.assert_true(mm.magics['line']['foo'].__self__ is foo2)
839 nt.assert_true(mm.magics['line']['foo'].__self__ is foo2)
840
840
841 def test_alias_magic():
841 def test_alias_magic():
842 """Test %alias_magic."""
842 """Test %alias_magic."""
843 ip = get_ipython()
843 ip = get_ipython()
844 mm = ip.magics_manager
844 mm = ip.magics_manager
845
845
846 # Basic operation: both cell and line magics are created, if possible.
846 # Basic operation: both cell and line magics are created, if possible.
847 ip.run_line_magic('alias_magic', 'timeit_alias timeit')
847 ip.run_line_magic('alias_magic', 'timeit_alias timeit')
848 nt.assert_in('timeit_alias', mm.magics['line'])
848 nt.assert_in('timeit_alias', mm.magics['line'])
849 nt.assert_in('timeit_alias', mm.magics['cell'])
849 nt.assert_in('timeit_alias', mm.magics['cell'])
850
850
851 # --cell is specified, line magic not created.
851 # --cell is specified, line magic not created.
852 ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit')
852 ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit')
853 nt.assert_not_in('timeit_cell_alias', mm.magics['line'])
853 nt.assert_not_in('timeit_cell_alias', mm.magics['line'])
854 nt.assert_in('timeit_cell_alias', mm.magics['cell'])
854 nt.assert_in('timeit_cell_alias', mm.magics['cell'])
855
855
856 # Test that line alias is created successfully.
856 # Test that line alias is created successfully.
857 ip.run_line_magic('alias_magic', '--line env_alias env')
857 ip.run_line_magic('alias_magic', '--line env_alias env')
858 nt.assert_equal(ip.run_line_magic('env', ''),
858 nt.assert_equal(ip.run_line_magic('env', ''),
859 ip.run_line_magic('env_alias', ''))
859 ip.run_line_magic('env_alias', ''))
860
860
861 def test_save():
861 def test_save():
862 """Test %save."""
862 """Test %save."""
863 ip = get_ipython()
863 ip = get_ipython()
864 ip.history_manager.reset() # Clear any existing history.
864 ip.history_manager.reset() # Clear any existing history.
865 cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"]
865 cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"]
866 for i, cmd in enumerate(cmds, start=1):
866 for i, cmd in enumerate(cmds, start=1):
867 ip.history_manager.store_inputs(i, cmd)
867 ip.history_manager.store_inputs(i, cmd)
868 with TemporaryDirectory() as tmpdir:
868 with TemporaryDirectory() as tmpdir:
869 file = os.path.join(tmpdir, "testsave.py")
869 file = os.path.join(tmpdir, "testsave.py")
870 ip.run_line_magic("save", "%s 1-10" % file)
870 ip.run_line_magic("save", "%s 1-10" % file)
871 with open(file) as f:
871 with open(file) as f:
872 content = f.read()
872 content = f.read()
873 nt.assert_equal(content.count(cmds[0]), 1)
873 nt.assert_equal(content.count(cmds[0]), 1)
874 nt.assert_in('coding: utf-8', content)
874 nt.assert_in('coding: utf-8', content)
875 ip.run_line_magic("save", "-a %s 1-10" % file)
875 ip.run_line_magic("save", "-a %s 1-10" % file)
876 with open(file) as f:
876 with open(file) as f:
877 content = f.read()
877 content = f.read()
878 nt.assert_equal(content.count(cmds[0]), 2)
878 nt.assert_equal(content.count(cmds[0]), 2)
879 nt.assert_in('coding: utf-8', content)
879 nt.assert_in('coding: utf-8', content)
880
880
881
881
882 def test_store():
882 def test_store():
883 """Test %store."""
883 """Test %store."""
884 ip = get_ipython()
884 ip = get_ipython()
885 ip.run_line_magic('load_ext', 'storemagic')
885 ip.run_line_magic('load_ext', 'storemagic')
886
886
887 # make sure the storage is empty
887 # make sure the storage is empty
888 ip.run_line_magic('store', '-z')
888 ip.run_line_magic('store', '-z')
889 ip.user_ns['var'] = 42
889 ip.user_ns['var'] = 42
890 ip.run_line_magic('store', 'var')
890 ip.run_line_magic('store', 'var')
891 ip.user_ns['var'] = 39
891 ip.user_ns['var'] = 39
892 ip.run_line_magic('store', '-r')
892 ip.run_line_magic('store', '-r')
893 nt.assert_equal(ip.user_ns['var'], 42)
893 nt.assert_equal(ip.user_ns['var'], 42)
894
894
895 ip.run_line_magic('store', '-d var')
895 ip.run_line_magic('store', '-d var')
896 ip.user_ns['var'] = 39
896 ip.user_ns['var'] = 39
897 ip.run_line_magic('store' , '-r')
897 ip.run_line_magic('store' , '-r')
898 nt.assert_equal(ip.user_ns['var'], 39)
898 nt.assert_equal(ip.user_ns['var'], 39)
899
899
900
900
901 def _run_edit_test(arg_s, exp_filename=None,
901 def _run_edit_test(arg_s, exp_filename=None,
902 exp_lineno=-1,
902 exp_lineno=-1,
903 exp_contents=None,
903 exp_contents=None,
904 exp_is_temp=None):
904 exp_is_temp=None):
905 ip = get_ipython()
905 ip = get_ipython()
906 M = code.CodeMagics(ip)
906 M = code.CodeMagics(ip)
907 last_call = ['','']
907 last_call = ['','']
908 opts,args = M.parse_options(arg_s,'prxn:')
908 opts,args = M.parse_options(arg_s,'prxn:')
909 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
909 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
910
910
911 if exp_filename is not None:
911 if exp_filename is not None:
912 nt.assert_equal(exp_filename, filename)
912 nt.assert_equal(exp_filename, filename)
913 if exp_contents is not None:
913 if exp_contents is not None:
914 with io.open(filename, 'r') as f:
914 with io.open(filename, 'r') as f:
915 contents = f.read()
915 contents = f.read()
916 nt.assert_equal(exp_contents, contents)
916 nt.assert_equal(exp_contents, contents)
917 if exp_lineno != -1:
917 if exp_lineno != -1:
918 nt.assert_equal(exp_lineno, lineno)
918 nt.assert_equal(exp_lineno, lineno)
919 if exp_is_temp is not None:
919 if exp_is_temp is not None:
920 nt.assert_equal(exp_is_temp, is_temp)
920 nt.assert_equal(exp_is_temp, is_temp)
921
921
922
922
923 def test_edit_interactive():
923 def test_edit_interactive():
924 """%edit on interactively defined objects"""
924 """%edit on interactively defined objects"""
925 ip = get_ipython()
925 ip = get_ipython()
926 n = ip.execution_count
926 n = ip.execution_count
927 ip.run_cell(u"def foo(): return 1", store_history=True)
927 ip.run_cell(u"def foo(): return 1", store_history=True)
928
928
929 try:
929 try:
930 _run_edit_test("foo")
930 _run_edit_test("foo")
931 except code.InteractivelyDefined as e:
931 except code.InteractivelyDefined as e:
932 nt.assert_equal(e.index, n)
932 nt.assert_equal(e.index, n)
933 else:
933 else:
934 raise AssertionError("Should have raised InteractivelyDefined")
934 raise AssertionError("Should have raised InteractivelyDefined")
935
935
936
936
937 def test_edit_cell():
937 def test_edit_cell():
938 """%edit [cell id]"""
938 """%edit [cell id]"""
939 ip = get_ipython()
939 ip = get_ipython()
940
940
941 ip.run_cell(u"def foo(): return 1", store_history=True)
941 ip.run_cell(u"def foo(): return 1", store_history=True)
942
942
943 # test
943 # test
944 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
944 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
@@ -1,111 +1,112 b''
1 # -*- coding: utf-8
1 # -*- coding: utf-8
2 """Tests for prompt generation."""
2 """Tests for prompt generation."""
3
3
4 import unittest
4 import unittest
5
5
6 import os
6 import os
7
7
8 from IPython.testing import tools as tt, decorators as dec
8 from IPython.testing import tools as tt, decorators as dec
9 from IPython.core.prompts import PromptManager, LazyEvaluate
9 from IPython.core.prompts import PromptManager, LazyEvaluate
10 from IPython.testing.globalipapp import get_ipython
10 from IPython.testing.globalipapp import get_ipython
11 from IPython.utils.tempdir import TemporaryDirectory
11 from IPython.utils.tempdir import TemporaryDirectory
12 from IPython.utils import py3compat
12 from IPython.utils.py3compat import unicode_type
13 from IPython.utils.py3compat import unicode_type
13
14
14 ip = get_ipython()
15 ip = get_ipython()
15
16
16
17
17 class PromptTests(unittest.TestCase):
18 class PromptTests(unittest.TestCase):
18 def setUp(self):
19 def setUp(self):
19 self.pm = PromptManager(shell=ip, config=ip.config)
20 self.pm = PromptManager(shell=ip, config=ip.config)
20
21
21 def test_multiline_prompt(self):
22 def test_multiline_prompt(self):
22 self.pm.in_template = "[In]\n>>>"
23 self.pm.in_template = "[In]\n>>>"
23 self.pm.render('in')
24 self.pm.render('in')
24 self.assertEqual(self.pm.width, 3)
25 self.assertEqual(self.pm.width, 3)
25 self.assertEqual(self.pm.txtwidth, 3)
26 self.assertEqual(self.pm.txtwidth, 3)
26
27
27 self.pm.in_template = '[In]\n'
28 self.pm.in_template = '[In]\n'
28 self.pm.render('in')
29 self.pm.render('in')
29 self.assertEqual(self.pm.width, 0)
30 self.assertEqual(self.pm.width, 0)
30 self.assertEqual(self.pm.txtwidth, 0)
31 self.assertEqual(self.pm.txtwidth, 0)
31
32
32 def test_translate_abbreviations(self):
33 def test_translate_abbreviations(self):
33 def do_translate(template):
34 def do_translate(template):
34 self.pm.in_template = template
35 self.pm.in_template = template
35 return self.pm.templates['in']
36 return self.pm.templates['in']
36
37
37 pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'),
38 pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'),
38 (r'\T', '{time}'),
39 (r'\T', '{time}'),
39 (r'\n', '\n')
40 (r'\n', '\n')
40 ]
41 ]
41
42
42 tt.check_pairs(do_translate, pairs)
43 tt.check_pairs(do_translate, pairs)
43
44
44 def test_user_ns(self):
45 def test_user_ns(self):
45 self.pm.color_scheme = 'NoColor'
46 self.pm.color_scheme = 'NoColor'
46 ip.ex("foo='bar'")
47 ip.ex("foo='bar'")
47 self.pm.in_template = "In [{foo}]"
48 self.pm.in_template = "In [{foo}]"
48 prompt = self.pm.render('in')
49 prompt = self.pm.render('in')
49 self.assertEqual(prompt, u'In [bar]')
50 self.assertEqual(prompt, u'In [bar]')
50
51
51 def test_builtins(self):
52 def test_builtins(self):
52 self.pm.color_scheme = 'NoColor'
53 self.pm.color_scheme = 'NoColor'
53 self.pm.in_template = "In [{int}]"
54 self.pm.in_template = "In [{int}]"
54 prompt = self.pm.render('in')
55 prompt = self.pm.render('in')
55 self.assertEqual(prompt, u"In [%r]" % int)
56 self.assertEqual(prompt, u"In [%r]" % int)
56
57
57 def test_undefined(self):
58 def test_undefined(self):
58 self.pm.color_scheme = 'NoColor'
59 self.pm.color_scheme = 'NoColor'
59 self.pm.in_template = "In [{foo_dne}]"
60 self.pm.in_template = "In [{foo_dne}]"
60 prompt = self.pm.render('in')
61 prompt = self.pm.render('in')
61 self.assertEqual(prompt, u"In [<ERROR: 'foo_dne' not found>]")
62 self.assertEqual(prompt, u"In [<ERROR: 'foo_dne' not found>]")
62
63
63 def test_render(self):
64 def test_render(self):
64 self.pm.in_template = r'\#>'
65 self.pm.in_template = r'\#>'
65 self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count)
66 self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count)
66
67
67 @dec.onlyif_unicode_paths
68 @dec.onlyif_unicode_paths
68 def test_render_unicode_cwd(self):
69 def test_render_unicode_cwd(self):
69 save = os.getcwdu()
70 save = py3compat.getcwd()
70 with TemporaryDirectory(u'ΓΌnicΓΈdΓ©') as td:
71 with TemporaryDirectory(u'ΓΌnicΓΈdΓ©') as td:
71 os.chdir(td)
72 os.chdir(td)
72 self.pm.in_template = r'\w [\#]'
73 self.pm.in_template = r'\w [\#]'
73 p = self.pm.render('in', color=False)
74 p = self.pm.render('in', color=False)
74 self.assertEqual(p, u"%s [%i]" % (os.getcwdu(), ip.execution_count))
75 self.assertEqual(p, u"%s [%i]" % (py3compat.getcwd(), ip.execution_count))
75 os.chdir(save)
76 os.chdir(save)
76
77
77 def test_lazy_eval_unicode(self):
78 def test_lazy_eval_unicode(self):
78 u = u'ΓΌnicΓΈdΓ©'
79 u = u'ΓΌnicΓΈdΓ©'
79 lz = LazyEvaluate(lambda : u)
80 lz = LazyEvaluate(lambda : u)
80 # str(lz) would fail
81 # str(lz) would fail
81 self.assertEqual(unicode_type(lz), u)
82 self.assertEqual(unicode_type(lz), u)
82 self.assertEqual(format(lz), u)
83 self.assertEqual(format(lz), u)
83
84
84 def test_lazy_eval_nonascii_bytes(self):
85 def test_lazy_eval_nonascii_bytes(self):
85 u = u'ΓΌnicΓΈdΓ©'
86 u = u'ΓΌnicΓΈdΓ©'
86 b = u.encode('utf8')
87 b = u.encode('utf8')
87 lz = LazyEvaluate(lambda : b)
88 lz = LazyEvaluate(lambda : b)
88 # unicode(lz) would fail
89 # unicode(lz) would fail
89 self.assertEqual(str(lz), str(b))
90 self.assertEqual(str(lz), str(b))
90 self.assertEqual(format(lz), str(b))
91 self.assertEqual(format(lz), str(b))
91
92
92 def test_lazy_eval_float(self):
93 def test_lazy_eval_float(self):
93 f = 0.503
94 f = 0.503
94 lz = LazyEvaluate(lambda : f)
95 lz = LazyEvaluate(lambda : f)
95
96
96 self.assertEqual(str(lz), str(f))
97 self.assertEqual(str(lz), str(f))
97 self.assertEqual(unicode_type(lz), unicode_type(f))
98 self.assertEqual(unicode_type(lz), unicode_type(f))
98 self.assertEqual(format(lz), str(f))
99 self.assertEqual(format(lz), str(f))
99 self.assertEqual(format(lz, '.1'), '0.5')
100 self.assertEqual(format(lz, '.1'), '0.5')
100
101
101 @dec.skip_win32
102 @dec.skip_win32
102 def test_cwd_x(self):
103 def test_cwd_x(self):
103 self.pm.in_template = r"\X0"
104 self.pm.in_template = r"\X0"
104 save = os.getcwdu()
105 save = py3compat.getcwd()
105 os.chdir(os.path.expanduser('~'))
106 os.chdir(os.path.expanduser('~'))
106 p = self.pm.render('in', color=False)
107 p = self.pm.render('in', color=False)
107 try:
108 try:
108 self.assertEqual(p, '~')
109 self.assertEqual(p, '~')
109 finally:
110 finally:
110 os.chdir(save)
111 os.chdir(save)
111
112
@@ -1,458 +1,458 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for code execution (%run and related), which is particularly tricky.
2 """Tests for code execution (%run and related), which is particularly tricky.
3
3
4 Because of how %run manages namespaces, and the fact that we are trying here to
4 Because of how %run manages namespaces, and the fact that we are trying here to
5 verify subtle object deletion and reference counting issues, the %run tests
5 verify subtle object deletion and reference counting issues, the %run tests
6 will be kept in this separate file. This makes it easier to aggregate in one
6 will be kept in this separate file. This makes it easier to aggregate in one
7 place the tricks needed to handle it; most other magics are much easier to test
7 place the tricks needed to handle it; most other magics are much easier to test
8 and we do so in a common test_magic file.
8 and we do so in a common test_magic file.
9 """
9 """
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 import functools
16 import functools
17 import os
17 import os
18 from os.path import join as pjoin
18 from os.path import join as pjoin
19 import random
19 import random
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import textwrap
22 import textwrap
23 import unittest
23 import unittest
24
24
25 import nose.tools as nt
25 import nose.tools as nt
26 from nose import SkipTest
26 from nose import SkipTest
27
27
28 from IPython.testing import decorators as dec
28 from IPython.testing import decorators as dec
29 from IPython.testing import tools as tt
29 from IPython.testing import tools as tt
30 from IPython.utils import py3compat
30 from IPython.utils import py3compat
31 from IPython.utils.tempdir import TemporaryDirectory
31 from IPython.utils.tempdir import TemporaryDirectory
32 from IPython.core import debugger
32 from IPython.core import debugger
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Test functions begin
35 # Test functions begin
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 def doctest_refbug():
38 def doctest_refbug():
39 """Very nasty problem with references held by multiple runs of a script.
39 """Very nasty problem with references held by multiple runs of a script.
40 See: https://github.com/ipython/ipython/issues/141
40 See: https://github.com/ipython/ipython/issues/141
41
41
42 In [1]: _ip.clear_main_mod_cache()
42 In [1]: _ip.clear_main_mod_cache()
43 # random
43 # random
44
44
45 In [2]: %run refbug
45 In [2]: %run refbug
46
46
47 In [3]: call_f()
47 In [3]: call_f()
48 lowercased: hello
48 lowercased: hello
49
49
50 In [4]: %run refbug
50 In [4]: %run refbug
51
51
52 In [5]: call_f()
52 In [5]: call_f()
53 lowercased: hello
53 lowercased: hello
54 lowercased: hello
54 lowercased: hello
55 """
55 """
56
56
57
57
58 def doctest_run_builtins():
58 def doctest_run_builtins():
59 r"""Check that %run doesn't damage __builtins__.
59 r"""Check that %run doesn't damage __builtins__.
60
60
61 In [1]: import tempfile
61 In [1]: import tempfile
62
62
63 In [2]: bid1 = id(__builtins__)
63 In [2]: bid1 = id(__builtins__)
64
64
65 In [3]: fname = tempfile.mkstemp('.py')[1]
65 In [3]: fname = tempfile.mkstemp('.py')[1]
66
66
67 In [3]: f = open(fname,'w')
67 In [3]: f = open(fname,'w')
68
68
69 In [4]: dummy= f.write('pass\n')
69 In [4]: dummy= f.write('pass\n')
70
70
71 In [5]: f.flush()
71 In [5]: f.flush()
72
72
73 In [6]: t1 = type(__builtins__)
73 In [6]: t1 = type(__builtins__)
74
74
75 In [7]: %run $fname
75 In [7]: %run $fname
76
76
77 In [7]: f.close()
77 In [7]: f.close()
78
78
79 In [8]: bid2 = id(__builtins__)
79 In [8]: bid2 = id(__builtins__)
80
80
81 In [9]: t2 = type(__builtins__)
81 In [9]: t2 = type(__builtins__)
82
82
83 In [10]: t1 == t2
83 In [10]: t1 == t2
84 Out[10]: True
84 Out[10]: True
85
85
86 In [10]: bid1 == bid2
86 In [10]: bid1 == bid2
87 Out[10]: True
87 Out[10]: True
88
88
89 In [12]: try:
89 In [12]: try:
90 ....: os.unlink(fname)
90 ....: os.unlink(fname)
91 ....: except:
91 ....: except:
92 ....: pass
92 ....: pass
93 ....:
93 ....:
94 """
94 """
95
95
96
96
97 def doctest_run_option_parser():
97 def doctest_run_option_parser():
98 r"""Test option parser in %run.
98 r"""Test option parser in %run.
99
99
100 In [1]: %run print_argv.py
100 In [1]: %run print_argv.py
101 []
101 []
102
102
103 In [2]: %run print_argv.py print*.py
103 In [2]: %run print_argv.py print*.py
104 ['print_argv.py']
104 ['print_argv.py']
105
105
106 In [3]: %run -G print_argv.py print*.py
106 In [3]: %run -G print_argv.py print*.py
107 ['print*.py']
107 ['print*.py']
108
108
109 """
109 """
110
110
111
111
112 @dec.skip_win32
112 @dec.skip_win32
113 def doctest_run_option_parser_for_posix():
113 def doctest_run_option_parser_for_posix():
114 r"""Test option parser in %run (Linux/OSX specific).
114 r"""Test option parser in %run (Linux/OSX specific).
115
115
116 You need double quote to escape glob in POSIX systems:
116 You need double quote to escape glob in POSIX systems:
117
117
118 In [1]: %run print_argv.py print\\*.py
118 In [1]: %run print_argv.py print\\*.py
119 ['print*.py']
119 ['print*.py']
120
120
121 You can't use quote to escape glob in POSIX systems:
121 You can't use quote to escape glob in POSIX systems:
122
122
123 In [2]: %run print_argv.py 'print*.py'
123 In [2]: %run print_argv.py 'print*.py'
124 ['print_argv.py']
124 ['print_argv.py']
125
125
126 """
126 """
127
127
128
128
129 @dec.skip_if_not_win32
129 @dec.skip_if_not_win32
130 def doctest_run_option_parser_for_windows():
130 def doctest_run_option_parser_for_windows():
131 r"""Test option parser in %run (Windows specific).
131 r"""Test option parser in %run (Windows specific).
132
132
133 In Windows, you can't escape ``*` `by backslash:
133 In Windows, you can't escape ``*` `by backslash:
134
134
135 In [1]: %run print_argv.py print\\*.py
135 In [1]: %run print_argv.py print\\*.py
136 ['print\\*.py']
136 ['print\\*.py']
137
137
138 You can use quote to escape glob:
138 You can use quote to escape glob:
139
139
140 In [2]: %run print_argv.py 'print*.py'
140 In [2]: %run print_argv.py 'print*.py'
141 ['print*.py']
141 ['print*.py']
142
142
143 """
143 """
144
144
145
145
146 @py3compat.doctest_refactor_print
146 @py3compat.doctest_refactor_print
147 def doctest_reset_del():
147 def doctest_reset_del():
148 """Test that resetting doesn't cause errors in __del__ methods.
148 """Test that resetting doesn't cause errors in __del__ methods.
149
149
150 In [2]: class A(object):
150 In [2]: class A(object):
151 ...: def __del__(self):
151 ...: def __del__(self):
152 ...: print str("Hi")
152 ...: print str("Hi")
153 ...:
153 ...:
154
154
155 In [3]: a = A()
155 In [3]: a = A()
156
156
157 In [4]: get_ipython().reset()
157 In [4]: get_ipython().reset()
158 Hi
158 Hi
159
159
160 In [5]: 1+1
160 In [5]: 1+1
161 Out[5]: 2
161 Out[5]: 2
162 """
162 """
163
163
164 # For some tests, it will be handy to organize them in a class with a common
164 # For some tests, it will be handy to organize them in a class with a common
165 # setup that makes a temp file
165 # setup that makes a temp file
166
166
167 class TestMagicRunPass(tt.TempFileMixin):
167 class TestMagicRunPass(tt.TempFileMixin):
168
168
169 def setup(self):
169 def setup(self):
170 """Make a valid python temp file."""
170 """Make a valid python temp file."""
171 self.mktmp('pass\n')
171 self.mktmp('pass\n')
172
172
173 def run_tmpfile(self):
173 def run_tmpfile(self):
174 _ip = get_ipython()
174 _ip = get_ipython()
175 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
175 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
176 # See below and ticket https://bugs.launchpad.net/bugs/366353
176 # See below and ticket https://bugs.launchpad.net/bugs/366353
177 _ip.magic('run %s' % self.fname)
177 _ip.magic('run %s' % self.fname)
178
178
179 def run_tmpfile_p(self):
179 def run_tmpfile_p(self):
180 _ip = get_ipython()
180 _ip = get_ipython()
181 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
181 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
182 # See below and ticket https://bugs.launchpad.net/bugs/366353
182 # See below and ticket https://bugs.launchpad.net/bugs/366353
183 _ip.magic('run -p %s' % self.fname)
183 _ip.magic('run -p %s' % self.fname)
184
184
185 def test_builtins_id(self):
185 def test_builtins_id(self):
186 """Check that %run doesn't damage __builtins__ """
186 """Check that %run doesn't damage __builtins__ """
187 _ip = get_ipython()
187 _ip = get_ipython()
188 # Test that the id of __builtins__ is not modified by %run
188 # Test that the id of __builtins__ is not modified by %run
189 bid1 = id(_ip.user_ns['__builtins__'])
189 bid1 = id(_ip.user_ns['__builtins__'])
190 self.run_tmpfile()
190 self.run_tmpfile()
191 bid2 = id(_ip.user_ns['__builtins__'])
191 bid2 = id(_ip.user_ns['__builtins__'])
192 nt.assert_equal(bid1, bid2)
192 nt.assert_equal(bid1, bid2)
193
193
194 def test_builtins_type(self):
194 def test_builtins_type(self):
195 """Check that the type of __builtins__ doesn't change with %run.
195 """Check that the type of __builtins__ doesn't change with %run.
196
196
197 However, the above could pass if __builtins__ was already modified to
197 However, the above could pass if __builtins__ was already modified to
198 be a dict (it should be a module) by a previous use of %run. So we
198 be a dict (it should be a module) by a previous use of %run. So we
199 also check explicitly that it really is a module:
199 also check explicitly that it really is a module:
200 """
200 """
201 _ip = get_ipython()
201 _ip = get_ipython()
202 self.run_tmpfile()
202 self.run_tmpfile()
203 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
203 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
204
204
205 def test_prompts(self):
205 def test_prompts(self):
206 """Test that prompts correctly generate after %run"""
206 """Test that prompts correctly generate after %run"""
207 self.run_tmpfile()
207 self.run_tmpfile()
208 _ip = get_ipython()
208 _ip = get_ipython()
209 p2 = _ip.prompt_manager.render('in2').strip()
209 p2 = _ip.prompt_manager.render('in2').strip()
210 nt.assert_equal(p2[:3], '...')
210 nt.assert_equal(p2[:3], '...')
211
211
212 def test_run_profile( self ):
212 def test_run_profile( self ):
213 """Test that the option -p, which invokes the profiler, do not
213 """Test that the option -p, which invokes the profiler, do not
214 crash by invoking execfile"""
214 crash by invoking execfile"""
215 _ip = get_ipython()
215 _ip = get_ipython()
216 self.run_tmpfile_p()
216 self.run_tmpfile_p()
217
217
218
218
219 class TestMagicRunSimple(tt.TempFileMixin):
219 class TestMagicRunSimple(tt.TempFileMixin):
220
220
221 def test_simpledef(self):
221 def test_simpledef(self):
222 """Test that simple class definitions work."""
222 """Test that simple class definitions work."""
223 src = ("class foo: pass\n"
223 src = ("class foo: pass\n"
224 "def f(): return foo()")
224 "def f(): return foo()")
225 self.mktmp(src)
225 self.mktmp(src)
226 _ip.magic('run %s' % self.fname)
226 _ip.magic('run %s' % self.fname)
227 _ip.run_cell('t = isinstance(f(), foo)')
227 _ip.run_cell('t = isinstance(f(), foo)')
228 nt.assert_true(_ip.user_ns['t'])
228 nt.assert_true(_ip.user_ns['t'])
229
229
230 def test_obj_del(self):
230 def test_obj_del(self):
231 """Test that object's __del__ methods are called on exit."""
231 """Test that object's __del__ methods are called on exit."""
232 if sys.platform == 'win32':
232 if sys.platform == 'win32':
233 try:
233 try:
234 import win32api
234 import win32api
235 except ImportError:
235 except ImportError:
236 raise SkipTest("Test requires pywin32")
236 raise SkipTest("Test requires pywin32")
237 src = ("class A(object):\n"
237 src = ("class A(object):\n"
238 " def __del__(self):\n"
238 " def __del__(self):\n"
239 " print 'object A deleted'\n"
239 " print 'object A deleted'\n"
240 "a = A()\n")
240 "a = A()\n")
241 self.mktmp(py3compat.doctest_refactor_print(src))
241 self.mktmp(py3compat.doctest_refactor_print(src))
242 if dec.module_not_available('sqlite3'):
242 if dec.module_not_available('sqlite3'):
243 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
243 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
244 else:
244 else:
245 err = None
245 err = None
246 tt.ipexec_validate(self.fname, 'object A deleted', err)
246 tt.ipexec_validate(self.fname, 'object A deleted', err)
247
247
248 def test_aggressive_namespace_cleanup(self):
248 def test_aggressive_namespace_cleanup(self):
249 """Test that namespace cleanup is not too aggressive GH-238
249 """Test that namespace cleanup is not too aggressive GH-238
250
250
251 Returning from another run magic deletes the namespace"""
251 Returning from another run magic deletes the namespace"""
252 # see ticket https://github.com/ipython/ipython/issues/238
252 # see ticket https://github.com/ipython/ipython/issues/238
253 class secondtmp(tt.TempFileMixin): pass
253 class secondtmp(tt.TempFileMixin): pass
254 empty = secondtmp()
254 empty = secondtmp()
255 empty.mktmp('')
255 empty.mktmp('')
256 # On Windows, the filename will have \users in it, so we need to use the
256 # On Windows, the filename will have \users in it, so we need to use the
257 # repr so that the \u becomes \\u.
257 # repr so that the \u becomes \\u.
258 src = ("ip = get_ipython()\n"
258 src = ("ip = get_ipython()\n"
259 "for i in range(5):\n"
259 "for i in range(5):\n"
260 " try:\n"
260 " try:\n"
261 " ip.magic(%r)\n"
261 " ip.magic(%r)\n"
262 " except NameError as e:\n"
262 " except NameError as e:\n"
263 " print(i)\n"
263 " print(i)\n"
264 " break\n" % ('run ' + empty.fname))
264 " break\n" % ('run ' + empty.fname))
265 self.mktmp(src)
265 self.mktmp(src)
266 _ip.magic('run %s' % self.fname)
266 _ip.magic('run %s' % self.fname)
267 _ip.run_cell('ip == get_ipython()')
267 _ip.run_cell('ip == get_ipython()')
268 nt.assert_equal(_ip.user_ns['i'], 4)
268 nt.assert_equal(_ip.user_ns['i'], 4)
269
269
270 def test_run_second(self):
270 def test_run_second(self):
271 """Test that running a second file doesn't clobber the first, gh-3547
271 """Test that running a second file doesn't clobber the first, gh-3547
272 """
272 """
273 self.mktmp("avar = 1\n"
273 self.mktmp("avar = 1\n"
274 "def afunc():\n"
274 "def afunc():\n"
275 " return avar\n")
275 " return avar\n")
276
276
277 empty = tt.TempFileMixin()
277 empty = tt.TempFileMixin()
278 empty.mktmp("")
278 empty.mktmp("")
279
279
280 _ip.magic('run %s' % self.fname)
280 _ip.magic('run %s' % self.fname)
281 _ip.magic('run %s' % empty.fname)
281 _ip.magic('run %s' % empty.fname)
282 nt.assert_equal(_ip.user_ns['afunc'](), 1)
282 nt.assert_equal(_ip.user_ns['afunc'](), 1)
283
283
284 @dec.skip_win32
284 @dec.skip_win32
285 def test_tclass(self):
285 def test_tclass(self):
286 mydir = os.path.dirname(__file__)
286 mydir = os.path.dirname(__file__)
287 tc = os.path.join(mydir, 'tclass')
287 tc = os.path.join(mydir, 'tclass')
288 src = ("%%run '%s' C-first\n"
288 src = ("%%run '%s' C-first\n"
289 "%%run '%s' C-second\n"
289 "%%run '%s' C-second\n"
290 "%%run '%s' C-third\n") % (tc, tc, tc)
290 "%%run '%s' C-third\n") % (tc, tc, tc)
291 self.mktmp(src, '.ipy')
291 self.mktmp(src, '.ipy')
292 out = """\
292 out = """\
293 ARGV 1-: ['C-first']
293 ARGV 1-: ['C-first']
294 ARGV 1-: ['C-second']
294 ARGV 1-: ['C-second']
295 tclass.py: deleting object: C-first
295 tclass.py: deleting object: C-first
296 ARGV 1-: ['C-third']
296 ARGV 1-: ['C-third']
297 tclass.py: deleting object: C-second
297 tclass.py: deleting object: C-second
298 tclass.py: deleting object: C-third
298 tclass.py: deleting object: C-third
299 """
299 """
300 if dec.module_not_available('sqlite3'):
300 if dec.module_not_available('sqlite3'):
301 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
301 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
302 else:
302 else:
303 err = None
303 err = None
304 tt.ipexec_validate(self.fname, out, err)
304 tt.ipexec_validate(self.fname, out, err)
305
305
306 def test_run_i_after_reset(self):
306 def test_run_i_after_reset(self):
307 """Check that %run -i still works after %reset (gh-693)"""
307 """Check that %run -i still works after %reset (gh-693)"""
308 src = "yy = zz\n"
308 src = "yy = zz\n"
309 self.mktmp(src)
309 self.mktmp(src)
310 _ip.run_cell("zz = 23")
310 _ip.run_cell("zz = 23")
311 _ip.magic('run -i %s' % self.fname)
311 _ip.magic('run -i %s' % self.fname)
312 nt.assert_equal(_ip.user_ns['yy'], 23)
312 nt.assert_equal(_ip.user_ns['yy'], 23)
313 _ip.magic('reset -f')
313 _ip.magic('reset -f')
314 _ip.run_cell("zz = 23")
314 _ip.run_cell("zz = 23")
315 _ip.magic('run -i %s' % self.fname)
315 _ip.magic('run -i %s' % self.fname)
316 nt.assert_equal(_ip.user_ns['yy'], 23)
316 nt.assert_equal(_ip.user_ns['yy'], 23)
317
317
318 def test_unicode(self):
318 def test_unicode(self):
319 """Check that files in odd encodings are accepted."""
319 """Check that files in odd encodings are accepted."""
320 mydir = os.path.dirname(__file__)
320 mydir = os.path.dirname(__file__)
321 na = os.path.join(mydir, 'nonascii.py')
321 na = os.path.join(mydir, 'nonascii.py')
322 _ip.magic('run "%s"' % na)
322 _ip.magic('run "%s"' % na)
323 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
323 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
324
324
325 def test_run_py_file_attribute(self):
325 def test_run_py_file_attribute(self):
326 """Test handling of `__file__` attribute in `%run <file>.py`."""
326 """Test handling of `__file__` attribute in `%run <file>.py`."""
327 src = "t = __file__\n"
327 src = "t = __file__\n"
328 self.mktmp(src)
328 self.mktmp(src)
329 _missing = object()
329 _missing = object()
330 file1 = _ip.user_ns.get('__file__', _missing)
330 file1 = _ip.user_ns.get('__file__', _missing)
331 _ip.magic('run %s' % self.fname)
331 _ip.magic('run %s' % self.fname)
332 file2 = _ip.user_ns.get('__file__', _missing)
332 file2 = _ip.user_ns.get('__file__', _missing)
333
333
334 # Check that __file__ was equal to the filename in the script's
334 # Check that __file__ was equal to the filename in the script's
335 # namespace.
335 # namespace.
336 nt.assert_equal(_ip.user_ns['t'], self.fname)
336 nt.assert_equal(_ip.user_ns['t'], self.fname)
337
337
338 # Check that __file__ was not leaked back into user_ns.
338 # Check that __file__ was not leaked back into user_ns.
339 nt.assert_equal(file1, file2)
339 nt.assert_equal(file1, file2)
340
340
341 def test_run_ipy_file_attribute(self):
341 def test_run_ipy_file_attribute(self):
342 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
342 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
343 src = "t = __file__\n"
343 src = "t = __file__\n"
344 self.mktmp(src, ext='.ipy')
344 self.mktmp(src, ext='.ipy')
345 _missing = object()
345 _missing = object()
346 file1 = _ip.user_ns.get('__file__', _missing)
346 file1 = _ip.user_ns.get('__file__', _missing)
347 _ip.magic('run %s' % self.fname)
347 _ip.magic('run %s' % self.fname)
348 file2 = _ip.user_ns.get('__file__', _missing)
348 file2 = _ip.user_ns.get('__file__', _missing)
349
349
350 # Check that __file__ was equal to the filename in the script's
350 # Check that __file__ was equal to the filename in the script's
351 # namespace.
351 # namespace.
352 nt.assert_equal(_ip.user_ns['t'], self.fname)
352 nt.assert_equal(_ip.user_ns['t'], self.fname)
353
353
354 # Check that __file__ was not leaked back into user_ns.
354 # Check that __file__ was not leaked back into user_ns.
355 nt.assert_equal(file1, file2)
355 nt.assert_equal(file1, file2)
356
356
357 def test_run_formatting(self):
357 def test_run_formatting(self):
358 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
358 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
359 src = "pass"
359 src = "pass"
360 self.mktmp(src)
360 self.mktmp(src)
361 _ip.magic('run -t -N 1 %s' % self.fname)
361 _ip.magic('run -t -N 1 %s' % self.fname)
362 _ip.magic('run -t -N 10 %s' % self.fname)
362 _ip.magic('run -t -N 10 %s' % self.fname)
363
363
364 def test_ignore_sys_exit(self):
364 def test_ignore_sys_exit(self):
365 """Test the -e option to ignore sys.exit()"""
365 """Test the -e option to ignore sys.exit()"""
366 src = "import sys; sys.exit(1)"
366 src = "import sys; sys.exit(1)"
367 self.mktmp(src)
367 self.mktmp(src)
368 with tt.AssertPrints('SystemExit'):
368 with tt.AssertPrints('SystemExit'):
369 _ip.magic('run %s' % self.fname)
369 _ip.magic('run %s' % self.fname)
370
370
371 with tt.AssertNotPrints('SystemExit'):
371 with tt.AssertNotPrints('SystemExit'):
372 _ip.magic('run -e %s' % self.fname)
372 _ip.magic('run -e %s' % self.fname)
373
373
374
374
375
375
376 class TestMagicRunWithPackage(unittest.TestCase):
376 class TestMagicRunWithPackage(unittest.TestCase):
377
377
378 def writefile(self, name, content):
378 def writefile(self, name, content):
379 path = os.path.join(self.tempdir.name, name)
379 path = os.path.join(self.tempdir.name, name)
380 d = os.path.dirname(path)
380 d = os.path.dirname(path)
381 if not os.path.isdir(d):
381 if not os.path.isdir(d):
382 os.makedirs(d)
382 os.makedirs(d)
383 with open(path, 'w') as f:
383 with open(path, 'w') as f:
384 f.write(textwrap.dedent(content))
384 f.write(textwrap.dedent(content))
385
385
386 def setUp(self):
386 def setUp(self):
387 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
387 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
388 """Temporary valid python package name."""
388 """Temporary valid python package name."""
389
389
390 self.value = int(random.random() * 10000)
390 self.value = int(random.random() * 10000)
391
391
392 self.tempdir = TemporaryDirectory()
392 self.tempdir = TemporaryDirectory()
393 self.__orig_cwd = os.getcwdu()
393 self.__orig_cwd = py3compat.getcwd()
394 sys.path.insert(0, self.tempdir.name)
394 sys.path.insert(0, self.tempdir.name)
395
395
396 self.writefile(os.path.join(package, '__init__.py'), '')
396 self.writefile(os.path.join(package, '__init__.py'), '')
397 self.writefile(os.path.join(package, 'sub.py'), """
397 self.writefile(os.path.join(package, 'sub.py'), """
398 x = {0!r}
398 x = {0!r}
399 """.format(self.value))
399 """.format(self.value))
400 self.writefile(os.path.join(package, 'relative.py'), """
400 self.writefile(os.path.join(package, 'relative.py'), """
401 from .sub import x
401 from .sub import x
402 """)
402 """)
403 self.writefile(os.path.join(package, 'absolute.py'), """
403 self.writefile(os.path.join(package, 'absolute.py'), """
404 from {0}.sub import x
404 from {0}.sub import x
405 """.format(package))
405 """.format(package))
406
406
407 def tearDown(self):
407 def tearDown(self):
408 os.chdir(self.__orig_cwd)
408 os.chdir(self.__orig_cwd)
409 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
409 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
410 self.tempdir.cleanup()
410 self.tempdir.cleanup()
411
411
412 def check_run_submodule(self, submodule, opts=''):
412 def check_run_submodule(self, submodule, opts=''):
413 _ip.user_ns.pop('x', None)
413 _ip.user_ns.pop('x', None)
414 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
414 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
415 self.assertEqual(_ip.user_ns['x'], self.value,
415 self.assertEqual(_ip.user_ns['x'], self.value,
416 'Variable `x` is not loaded from module `{0}`.'
416 'Variable `x` is not loaded from module `{0}`.'
417 .format(submodule))
417 .format(submodule))
418
418
419 def test_run_submodule_with_absolute_import(self):
419 def test_run_submodule_with_absolute_import(self):
420 self.check_run_submodule('absolute')
420 self.check_run_submodule('absolute')
421
421
422 def test_run_submodule_with_relative_import(self):
422 def test_run_submodule_with_relative_import(self):
423 """Run submodule that has a relative import statement (#2727)."""
423 """Run submodule that has a relative import statement (#2727)."""
424 self.check_run_submodule('relative')
424 self.check_run_submodule('relative')
425
425
426 def test_prun_submodule_with_absolute_import(self):
426 def test_prun_submodule_with_absolute_import(self):
427 self.check_run_submodule('absolute', '-p')
427 self.check_run_submodule('absolute', '-p')
428
428
429 def test_prun_submodule_with_relative_import(self):
429 def test_prun_submodule_with_relative_import(self):
430 self.check_run_submodule('relative', '-p')
430 self.check_run_submodule('relative', '-p')
431
431
432 def with_fake_debugger(func):
432 def with_fake_debugger(func):
433 @functools.wraps(func)
433 @functools.wraps(func)
434 def wrapper(*args, **kwds):
434 def wrapper(*args, **kwds):
435 with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)):
435 with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)):
436 return func(*args, **kwds)
436 return func(*args, **kwds)
437 return wrapper
437 return wrapper
438
438
439 @with_fake_debugger
439 @with_fake_debugger
440 def test_debug_run_submodule_with_absolute_import(self):
440 def test_debug_run_submodule_with_absolute_import(self):
441 self.check_run_submodule('absolute', '-d')
441 self.check_run_submodule('absolute', '-d')
442
442
443 @with_fake_debugger
443 @with_fake_debugger
444 def test_debug_run_submodule_with_relative_import(self):
444 def test_debug_run_submodule_with_relative_import(self):
445 self.check_run_submodule('relative', '-d')
445 self.check_run_submodule('relative', '-d')
446
446
447 def test_run__name__():
447 def test_run__name__():
448 with TemporaryDirectory() as td:
448 with TemporaryDirectory() as td:
449 path = pjoin(td, 'foo.py')
449 path = pjoin(td, 'foo.py')
450 with open(path, 'w') as f:
450 with open(path, 'w') as f:
451 f.write("q = __name__")
451 f.write("q = __name__")
452
452
453 _ip.user_ns.pop('q', None)
453 _ip.user_ns.pop('q', None)
454 _ip.magic('run {}'.format(path))
454 _ip.magic('run {}'.format(path))
455 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
455 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
456
456
457 _ip.magic('run -n {}'.format(path))
457 _ip.magic('run -n {}'.format(path))
458 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
458 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
@@ -1,172 +1,171 b''
1 """Manage IPython.parallel clusters in the notebook.
1 """Manage IPython.parallel clusters in the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
20
21 from tornado import web
19 from tornado import web
22 from zmq.eventloop import ioloop
20 from zmq.eventloop import ioloop
23
21
24 from IPython.config.configurable import LoggingConfigurable
22 from IPython.config.configurable import LoggingConfigurable
25 from IPython.utils.traitlets import Dict, Instance, CFloat
23 from IPython.utils.traitlets import Dict, Instance, CFloat
26 from IPython.parallel.apps.ipclusterapp import IPClusterStart
24 from IPython.parallel.apps.ipclusterapp import IPClusterStart
27 from IPython.core.profileapp import list_profiles_in
25 from IPython.core.profileapp import list_profiles_in
28 from IPython.core.profiledir import ProfileDir
26 from IPython.core.profiledir import ProfileDir
27 from IPython.utils import py3compat
29 from IPython.utils.path import get_ipython_dir
28 from IPython.utils.path import get_ipython_dir
30
29
31
30
32 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
33 # Classes
32 # Classes
34 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
35
34
36
35
37 class DummyIPClusterStart(IPClusterStart):
36 class DummyIPClusterStart(IPClusterStart):
38 """Dummy subclass to skip init steps that conflict with global app.
37 """Dummy subclass to skip init steps that conflict with global app.
39
38
40 Instantiating and initializing this class should result in fully configured
39 Instantiating and initializing this class should result in fully configured
41 launchers, but no other side effects or state.
40 launchers, but no other side effects or state.
42 """
41 """
43
42
44 def init_signal(self):
43 def init_signal(self):
45 pass
44 pass
46 def reinit_logging(self):
45 def reinit_logging(self):
47 pass
46 pass
48
47
49
48
50 class ClusterManager(LoggingConfigurable):
49 class ClusterManager(LoggingConfigurable):
51
50
52 profiles = Dict()
51 profiles = Dict()
53
52
54 delay = CFloat(1., config=True,
53 delay = CFloat(1., config=True,
55 help="delay (in s) between starting the controller and the engines")
54 help="delay (in s) between starting the controller and the engines")
56
55
57 loop = Instance('zmq.eventloop.ioloop.IOLoop')
56 loop = Instance('zmq.eventloop.ioloop.IOLoop')
58 def _loop_default(self):
57 def _loop_default(self):
59 from zmq.eventloop.ioloop import IOLoop
58 from zmq.eventloop.ioloop import IOLoop
60 return IOLoop.instance()
59 return IOLoop.instance()
61
60
62 def build_launchers(self, profile_dir):
61 def build_launchers(self, profile_dir):
63 starter = DummyIPClusterStart(log=self.log)
62 starter = DummyIPClusterStart(log=self.log)
64 starter.initialize(['--profile-dir', profile_dir])
63 starter.initialize(['--profile-dir', profile_dir])
65 cl = starter.controller_launcher
64 cl = starter.controller_launcher
66 esl = starter.engine_launcher
65 esl = starter.engine_launcher
67 n = starter.n
66 n = starter.n
68 return cl, esl, n
67 return cl, esl, n
69
68
70 def get_profile_dir(self, name, path):
69 def get_profile_dir(self, name, path):
71 p = ProfileDir.find_profile_dir_by_name(path,name=name)
70 p = ProfileDir.find_profile_dir_by_name(path,name=name)
72 return p.location
71 return p.location
73
72
74 def update_profiles(self):
73 def update_profiles(self):
75 """List all profiles in the ipython_dir and cwd.
74 """List all profiles in the ipython_dir and cwd.
76 """
75 """
77 for path in [get_ipython_dir(), os.getcwdu()]:
76 for path in [get_ipython_dir(), py3compat.getcwd()]:
78 for profile in list_profiles_in(path):
77 for profile in list_profiles_in(path):
79 pd = self.get_profile_dir(profile, path)
78 pd = self.get_profile_dir(profile, path)
80 if profile not in self.profiles:
79 if profile not in self.profiles:
81 self.log.debug("Adding cluster profile '%s'" % profile)
80 self.log.debug("Adding cluster profile '%s'" % profile)
82 self.profiles[profile] = {
81 self.profiles[profile] = {
83 'profile': profile,
82 'profile': profile,
84 'profile_dir': pd,
83 'profile_dir': pd,
85 'status': 'stopped'
84 'status': 'stopped'
86 }
85 }
87
86
88 def list_profiles(self):
87 def list_profiles(self):
89 self.update_profiles()
88 self.update_profiles()
90 # sorted list, but ensure that 'default' always comes first
89 # sorted list, but ensure that 'default' always comes first
91 default_first = lambda name: name if name != 'default' else ''
90 default_first = lambda name: name if name != 'default' else ''
92 result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)]
91 result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)]
93 return result
92 return result
94
93
95 def check_profile(self, profile):
94 def check_profile(self, profile):
96 if profile not in self.profiles:
95 if profile not in self.profiles:
97 raise web.HTTPError(404, u'profile not found')
96 raise web.HTTPError(404, u'profile not found')
98
97
99 def profile_info(self, profile):
98 def profile_info(self, profile):
100 self.check_profile(profile)
99 self.check_profile(profile)
101 result = {}
100 result = {}
102 data = self.profiles.get(profile)
101 data = self.profiles.get(profile)
103 result['profile'] = profile
102 result['profile'] = profile
104 result['profile_dir'] = data['profile_dir']
103 result['profile_dir'] = data['profile_dir']
105 result['status'] = data['status']
104 result['status'] = data['status']
106 if 'n' in data:
105 if 'n' in data:
107 result['n'] = data['n']
106 result['n'] = data['n']
108 return result
107 return result
109
108
110 def start_cluster(self, profile, n=None):
109 def start_cluster(self, profile, n=None):
111 """Start a cluster for a given profile."""
110 """Start a cluster for a given profile."""
112 self.check_profile(profile)
111 self.check_profile(profile)
113 data = self.profiles[profile]
112 data = self.profiles[profile]
114 if data['status'] == 'running':
113 if data['status'] == 'running':
115 raise web.HTTPError(409, u'cluster already running')
114 raise web.HTTPError(409, u'cluster already running')
116 cl, esl, default_n = self.build_launchers(data['profile_dir'])
115 cl, esl, default_n = self.build_launchers(data['profile_dir'])
117 n = n if n is not None else default_n
116 n = n if n is not None else default_n
118 def clean_data():
117 def clean_data():
119 data.pop('controller_launcher',None)
118 data.pop('controller_launcher',None)
120 data.pop('engine_set_launcher',None)
119 data.pop('engine_set_launcher',None)
121 data.pop('n',None)
120 data.pop('n',None)
122 data['status'] = 'stopped'
121 data['status'] = 'stopped'
123 def engines_stopped(r):
122 def engines_stopped(r):
124 self.log.debug('Engines stopped')
123 self.log.debug('Engines stopped')
125 if cl.running:
124 if cl.running:
126 cl.stop()
125 cl.stop()
127 clean_data()
126 clean_data()
128 esl.on_stop(engines_stopped)
127 esl.on_stop(engines_stopped)
129 def controller_stopped(r):
128 def controller_stopped(r):
130 self.log.debug('Controller stopped')
129 self.log.debug('Controller stopped')
131 if esl.running:
130 if esl.running:
132 esl.stop()
131 esl.stop()
133 clean_data()
132 clean_data()
134 cl.on_stop(controller_stopped)
133 cl.on_stop(controller_stopped)
135
134
136 dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop)
135 dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop)
137 dc.start()
136 dc.start()
138 dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop)
137 dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop)
139 dc.start()
138 dc.start()
140
139
141 self.log.debug('Cluster started')
140 self.log.debug('Cluster started')
142 data['controller_launcher'] = cl
141 data['controller_launcher'] = cl
143 data['engine_set_launcher'] = esl
142 data['engine_set_launcher'] = esl
144 data['n'] = n
143 data['n'] = n
145 data['status'] = 'running'
144 data['status'] = 'running'
146 return self.profile_info(profile)
145 return self.profile_info(profile)
147
146
148 def stop_cluster(self, profile):
147 def stop_cluster(self, profile):
149 """Stop a cluster for a given profile."""
148 """Stop a cluster for a given profile."""
150 self.check_profile(profile)
149 self.check_profile(profile)
151 data = self.profiles[profile]
150 data = self.profiles[profile]
152 if data['status'] == 'stopped':
151 if data['status'] == 'stopped':
153 raise web.HTTPError(409, u'cluster not running')
152 raise web.HTTPError(409, u'cluster not running')
154 data = self.profiles[profile]
153 data = self.profiles[profile]
155 cl = data['controller_launcher']
154 cl = data['controller_launcher']
156 esl = data['engine_set_launcher']
155 esl = data['engine_set_launcher']
157 if cl.running:
156 if cl.running:
158 cl.stop()
157 cl.stop()
159 if esl.running:
158 if esl.running:
160 esl.stop()
159 esl.stop()
161 # Return a temp info dict, the real one is updated in the on_stop
160 # Return a temp info dict, the real one is updated in the on_stop
162 # logic above.
161 # logic above.
163 result = {
162 result = {
164 'profile': data['profile'],
163 'profile': data['profile'],
165 'profile_dir': data['profile_dir'],
164 'profile_dir': data['profile_dir'],
166 'status': 'stopped'
165 'status': 'stopped'
167 }
166 }
168 return result
167 return result
169
168
170 def stop_all_clusters(self):
169 def stop_all_clusters(self):
171 for p in self.profiles.keys():
170 for p in self.profiles.keys():
172 self.stop_cluster(p)
171 self.stop_cluster(p)
@@ -1,173 +1,174 b''
1 """A base class notebook manager.
1 """A base class notebook manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 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 import os
20 import os
21
21
22 from IPython.config.configurable import LoggingConfigurable
22 from IPython.config.configurable import LoggingConfigurable
23 from IPython.nbformat import current
23 from IPython.nbformat import current
24 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
24 from IPython.utils import py3compat
25 from IPython.utils.traitlets import Unicode, TraitError
25
26
26 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
27 # Classes
28 # Classes
28 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
29
30
30 class NotebookManager(LoggingConfigurable):
31 class NotebookManager(LoggingConfigurable):
31
32
32 # Todo:
33 # Todo:
33 # The notebook_dir attribute is used to mean a couple of different things:
34 # The notebook_dir attribute is used to mean a couple of different things:
34 # 1. Where the notebooks are stored if FileNotebookManager is used.
35 # 1. Where the notebooks are stored if FileNotebookManager is used.
35 # 2. The cwd of the kernel for a project.
36 # 2. The cwd of the kernel for a project.
36 # Right now we use this attribute in a number of different places and
37 # Right now we use this attribute in a number of different places and
37 # we are going to have to disentangle all of this.
38 # we are going to have to disentangle all of this.
38 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
39 notebook_dir = Unicode(py3compat.getcwd(), config=True, help="""
39 The directory to use for notebooks.
40 The directory to use for notebooks.
40 """)
41 """)
41
42
42 filename_ext = Unicode(u'.ipynb')
43 filename_ext = Unicode(u'.ipynb')
43
44
44 def path_exists(self, path):
45 def path_exists(self, path):
45 """Does the API-style path (directory) actually exist?
46 """Does the API-style path (directory) actually exist?
46
47
47 Override this method in subclasses.
48 Override this method in subclasses.
48
49
49 Parameters
50 Parameters
50 ----------
51 ----------
51 path : string
52 path : string
52 The
53 The
53
54
54 Returns
55 Returns
55 -------
56 -------
56 exists : bool
57 exists : bool
57 Whether the path does indeed exist.
58 Whether the path does indeed exist.
58 """
59 """
59 raise NotImplementedError
60 raise NotImplementedError
60
61
61 def _notebook_dir_changed(self, name, old, new):
62 def _notebook_dir_changed(self, name, old, new):
62 """Do a bit of validation of the notebook dir."""
63 """Do a bit of validation of the notebook dir."""
63 if not os.path.isabs(new):
64 if not os.path.isabs(new):
64 # If we receive a non-absolute path, make it absolute.
65 # If we receive a non-absolute path, make it absolute.
65 self.notebook_dir = os.path.abspath(new)
66 self.notebook_dir = os.path.abspath(new)
66 return
67 return
67 if os.path.exists(new) and not os.path.isdir(new):
68 if os.path.exists(new) and not os.path.isdir(new):
68 raise TraitError("notebook dir %r is not a directory" % new)
69 raise TraitError("notebook dir %r is not a directory" % new)
69 if not os.path.exists(new):
70 if not os.path.exists(new):
70 self.log.info("Creating notebook dir %s", new)
71 self.log.info("Creating notebook dir %s", new)
71 try:
72 try:
72 os.mkdir(new)
73 os.mkdir(new)
73 except:
74 except:
74 raise TraitError("Couldn't create notebook dir %r" % new)
75 raise TraitError("Couldn't create notebook dir %r" % new)
75
76
76 # Main notebook API
77 # Main notebook API
77
78
78 def increment_filename(self, basename, path=''):
79 def increment_filename(self, basename, path=''):
79 """Increment a notebook filename without the .ipynb to make it unique.
80 """Increment a notebook filename without the .ipynb to make it unique.
80
81
81 Parameters
82 Parameters
82 ----------
83 ----------
83 basename : unicode
84 basename : unicode
84 The name of a notebook without the ``.ipynb`` file extension.
85 The name of a notebook without the ``.ipynb`` file extension.
85 path : unicode
86 path : unicode
86 The URL path of the notebooks directory
87 The URL path of the notebooks directory
87 """
88 """
88 return basename
89 return basename
89
90
90 def list_notebooks(self, path=''):
91 def list_notebooks(self, path=''):
91 """Return a list of notebook dicts without content.
92 """Return a list of notebook dicts without content.
92
93
93 This returns a list of dicts, each of the form::
94 This returns a list of dicts, each of the form::
94
95
95 dict(notebook_id=notebook,name=name)
96 dict(notebook_id=notebook,name=name)
96
97
97 This list of dicts should be sorted by name::
98 This list of dicts should be sorted by name::
98
99
99 data = sorted(data, key=lambda item: item['name'])
100 data = sorted(data, key=lambda item: item['name'])
100 """
101 """
101 raise NotImplementedError('must be implemented in a subclass')
102 raise NotImplementedError('must be implemented in a subclass')
102
103
103 def get_notebook_model(self, name, path='', content=True):
104 def get_notebook_model(self, name, path='', content=True):
104 """Get the notebook model with or without content."""
105 """Get the notebook model with or without content."""
105 raise NotImplementedError('must be implemented in a subclass')
106 raise NotImplementedError('must be implemented in a subclass')
106
107
107 def save_notebook_model(self, model, name, path=''):
108 def save_notebook_model(self, model, name, path=''):
108 """Save the notebook model and return the model with no content."""
109 """Save the notebook model and return the model with no content."""
109 raise NotImplementedError('must be implemented in a subclass')
110 raise NotImplementedError('must be implemented in a subclass')
110
111
111 def update_notebook_model(self, model, name, path=''):
112 def update_notebook_model(self, model, name, path=''):
112 """Update the notebook model and return the model with no content."""
113 """Update the notebook model and return the model with no content."""
113 raise NotImplementedError('must be implemented in a subclass')
114 raise NotImplementedError('must be implemented in a subclass')
114
115
115 def delete_notebook_model(self, name, path=''):
116 def delete_notebook_model(self, name, path=''):
116 """Delete notebook by name and path."""
117 """Delete notebook by name and path."""
117 raise NotImplementedError('must be implemented in a subclass')
118 raise NotImplementedError('must be implemented in a subclass')
118
119
119 def create_notebook_model(self, model=None, path=''):
120 def create_notebook_model(self, model=None, path=''):
120 """Create a new notebook and return its model with no content."""
121 """Create a new notebook and return its model with no content."""
121 path = path.strip('/')
122 path = path.strip('/')
122 if model is None:
123 if model is None:
123 model = {}
124 model = {}
124 if 'content' not in model:
125 if 'content' not in model:
125 metadata = current.new_metadata(name=u'')
126 metadata = current.new_metadata(name=u'')
126 model['content'] = current.new_notebook(metadata=metadata)
127 model['content'] = current.new_notebook(metadata=metadata)
127 if 'name' not in model:
128 if 'name' not in model:
128 model['name'] = self.increment_filename('Untitled', path)
129 model['name'] = self.increment_filename('Untitled', path)
129
130
130 model['path'] = path
131 model['path'] = path
131 model = self.save_notebook_model(model, model['name'], model['path'])
132 model = self.save_notebook_model(model, model['name'], model['path'])
132 return model
133 return model
133
134
134 def copy_notebook(self, from_name, to_name=None, path=''):
135 def copy_notebook(self, from_name, to_name=None, path=''):
135 """Copy an existing notebook and return its new model.
136 """Copy an existing notebook and return its new model.
136
137
137 If to_name not specified, increment `from_name-Copy#.ipynb`.
138 If to_name not specified, increment `from_name-Copy#.ipynb`.
138 """
139 """
139 path = path.strip('/')
140 path = path.strip('/')
140 model = self.get_notebook_model(from_name, path)
141 model = self.get_notebook_model(from_name, path)
141 if not to_name:
142 if not to_name:
142 base = os.path.splitext(from_name)[0] + '-Copy'
143 base = os.path.splitext(from_name)[0] + '-Copy'
143 to_name = self.increment_filename(base, path)
144 to_name = self.increment_filename(base, path)
144 model['name'] = to_name
145 model['name'] = to_name
145 model = self.save_notebook_model(model, to_name, path)
146 model = self.save_notebook_model(model, to_name, path)
146 return model
147 return model
147
148
148 # Checkpoint-related
149 # Checkpoint-related
149
150
150 def create_checkpoint(self, name, path=''):
151 def create_checkpoint(self, name, path=''):
151 """Create a checkpoint of the current state of a notebook
152 """Create a checkpoint of the current state of a notebook
152
153
153 Returns a checkpoint_id for the new checkpoint.
154 Returns a checkpoint_id for the new checkpoint.
154 """
155 """
155 raise NotImplementedError("must be implemented in a subclass")
156 raise NotImplementedError("must be implemented in a subclass")
156
157
157 def list_checkpoints(self, name, path=''):
158 def list_checkpoints(self, name, path=''):
158 """Return a list of checkpoints for a given notebook"""
159 """Return a list of checkpoints for a given notebook"""
159 return []
160 return []
160
161
161 def restore_checkpoint(self, checkpoint_id, name, path=''):
162 def restore_checkpoint(self, checkpoint_id, name, path=''):
162 """Restore a notebook from one of its checkpoints"""
163 """Restore a notebook from one of its checkpoints"""
163 raise NotImplementedError("must be implemented in a subclass")
164 raise NotImplementedError("must be implemented in a subclass")
164
165
165 def delete_checkpoint(self, checkpoint_id, name, path=''):
166 def delete_checkpoint(self, checkpoint_id, name, path=''):
166 """delete a checkpoint for a notebook"""
167 """delete a checkpoint for a notebook"""
167 raise NotImplementedError("must be implemented in a subclass")
168 raise NotImplementedError("must be implemented in a subclass")
168
169
169 def log_info(self):
170 def log_info(self):
170 self.log.info(self.info_string())
171 self.log.info(self.info_string())
171
172
172 def info_string(self):
173 def info_string(self):
173 return "Serving notebooks"
174 return "Serving notebooks"
@@ -1,275 +1,276 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 The Base Application class for IPython.parallel apps
3 The Base Application class for IPython.parallel apps
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Min RK
8 * Min RK
9
9
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import logging
24 import logging
25 import re
25 import re
26 import sys
26 import sys
27
27
28 from subprocess import Popen, PIPE
28 from subprocess import Popen, PIPE
29
29
30 from IPython.config.application import catch_config_error, LevelFormatter
30 from IPython.config.application import catch_config_error, LevelFormatter
31 from IPython.core import release
31 from IPython.core import release
32 from IPython.core.crashhandler import CrashHandler
32 from IPython.core.crashhandler import CrashHandler
33 from IPython.core.application import (
33 from IPython.core.application import (
34 BaseIPythonApplication,
34 BaseIPythonApplication,
35 base_aliases as base_ip_aliases,
35 base_aliases as base_ip_aliases,
36 base_flags as base_ip_flags
36 base_flags as base_ip_flags
37 )
37 )
38 from IPython.utils.path import expand_path
38 from IPython.utils.path import expand_path
39 from IPython.utils import py3compat
39 from IPython.utils.py3compat import unicode_type
40 from IPython.utils.py3compat import unicode_type
40
41
41 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict
42
43
43 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
44 # Module errors
45 # Module errors
45 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
46
47
47 class PIDFileError(Exception):
48 class PIDFileError(Exception):
48 pass
49 pass
49
50
50
51
51 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
52 # Crash handler for this application
53 # Crash handler for this application
53 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
54
55
55 class ParallelCrashHandler(CrashHandler):
56 class ParallelCrashHandler(CrashHandler):
56 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
57 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
57
58
58 def __init__(self, app):
59 def __init__(self, app):
59 contact_name = release.authors['Min'][0]
60 contact_name = release.authors['Min'][0]
60 contact_email = release.author_email
61 contact_email = release.author_email
61 bug_tracker = 'https://github.com/ipython/ipython/issues'
62 bug_tracker = 'https://github.com/ipython/ipython/issues'
62 super(ParallelCrashHandler,self).__init__(
63 super(ParallelCrashHandler,self).__init__(
63 app, contact_name, contact_email, bug_tracker
64 app, contact_name, contact_email, bug_tracker
64 )
65 )
65
66
66
67
67 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
68 # Main application
69 # Main application
69 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
70 base_aliases = {}
71 base_aliases = {}
71 base_aliases.update(base_ip_aliases)
72 base_aliases.update(base_ip_aliases)
72 base_aliases.update({
73 base_aliases.update({
73 'work-dir' : 'BaseParallelApplication.work_dir',
74 'work-dir' : 'BaseParallelApplication.work_dir',
74 'log-to-file' : 'BaseParallelApplication.log_to_file',
75 'log-to-file' : 'BaseParallelApplication.log_to_file',
75 'clean-logs' : 'BaseParallelApplication.clean_logs',
76 'clean-logs' : 'BaseParallelApplication.clean_logs',
76 'log-url' : 'BaseParallelApplication.log_url',
77 'log-url' : 'BaseParallelApplication.log_url',
77 'cluster-id' : 'BaseParallelApplication.cluster_id',
78 'cluster-id' : 'BaseParallelApplication.cluster_id',
78 })
79 })
79
80
80 base_flags = {
81 base_flags = {
81 'log-to-file' : (
82 'log-to-file' : (
82 {'BaseParallelApplication' : {'log_to_file' : True}},
83 {'BaseParallelApplication' : {'log_to_file' : True}},
83 "send log output to a file"
84 "send log output to a file"
84 )
85 )
85 }
86 }
86 base_flags.update(base_ip_flags)
87 base_flags.update(base_ip_flags)
87
88
88 class BaseParallelApplication(BaseIPythonApplication):
89 class BaseParallelApplication(BaseIPythonApplication):
89 """The base Application for IPython.parallel apps
90 """The base Application for IPython.parallel apps
90
91
91 Principle extensions to BaseIPyythonApplication:
92 Principle extensions to BaseIPyythonApplication:
92
93
93 * work_dir
94 * work_dir
94 * remote logging via pyzmq
95 * remote logging via pyzmq
95 * IOLoop instance
96 * IOLoop instance
96 """
97 """
97
98
98 crash_handler_class = ParallelCrashHandler
99 crash_handler_class = ParallelCrashHandler
99
100
100 def _log_level_default(self):
101 def _log_level_default(self):
101 # temporarily override default_log_level to INFO
102 # temporarily override default_log_level to INFO
102 return logging.INFO
103 return logging.INFO
103
104
104 def _log_format_default(self):
105 def _log_format_default(self):
105 """override default log format to include time"""
106 """override default log format to include time"""
106 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
107 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
107
108
108 work_dir = Unicode(os.getcwdu(), config=True,
109 work_dir = Unicode(py3compat.getcwd(), config=True,
109 help='Set the working dir for the process.'
110 help='Set the working dir for the process.'
110 )
111 )
111 def _work_dir_changed(self, name, old, new):
112 def _work_dir_changed(self, name, old, new):
112 self.work_dir = unicode_type(expand_path(new))
113 self.work_dir = unicode_type(expand_path(new))
113
114
114 log_to_file = Bool(config=True,
115 log_to_file = Bool(config=True,
115 help="whether to log to a file")
116 help="whether to log to a file")
116
117
117 clean_logs = Bool(False, config=True,
118 clean_logs = Bool(False, config=True,
118 help="whether to cleanup old logfiles before starting")
119 help="whether to cleanup old logfiles before starting")
119
120
120 log_url = Unicode('', config=True,
121 log_url = Unicode('', config=True,
121 help="The ZMQ URL of the iplogger to aggregate logging.")
122 help="The ZMQ URL of the iplogger to aggregate logging.")
122
123
123 cluster_id = Unicode('', config=True,
124 cluster_id = Unicode('', config=True,
124 help="""String id to add to runtime files, to prevent name collisions when
125 help="""String id to add to runtime files, to prevent name collisions when
125 using multiple clusters with a single profile simultaneously.
126 using multiple clusters with a single profile simultaneously.
126
127
127 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
128 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
128
129
129 Since this is text inserted into filenames, typical recommendations apply:
130 Since this is text inserted into filenames, typical recommendations apply:
130 Simple character strings are ideal, and spaces are not recommended (but should
131 Simple character strings are ideal, and spaces are not recommended (but should
131 generally work).
132 generally work).
132 """
133 """
133 )
134 )
134 def _cluster_id_changed(self, name, old, new):
135 def _cluster_id_changed(self, name, old, new):
135 self.name = self.__class__.name
136 self.name = self.__class__.name
136 if new:
137 if new:
137 self.name += '-%s'%new
138 self.name += '-%s'%new
138
139
139 def _config_files_default(self):
140 def _config_files_default(self):
140 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
141 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
141
142
142 loop = Instance('zmq.eventloop.ioloop.IOLoop')
143 loop = Instance('zmq.eventloop.ioloop.IOLoop')
143 def _loop_default(self):
144 def _loop_default(self):
144 from zmq.eventloop.ioloop import IOLoop
145 from zmq.eventloop.ioloop import IOLoop
145 return IOLoop.instance()
146 return IOLoop.instance()
146
147
147 aliases = Dict(base_aliases)
148 aliases = Dict(base_aliases)
148 flags = Dict(base_flags)
149 flags = Dict(base_flags)
149
150
150 @catch_config_error
151 @catch_config_error
151 def initialize(self, argv=None):
152 def initialize(self, argv=None):
152 """initialize the app"""
153 """initialize the app"""
153 super(BaseParallelApplication, self).initialize(argv)
154 super(BaseParallelApplication, self).initialize(argv)
154 self.to_work_dir()
155 self.to_work_dir()
155 self.reinit_logging()
156 self.reinit_logging()
156
157
157 def to_work_dir(self):
158 def to_work_dir(self):
158 wd = self.work_dir
159 wd = self.work_dir
159 if unicode_type(wd) != os.getcwdu():
160 if unicode_type(wd) != py3compat.getcwd():
160 os.chdir(wd)
161 os.chdir(wd)
161 self.log.info("Changing to working dir: %s" % wd)
162 self.log.info("Changing to working dir: %s" % wd)
162 # This is the working dir by now.
163 # This is the working dir by now.
163 sys.path.insert(0, '')
164 sys.path.insert(0, '')
164
165
165 def reinit_logging(self):
166 def reinit_logging(self):
166 # Remove old log files
167 # Remove old log files
167 log_dir = self.profile_dir.log_dir
168 log_dir = self.profile_dir.log_dir
168 if self.clean_logs:
169 if self.clean_logs:
169 for f in os.listdir(log_dir):
170 for f in os.listdir(log_dir):
170 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
171 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
171 try:
172 try:
172 os.remove(os.path.join(log_dir, f))
173 os.remove(os.path.join(log_dir, f))
173 except (OSError, IOError):
174 except (OSError, IOError):
174 # probably just conflict from sibling process
175 # probably just conflict from sibling process
175 # already removing it
176 # already removing it
176 pass
177 pass
177 if self.log_to_file:
178 if self.log_to_file:
178 # Start logging to the new log file
179 # Start logging to the new log file
179 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
180 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
180 logfile = os.path.join(log_dir, log_filename)
181 logfile = os.path.join(log_dir, log_filename)
181 open_log_file = open(logfile, 'w')
182 open_log_file = open(logfile, 'w')
182 else:
183 else:
183 open_log_file = None
184 open_log_file = None
184 if open_log_file is not None:
185 if open_log_file is not None:
185 while self.log.handlers:
186 while self.log.handlers:
186 self.log.removeHandler(self.log.handlers[0])
187 self.log.removeHandler(self.log.handlers[0])
187 self._log_handler = logging.StreamHandler(open_log_file)
188 self._log_handler = logging.StreamHandler(open_log_file)
188 self.log.addHandler(self._log_handler)
189 self.log.addHandler(self._log_handler)
189 else:
190 else:
190 self._log_handler = self.log.handlers[0]
191 self._log_handler = self.log.handlers[0]
191 # Add timestamps to log format:
192 # Add timestamps to log format:
192 self._log_formatter = LevelFormatter(self.log_format,
193 self._log_formatter = LevelFormatter(self.log_format,
193 datefmt=self.log_datefmt)
194 datefmt=self.log_datefmt)
194 self._log_handler.setFormatter(self._log_formatter)
195 self._log_handler.setFormatter(self._log_formatter)
195 # do not propagate log messages to root logger
196 # do not propagate log messages to root logger
196 # ipcluster app will sometimes print duplicate messages during shutdown
197 # ipcluster app will sometimes print duplicate messages during shutdown
197 # if this is 1 (default):
198 # if this is 1 (default):
198 self.log.propagate = False
199 self.log.propagate = False
199
200
200 def write_pid_file(self, overwrite=False):
201 def write_pid_file(self, overwrite=False):
201 """Create a .pid file in the pid_dir with my pid.
202 """Create a .pid file in the pid_dir with my pid.
202
203
203 This must be called after pre_construct, which sets `self.pid_dir`.
204 This must be called after pre_construct, which sets `self.pid_dir`.
204 This raises :exc:`PIDFileError` if the pid file exists already.
205 This raises :exc:`PIDFileError` if the pid file exists already.
205 """
206 """
206 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
207 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
207 if os.path.isfile(pid_file):
208 if os.path.isfile(pid_file):
208 pid = self.get_pid_from_file()
209 pid = self.get_pid_from_file()
209 if not overwrite:
210 if not overwrite:
210 raise PIDFileError(
211 raise PIDFileError(
211 'The pid file [%s] already exists. \nThis could mean that this '
212 'The pid file [%s] already exists. \nThis could mean that this '
212 'server is already running with [pid=%s].' % (pid_file, pid)
213 'server is already running with [pid=%s].' % (pid_file, pid)
213 )
214 )
214 with open(pid_file, 'w') as f:
215 with open(pid_file, 'w') as f:
215 self.log.info("Creating pid file: %s" % pid_file)
216 self.log.info("Creating pid file: %s" % pid_file)
216 f.write(repr(os.getpid())+'\n')
217 f.write(repr(os.getpid())+'\n')
217
218
218 def remove_pid_file(self):
219 def remove_pid_file(self):
219 """Remove the pid file.
220 """Remove the pid file.
220
221
221 This should be called at shutdown by registering a callback with
222 This should be called at shutdown by registering a callback with
222 :func:`reactor.addSystemEventTrigger`. This needs to return
223 :func:`reactor.addSystemEventTrigger`. This needs to return
223 ``None``.
224 ``None``.
224 """
225 """
225 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
226 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
226 if os.path.isfile(pid_file):
227 if os.path.isfile(pid_file):
227 try:
228 try:
228 self.log.info("Removing pid file: %s" % pid_file)
229 self.log.info("Removing pid file: %s" % pid_file)
229 os.remove(pid_file)
230 os.remove(pid_file)
230 except:
231 except:
231 self.log.warn("Error removing the pid file: %s" % pid_file)
232 self.log.warn("Error removing the pid file: %s" % pid_file)
232
233
233 def get_pid_from_file(self):
234 def get_pid_from_file(self):
234 """Get the pid from the pid file.
235 """Get the pid from the pid file.
235
236
236 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
237 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
237 """
238 """
238 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
239 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
239 if os.path.isfile(pid_file):
240 if os.path.isfile(pid_file):
240 with open(pid_file, 'r') as f:
241 with open(pid_file, 'r') as f:
241 s = f.read().strip()
242 s = f.read().strip()
242 try:
243 try:
243 pid = int(s)
244 pid = int(s)
244 except:
245 except:
245 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
246 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
246 return pid
247 return pid
247 else:
248 else:
248 raise PIDFileError('pid file not found: %s' % pid_file)
249 raise PIDFileError('pid file not found: %s' % pid_file)
249
250
250 def check_pid(self, pid):
251 def check_pid(self, pid):
251 if os.name == 'nt':
252 if os.name == 'nt':
252 try:
253 try:
253 import ctypes
254 import ctypes
254 # returns 0 if no such process (of ours) exists
255 # returns 0 if no such process (of ours) exists
255 # positive int otherwise
256 # positive int otherwise
256 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
257 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
257 except Exception:
258 except Exception:
258 self.log.warn(
259 self.log.warn(
259 "Could not determine whether pid %i is running via `OpenProcess`. "
260 "Could not determine whether pid %i is running via `OpenProcess`. "
260 " Making the likely assumption that it is."%pid
261 " Making the likely assumption that it is."%pid
261 )
262 )
262 return True
263 return True
263 return bool(p)
264 return bool(p)
264 else:
265 else:
265 try:
266 try:
266 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
267 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
267 output,_ = p.communicate()
268 output,_ = p.communicate()
268 except OSError:
269 except OSError:
269 self.log.warn(
270 self.log.warn(
270 "Could not determine whether pid %i is running via `ps x`. "
271 "Could not determine whether pid %i is running via `ps x`. "
271 " Making the likely assumption that it is."%pid
272 " Making the likely assumption that it is."%pid
272 )
273 )
273 return True
274 return True
274 pids = list(map(int, re.findall(r'^\W*\d+', output, re.MULTILINE)))
275 pids = list(map(int, re.findall(r'^\W*\d+', output, re.MULTILINE)))
275 return pid in pids
276 return pid in pids
@@ -1,764 +1,764 b''
1 """Nose Plugin that supports IPython doctests.
1 """Nose Plugin that supports IPython doctests.
2
2
3 Limitations:
3 Limitations:
4
4
5 - When generating examples for use as doctests, make sure that you have
5 - When generating examples for use as doctests, make sure that you have
6 pretty-printing OFF. This can be done either by setting the
6 pretty-printing OFF. This can be done either by setting the
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8 by interactively disabling it with %Pprint. This is required so that IPython
8 by interactively disabling it with %Pprint. This is required so that IPython
9 output matches that of normal Python, which is used by doctest for internal
9 output matches that of normal Python, which is used by doctest for internal
10 execution.
10 execution.
11
11
12 - Do not rely on specific prompt numbers for results (such as using
12 - Do not rely on specific prompt numbers for results (such as using
13 '_34==True', for example). For IPython tests run via an external process the
13 '_34==True', for example). For IPython tests run via an external process the
14 prompt numbers may be different, and IPython tests run as normal python code
14 prompt numbers may be different, and IPython tests run as normal python code
15 won't even have these special _NN variables set at all.
15 won't even have these special _NN variables set at all.
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Module imports
19 # Module imports
20
20
21 # From the standard library
21 # From the standard library
22 import doctest
22 import doctest
23 import inspect
23 import inspect
24 import logging
24 import logging
25 import os
25 import os
26 import re
26 import re
27 import sys
27 import sys
28 import traceback
28 import traceback
29 import unittest
29 import unittest
30
30
31 from inspect import getmodule
31 from inspect import getmodule
32
32
33 # We are overriding the default doctest runner, so we need to import a few
33 # We are overriding the default doctest runner, so we need to import a few
34 # things from doctest directly
34 # things from doctest directly
35 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
35 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
36 _unittest_reportflags, DocTestRunner,
36 _unittest_reportflags, DocTestRunner,
37 _extract_future_flags, pdb, _OutputRedirectingPdb,
37 _extract_future_flags, pdb, _OutputRedirectingPdb,
38 _exception_traceback,
38 _exception_traceback,
39 linecache)
39 linecache)
40
40
41 # Third-party modules
41 # Third-party modules
42 import nose.core
42 import nose.core
43
43
44 from nose.plugins import doctests, Plugin
44 from nose.plugins import doctests, Plugin
45 from nose.util import anyp, getpackage, test_address, resolve_name, tolist
45 from nose.util import anyp, getpackage, test_address, resolve_name, tolist
46
46
47 # Our own imports
47 # Our own imports
48 from IPython.utils.py3compat import builtin_mod, PY3
48 from IPython.utils.py3compat import builtin_mod, PY3, getcwd
49
49
50 if PY3:
50 if PY3:
51 from io import StringIO
51 from io import StringIO
52 else:
52 else:
53 from StringIO import StringIO
53 from StringIO import StringIO
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Module globals and other constants
56 # Module globals and other constants
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Classes and functions
63 # Classes and functions
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66 def is_extension_module(filename):
66 def is_extension_module(filename):
67 """Return whether the given filename is an extension module.
67 """Return whether the given filename is an extension module.
68
68
69 This simply checks that the extension is either .so or .pyd.
69 This simply checks that the extension is either .so or .pyd.
70 """
70 """
71 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
71 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
72
72
73
73
74 class DocTestSkip(object):
74 class DocTestSkip(object):
75 """Object wrapper for doctests to be skipped."""
75 """Object wrapper for doctests to be skipped."""
76
76
77 ds_skip = """Doctest to skip.
77 ds_skip = """Doctest to skip.
78 >>> 1 #doctest: +SKIP
78 >>> 1 #doctest: +SKIP
79 """
79 """
80
80
81 def __init__(self,obj):
81 def __init__(self,obj):
82 self.obj = obj
82 self.obj = obj
83
83
84 def __getattribute__(self,key):
84 def __getattribute__(self,key):
85 if key == '__doc__':
85 if key == '__doc__':
86 return DocTestSkip.ds_skip
86 return DocTestSkip.ds_skip
87 else:
87 else:
88 return getattr(object.__getattribute__(self,'obj'),key)
88 return getattr(object.__getattribute__(self,'obj'),key)
89
89
90 # Modified version of the one in the stdlib, that fixes a python bug (doctests
90 # Modified version of the one in the stdlib, that fixes a python bug (doctests
91 # not found in extension modules, http://bugs.python.org/issue3158)
91 # not found in extension modules, http://bugs.python.org/issue3158)
92 class DocTestFinder(doctest.DocTestFinder):
92 class DocTestFinder(doctest.DocTestFinder):
93
93
94 def _from_module(self, module, object):
94 def _from_module(self, module, object):
95 """
95 """
96 Return true if the given object is defined in the given
96 Return true if the given object is defined in the given
97 module.
97 module.
98 """
98 """
99 if module is None:
99 if module is None:
100 return True
100 return True
101 elif inspect.isfunction(object):
101 elif inspect.isfunction(object):
102 return module.__dict__ is object.__globals__
102 return module.__dict__ is object.__globals__
103 elif inspect.isbuiltin(object):
103 elif inspect.isbuiltin(object):
104 return module.__name__ == object.__module__
104 return module.__name__ == object.__module__
105 elif inspect.isclass(object):
105 elif inspect.isclass(object):
106 return module.__name__ == object.__module__
106 return module.__name__ == object.__module__
107 elif inspect.ismethod(object):
107 elif inspect.ismethod(object):
108 # This one may be a bug in cython that fails to correctly set the
108 # This one may be a bug in cython that fails to correctly set the
109 # __module__ attribute of methods, but since the same error is easy
109 # __module__ attribute of methods, but since the same error is easy
110 # to make by extension code writers, having this safety in place
110 # to make by extension code writers, having this safety in place
111 # isn't such a bad idea
111 # isn't such a bad idea
112 return module.__name__ == object.__self__.__class__.__module__
112 return module.__name__ == object.__self__.__class__.__module__
113 elif inspect.getmodule(object) is not None:
113 elif inspect.getmodule(object) is not None:
114 return module is inspect.getmodule(object)
114 return module is inspect.getmodule(object)
115 elif hasattr(object, '__module__'):
115 elif hasattr(object, '__module__'):
116 return module.__name__ == object.__module__
116 return module.__name__ == object.__module__
117 elif isinstance(object, property):
117 elif isinstance(object, property):
118 return True # [XX] no way not be sure.
118 return True # [XX] no way not be sure.
119 else:
119 else:
120 raise ValueError("object must be a class or function, got %r" % object)
120 raise ValueError("object must be a class or function, got %r" % object)
121
121
122 def _find(self, tests, obj, name, module, source_lines, globs, seen):
122 def _find(self, tests, obj, name, module, source_lines, globs, seen):
123 """
123 """
124 Find tests for the given object and any contained objects, and
124 Find tests for the given object and any contained objects, and
125 add them to `tests`.
125 add them to `tests`.
126 """
126 """
127 #print '_find for:', obj, name, module # dbg
127 #print '_find for:', obj, name, module # dbg
128 if hasattr(obj,"skip_doctest"):
128 if hasattr(obj,"skip_doctest"):
129 #print 'SKIPPING DOCTEST FOR:',obj # dbg
129 #print 'SKIPPING DOCTEST FOR:',obj # dbg
130 obj = DocTestSkip(obj)
130 obj = DocTestSkip(obj)
131
131
132 doctest.DocTestFinder._find(self,tests, obj, name, module,
132 doctest.DocTestFinder._find(self,tests, obj, name, module,
133 source_lines, globs, seen)
133 source_lines, globs, seen)
134
134
135 # Below we re-run pieces of the above method with manual modifications,
135 # Below we re-run pieces of the above method with manual modifications,
136 # because the original code is buggy and fails to correctly identify
136 # because the original code is buggy and fails to correctly identify
137 # doctests in extension modules.
137 # doctests in extension modules.
138
138
139 # Local shorthands
139 # Local shorthands
140 from inspect import isroutine, isclass, ismodule
140 from inspect import isroutine, isclass, ismodule
141
141
142 # Look for tests in a module's contained objects.
142 # Look for tests in a module's contained objects.
143 if inspect.ismodule(obj) and self._recurse:
143 if inspect.ismodule(obj) and self._recurse:
144 for valname, val in obj.__dict__.items():
144 for valname, val in obj.__dict__.items():
145 valname1 = '%s.%s' % (name, valname)
145 valname1 = '%s.%s' % (name, valname)
146 if ( (isroutine(val) or isclass(val))
146 if ( (isroutine(val) or isclass(val))
147 and self._from_module(module, val) ):
147 and self._from_module(module, val) ):
148
148
149 self._find(tests, val, valname1, module, source_lines,
149 self._find(tests, val, valname1, module, source_lines,
150 globs, seen)
150 globs, seen)
151
151
152 # Look for tests in a class's contained objects.
152 # Look for tests in a class's contained objects.
153 if inspect.isclass(obj) and self._recurse:
153 if inspect.isclass(obj) and self._recurse:
154 #print 'RECURSE into class:',obj # dbg
154 #print 'RECURSE into class:',obj # dbg
155 for valname, val in obj.__dict__.items():
155 for valname, val in obj.__dict__.items():
156 # Special handling for staticmethod/classmethod.
156 # Special handling for staticmethod/classmethod.
157 if isinstance(val, staticmethod):
157 if isinstance(val, staticmethod):
158 val = getattr(obj, valname)
158 val = getattr(obj, valname)
159 if isinstance(val, classmethod):
159 if isinstance(val, classmethod):
160 val = getattr(obj, valname).__func__
160 val = getattr(obj, valname).__func__
161
161
162 # Recurse to methods, properties, and nested classes.
162 # Recurse to methods, properties, and nested classes.
163 if ((inspect.isfunction(val) or inspect.isclass(val) or
163 if ((inspect.isfunction(val) or inspect.isclass(val) or
164 inspect.ismethod(val) or
164 inspect.ismethod(val) or
165 isinstance(val, property)) and
165 isinstance(val, property)) and
166 self._from_module(module, val)):
166 self._from_module(module, val)):
167 valname = '%s.%s' % (name, valname)
167 valname = '%s.%s' % (name, valname)
168 self._find(tests, val, valname, module, source_lines,
168 self._find(tests, val, valname, module, source_lines,
169 globs, seen)
169 globs, seen)
170
170
171
171
172 class IPDoctestOutputChecker(doctest.OutputChecker):
172 class IPDoctestOutputChecker(doctest.OutputChecker):
173 """Second-chance checker with support for random tests.
173 """Second-chance checker with support for random tests.
174
174
175 If the default comparison doesn't pass, this checker looks in the expected
175 If the default comparison doesn't pass, this checker looks in the expected
176 output string for flags that tell us to ignore the output.
176 output string for flags that tell us to ignore the output.
177 """
177 """
178
178
179 random_re = re.compile(r'#\s*random\s+')
179 random_re = re.compile(r'#\s*random\s+')
180
180
181 def check_output(self, want, got, optionflags):
181 def check_output(self, want, got, optionflags):
182 """Check output, accepting special markers embedded in the output.
182 """Check output, accepting special markers embedded in the output.
183
183
184 If the output didn't pass the default validation but the special string
184 If the output didn't pass the default validation but the special string
185 '#random' is included, we accept it."""
185 '#random' is included, we accept it."""
186
186
187 # Let the original tester verify first, in case people have valid tests
187 # Let the original tester verify first, in case people have valid tests
188 # that happen to have a comment saying '#random' embedded in.
188 # that happen to have a comment saying '#random' embedded in.
189 ret = doctest.OutputChecker.check_output(self, want, got,
189 ret = doctest.OutputChecker.check_output(self, want, got,
190 optionflags)
190 optionflags)
191 if not ret and self.random_re.search(want):
191 if not ret and self.random_re.search(want):
192 #print >> sys.stderr, 'RANDOM OK:',want # dbg
192 #print >> sys.stderr, 'RANDOM OK:',want # dbg
193 return True
193 return True
194
194
195 return ret
195 return ret
196
196
197
197
198 class DocTestCase(doctests.DocTestCase):
198 class DocTestCase(doctests.DocTestCase):
199 """Proxy for DocTestCase: provides an address() method that
199 """Proxy for DocTestCase: provides an address() method that
200 returns the correct address for the doctest case. Otherwise
200 returns the correct address for the doctest case. Otherwise
201 acts as a proxy to the test case. To provide hints for address(),
201 acts as a proxy to the test case. To provide hints for address(),
202 an obj may also be passed -- this will be used as the test object
202 an obj may also be passed -- this will be used as the test object
203 for purposes of determining the test address, if it is provided.
203 for purposes of determining the test address, if it is provided.
204 """
204 """
205
205
206 # Note: this method was taken from numpy's nosetester module.
206 # Note: this method was taken from numpy's nosetester module.
207
207
208 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
208 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
209 # its constructor that blocks non-default arguments from being passed
209 # its constructor that blocks non-default arguments from being passed
210 # down into doctest.DocTestCase
210 # down into doctest.DocTestCase
211
211
212 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
212 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
213 checker=None, obj=None, result_var='_'):
213 checker=None, obj=None, result_var='_'):
214 self._result_var = result_var
214 self._result_var = result_var
215 doctests.DocTestCase.__init__(self, test,
215 doctests.DocTestCase.__init__(self, test,
216 optionflags=optionflags,
216 optionflags=optionflags,
217 setUp=setUp, tearDown=tearDown,
217 setUp=setUp, tearDown=tearDown,
218 checker=checker)
218 checker=checker)
219 # Now we must actually copy the original constructor from the stdlib
219 # Now we must actually copy the original constructor from the stdlib
220 # doctest class, because we can't call it directly and a bug in nose
220 # doctest class, because we can't call it directly and a bug in nose
221 # means it never gets passed the right arguments.
221 # means it never gets passed the right arguments.
222
222
223 self._dt_optionflags = optionflags
223 self._dt_optionflags = optionflags
224 self._dt_checker = checker
224 self._dt_checker = checker
225 self._dt_test = test
225 self._dt_test = test
226 self._dt_test_globs_ori = test.globs
226 self._dt_test_globs_ori = test.globs
227 self._dt_setUp = setUp
227 self._dt_setUp = setUp
228 self._dt_tearDown = tearDown
228 self._dt_tearDown = tearDown
229
229
230 # XXX - store this runner once in the object!
230 # XXX - store this runner once in the object!
231 runner = IPDocTestRunner(optionflags=optionflags,
231 runner = IPDocTestRunner(optionflags=optionflags,
232 checker=checker, verbose=False)
232 checker=checker, verbose=False)
233 self._dt_runner = runner
233 self._dt_runner = runner
234
234
235
235
236 # Each doctest should remember the directory it was loaded from, so
236 # Each doctest should remember the directory it was loaded from, so
237 # things like %run work without too many contortions
237 # things like %run work without too many contortions
238 self._ori_dir = os.path.dirname(test.filename)
238 self._ori_dir = os.path.dirname(test.filename)
239
239
240 # Modified runTest from the default stdlib
240 # Modified runTest from the default stdlib
241 def runTest(self):
241 def runTest(self):
242 test = self._dt_test
242 test = self._dt_test
243 runner = self._dt_runner
243 runner = self._dt_runner
244
244
245 old = sys.stdout
245 old = sys.stdout
246 new = StringIO()
246 new = StringIO()
247 optionflags = self._dt_optionflags
247 optionflags = self._dt_optionflags
248
248
249 if not (optionflags & REPORTING_FLAGS):
249 if not (optionflags & REPORTING_FLAGS):
250 # The option flags don't include any reporting flags,
250 # The option flags don't include any reporting flags,
251 # so add the default reporting flags
251 # so add the default reporting flags
252 optionflags |= _unittest_reportflags
252 optionflags |= _unittest_reportflags
253
253
254 try:
254 try:
255 # Save our current directory and switch out to the one where the
255 # Save our current directory and switch out to the one where the
256 # test was originally created, in case another doctest did a
256 # test was originally created, in case another doctest did a
257 # directory change. We'll restore this in the finally clause.
257 # directory change. We'll restore this in the finally clause.
258 curdir = os.getcwdu()
258 curdir = getcwd()
259 #print 'runTest in dir:', self._ori_dir # dbg
259 #print 'runTest in dir:', self._ori_dir # dbg
260 os.chdir(self._ori_dir)
260 os.chdir(self._ori_dir)
261
261
262 runner.DIVIDER = "-"*70
262 runner.DIVIDER = "-"*70
263 failures, tries = runner.run(test,out=new.write,
263 failures, tries = runner.run(test,out=new.write,
264 clear_globs=False)
264 clear_globs=False)
265 finally:
265 finally:
266 sys.stdout = old
266 sys.stdout = old
267 os.chdir(curdir)
267 os.chdir(curdir)
268
268
269 if failures:
269 if failures:
270 raise self.failureException(self.format_failure(new.getvalue()))
270 raise self.failureException(self.format_failure(new.getvalue()))
271
271
272 def setUp(self):
272 def setUp(self):
273 """Modified test setup that syncs with ipython namespace"""
273 """Modified test setup that syncs with ipython namespace"""
274 #print "setUp test", self._dt_test.examples # dbg
274 #print "setUp test", self._dt_test.examples # dbg
275 if isinstance(self._dt_test.examples[0], IPExample):
275 if isinstance(self._dt_test.examples[0], IPExample):
276 # for IPython examples *only*, we swap the globals with the ipython
276 # for IPython examples *only*, we swap the globals with the ipython
277 # namespace, after updating it with the globals (which doctest
277 # namespace, after updating it with the globals (which doctest
278 # fills with the necessary info from the module being tested).
278 # fills with the necessary info from the module being tested).
279 self.user_ns_orig = {}
279 self.user_ns_orig = {}
280 self.user_ns_orig.update(_ip.user_ns)
280 self.user_ns_orig.update(_ip.user_ns)
281 _ip.user_ns.update(self._dt_test.globs)
281 _ip.user_ns.update(self._dt_test.globs)
282 # We must remove the _ key in the namespace, so that Python's
282 # We must remove the _ key in the namespace, so that Python's
283 # doctest code sets it naturally
283 # doctest code sets it naturally
284 _ip.user_ns.pop('_', None)
284 _ip.user_ns.pop('_', None)
285 _ip.user_ns['__builtins__'] = builtin_mod
285 _ip.user_ns['__builtins__'] = builtin_mod
286 self._dt_test.globs = _ip.user_ns
286 self._dt_test.globs = _ip.user_ns
287
287
288 super(DocTestCase, self).setUp()
288 super(DocTestCase, self).setUp()
289
289
290 def tearDown(self):
290 def tearDown(self):
291
291
292 # Undo the test.globs reassignment we made, so that the parent class
292 # Undo the test.globs reassignment we made, so that the parent class
293 # teardown doesn't destroy the ipython namespace
293 # teardown doesn't destroy the ipython namespace
294 if isinstance(self._dt_test.examples[0], IPExample):
294 if isinstance(self._dt_test.examples[0], IPExample):
295 self._dt_test.globs = self._dt_test_globs_ori
295 self._dt_test.globs = self._dt_test_globs_ori
296 _ip.user_ns.clear()
296 _ip.user_ns.clear()
297 _ip.user_ns.update(self.user_ns_orig)
297 _ip.user_ns.update(self.user_ns_orig)
298
298
299 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
299 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
300 # it does look like one to me: its tearDown method tries to run
300 # it does look like one to me: its tearDown method tries to run
301 #
301 #
302 # delattr(builtin_mod, self._result_var)
302 # delattr(builtin_mod, self._result_var)
303 #
303 #
304 # without checking that the attribute really is there; it implicitly
304 # without checking that the attribute really is there; it implicitly
305 # assumes it should have been set via displayhook. But if the
305 # assumes it should have been set via displayhook. But if the
306 # displayhook was never called, this doesn't necessarily happen. I
306 # displayhook was never called, this doesn't necessarily happen. I
307 # haven't been able to find a little self-contained example outside of
307 # haven't been able to find a little self-contained example outside of
308 # ipython that would show the problem so I can report it to the nose
308 # ipython that would show the problem so I can report it to the nose
309 # team, but it does happen a lot in our code.
309 # team, but it does happen a lot in our code.
310 #
310 #
311 # So here, we just protect as narrowly as possible by trapping an
311 # So here, we just protect as narrowly as possible by trapping an
312 # attribute error whose message would be the name of self._result_var,
312 # attribute error whose message would be the name of self._result_var,
313 # and letting any other error propagate.
313 # and letting any other error propagate.
314 try:
314 try:
315 super(DocTestCase, self).tearDown()
315 super(DocTestCase, self).tearDown()
316 except AttributeError as exc:
316 except AttributeError as exc:
317 if exc.args[0] != self._result_var:
317 if exc.args[0] != self._result_var:
318 raise
318 raise
319
319
320
320
321 # A simple subclassing of the original with a different class name, so we can
321 # A simple subclassing of the original with a different class name, so we can
322 # distinguish and treat differently IPython examples from pure python ones.
322 # distinguish and treat differently IPython examples from pure python ones.
323 class IPExample(doctest.Example): pass
323 class IPExample(doctest.Example): pass
324
324
325
325
326 class IPExternalExample(doctest.Example):
326 class IPExternalExample(doctest.Example):
327 """Doctest examples to be run in an external process."""
327 """Doctest examples to be run in an external process."""
328
328
329 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
329 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
330 options=None):
330 options=None):
331 # Parent constructor
331 # Parent constructor
332 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
332 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
333
333
334 # An EXTRA newline is needed to prevent pexpect hangs
334 # An EXTRA newline is needed to prevent pexpect hangs
335 self.source += '\n'
335 self.source += '\n'
336
336
337
337
338 class IPDocTestParser(doctest.DocTestParser):
338 class IPDocTestParser(doctest.DocTestParser):
339 """
339 """
340 A class used to parse strings containing doctest examples.
340 A class used to parse strings containing doctest examples.
341
341
342 Note: This is a version modified to properly recognize IPython input and
342 Note: This is a version modified to properly recognize IPython input and
343 convert any IPython examples into valid Python ones.
343 convert any IPython examples into valid Python ones.
344 """
344 """
345 # This regular expression is used to find doctest examples in a
345 # This regular expression is used to find doctest examples in a
346 # string. It defines three groups: `source` is the source code
346 # string. It defines three groups: `source` is the source code
347 # (including leading indentation and prompts); `indent` is the
347 # (including leading indentation and prompts); `indent` is the
348 # indentation of the first (PS1) line of the source code; and
348 # indentation of the first (PS1) line of the source code; and
349 # `want` is the expected output (including leading indentation).
349 # `want` is the expected output (including leading indentation).
350
350
351 # Classic Python prompts or default IPython ones
351 # Classic Python prompts or default IPython ones
352 _PS1_PY = r'>>>'
352 _PS1_PY = r'>>>'
353 _PS2_PY = r'\.\.\.'
353 _PS2_PY = r'\.\.\.'
354
354
355 _PS1_IP = r'In\ \[\d+\]:'
355 _PS1_IP = r'In\ \[\d+\]:'
356 _PS2_IP = r'\ \ \ \.\.\.+:'
356 _PS2_IP = r'\ \ \ \.\.\.+:'
357
357
358 _RE_TPL = r'''
358 _RE_TPL = r'''
359 # Source consists of a PS1 line followed by zero or more PS2 lines.
359 # Source consists of a PS1 line followed by zero or more PS2 lines.
360 (?P<source>
360 (?P<source>
361 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
361 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
362 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
362 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
363 \n? # a newline
363 \n? # a newline
364 # Want consists of any non-blank lines that do not start with PS1.
364 # Want consists of any non-blank lines that do not start with PS1.
365 (?P<want> (?:(?![ ]*$) # Not a blank line
365 (?P<want> (?:(?![ ]*$) # Not a blank line
366 (?![ ]*%s) # Not a line starting with PS1
366 (?![ ]*%s) # Not a line starting with PS1
367 (?![ ]*%s) # Not a line starting with PS2
367 (?![ ]*%s) # Not a line starting with PS2
368 .*$\n? # But any other line
368 .*$\n? # But any other line
369 )*)
369 )*)
370 '''
370 '''
371
371
372 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
372 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
373 re.MULTILINE | re.VERBOSE)
373 re.MULTILINE | re.VERBOSE)
374
374
375 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
375 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
376 re.MULTILINE | re.VERBOSE)
376 re.MULTILINE | re.VERBOSE)
377
377
378 # Mark a test as being fully random. In this case, we simply append the
378 # Mark a test as being fully random. In this case, we simply append the
379 # random marker ('#random') to each individual example's output. This way
379 # random marker ('#random') to each individual example's output. This way
380 # we don't need to modify any other code.
380 # we don't need to modify any other code.
381 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
381 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
382
382
383 # Mark tests to be executed in an external process - currently unsupported.
383 # Mark tests to be executed in an external process - currently unsupported.
384 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
384 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
385
385
386 def ip2py(self,source):
386 def ip2py(self,source):
387 """Convert input IPython source into valid Python."""
387 """Convert input IPython source into valid Python."""
388 block = _ip.input_transformer_manager.transform_cell(source)
388 block = _ip.input_transformer_manager.transform_cell(source)
389 if len(block.splitlines()) == 1:
389 if len(block.splitlines()) == 1:
390 return _ip.prefilter(block)
390 return _ip.prefilter(block)
391 else:
391 else:
392 return block
392 return block
393
393
394 def parse(self, string, name='<string>'):
394 def parse(self, string, name='<string>'):
395 """
395 """
396 Divide the given string into examples and intervening text,
396 Divide the given string into examples and intervening text,
397 and return them as a list of alternating Examples and strings.
397 and return them as a list of alternating Examples and strings.
398 Line numbers for the Examples are 0-based. The optional
398 Line numbers for the Examples are 0-based. The optional
399 argument `name` is a name identifying this string, and is only
399 argument `name` is a name identifying this string, and is only
400 used for error messages.
400 used for error messages.
401 """
401 """
402
402
403 #print 'Parse string:\n',string # dbg
403 #print 'Parse string:\n',string # dbg
404
404
405 string = string.expandtabs()
405 string = string.expandtabs()
406 # If all lines begin with the same indentation, then strip it.
406 # If all lines begin with the same indentation, then strip it.
407 min_indent = self._min_indent(string)
407 min_indent = self._min_indent(string)
408 if min_indent > 0:
408 if min_indent > 0:
409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
410
410
411 output = []
411 output = []
412 charno, lineno = 0, 0
412 charno, lineno = 0, 0
413
413
414 # We make 'all random' tests by adding the '# random' mark to every
414 # We make 'all random' tests by adding the '# random' mark to every
415 # block of output in the test.
415 # block of output in the test.
416 if self._RANDOM_TEST.search(string):
416 if self._RANDOM_TEST.search(string):
417 random_marker = '\n# random'
417 random_marker = '\n# random'
418 else:
418 else:
419 random_marker = ''
419 random_marker = ''
420
420
421 # Whether to convert the input from ipython to python syntax
421 # Whether to convert the input from ipython to python syntax
422 ip2py = False
422 ip2py = False
423 # Find all doctest examples in the string. First, try them as Python
423 # Find all doctest examples in the string. First, try them as Python
424 # examples, then as IPython ones
424 # examples, then as IPython ones
425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
426 if terms:
426 if terms:
427 # Normal Python example
427 # Normal Python example
428 #print '-'*70 # dbg
428 #print '-'*70 # dbg
429 #print 'PyExample, Source:\n',string # dbg
429 #print 'PyExample, Source:\n',string # dbg
430 #print '-'*70 # dbg
430 #print '-'*70 # dbg
431 Example = doctest.Example
431 Example = doctest.Example
432 else:
432 else:
433 # It's an ipython example. Note that IPExamples are run
433 # It's an ipython example. Note that IPExamples are run
434 # in-process, so their syntax must be turned into valid python.
434 # in-process, so their syntax must be turned into valid python.
435 # IPExternalExamples are run out-of-process (via pexpect) so they
435 # IPExternalExamples are run out-of-process (via pexpect) so they
436 # don't need any filtering (a real ipython will be executing them).
436 # don't need any filtering (a real ipython will be executing them).
437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
438 if self._EXTERNAL_IP.search(string):
438 if self._EXTERNAL_IP.search(string):
439 #print '-'*70 # dbg
439 #print '-'*70 # dbg
440 #print 'IPExternalExample, Source:\n',string # dbg
440 #print 'IPExternalExample, Source:\n',string # dbg
441 #print '-'*70 # dbg
441 #print '-'*70 # dbg
442 Example = IPExternalExample
442 Example = IPExternalExample
443 else:
443 else:
444 #print '-'*70 # dbg
444 #print '-'*70 # dbg
445 #print 'IPExample, Source:\n',string # dbg
445 #print 'IPExample, Source:\n',string # dbg
446 #print '-'*70 # dbg
446 #print '-'*70 # dbg
447 Example = IPExample
447 Example = IPExample
448 ip2py = True
448 ip2py = True
449
449
450 for m in terms:
450 for m in terms:
451 # Add the pre-example text to `output`.
451 # Add the pre-example text to `output`.
452 output.append(string[charno:m.start()])
452 output.append(string[charno:m.start()])
453 # Update lineno (lines before this example)
453 # Update lineno (lines before this example)
454 lineno += string.count('\n', charno, m.start())
454 lineno += string.count('\n', charno, m.start())
455 # Extract info from the regexp match.
455 # Extract info from the regexp match.
456 (source, options, want, exc_msg) = \
456 (source, options, want, exc_msg) = \
457 self._parse_example(m, name, lineno,ip2py)
457 self._parse_example(m, name, lineno,ip2py)
458
458
459 # Append the random-output marker (it defaults to empty in most
459 # Append the random-output marker (it defaults to empty in most
460 # cases, it's only non-empty for 'all-random' tests):
460 # cases, it's only non-empty for 'all-random' tests):
461 want += random_marker
461 want += random_marker
462
462
463 if Example is IPExternalExample:
463 if Example is IPExternalExample:
464 options[doctest.NORMALIZE_WHITESPACE] = True
464 options[doctest.NORMALIZE_WHITESPACE] = True
465 want += '\n'
465 want += '\n'
466
466
467 # Create an Example, and add it to the list.
467 # Create an Example, and add it to the list.
468 if not self._IS_BLANK_OR_COMMENT(source):
468 if not self._IS_BLANK_OR_COMMENT(source):
469 output.append(Example(source, want, exc_msg,
469 output.append(Example(source, want, exc_msg,
470 lineno=lineno,
470 lineno=lineno,
471 indent=min_indent+len(m.group('indent')),
471 indent=min_indent+len(m.group('indent')),
472 options=options))
472 options=options))
473 # Update lineno (lines inside this example)
473 # Update lineno (lines inside this example)
474 lineno += string.count('\n', m.start(), m.end())
474 lineno += string.count('\n', m.start(), m.end())
475 # Update charno.
475 # Update charno.
476 charno = m.end()
476 charno = m.end()
477 # Add any remaining post-example text to `output`.
477 # Add any remaining post-example text to `output`.
478 output.append(string[charno:])
478 output.append(string[charno:])
479 return output
479 return output
480
480
481 def _parse_example(self, m, name, lineno,ip2py=False):
481 def _parse_example(self, m, name, lineno,ip2py=False):
482 """
482 """
483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
484 return a pair `(source, want)`, where `source` is the matched
484 return a pair `(source, want)`, where `source` is the matched
485 example's source code (with prompts and indentation stripped);
485 example's source code (with prompts and indentation stripped);
486 and `want` is the example's expected output (with indentation
486 and `want` is the example's expected output (with indentation
487 stripped).
487 stripped).
488
488
489 `name` is the string's name, and `lineno` is the line number
489 `name` is the string's name, and `lineno` is the line number
490 where the example starts; both are used for error messages.
490 where the example starts; both are used for error messages.
491
491
492 Optional:
492 Optional:
493 `ip2py`: if true, filter the input via IPython to convert the syntax
493 `ip2py`: if true, filter the input via IPython to convert the syntax
494 into valid python.
494 into valid python.
495 """
495 """
496
496
497 # Get the example's indentation level.
497 # Get the example's indentation level.
498 indent = len(m.group('indent'))
498 indent = len(m.group('indent'))
499
499
500 # Divide source into lines; check that they're properly
500 # Divide source into lines; check that they're properly
501 # indented; and then strip their indentation & prompts.
501 # indented; and then strip their indentation & prompts.
502 source_lines = m.group('source').split('\n')
502 source_lines = m.group('source').split('\n')
503
503
504 # We're using variable-length input prompts
504 # We're using variable-length input prompts
505 ps1 = m.group('ps1')
505 ps1 = m.group('ps1')
506 ps2 = m.group('ps2')
506 ps2 = m.group('ps2')
507 ps1_len = len(ps1)
507 ps1_len = len(ps1)
508
508
509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
510 if ps2:
510 if ps2:
511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
512
512
513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
514
514
515 if ip2py:
515 if ip2py:
516 # Convert source input from IPython into valid Python syntax
516 # Convert source input from IPython into valid Python syntax
517 source = self.ip2py(source)
517 source = self.ip2py(source)
518
518
519 # Divide want into lines; check that it's properly indented; and
519 # Divide want into lines; check that it's properly indented; and
520 # then strip the indentation. Spaces before the last newline should
520 # then strip the indentation. Spaces before the last newline should
521 # be preserved, so plain rstrip() isn't good enough.
521 # be preserved, so plain rstrip() isn't good enough.
522 want = m.group('want')
522 want = m.group('want')
523 want_lines = want.split('\n')
523 want_lines = want.split('\n')
524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
525 del want_lines[-1] # forget final newline & spaces after it
525 del want_lines[-1] # forget final newline & spaces after it
526 self._check_prefix(want_lines, ' '*indent, name,
526 self._check_prefix(want_lines, ' '*indent, name,
527 lineno + len(source_lines))
527 lineno + len(source_lines))
528
528
529 # Remove ipython output prompt that might be present in the first line
529 # Remove ipython output prompt that might be present in the first line
530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
531
531
532 want = '\n'.join([wl[indent:] for wl in want_lines])
532 want = '\n'.join([wl[indent:] for wl in want_lines])
533
533
534 # If `want` contains a traceback message, then extract it.
534 # If `want` contains a traceback message, then extract it.
535 m = self._EXCEPTION_RE.match(want)
535 m = self._EXCEPTION_RE.match(want)
536 if m:
536 if m:
537 exc_msg = m.group('msg')
537 exc_msg = m.group('msg')
538 else:
538 else:
539 exc_msg = None
539 exc_msg = None
540
540
541 # Extract options from the source.
541 # Extract options from the source.
542 options = self._find_options(source, name, lineno)
542 options = self._find_options(source, name, lineno)
543
543
544 return source, options, want, exc_msg
544 return source, options, want, exc_msg
545
545
546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
547 """
547 """
548 Given the lines of a source string (including prompts and
548 Given the lines of a source string (including prompts and
549 leading indentation), check to make sure that every prompt is
549 leading indentation), check to make sure that every prompt is
550 followed by a space character. If any line is not followed by
550 followed by a space character. If any line is not followed by
551 a space character, then raise ValueError.
551 a space character, then raise ValueError.
552
552
553 Note: IPython-modified version which takes the input prompt length as a
553 Note: IPython-modified version which takes the input prompt length as a
554 parameter, so that prompts of variable length can be dealt with.
554 parameter, so that prompts of variable length can be dealt with.
555 """
555 """
556 space_idx = indent+ps1_len
556 space_idx = indent+ps1_len
557 min_len = space_idx+1
557 min_len = space_idx+1
558 for i, line in enumerate(lines):
558 for i, line in enumerate(lines):
559 if len(line) >= min_len and line[space_idx] != ' ':
559 if len(line) >= min_len and line[space_idx] != ' ':
560 raise ValueError('line %r of the docstring for %s '
560 raise ValueError('line %r of the docstring for %s '
561 'lacks blank after %s: %r' %
561 'lacks blank after %s: %r' %
562 (lineno+i+1, name,
562 (lineno+i+1, name,
563 line[indent:space_idx], line))
563 line[indent:space_idx], line))
564
564
565
565
566 SKIP = doctest.register_optionflag('SKIP')
566 SKIP = doctest.register_optionflag('SKIP')
567
567
568
568
569 class IPDocTestRunner(doctest.DocTestRunner,object):
569 class IPDocTestRunner(doctest.DocTestRunner,object):
570 """Test runner that synchronizes the IPython namespace with test globals.
570 """Test runner that synchronizes the IPython namespace with test globals.
571 """
571 """
572
572
573 def run(self, test, compileflags=None, out=None, clear_globs=True):
573 def run(self, test, compileflags=None, out=None, clear_globs=True):
574
574
575 # Hack: ipython needs access to the execution context of the example,
575 # Hack: ipython needs access to the execution context of the example,
576 # so that it can propagate user variables loaded by %run into
576 # so that it can propagate user variables loaded by %run into
577 # test.globs. We put them here into our modified %run as a function
577 # test.globs. We put them here into our modified %run as a function
578 # attribute. Our new %run will then only make the namespace update
578 # attribute. Our new %run will then only make the namespace update
579 # when called (rather than unconconditionally updating test.globs here
579 # when called (rather than unconconditionally updating test.globs here
580 # for all examples, most of which won't be calling %run anyway).
580 # for all examples, most of which won't be calling %run anyway).
581 #_ip._ipdoctest_test_globs = test.globs
581 #_ip._ipdoctest_test_globs = test.globs
582 #_ip._ipdoctest_test_filename = test.filename
582 #_ip._ipdoctest_test_filename = test.filename
583
583
584 test.globs.update(_ip.user_ns)
584 test.globs.update(_ip.user_ns)
585
585
586 return super(IPDocTestRunner,self).run(test,
586 return super(IPDocTestRunner,self).run(test,
587 compileflags,out,clear_globs)
587 compileflags,out,clear_globs)
588
588
589
589
590 class DocFileCase(doctest.DocFileCase):
590 class DocFileCase(doctest.DocFileCase):
591 """Overrides to provide filename
591 """Overrides to provide filename
592 """
592 """
593 def address(self):
593 def address(self):
594 return (self._dt_test.filename, None, None)
594 return (self._dt_test.filename, None, None)
595
595
596
596
597 class ExtensionDoctest(doctests.Doctest):
597 class ExtensionDoctest(doctests.Doctest):
598 """Nose Plugin that supports doctests in extension modules.
598 """Nose Plugin that supports doctests in extension modules.
599 """
599 """
600 name = 'extdoctest' # call nosetests with --with-extdoctest
600 name = 'extdoctest' # call nosetests with --with-extdoctest
601 enabled = True
601 enabled = True
602
602
603 def options(self, parser, env=os.environ):
603 def options(self, parser, env=os.environ):
604 Plugin.options(self, parser, env)
604 Plugin.options(self, parser, env)
605 parser.add_option('--doctest-tests', action='store_true',
605 parser.add_option('--doctest-tests', action='store_true',
606 dest='doctest_tests',
606 dest='doctest_tests',
607 default=env.get('NOSE_DOCTEST_TESTS',True),
607 default=env.get('NOSE_DOCTEST_TESTS',True),
608 help="Also look for doctests in test modules. "
608 help="Also look for doctests in test modules. "
609 "Note that classes, methods and functions should "
609 "Note that classes, methods and functions should "
610 "have either doctests or non-doctest tests, "
610 "have either doctests or non-doctest tests, "
611 "not both. [NOSE_DOCTEST_TESTS]")
611 "not both. [NOSE_DOCTEST_TESTS]")
612 parser.add_option('--doctest-extension', action="append",
612 parser.add_option('--doctest-extension', action="append",
613 dest="doctestExtension",
613 dest="doctestExtension",
614 help="Also look for doctests in files with "
614 help="Also look for doctests in files with "
615 "this extension [NOSE_DOCTEST_EXTENSION]")
615 "this extension [NOSE_DOCTEST_EXTENSION]")
616 # Set the default as a list, if given in env; otherwise
616 # Set the default as a list, if given in env; otherwise
617 # an additional value set on the command line will cause
617 # an additional value set on the command line will cause
618 # an error.
618 # an error.
619 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
619 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
620 if env_setting is not None:
620 if env_setting is not None:
621 parser.set_defaults(doctestExtension=tolist(env_setting))
621 parser.set_defaults(doctestExtension=tolist(env_setting))
622
622
623
623
624 def configure(self, options, config):
624 def configure(self, options, config):
625 Plugin.configure(self, options, config)
625 Plugin.configure(self, options, config)
626 # Pull standard doctest plugin out of config; we will do doctesting
626 # Pull standard doctest plugin out of config; we will do doctesting
627 config.plugins.plugins = [p for p in config.plugins.plugins
627 config.plugins.plugins = [p for p in config.plugins.plugins
628 if p.name != 'doctest']
628 if p.name != 'doctest']
629 self.doctest_tests = options.doctest_tests
629 self.doctest_tests = options.doctest_tests
630 self.extension = tolist(options.doctestExtension)
630 self.extension = tolist(options.doctestExtension)
631
631
632 self.parser = doctest.DocTestParser()
632 self.parser = doctest.DocTestParser()
633 self.finder = DocTestFinder()
633 self.finder = DocTestFinder()
634 self.checker = IPDoctestOutputChecker()
634 self.checker = IPDoctestOutputChecker()
635 self.globs = None
635 self.globs = None
636 self.extraglobs = None
636 self.extraglobs = None
637
637
638
638
639 def loadTestsFromExtensionModule(self,filename):
639 def loadTestsFromExtensionModule(self,filename):
640 bpath,mod = os.path.split(filename)
640 bpath,mod = os.path.split(filename)
641 modname = os.path.splitext(mod)[0]
641 modname = os.path.splitext(mod)[0]
642 try:
642 try:
643 sys.path.append(bpath)
643 sys.path.append(bpath)
644 module = __import__(modname)
644 module = __import__(modname)
645 tests = list(self.loadTestsFromModule(module))
645 tests = list(self.loadTestsFromModule(module))
646 finally:
646 finally:
647 sys.path.pop()
647 sys.path.pop()
648 return tests
648 return tests
649
649
650 # NOTE: the method below is almost a copy of the original one in nose, with
650 # NOTE: the method below is almost a copy of the original one in nose, with
651 # a few modifications to control output checking.
651 # a few modifications to control output checking.
652
652
653 def loadTestsFromModule(self, module):
653 def loadTestsFromModule(self, module):
654 #print '*** ipdoctest - lTM',module # dbg
654 #print '*** ipdoctest - lTM',module # dbg
655
655
656 if not self.matches(module.__name__):
656 if not self.matches(module.__name__):
657 log.debug("Doctest doesn't want module %s", module)
657 log.debug("Doctest doesn't want module %s", module)
658 return
658 return
659
659
660 tests = self.finder.find(module,globs=self.globs,
660 tests = self.finder.find(module,globs=self.globs,
661 extraglobs=self.extraglobs)
661 extraglobs=self.extraglobs)
662 if not tests:
662 if not tests:
663 return
663 return
664
664
665 # always use whitespace and ellipsis options
665 # always use whitespace and ellipsis options
666 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
666 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
667
667
668 tests.sort()
668 tests.sort()
669 module_file = module.__file__
669 module_file = module.__file__
670 if module_file[-4:] in ('.pyc', '.pyo'):
670 if module_file[-4:] in ('.pyc', '.pyo'):
671 module_file = module_file[:-1]
671 module_file = module_file[:-1]
672 for test in tests:
672 for test in tests:
673 if not test.examples:
673 if not test.examples:
674 continue
674 continue
675 if not test.filename:
675 if not test.filename:
676 test.filename = module_file
676 test.filename = module_file
677
677
678 yield DocTestCase(test,
678 yield DocTestCase(test,
679 optionflags=optionflags,
679 optionflags=optionflags,
680 checker=self.checker)
680 checker=self.checker)
681
681
682
682
683 def loadTestsFromFile(self, filename):
683 def loadTestsFromFile(self, filename):
684 #print "ipdoctest - from file", filename # dbg
684 #print "ipdoctest - from file", filename # dbg
685 if is_extension_module(filename):
685 if is_extension_module(filename):
686 for t in self.loadTestsFromExtensionModule(filename):
686 for t in self.loadTestsFromExtensionModule(filename):
687 yield t
687 yield t
688 else:
688 else:
689 if self.extension and anyp(filename.endswith, self.extension):
689 if self.extension and anyp(filename.endswith, self.extension):
690 name = os.path.basename(filename)
690 name = os.path.basename(filename)
691 dh = open(filename)
691 dh = open(filename)
692 try:
692 try:
693 doc = dh.read()
693 doc = dh.read()
694 finally:
694 finally:
695 dh.close()
695 dh.close()
696 test = self.parser.get_doctest(
696 test = self.parser.get_doctest(
697 doc, globs={'__file__': filename}, name=name,
697 doc, globs={'__file__': filename}, name=name,
698 filename=filename, lineno=0)
698 filename=filename, lineno=0)
699 if test.examples:
699 if test.examples:
700 #print 'FileCase:',test.examples # dbg
700 #print 'FileCase:',test.examples # dbg
701 yield DocFileCase(test)
701 yield DocFileCase(test)
702 else:
702 else:
703 yield False # no tests to load
703 yield False # no tests to load
704
704
705
705
706 class IPythonDoctest(ExtensionDoctest):
706 class IPythonDoctest(ExtensionDoctest):
707 """Nose Plugin that supports doctests in extension modules.
707 """Nose Plugin that supports doctests in extension modules.
708 """
708 """
709 name = 'ipdoctest' # call nosetests with --with-ipdoctest
709 name = 'ipdoctest' # call nosetests with --with-ipdoctest
710 enabled = True
710 enabled = True
711
711
712 def makeTest(self, obj, parent):
712 def makeTest(self, obj, parent):
713 """Look for doctests in the given object, which will be a
713 """Look for doctests in the given object, which will be a
714 function, method or class.
714 function, method or class.
715 """
715 """
716 #print 'Plugin analyzing:', obj, parent # dbg
716 #print 'Plugin analyzing:', obj, parent # dbg
717 # always use whitespace and ellipsis options
717 # always use whitespace and ellipsis options
718 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
718 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
719
719
720 doctests = self.finder.find(obj, module=getmodule(parent))
720 doctests = self.finder.find(obj, module=getmodule(parent))
721 if doctests:
721 if doctests:
722 for test in doctests:
722 for test in doctests:
723 if len(test.examples) == 0:
723 if len(test.examples) == 0:
724 continue
724 continue
725
725
726 yield DocTestCase(test, obj=obj,
726 yield DocTestCase(test, obj=obj,
727 optionflags=optionflags,
727 optionflags=optionflags,
728 checker=self.checker)
728 checker=self.checker)
729
729
730 def options(self, parser, env=os.environ):
730 def options(self, parser, env=os.environ):
731 #print "Options for nose plugin:", self.name # dbg
731 #print "Options for nose plugin:", self.name # dbg
732 Plugin.options(self, parser, env)
732 Plugin.options(self, parser, env)
733 parser.add_option('--ipdoctest-tests', action='store_true',
733 parser.add_option('--ipdoctest-tests', action='store_true',
734 dest='ipdoctest_tests',
734 dest='ipdoctest_tests',
735 default=env.get('NOSE_IPDOCTEST_TESTS',True),
735 default=env.get('NOSE_IPDOCTEST_TESTS',True),
736 help="Also look for doctests in test modules. "
736 help="Also look for doctests in test modules. "
737 "Note that classes, methods and functions should "
737 "Note that classes, methods and functions should "
738 "have either doctests or non-doctest tests, "
738 "have either doctests or non-doctest tests, "
739 "not both. [NOSE_IPDOCTEST_TESTS]")
739 "not both. [NOSE_IPDOCTEST_TESTS]")
740 parser.add_option('--ipdoctest-extension', action="append",
740 parser.add_option('--ipdoctest-extension', action="append",
741 dest="ipdoctest_extension",
741 dest="ipdoctest_extension",
742 help="Also look for doctests in files with "
742 help="Also look for doctests in files with "
743 "this extension [NOSE_IPDOCTEST_EXTENSION]")
743 "this extension [NOSE_IPDOCTEST_EXTENSION]")
744 # Set the default as a list, if given in env; otherwise
744 # Set the default as a list, if given in env; otherwise
745 # an additional value set on the command line will cause
745 # an additional value set on the command line will cause
746 # an error.
746 # an error.
747 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
747 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
748 if env_setting is not None:
748 if env_setting is not None:
749 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
749 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
750
750
751 def configure(self, options, config):
751 def configure(self, options, config):
752 #print "Configuring nose plugin:", self.name # dbg
752 #print "Configuring nose plugin:", self.name # dbg
753 Plugin.configure(self, options, config)
753 Plugin.configure(self, options, config)
754 # Pull standard doctest plugin out of config; we will do doctesting
754 # Pull standard doctest plugin out of config; we will do doctesting
755 config.plugins.plugins = [p for p in config.plugins.plugins
755 config.plugins.plugins = [p for p in config.plugins.plugins
756 if p.name != 'doctest']
756 if p.name != 'doctest']
757 self.doctest_tests = options.ipdoctest_tests
757 self.doctest_tests = options.ipdoctest_tests
758 self.extension = tolist(options.ipdoctest_extension)
758 self.extension = tolist(options.ipdoctest_extension)
759
759
760 self.parser = IPDocTestParser()
760 self.parser = IPDocTestParser()
761 self.finder = DocTestFinder(parser=self.parser)
761 self.finder = DocTestFinder(parser=self.parser)
762 self.checker = IPDoctestOutputChecker()
762 self.checker = IPDoctestOutputChecker()
763 self.globs = None
763 self.globs = None
764 self.extraglobs = None
764 self.extraglobs = None
@@ -1,187 +1,187 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 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # stdlib
18 # stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import ctypes
21 import ctypes
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
25 from subprocess import STDOUT
26
26
27 # our own imports
27 # our own imports
28 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
28 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 from . import py3compat
29 from . import py3compat
30 from .encoding import DEFAULT_ENCODING
30 from .encoding import DEFAULT_ENCODING
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Function definitions
33 # Function definitions
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class AvoidUNCPath(object):
36 class AvoidUNCPath(object):
37 """A context manager to protect command execution from UNC paths.
37 """A context manager to protect command execution from UNC paths.
38
38
39 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
39 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 This context manager temporarily changes directory to the 'C:' drive on
40 This context manager temporarily changes directory to the 'C:' drive on
41 entering, and restores the original working directory on exit.
41 entering, and restores the original working directory on exit.
42
42
43 The context manager returns the starting working directory *if* it made a
43 The context manager returns the starting working directory *if* it made a
44 change and None otherwise, so that users can apply the necessary adjustment
44 change and None otherwise, so that users can apply the necessary adjustment
45 to their system calls in the event of a change.
45 to their system calls in the event of a change.
46
46
47 Examples
47 Examples
48 --------
48 --------
49 ::
49 ::
50 cmd = 'dir'
50 cmd = 'dir'
51 with AvoidUNCPath() as path:
51 with AvoidUNCPath() as path:
52 if path is not None:
52 if path is not None:
53 cmd = '"pushd %s &&"%s' % (path, cmd)
53 cmd = '"pushd %s &&"%s' % (path, cmd)
54 os.system(cmd)
54 os.system(cmd)
55 """
55 """
56 def __enter__(self):
56 def __enter__(self):
57 self.path = os.getcwdu()
57 self.path = py3compat.getcwd()
58 self.is_unc_path = self.path.startswith(r"\\")
58 self.is_unc_path = self.path.startswith(r"\\")
59 if self.is_unc_path:
59 if self.is_unc_path:
60 # change to c drive (as cmd.exe cannot handle UNC addresses)
60 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 os.chdir("C:")
61 os.chdir("C:")
62 return self.path
62 return self.path
63 else:
63 else:
64 # We return None to signal that there was no change in the working
64 # We return None to signal that there was no change in the working
65 # directory
65 # directory
66 return None
66 return None
67
67
68 def __exit__(self, exc_type, exc_value, traceback):
68 def __exit__(self, exc_type, exc_value, traceback):
69 if self.is_unc_path:
69 if self.is_unc_path:
70 os.chdir(self.path)
70 os.chdir(self.path)
71
71
72
72
73 def _find_cmd(cmd):
73 def _find_cmd(cmd):
74 """Find the full path to a .bat or .exe using the win32api module."""
74 """Find the full path to a .bat or .exe using the win32api module."""
75 try:
75 try:
76 from win32api import SearchPath
76 from win32api import SearchPath
77 except ImportError:
77 except ImportError:
78 raise ImportError('you need to have pywin32 installed for this to work')
78 raise ImportError('you need to have pywin32 installed for this to work')
79 else:
79 else:
80 PATH = os.environ['PATH']
80 PATH = os.environ['PATH']
81 extensions = ['.exe', '.com', '.bat', '.py']
81 extensions = ['.exe', '.com', '.bat', '.py']
82 path = None
82 path = None
83 for ext in extensions:
83 for ext in extensions:
84 try:
84 try:
85 path = SearchPath(PATH, cmd, ext)[0]
85 path = SearchPath(PATH, cmd, ext)[0]
86 except:
86 except:
87 pass
87 pass
88 if path is None:
88 if path is None:
89 raise OSError("command %r not found" % cmd)
89 raise OSError("command %r not found" % cmd)
90 else:
90 else:
91 return path
91 return path
92
92
93
93
94 def _system_body(p):
94 def _system_body(p):
95 """Callback for _system."""
95 """Callback for _system."""
96 enc = DEFAULT_ENCODING
96 enc = DEFAULT_ENCODING
97 for line in read_no_interrupt(p.stdout).splitlines():
97 for line in read_no_interrupt(p.stdout).splitlines():
98 line = line.decode(enc, 'replace')
98 line = line.decode(enc, 'replace')
99 print(line, file=sys.stdout)
99 print(line, file=sys.stdout)
100 for line in read_no_interrupt(p.stderr).splitlines():
100 for line in read_no_interrupt(p.stderr).splitlines():
101 line = line.decode(enc, 'replace')
101 line = line.decode(enc, 'replace')
102 print(line, file=sys.stderr)
102 print(line, file=sys.stderr)
103
103
104 # Wait to finish for returncode
104 # Wait to finish for returncode
105 return p.wait()
105 return p.wait()
106
106
107
107
108 def system(cmd):
108 def system(cmd):
109 """Win32 version of os.system() that works with network shares.
109 """Win32 version of os.system() that works with network shares.
110
110
111 Note that this implementation returns None, as meant for use in IPython.
111 Note that this implementation returns None, as meant for use in IPython.
112
112
113 Parameters
113 Parameters
114 ----------
114 ----------
115 cmd : str
115 cmd : str
116 A command to be executed in the system shell.
116 A command to be executed in the system shell.
117
117
118 Returns
118 Returns
119 -------
119 -------
120 None : we explicitly do NOT return the subprocess status code, as this
120 None : we explicitly do NOT return the subprocess status code, as this
121 utility is meant to be used extensively in IPython, where any return value
121 utility is meant to be used extensively in IPython, where any return value
122 would trigger :func:`sys.displayhook` calls.
122 would trigger :func:`sys.displayhook` calls.
123 """
123 """
124 # The controller provides interactivity with both
124 # The controller provides interactivity with both
125 # stdin and stdout
125 # stdin and stdout
126 #import _process_win32_controller
126 #import _process_win32_controller
127 #_process_win32_controller.system(cmd)
127 #_process_win32_controller.system(cmd)
128
128
129 with AvoidUNCPath() as path:
129 with AvoidUNCPath() as path:
130 if path is not None:
130 if path is not None:
131 cmd = '"pushd %s &&"%s' % (path, cmd)
131 cmd = '"pushd %s &&"%s' % (path, cmd)
132 return process_handler(cmd, _system_body)
132 return process_handler(cmd, _system_body)
133
133
134 def getoutput(cmd):
134 def getoutput(cmd):
135 """Return standard output of executing cmd in a shell.
135 """Return standard output of executing cmd in a shell.
136
136
137 Accepts the same arguments as os.system().
137 Accepts the same arguments as os.system().
138
138
139 Parameters
139 Parameters
140 ----------
140 ----------
141 cmd : str
141 cmd : str
142 A command to be executed in the system shell.
142 A command to be executed in the system shell.
143
143
144 Returns
144 Returns
145 -------
145 -------
146 stdout : str
146 stdout : str
147 """
147 """
148
148
149 with AvoidUNCPath() as path:
149 with AvoidUNCPath() as path:
150 if path is not None:
150 if path is not None:
151 cmd = '"pushd %s &&"%s' % (path, cmd)
151 cmd = '"pushd %s &&"%s' % (path, cmd)
152 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
152 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
153
153
154 if out is None:
154 if out is None:
155 out = b''
155 out = b''
156 return py3compat.bytes_to_str(out)
156 return py3compat.bytes_to_str(out)
157
157
158 try:
158 try:
159 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
159 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
160 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
160 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
161 CommandLineToArgvW.restype = POINTER(LPCWSTR)
161 CommandLineToArgvW.restype = POINTER(LPCWSTR)
162 LocalFree = ctypes.windll.kernel32.LocalFree
162 LocalFree = ctypes.windll.kernel32.LocalFree
163 LocalFree.res_type = HLOCAL
163 LocalFree.res_type = HLOCAL
164 LocalFree.arg_types = [HLOCAL]
164 LocalFree.arg_types = [HLOCAL]
165
165
166 def arg_split(commandline, posix=False, strict=True):
166 def arg_split(commandline, posix=False, strict=True):
167 """Split a command line's arguments in a shell-like manner.
167 """Split a command line's arguments in a shell-like manner.
168
168
169 This is a special version for windows that use a ctypes call to CommandLineToArgvW
169 This is a special version for windows that use a ctypes call to CommandLineToArgvW
170 to do the argv splitting. The posix paramter is ignored.
170 to do the argv splitting. The posix paramter is ignored.
171
171
172 If strict=False, process_common.arg_split(...strict=False) is used instead.
172 If strict=False, process_common.arg_split(...strict=False) is used instead.
173 """
173 """
174 #CommandLineToArgvW returns path to executable if called with empty string.
174 #CommandLineToArgvW returns path to executable if called with empty string.
175 if commandline.strip() == "":
175 if commandline.strip() == "":
176 return []
176 return []
177 if not strict:
177 if not strict:
178 # not really a cl-arg, fallback on _process_common
178 # not really a cl-arg, fallback on _process_common
179 return py_arg_split(commandline, posix=posix, strict=strict)
179 return py_arg_split(commandline, posix=posix, strict=strict)
180 argvn = c_int()
180 argvn = c_int()
181 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
181 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
182 result_array_type = LPCWSTR * argvn.value
182 result_array_type = LPCWSTR * argvn.value
183 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
183 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
184 retval = LocalFree(result_pointer)
184 retval = LocalFree(result_pointer)
185 return result
185 return result
186 except AttributeError:
186 except AttributeError:
187 arg_split = py_arg_split
187 arg_split = py_arg_split
@@ -1,577 +1,577 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 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # stdlib
15 # stdlib
16 import os, sys, threading
16 import os, sys, threading
17 import ctypes, msvcrt
17 import ctypes, msvcrt
18
18
19 # local imports
19 # local imports
20 from .py3compat import unicode_type
20 from . import py3compat
21
21
22 # Win32 API types needed for the API calls
22 # Win32 API types needed for the API calls
23 from ctypes import POINTER
23 from ctypes import POINTER
24 from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \
24 from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \
25 ULONG, LPCWSTR
25 ULONG, LPCWSTR
26 LPDWORD = POINTER(DWORD)
26 LPDWORD = POINTER(DWORD)
27 LPHANDLE = POINTER(HANDLE)
27 LPHANDLE = POINTER(HANDLE)
28 ULONG_PTR = POINTER(ULONG)
28 ULONG_PTR = POINTER(ULONG)
29 class SECURITY_ATTRIBUTES(ctypes.Structure):
29 class SECURITY_ATTRIBUTES(ctypes.Structure):
30 _fields_ = [("nLength", DWORD),
30 _fields_ = [("nLength", DWORD),
31 ("lpSecurityDescriptor", LPVOID),
31 ("lpSecurityDescriptor", LPVOID),
32 ("bInheritHandle", BOOL)]
32 ("bInheritHandle", BOOL)]
33 LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
33 LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
34 class STARTUPINFO(ctypes.Structure):
34 class STARTUPINFO(ctypes.Structure):
35 _fields_ = [("cb", DWORD),
35 _fields_ = [("cb", DWORD),
36 ("lpReserved", LPCWSTR),
36 ("lpReserved", LPCWSTR),
37 ("lpDesktop", LPCWSTR),
37 ("lpDesktop", LPCWSTR),
38 ("lpTitle", LPCWSTR),
38 ("lpTitle", LPCWSTR),
39 ("dwX", DWORD),
39 ("dwX", DWORD),
40 ("dwY", DWORD),
40 ("dwY", DWORD),
41 ("dwXSize", DWORD),
41 ("dwXSize", DWORD),
42 ("dwYSize", DWORD),
42 ("dwYSize", DWORD),
43 ("dwXCountChars", DWORD),
43 ("dwXCountChars", DWORD),
44 ("dwYCountChars", DWORD),
44 ("dwYCountChars", DWORD),
45 ("dwFillAttribute", DWORD),
45 ("dwFillAttribute", DWORD),
46 ("dwFlags", DWORD),
46 ("dwFlags", DWORD),
47 ("wShowWindow", WORD),
47 ("wShowWindow", WORD),
48 ("cbReserved2", WORD),
48 ("cbReserved2", WORD),
49 ("lpReserved2", LPVOID),
49 ("lpReserved2", LPVOID),
50 ("hStdInput", HANDLE),
50 ("hStdInput", HANDLE),
51 ("hStdOutput", HANDLE),
51 ("hStdOutput", HANDLE),
52 ("hStdError", HANDLE)]
52 ("hStdError", HANDLE)]
53 LPSTARTUPINFO = POINTER(STARTUPINFO)
53 LPSTARTUPINFO = POINTER(STARTUPINFO)
54 class PROCESS_INFORMATION(ctypes.Structure):
54 class PROCESS_INFORMATION(ctypes.Structure):
55 _fields_ = [("hProcess", HANDLE),
55 _fields_ = [("hProcess", HANDLE),
56 ("hThread", HANDLE),
56 ("hThread", HANDLE),
57 ("dwProcessId", DWORD),
57 ("dwProcessId", DWORD),
58 ("dwThreadId", DWORD)]
58 ("dwThreadId", DWORD)]
59 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
59 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
60
60
61 # Win32 API constants needed
61 # Win32 API constants needed
62 ERROR_HANDLE_EOF = 38
62 ERROR_HANDLE_EOF = 38
63 ERROR_BROKEN_PIPE = 109
63 ERROR_BROKEN_PIPE = 109
64 ERROR_NO_DATA = 232
64 ERROR_NO_DATA = 232
65 HANDLE_FLAG_INHERIT = 0x0001
65 HANDLE_FLAG_INHERIT = 0x0001
66 STARTF_USESTDHANDLES = 0x0100
66 STARTF_USESTDHANDLES = 0x0100
67 CREATE_SUSPENDED = 0x0004
67 CREATE_SUSPENDED = 0x0004
68 CREATE_NEW_CONSOLE = 0x0010
68 CREATE_NEW_CONSOLE = 0x0010
69 CREATE_NO_WINDOW = 0x08000000
69 CREATE_NO_WINDOW = 0x08000000
70 STILL_ACTIVE = 259
70 STILL_ACTIVE = 259
71 WAIT_TIMEOUT = 0x0102
71 WAIT_TIMEOUT = 0x0102
72 WAIT_FAILED = 0xFFFFFFFF
72 WAIT_FAILED = 0xFFFFFFFF
73 INFINITE = 0xFFFFFFFF
73 INFINITE = 0xFFFFFFFF
74 DUPLICATE_SAME_ACCESS = 0x00000002
74 DUPLICATE_SAME_ACCESS = 0x00000002
75 ENABLE_ECHO_INPUT = 0x0004
75 ENABLE_ECHO_INPUT = 0x0004
76 ENABLE_LINE_INPUT = 0x0002
76 ENABLE_LINE_INPUT = 0x0002
77 ENABLE_PROCESSED_INPUT = 0x0001
77 ENABLE_PROCESSED_INPUT = 0x0001
78
78
79 # Win32 API functions needed
79 # Win32 API functions needed
80 GetLastError = ctypes.windll.kernel32.GetLastError
80 GetLastError = ctypes.windll.kernel32.GetLastError
81 GetLastError.argtypes = []
81 GetLastError.argtypes = []
82 GetLastError.restype = DWORD
82 GetLastError.restype = DWORD
83
83
84 CreateFile = ctypes.windll.kernel32.CreateFileW
84 CreateFile = ctypes.windll.kernel32.CreateFileW
85 CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE]
85 CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE]
86 CreateFile.restype = HANDLE
86 CreateFile.restype = HANDLE
87
87
88 CreatePipe = ctypes.windll.kernel32.CreatePipe
88 CreatePipe = ctypes.windll.kernel32.CreatePipe
89 CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE),
89 CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE),
90 LPSECURITY_ATTRIBUTES, DWORD]
90 LPSECURITY_ATTRIBUTES, DWORD]
91 CreatePipe.restype = BOOL
91 CreatePipe.restype = BOOL
92
92
93 CreateProcess = ctypes.windll.kernel32.CreateProcessW
93 CreateProcess = ctypes.windll.kernel32.CreateProcessW
94 CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES,
94 CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES,
95 LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO,
95 LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO,
96 LPPROCESS_INFORMATION]
96 LPPROCESS_INFORMATION]
97 CreateProcess.restype = BOOL
97 CreateProcess.restype = BOOL
98
98
99 GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess
99 GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess
100 GetExitCodeProcess.argtypes = [HANDLE, LPDWORD]
100 GetExitCodeProcess.argtypes = [HANDLE, LPDWORD]
101 GetExitCodeProcess.restype = BOOL
101 GetExitCodeProcess.restype = BOOL
102
102
103 GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
103 GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
104 GetCurrentProcess.argtypes = []
104 GetCurrentProcess.argtypes = []
105 GetCurrentProcess.restype = HANDLE
105 GetCurrentProcess.restype = HANDLE
106
106
107 ResumeThread = ctypes.windll.kernel32.ResumeThread
107 ResumeThread = ctypes.windll.kernel32.ResumeThread
108 ResumeThread.argtypes = [HANDLE]
108 ResumeThread.argtypes = [HANDLE]
109 ResumeThread.restype = DWORD
109 ResumeThread.restype = DWORD
110
110
111 ReadFile = ctypes.windll.kernel32.ReadFile
111 ReadFile = ctypes.windll.kernel32.ReadFile
112 ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
112 ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
113 ReadFile.restype = BOOL
113 ReadFile.restype = BOOL
114
114
115 WriteFile = ctypes.windll.kernel32.WriteFile
115 WriteFile = ctypes.windll.kernel32.WriteFile
116 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
116 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
117 WriteFile.restype = BOOL
117 WriteFile.restype = BOOL
118
118
119 GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
119 GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
120 GetConsoleMode.argtypes = [HANDLE, LPDWORD]
120 GetConsoleMode.argtypes = [HANDLE, LPDWORD]
121 GetConsoleMode.restype = BOOL
121 GetConsoleMode.restype = BOOL
122
122
123 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
123 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
124 SetConsoleMode.argtypes = [HANDLE, DWORD]
124 SetConsoleMode.argtypes = [HANDLE, DWORD]
125 SetConsoleMode.restype = BOOL
125 SetConsoleMode.restype = BOOL
126
126
127 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
127 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
128 FlushConsoleInputBuffer.argtypes = [HANDLE]
128 FlushConsoleInputBuffer.argtypes = [HANDLE]
129 FlushConsoleInputBuffer.restype = BOOL
129 FlushConsoleInputBuffer.restype = BOOL
130
130
131 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
131 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
132 WaitForSingleObject.argtypes = [HANDLE, DWORD]
132 WaitForSingleObject.argtypes = [HANDLE, DWORD]
133 WaitForSingleObject.restype = DWORD
133 WaitForSingleObject.restype = DWORD
134
134
135 DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle
135 DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle
136 DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE,
136 DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE,
137 DWORD, BOOL, DWORD]
137 DWORD, BOOL, DWORD]
138 DuplicateHandle.restype = BOOL
138 DuplicateHandle.restype = BOOL
139
139
140 SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
140 SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
141 SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD]
141 SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD]
142 SetHandleInformation.restype = BOOL
142 SetHandleInformation.restype = BOOL
143
143
144 CloseHandle = ctypes.windll.kernel32.CloseHandle
144 CloseHandle = ctypes.windll.kernel32.CloseHandle
145 CloseHandle.argtypes = [HANDLE]
145 CloseHandle.argtypes = [HANDLE]
146 CloseHandle.restype = BOOL
146 CloseHandle.restype = BOOL
147
147
148 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
148 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
149 CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)]
149 CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)]
150 CommandLineToArgvW.restype = POINTER(LPCWSTR)
150 CommandLineToArgvW.restype = POINTER(LPCWSTR)
151
151
152 LocalFree = ctypes.windll.kernel32.LocalFree
152 LocalFree = ctypes.windll.kernel32.LocalFree
153 LocalFree.argtypes = [HLOCAL]
153 LocalFree.argtypes = [HLOCAL]
154 LocalFree.restype = HLOCAL
154 LocalFree.restype = HLOCAL
155
155
156 class AvoidUNCPath(object):
156 class AvoidUNCPath(object):
157 """A context manager to protect command execution from UNC paths.
157 """A context manager to protect command execution from UNC paths.
158
158
159 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
159 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
160 This context manager temporarily changes directory to the 'C:' drive on
160 This context manager temporarily changes directory to the 'C:' drive on
161 entering, and restores the original working directory on exit.
161 entering, and restores the original working directory on exit.
162
162
163 The context manager returns the starting working directory *if* it made a
163 The context manager returns the starting working directory *if* it made a
164 change and None otherwise, so that users can apply the necessary adjustment
164 change and None otherwise, so that users can apply the necessary adjustment
165 to their system calls in the event of a change.
165 to their system calls in the event of a change.
166
166
167 Examples
167 Examples
168 --------
168 --------
169 ::
169 ::
170 cmd = 'dir'
170 cmd = 'dir'
171 with AvoidUNCPath() as path:
171 with AvoidUNCPath() as path:
172 if path is not None:
172 if path is not None:
173 cmd = '"pushd %s &&"%s' % (path, cmd)
173 cmd = '"pushd %s &&"%s' % (path, cmd)
174 os.system(cmd)
174 os.system(cmd)
175 """
175 """
176 def __enter__(self):
176 def __enter__(self):
177 self.path = os.getcwdu()
177 self.path = py3compat.getcwd()
178 self.is_unc_path = self.path.startswith(r"\\")
178 self.is_unc_path = self.path.startswith(r"\\")
179 if self.is_unc_path:
179 if self.is_unc_path:
180 # change to c drive (as cmd.exe cannot handle UNC addresses)
180 # change to c drive (as cmd.exe cannot handle UNC addresses)
181 os.chdir("C:")
181 os.chdir("C:")
182 return self.path
182 return self.path
183 else:
183 else:
184 # We return None to signal that there was no change in the working
184 # We return None to signal that there was no change in the working
185 # directory
185 # directory
186 return None
186 return None
187
187
188 def __exit__(self, exc_type, exc_value, traceback):
188 def __exit__(self, exc_type, exc_value, traceback):
189 if self.is_unc_path:
189 if self.is_unc_path:
190 os.chdir(self.path)
190 os.chdir(self.path)
191
191
192
192
193 class Win32ShellCommandController(object):
193 class Win32ShellCommandController(object):
194 """Runs a shell command in a 'with' context.
194 """Runs a shell command in a 'with' context.
195
195
196 This implementation is Win32-specific.
196 This implementation is Win32-specific.
197
197
198 Example:
198 Example:
199 # Runs the command interactively with default console stdin/stdout
199 # Runs the command interactively with default console stdin/stdout
200 with ShellCommandController('python -i') as scc:
200 with ShellCommandController('python -i') as scc:
201 scc.run()
201 scc.run()
202
202
203 # Runs the command using the provided functions for stdin/stdout
203 # Runs the command using the provided functions for stdin/stdout
204 def my_stdout_func(s):
204 def my_stdout_func(s):
205 # print or save the string 's'
205 # print or save the string 's'
206 write_to_stdout(s)
206 write_to_stdout(s)
207 def my_stdin_func():
207 def my_stdin_func():
208 # If input is available, return it as a string.
208 # If input is available, return it as a string.
209 if input_available():
209 if input_available():
210 return get_input()
210 return get_input()
211 # If no input available, return None after a short delay to
211 # If no input available, return None after a short delay to
212 # keep from blocking.
212 # keep from blocking.
213 else:
213 else:
214 time.sleep(0.01)
214 time.sleep(0.01)
215 return None
215 return None
216
216
217 with ShellCommandController('python -i') as scc:
217 with ShellCommandController('python -i') as scc:
218 scc.run(my_stdout_func, my_stdin_func)
218 scc.run(my_stdout_func, my_stdin_func)
219 """
219 """
220
220
221 def __init__(self, cmd, mergeout = True):
221 def __init__(self, cmd, mergeout = True):
222 """Initializes the shell command controller.
222 """Initializes the shell command controller.
223
223
224 The cmd is the program to execute, and mergeout is
224 The cmd is the program to execute, and mergeout is
225 whether to blend stdout and stderr into one output
225 whether to blend stdout and stderr into one output
226 in stdout. Merging them together in this fashion more
226 in stdout. Merging them together in this fashion more
227 reliably keeps stdout and stderr in the correct order
227 reliably keeps stdout and stderr in the correct order
228 especially for interactive shell usage.
228 especially for interactive shell usage.
229 """
229 """
230 self.cmd = cmd
230 self.cmd = cmd
231 self.mergeout = mergeout
231 self.mergeout = mergeout
232
232
233 def __enter__(self):
233 def __enter__(self):
234 cmd = self.cmd
234 cmd = self.cmd
235 mergeout = self.mergeout
235 mergeout = self.mergeout
236
236
237 self.hstdout, self.hstdin, self.hstderr = None, None, None
237 self.hstdout, self.hstdin, self.hstderr = None, None, None
238 self.piProcInfo = None
238 self.piProcInfo = None
239 try:
239 try:
240 p_hstdout, c_hstdout, p_hstderr, \
240 p_hstdout, c_hstdout, p_hstderr, \
241 c_hstderr, p_hstdin, c_hstdin = [None]*6
241 c_hstderr, p_hstdin, c_hstdin = [None]*6
242
242
243 # SECURITY_ATTRIBUTES with inherit handle set to True
243 # SECURITY_ATTRIBUTES with inherit handle set to True
244 saAttr = SECURITY_ATTRIBUTES()
244 saAttr = SECURITY_ATTRIBUTES()
245 saAttr.nLength = ctypes.sizeof(saAttr)
245 saAttr.nLength = ctypes.sizeof(saAttr)
246 saAttr.bInheritHandle = True
246 saAttr.bInheritHandle = True
247 saAttr.lpSecurityDescriptor = None
247 saAttr.lpSecurityDescriptor = None
248
248
249 def create_pipe(uninherit):
249 def create_pipe(uninherit):
250 """Creates a Windows pipe, which consists of two handles.
250 """Creates a Windows pipe, which consists of two handles.
251
251
252 The 'uninherit' parameter controls which handle is not
252 The 'uninherit' parameter controls which handle is not
253 inherited by the child process.
253 inherited by the child process.
254 """
254 """
255 handles = HANDLE(), HANDLE()
255 handles = HANDLE(), HANDLE()
256 if not CreatePipe(ctypes.byref(handles[0]),
256 if not CreatePipe(ctypes.byref(handles[0]),
257 ctypes.byref(handles[1]), ctypes.byref(saAttr), 0):
257 ctypes.byref(handles[1]), ctypes.byref(saAttr), 0):
258 raise ctypes.WinError()
258 raise ctypes.WinError()
259 if not SetHandleInformation(handles[uninherit],
259 if not SetHandleInformation(handles[uninherit],
260 HANDLE_FLAG_INHERIT, 0):
260 HANDLE_FLAG_INHERIT, 0):
261 raise ctypes.WinError()
261 raise ctypes.WinError()
262 return handles[0].value, handles[1].value
262 return handles[0].value, handles[1].value
263
263
264 p_hstdout, c_hstdout = create_pipe(uninherit=0)
264 p_hstdout, c_hstdout = create_pipe(uninherit=0)
265 # 'mergeout' signals that stdout and stderr should be merged.
265 # 'mergeout' signals that stdout and stderr should be merged.
266 # We do that by using one pipe for both of them.
266 # We do that by using one pipe for both of them.
267 if mergeout:
267 if mergeout:
268 c_hstderr = HANDLE()
268 c_hstderr = HANDLE()
269 if not DuplicateHandle(GetCurrentProcess(), c_hstdout,
269 if not DuplicateHandle(GetCurrentProcess(), c_hstdout,
270 GetCurrentProcess(), ctypes.byref(c_hstderr),
270 GetCurrentProcess(), ctypes.byref(c_hstderr),
271 0, True, DUPLICATE_SAME_ACCESS):
271 0, True, DUPLICATE_SAME_ACCESS):
272 raise ctypes.WinError()
272 raise ctypes.WinError()
273 else:
273 else:
274 p_hstderr, c_hstderr = create_pipe(uninherit=0)
274 p_hstderr, c_hstderr = create_pipe(uninherit=0)
275 c_hstdin, p_hstdin = create_pipe(uninherit=1)
275 c_hstdin, p_hstdin = create_pipe(uninherit=1)
276
276
277 # Create the process object
277 # Create the process object
278 piProcInfo = PROCESS_INFORMATION()
278 piProcInfo = PROCESS_INFORMATION()
279 siStartInfo = STARTUPINFO()
279 siStartInfo = STARTUPINFO()
280 siStartInfo.cb = ctypes.sizeof(siStartInfo)
280 siStartInfo.cb = ctypes.sizeof(siStartInfo)
281 siStartInfo.hStdInput = c_hstdin
281 siStartInfo.hStdInput = c_hstdin
282 siStartInfo.hStdOutput = c_hstdout
282 siStartInfo.hStdOutput = c_hstdout
283 siStartInfo.hStdError = c_hstderr
283 siStartInfo.hStdError = c_hstderr
284 siStartInfo.dwFlags = STARTF_USESTDHANDLES
284 siStartInfo.dwFlags = STARTF_USESTDHANDLES
285 dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE
285 dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE
286
286
287 if not CreateProcess(None,
287 if not CreateProcess(None,
288 u"cmd.exe /c " + cmd,
288 u"cmd.exe /c " + cmd,
289 None, None, True, dwCreationFlags,
289 None, None, True, dwCreationFlags,
290 None, None, ctypes.byref(siStartInfo),
290 None, None, ctypes.byref(siStartInfo),
291 ctypes.byref(piProcInfo)):
291 ctypes.byref(piProcInfo)):
292 raise ctypes.WinError()
292 raise ctypes.WinError()
293
293
294 # Close this process's versions of the child handles
294 # Close this process's versions of the child handles
295 CloseHandle(c_hstdin)
295 CloseHandle(c_hstdin)
296 c_hstdin = None
296 c_hstdin = None
297 CloseHandle(c_hstdout)
297 CloseHandle(c_hstdout)
298 c_hstdout = None
298 c_hstdout = None
299 if c_hstderr != None:
299 if c_hstderr != None:
300 CloseHandle(c_hstderr)
300 CloseHandle(c_hstderr)
301 c_hstderr = None
301 c_hstderr = None
302
302
303 # Transfer ownership of the parent handles to the object
303 # Transfer ownership of the parent handles to the object
304 self.hstdin = p_hstdin
304 self.hstdin = p_hstdin
305 p_hstdin = None
305 p_hstdin = None
306 self.hstdout = p_hstdout
306 self.hstdout = p_hstdout
307 p_hstdout = None
307 p_hstdout = None
308 if not mergeout:
308 if not mergeout:
309 self.hstderr = p_hstderr
309 self.hstderr = p_hstderr
310 p_hstderr = None
310 p_hstderr = None
311 self.piProcInfo = piProcInfo
311 self.piProcInfo = piProcInfo
312
312
313 finally:
313 finally:
314 if p_hstdin:
314 if p_hstdin:
315 CloseHandle(p_hstdin)
315 CloseHandle(p_hstdin)
316 if c_hstdin:
316 if c_hstdin:
317 CloseHandle(c_hstdin)
317 CloseHandle(c_hstdin)
318 if p_hstdout:
318 if p_hstdout:
319 CloseHandle(p_hstdout)
319 CloseHandle(p_hstdout)
320 if c_hstdout:
320 if c_hstdout:
321 CloseHandle(c_hstdout)
321 CloseHandle(c_hstdout)
322 if p_hstderr:
322 if p_hstderr:
323 CloseHandle(p_hstderr)
323 CloseHandle(p_hstderr)
324 if c_hstderr:
324 if c_hstderr:
325 CloseHandle(c_hstderr)
325 CloseHandle(c_hstderr)
326
326
327 return self
327 return self
328
328
329 def _stdin_thread(self, handle, hprocess, func, stdout_func):
329 def _stdin_thread(self, handle, hprocess, func, stdout_func):
330 exitCode = DWORD()
330 exitCode = DWORD()
331 bytesWritten = DWORD(0)
331 bytesWritten = DWORD(0)
332 while True:
332 while True:
333 #print("stdin thread loop start")
333 #print("stdin thread loop start")
334 # Get the input string (may be bytes or unicode)
334 # Get the input string (may be bytes or unicode)
335 data = func()
335 data = func()
336
336
337 # None signals to poll whether the process has exited
337 # None signals to poll whether the process has exited
338 if data is None:
338 if data is None:
339 #print("checking for process completion")
339 #print("checking for process completion")
340 if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)):
340 if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)):
341 raise ctypes.WinError()
341 raise ctypes.WinError()
342 if exitCode.value != STILL_ACTIVE:
342 if exitCode.value != STILL_ACTIVE:
343 return
343 return
344 # TESTING: Does zero-sized writefile help?
344 # TESTING: Does zero-sized writefile help?
345 if not WriteFile(handle, "", 0,
345 if not WriteFile(handle, "", 0,
346 ctypes.byref(bytesWritten), None):
346 ctypes.byref(bytesWritten), None):
347 raise ctypes.WinError()
347 raise ctypes.WinError()
348 continue
348 continue
349 #print("\nGot str %s\n" % repr(data), file=sys.stderr)
349 #print("\nGot str %s\n" % repr(data), file=sys.stderr)
350
350
351 # Encode the string to the console encoding
351 # Encode the string to the console encoding
352 if isinstance(data, unicode): #FIXME: Python3
352 if isinstance(data, unicode): #FIXME: Python3
353 data = data.encode('utf_8')
353 data = data.encode('utf_8')
354
354
355 # What we have now must be a string of bytes
355 # What we have now must be a string of bytes
356 if not isinstance(data, str): #FIXME: Python3
356 if not isinstance(data, str): #FIXME: Python3
357 raise RuntimeError("internal stdin function string error")
357 raise RuntimeError("internal stdin function string error")
358
358
359 # An empty string signals EOF
359 # An empty string signals EOF
360 if len(data) == 0:
360 if len(data) == 0:
361 return
361 return
362
362
363 # In a windows console, sometimes the input is echoed,
363 # In a windows console, sometimes the input is echoed,
364 # but sometimes not. How do we determine when to do this?
364 # but sometimes not. How do we determine when to do this?
365 stdout_func(data)
365 stdout_func(data)
366 # WriteFile may not accept all the data at once.
366 # WriteFile may not accept all the data at once.
367 # Loop until everything is processed
367 # Loop until everything is processed
368 while len(data) != 0:
368 while len(data) != 0:
369 #print("Calling writefile")
369 #print("Calling writefile")
370 if not WriteFile(handle, data, len(data),
370 if not WriteFile(handle, data, len(data),
371 ctypes.byref(bytesWritten), None):
371 ctypes.byref(bytesWritten), None):
372 # This occurs at exit
372 # This occurs at exit
373 if GetLastError() == ERROR_NO_DATA:
373 if GetLastError() == ERROR_NO_DATA:
374 return
374 return
375 raise ctypes.WinError()
375 raise ctypes.WinError()
376 #print("Called writefile")
376 #print("Called writefile")
377 data = data[bytesWritten.value:]
377 data = data[bytesWritten.value:]
378
378
379 def _stdout_thread(self, handle, func):
379 def _stdout_thread(self, handle, func):
380 # Allocate the output buffer
380 # Allocate the output buffer
381 data = ctypes.create_string_buffer(4096)
381 data = ctypes.create_string_buffer(4096)
382 while True:
382 while True:
383 bytesRead = DWORD(0)
383 bytesRead = DWORD(0)
384 if not ReadFile(handle, data, 4096,
384 if not ReadFile(handle, data, 4096,
385 ctypes.byref(bytesRead), None):
385 ctypes.byref(bytesRead), None):
386 le = GetLastError()
386 le = GetLastError()
387 if le == ERROR_BROKEN_PIPE:
387 if le == ERROR_BROKEN_PIPE:
388 return
388 return
389 else:
389 else:
390 raise ctypes.WinError()
390 raise ctypes.WinError()
391 # FIXME: Python3
391 # FIXME: Python3
392 s = data.value[0:bytesRead.value]
392 s = data.value[0:bytesRead.value]
393 #print("\nv: %s" % repr(s), file=sys.stderr)
393 #print("\nv: %s" % repr(s), file=sys.stderr)
394 func(s.decode('utf_8', 'replace'))
394 func(s.decode('utf_8', 'replace'))
395
395
396 def run(self, stdout_func = None, stdin_func = None, stderr_func = None):
396 def run(self, stdout_func = None, stdin_func = None, stderr_func = None):
397 """Runs the process, using the provided functions for I/O.
397 """Runs the process, using the provided functions for I/O.
398
398
399 The function stdin_func should return strings whenever a
399 The function stdin_func should return strings whenever a
400 character or characters become available.
400 character or characters become available.
401 The functions stdout_func and stderr_func are called whenever
401 The functions stdout_func and stderr_func are called whenever
402 something is printed to stdout or stderr, respectively.
402 something is printed to stdout or stderr, respectively.
403 These functions are called from different threads (but not
403 These functions are called from different threads (but not
404 concurrently, because of the GIL).
404 concurrently, because of the GIL).
405 """
405 """
406 if stdout_func == None and stdin_func == None and stderr_func == None:
406 if stdout_func == None and stdin_func == None and stderr_func == None:
407 return self._run_stdio()
407 return self._run_stdio()
408
408
409 if stderr_func != None and self.mergeout:
409 if stderr_func != None and self.mergeout:
410 raise RuntimeError("Shell command was initiated with "
410 raise RuntimeError("Shell command was initiated with "
411 "merged stdin/stdout, but a separate stderr_func "
411 "merged stdin/stdout, but a separate stderr_func "
412 "was provided to the run() method")
412 "was provided to the run() method")
413
413
414 # Create a thread for each input/output handle
414 # Create a thread for each input/output handle
415 stdin_thread = None
415 stdin_thread = None
416 threads = []
416 threads = []
417 if stdin_func:
417 if stdin_func:
418 stdin_thread = threading.Thread(target=self._stdin_thread,
418 stdin_thread = threading.Thread(target=self._stdin_thread,
419 args=(self.hstdin, self.piProcInfo.hProcess,
419 args=(self.hstdin, self.piProcInfo.hProcess,
420 stdin_func, stdout_func))
420 stdin_func, stdout_func))
421 threads.append(threading.Thread(target=self._stdout_thread,
421 threads.append(threading.Thread(target=self._stdout_thread,
422 args=(self.hstdout, stdout_func)))
422 args=(self.hstdout, stdout_func)))
423 if not self.mergeout:
423 if not self.mergeout:
424 if stderr_func == None:
424 if stderr_func == None:
425 stderr_func = stdout_func
425 stderr_func = stdout_func
426 threads.append(threading.Thread(target=self._stdout_thread,
426 threads.append(threading.Thread(target=self._stdout_thread,
427 args=(self.hstderr, stderr_func)))
427 args=(self.hstderr, stderr_func)))
428 # Start the I/O threads and the process
428 # Start the I/O threads and the process
429 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
429 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
430 raise ctypes.WinError()
430 raise ctypes.WinError()
431 if stdin_thread is not None:
431 if stdin_thread is not None:
432 stdin_thread.start()
432 stdin_thread.start()
433 for thread in threads:
433 for thread in threads:
434 thread.start()
434 thread.start()
435 # Wait for the process to complete
435 # Wait for the process to complete
436 if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \
436 if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \
437 WAIT_FAILED:
437 WAIT_FAILED:
438 raise ctypes.WinError()
438 raise ctypes.WinError()
439 # Wait for the I/O threads to complete
439 # Wait for the I/O threads to complete
440 for thread in threads:
440 for thread in threads:
441 thread.join()
441 thread.join()
442
442
443 # Wait for the stdin thread to complete
443 # Wait for the stdin thread to complete
444 if stdin_thread is not None:
444 if stdin_thread is not None:
445 stdin_thread.join()
445 stdin_thread.join()
446
446
447 def _stdin_raw_nonblock(self):
447 def _stdin_raw_nonblock(self):
448 """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
448 """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
449 # WARNING: This is experimental, and produces inconsistent results.
449 # WARNING: This is experimental, and produces inconsistent results.
450 # It's possible for the handle not to be appropriate for use
450 # It's possible for the handle not to be appropriate for use
451 # with WaitForSingleObject, among other things.
451 # with WaitForSingleObject, among other things.
452 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
452 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
453 result = WaitForSingleObject(handle, 100)
453 result = WaitForSingleObject(handle, 100)
454 if result == WAIT_FAILED:
454 if result == WAIT_FAILED:
455 raise ctypes.WinError()
455 raise ctypes.WinError()
456 elif result == WAIT_TIMEOUT:
456 elif result == WAIT_TIMEOUT:
457 print(".", end='')
457 print(".", end='')
458 return None
458 return None
459 else:
459 else:
460 data = ctypes.create_string_buffer(256)
460 data = ctypes.create_string_buffer(256)
461 bytesRead = DWORD(0)
461 bytesRead = DWORD(0)
462 print('?', end='')
462 print('?', end='')
463
463
464 if not ReadFile(handle, data, 256,
464 if not ReadFile(handle, data, 256,
465 ctypes.byref(bytesRead), None):
465 ctypes.byref(bytesRead), None):
466 raise ctypes.WinError()
466 raise ctypes.WinError()
467 # This ensures the non-blocking works with an actual console
467 # This ensures the non-blocking works with an actual console
468 # Not checking the error, so the processing will still work with
468 # Not checking the error, so the processing will still work with
469 # other handle types
469 # other handle types
470 FlushConsoleInputBuffer(handle)
470 FlushConsoleInputBuffer(handle)
471
471
472 data = data.value
472 data = data.value
473 data = data.replace('\r\n', '\n')
473 data = data.replace('\r\n', '\n')
474 data = data.replace('\r', '\n')
474 data = data.replace('\r', '\n')
475 print(repr(data) + " ", end='')
475 print(repr(data) + " ", end='')
476 return data
476 return data
477
477
478 def _stdin_raw_block(self):
478 def _stdin_raw_block(self):
479 """Use a blocking stdin read"""
479 """Use a blocking stdin read"""
480 # The big problem with the blocking read is that it doesn't
480 # The big problem with the blocking read is that it doesn't
481 # exit when it's supposed to in all contexts. An extra
481 # exit when it's supposed to in all contexts. An extra
482 # key-press may be required to trigger the exit.
482 # key-press may be required to trigger the exit.
483 try:
483 try:
484 data = sys.stdin.read(1)
484 data = sys.stdin.read(1)
485 data = data.replace('\r', '\n')
485 data = data.replace('\r', '\n')
486 return data
486 return data
487 except WindowsError as we:
487 except WindowsError as we:
488 if we.winerror == ERROR_NO_DATA:
488 if we.winerror == ERROR_NO_DATA:
489 # This error occurs when the pipe is closed
489 # This error occurs when the pipe is closed
490 return None
490 return None
491 else:
491 else:
492 # Otherwise let the error propagate
492 # Otherwise let the error propagate
493 raise we
493 raise we
494
494
495 def _stdout_raw(self, s):
495 def _stdout_raw(self, s):
496 """Writes the string to stdout"""
496 """Writes the string to stdout"""
497 print(s, end='', file=sys.stdout)
497 print(s, end='', file=sys.stdout)
498 sys.stdout.flush()
498 sys.stdout.flush()
499
499
500 def _stderr_raw(self, s):
500 def _stderr_raw(self, s):
501 """Writes the string to stdout"""
501 """Writes the string to stdout"""
502 print(s, end='', file=sys.stderr)
502 print(s, end='', file=sys.stderr)
503 sys.stderr.flush()
503 sys.stderr.flush()
504
504
505 def _run_stdio(self):
505 def _run_stdio(self):
506 """Runs the process using the system standard I/O.
506 """Runs the process using the system standard I/O.
507
507
508 IMPORTANT: stdin needs to be asynchronous, so the Python
508 IMPORTANT: stdin needs to be asynchronous, so the Python
509 sys.stdin object is not used. Instead,
509 sys.stdin object is not used. Instead,
510 msvcrt.kbhit/getwch are used asynchronously.
510 msvcrt.kbhit/getwch are used asynchronously.
511 """
511 """
512 # Disable Line and Echo mode
512 # Disable Line and Echo mode
513 #lpMode = DWORD()
513 #lpMode = DWORD()
514 #handle = msvcrt.get_osfhandle(sys.stdin.fileno())
514 #handle = msvcrt.get_osfhandle(sys.stdin.fileno())
515 #if GetConsoleMode(handle, ctypes.byref(lpMode)):
515 #if GetConsoleMode(handle, ctypes.byref(lpMode)):
516 # set_console_mode = True
516 # set_console_mode = True
517 # if not SetConsoleMode(handle, lpMode.value &
517 # if not SetConsoleMode(handle, lpMode.value &
518 # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)):
518 # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)):
519 # raise ctypes.WinError()
519 # raise ctypes.WinError()
520
520
521 if self.mergeout:
521 if self.mergeout:
522 return self.run(stdout_func = self._stdout_raw,
522 return self.run(stdout_func = self._stdout_raw,
523 stdin_func = self._stdin_raw_block)
523 stdin_func = self._stdin_raw_block)
524 else:
524 else:
525 return self.run(stdout_func = self._stdout_raw,
525 return self.run(stdout_func = self._stdout_raw,
526 stdin_func = self._stdin_raw_block,
526 stdin_func = self._stdin_raw_block,
527 stderr_func = self._stderr_raw)
527 stderr_func = self._stderr_raw)
528
528
529 # Restore the previous console mode
529 # Restore the previous console mode
530 #if set_console_mode:
530 #if set_console_mode:
531 # if not SetConsoleMode(handle, lpMode.value):
531 # if not SetConsoleMode(handle, lpMode.value):
532 # raise ctypes.WinError()
532 # raise ctypes.WinError()
533
533
534 def __exit__(self, exc_type, exc_value, traceback):
534 def __exit__(self, exc_type, exc_value, traceback):
535 if self.hstdin:
535 if self.hstdin:
536 CloseHandle(self.hstdin)
536 CloseHandle(self.hstdin)
537 self.hstdin = None
537 self.hstdin = None
538 if self.hstdout:
538 if self.hstdout:
539 CloseHandle(self.hstdout)
539 CloseHandle(self.hstdout)
540 self.hstdout = None
540 self.hstdout = None
541 if self.hstderr:
541 if self.hstderr:
542 CloseHandle(self.hstderr)
542 CloseHandle(self.hstderr)
543 self.hstderr = None
543 self.hstderr = None
544 if self.piProcInfo != None:
544 if self.piProcInfo != None:
545 CloseHandle(self.piProcInfo.hProcess)
545 CloseHandle(self.piProcInfo.hProcess)
546 CloseHandle(self.piProcInfo.hThread)
546 CloseHandle(self.piProcInfo.hThread)
547 self.piProcInfo = None
547 self.piProcInfo = None
548
548
549
549
550 def system(cmd):
550 def system(cmd):
551 """Win32 version of os.system() that works with network shares.
551 """Win32 version of os.system() that works with network shares.
552
552
553 Note that this implementation returns None, as meant for use in IPython.
553 Note that this implementation returns None, as meant for use in IPython.
554
554
555 Parameters
555 Parameters
556 ----------
556 ----------
557 cmd : str
557 cmd : str
558 A command to be executed in the system shell.
558 A command to be executed in the system shell.
559
559
560 Returns
560 Returns
561 -------
561 -------
562 None : we explicitly do NOT return the subprocess status code, as this
562 None : we explicitly do NOT return the subprocess status code, as this
563 utility is meant to be used extensively in IPython, where any return value
563 utility is meant to be used extensively in IPython, where any return value
564 would trigger :func:`sys.displayhook` calls.
564 would trigger :func:`sys.displayhook` calls.
565 """
565 """
566 with AvoidUNCPath() as path:
566 with AvoidUNCPath() as path:
567 if path is not None:
567 if path is not None:
568 cmd = '"pushd %s &&"%s' % (path, cmd)
568 cmd = '"pushd %s &&"%s' % (path, cmd)
569 with Win32ShellCommandController(cmd) as scc:
569 with Win32ShellCommandController(cmd) as scc:
570 scc.run()
570 scc.run()
571
571
572
572
573 if __name__ == "__main__":
573 if __name__ == "__main__":
574 print("Test starting!")
574 print("Test starting!")
575 #system("cmd")
575 #system("cmd")
576 system("python -i")
576 system("python -i")
577 print("Test finished!")
577 print("Test finished!")
@@ -1,574 +1,574 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 #-----------------------------------------------------------------------------
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 sys
18 import sys
19 import errno
19 import errno
20 import shutil
20 import shutil
21 import random
21 import random
22 import tempfile
22 import tempfile
23 import warnings
23 import warnings
24 from hashlib import md5
24 from hashlib import md5
25 import glob
25 import glob
26
26
27 import IPython
27 import IPython
28 from IPython.testing.skipdoctest import skip_doctest
28 from IPython.testing.skipdoctest import skip_doctest
29 from IPython.utils.process import system
29 from IPython.utils.process import system
30 from IPython.utils.importstring import import_item
30 from IPython.utils.importstring import import_item
31 from IPython.utils import py3compat
31 from IPython.utils import py3compat
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Code
33 # Code
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 fs_encoding = sys.getfilesystemencoding()
36 fs_encoding = sys.getfilesystemencoding()
37
37
38 def _get_long_path_name(path):
38 def _get_long_path_name(path):
39 """Dummy no-op."""
39 """Dummy no-op."""
40 return path
40 return path
41
41
42 def _writable_dir(path):
42 def _writable_dir(path):
43 """Whether `path` is a directory, to which the user has write access."""
43 """Whether `path` is a directory, to which the user has write access."""
44 return os.path.isdir(path) and os.access(path, os.W_OK)
44 return os.path.isdir(path) and os.access(path, os.W_OK)
45
45
46 if sys.platform == 'win32':
46 if sys.platform == 'win32':
47 @skip_doctest
47 @skip_doctest
48 def _get_long_path_name(path):
48 def _get_long_path_name(path):
49 """Get a long path name (expand ~) on Windows using ctypes.
49 """Get a long path name (expand ~) on Windows using ctypes.
50
50
51 Examples
51 Examples
52 --------
52 --------
53
53
54 >>> get_long_path_name('c:\\docume~1')
54 >>> get_long_path_name('c:\\docume~1')
55 u'c:\\\\Documents and Settings'
55 u'c:\\\\Documents and Settings'
56
56
57 """
57 """
58 try:
58 try:
59 import ctypes
59 import ctypes
60 except ImportError:
60 except ImportError:
61 raise ImportError('you need to have ctypes installed for this to work')
61 raise ImportError('you need to have ctypes installed for this to work')
62 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
62 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
63 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
63 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
64 ctypes.c_uint ]
64 ctypes.c_uint ]
65
65
66 buf = ctypes.create_unicode_buffer(260)
66 buf = ctypes.create_unicode_buffer(260)
67 rv = _GetLongPathName(path, buf, 260)
67 rv = _GetLongPathName(path, buf, 260)
68 if rv == 0 or rv > 260:
68 if rv == 0 or rv > 260:
69 return path
69 return path
70 else:
70 else:
71 return buf.value
71 return buf.value
72
72
73
73
74 def get_long_path_name(path):
74 def get_long_path_name(path):
75 """Expand a path into its long form.
75 """Expand a path into its long form.
76
76
77 On Windows this expands any ~ in the paths. On other platforms, it is
77 On Windows this expands any ~ in the paths. On other platforms, it is
78 a null operation.
78 a null operation.
79 """
79 """
80 return _get_long_path_name(path)
80 return _get_long_path_name(path)
81
81
82
82
83 def unquote_filename(name, win32=(sys.platform=='win32')):
83 def unquote_filename(name, win32=(sys.platform=='win32')):
84 """ On Windows, remove leading and trailing quotes from filenames.
84 """ On Windows, remove leading and trailing quotes from filenames.
85 """
85 """
86 if win32:
86 if win32:
87 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
87 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
88 name = name[1:-1]
88 name = name[1:-1]
89 return name
89 return name
90
90
91 def compress_user(path):
91 def compress_user(path):
92 """Reverse of :func:`os.path.expanduser`
92 """Reverse of :func:`os.path.expanduser`
93 """
93 """
94 home = os.path.expanduser('~')
94 home = os.path.expanduser('~')
95 if path.startswith(home):
95 if path.startswith(home):
96 path = "~" + path[len(home):]
96 path = "~" + path[len(home):]
97 return path
97 return path
98
98
99 def get_py_filename(name, force_win32=None):
99 def get_py_filename(name, force_win32=None):
100 """Return a valid python filename in the current directory.
100 """Return a valid python filename in the current directory.
101
101
102 If the given name is not a file, it adds '.py' and searches again.
102 If the given name is not a file, it adds '.py' and searches again.
103 Raises IOError with an informative message if the file isn't found.
103 Raises IOError with an informative message if the file isn't found.
104
104
105 On Windows, apply Windows semantics to the filename. In particular, remove
105 On Windows, apply Windows semantics to the filename. In particular, remove
106 any quoting that has been applied to it. This option can be forced for
106 any quoting that has been applied to it. This option can be forced for
107 testing purposes.
107 testing purposes.
108 """
108 """
109
109
110 name = os.path.expanduser(name)
110 name = os.path.expanduser(name)
111 if force_win32 is None:
111 if force_win32 is None:
112 win32 = (sys.platform == 'win32')
112 win32 = (sys.platform == 'win32')
113 else:
113 else:
114 win32 = force_win32
114 win32 = force_win32
115 name = unquote_filename(name, win32=win32)
115 name = unquote_filename(name, win32=win32)
116 if not os.path.isfile(name) and not name.endswith('.py'):
116 if not os.path.isfile(name) and not name.endswith('.py'):
117 name += '.py'
117 name += '.py'
118 if os.path.isfile(name):
118 if os.path.isfile(name):
119 return name
119 return name
120 else:
120 else:
121 raise IOError('File `%r` not found.' % name)
121 raise IOError('File `%r` not found.' % name)
122
122
123
123
124 def filefind(filename, path_dirs=None):
124 def filefind(filename, path_dirs=None):
125 """Find a file by looking through a sequence of paths.
125 """Find a file by looking through a sequence of paths.
126
126
127 This iterates through a sequence of paths looking for a file and returns
127 This iterates through a sequence of paths looking for a file and returns
128 the full, absolute path of the first occurence of the file. If no set of
128 the full, absolute path of the first occurence of the file. If no set of
129 path dirs is given, the filename is tested as is, after running through
129 path dirs is given, the filename is tested as is, after running through
130 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
130 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
131
131
132 filefind('myfile.txt')
132 filefind('myfile.txt')
133
133
134 will find the file in the current working dir, but::
134 will find the file in the current working dir, but::
135
135
136 filefind('~/myfile.txt')
136 filefind('~/myfile.txt')
137
137
138 Will find the file in the users home directory. This function does not
138 Will find the file in the users home directory. This function does not
139 automatically try any paths, such as the cwd or the user's home directory.
139 automatically try any paths, such as the cwd or the user's home directory.
140
140
141 Parameters
141 Parameters
142 ----------
142 ----------
143 filename : str
143 filename : str
144 The filename to look for.
144 The filename to look for.
145 path_dirs : str, None or sequence of str
145 path_dirs : str, None or sequence of str
146 The sequence of paths to look for the file in. If None, the filename
146 The sequence of paths to look for the file in. If None, the filename
147 need to be absolute or be in the cwd. If a string, the string is
147 need to be absolute or be in the cwd. If a string, the string is
148 put into a sequence and the searched. If a sequence, walk through
148 put into a sequence and the searched. If a sequence, walk through
149 each element and join with ``filename``, calling :func:`expandvars`
149 each element and join with ``filename``, calling :func:`expandvars`
150 and :func:`expanduser` before testing for existence.
150 and :func:`expanduser` before testing for existence.
151
151
152 Returns
152 Returns
153 -------
153 -------
154 Raises :exc:`IOError` or returns absolute path to file.
154 Raises :exc:`IOError` or returns absolute path to file.
155 """
155 """
156
156
157 # If paths are quoted, abspath gets confused, strip them...
157 # If paths are quoted, abspath gets confused, strip them...
158 filename = filename.strip('"').strip("'")
158 filename = filename.strip('"').strip("'")
159 # If the input is an absolute path, just check it exists
159 # If the input is an absolute path, just check it exists
160 if os.path.isabs(filename) and os.path.isfile(filename):
160 if os.path.isabs(filename) and os.path.isfile(filename):
161 return filename
161 return filename
162
162
163 if path_dirs is None:
163 if path_dirs is None:
164 path_dirs = ("",)
164 path_dirs = ("",)
165 elif isinstance(path_dirs, py3compat.string_types):
165 elif isinstance(path_dirs, py3compat.string_types):
166 path_dirs = (path_dirs,)
166 path_dirs = (path_dirs,)
167
167
168 for path in path_dirs:
168 for path in path_dirs:
169 if path == '.': path = os.getcwdu()
169 if path == '.': path = py3compat.getcwd()
170 testname = expand_path(os.path.join(path, filename))
170 testname = expand_path(os.path.join(path, filename))
171 if os.path.isfile(testname):
171 if os.path.isfile(testname):
172 return os.path.abspath(testname)
172 return os.path.abspath(testname)
173
173
174 raise IOError("File %r does not exist in any of the search paths: %r" %
174 raise IOError("File %r does not exist in any of the search paths: %r" %
175 (filename, path_dirs) )
175 (filename, path_dirs) )
176
176
177
177
178 class HomeDirError(Exception):
178 class HomeDirError(Exception):
179 pass
179 pass
180
180
181
181
182 def get_home_dir(require_writable=False):
182 def get_home_dir(require_writable=False):
183 """Return the 'home' directory, as a unicode string.
183 """Return the 'home' directory, as a unicode string.
184
184
185 Uses os.path.expanduser('~'), and checks for writability.
185 Uses os.path.expanduser('~'), and checks for writability.
186
186
187 See stdlib docs for how this is determined.
187 See stdlib docs for how this is determined.
188 $HOME is first priority on *ALL* platforms.
188 $HOME is first priority on *ALL* platforms.
189
189
190 Parameters
190 Parameters
191 ----------
191 ----------
192
192
193 require_writable : bool [default: False]
193 require_writable : bool [default: False]
194 if True:
194 if True:
195 guarantees the return value is a writable directory, otherwise
195 guarantees the return value is a writable directory, otherwise
196 raises HomeDirError
196 raises HomeDirError
197 if False:
197 if False:
198 The path is resolved, but it is not guaranteed to exist or be writable.
198 The path is resolved, but it is not guaranteed to exist or be writable.
199 """
199 """
200
200
201 homedir = os.path.expanduser('~')
201 homedir = os.path.expanduser('~')
202 # Next line will make things work even when /home/ is a symlink to
202 # Next line will make things work even when /home/ is a symlink to
203 # /usr/home as it is on FreeBSD, for example
203 # /usr/home as it is on FreeBSD, for example
204 homedir = os.path.realpath(homedir)
204 homedir = os.path.realpath(homedir)
205
205
206 if not _writable_dir(homedir) and os.name == 'nt':
206 if not _writable_dir(homedir) and os.name == 'nt':
207 # expanduser failed, use the registry to get the 'My Documents' folder.
207 # expanduser failed, use the registry to get the 'My Documents' folder.
208 try:
208 try:
209 try:
209 try:
210 import winreg as wreg # Py 3
210 import winreg as wreg # Py 3
211 except ImportError:
211 except ImportError:
212 import _winreg as wreg # Py 2
212 import _winreg as wreg # Py 2
213 key = wreg.OpenKey(
213 key = wreg.OpenKey(
214 wreg.HKEY_CURRENT_USER,
214 wreg.HKEY_CURRENT_USER,
215 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
215 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
216 )
216 )
217 homedir = wreg.QueryValueEx(key,'Personal')[0]
217 homedir = wreg.QueryValueEx(key,'Personal')[0]
218 key.Close()
218 key.Close()
219 except:
219 except:
220 pass
220 pass
221
221
222 if (not require_writable) or _writable_dir(homedir):
222 if (not require_writable) or _writable_dir(homedir):
223 return py3compat.cast_unicode(homedir, fs_encoding)
223 return py3compat.cast_unicode(homedir, fs_encoding)
224 else:
224 else:
225 raise HomeDirError('%s is not a writable dir, '
225 raise HomeDirError('%s is not a writable dir, '
226 'set $HOME environment variable to override' % homedir)
226 'set $HOME environment variable to override' % homedir)
227
227
228 def get_xdg_dir():
228 def get_xdg_dir():
229 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
229 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
230
230
231 This is only for non-OS X posix (Linux,Unix,etc.) systems.
231 This is only for non-OS X posix (Linux,Unix,etc.) systems.
232 """
232 """
233
233
234 env = os.environ
234 env = os.environ
235
235
236 if os.name == 'posix' and sys.platform != 'darwin':
236 if os.name == 'posix' and sys.platform != 'darwin':
237 # Linux, Unix, AIX, etc.
237 # Linux, Unix, AIX, etc.
238 # use ~/.config if empty OR not set
238 # use ~/.config if empty OR not set
239 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
239 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
240 if xdg and _writable_dir(xdg):
240 if xdg and _writable_dir(xdg):
241 return py3compat.cast_unicode(xdg, fs_encoding)
241 return py3compat.cast_unicode(xdg, fs_encoding)
242
242
243 return None
243 return None
244
244
245
245
246 def get_xdg_cache_dir():
246 def get_xdg_cache_dir():
247 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
247 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
248
248
249 This is only for non-OS X posix (Linux,Unix,etc.) systems.
249 This is only for non-OS X posix (Linux,Unix,etc.) systems.
250 """
250 """
251
251
252 env = os.environ
252 env = os.environ
253
253
254 if os.name == 'posix' and sys.platform != 'darwin':
254 if os.name == 'posix' and sys.platform != 'darwin':
255 # Linux, Unix, AIX, etc.
255 # Linux, Unix, AIX, etc.
256 # use ~/.cache if empty OR not set
256 # use ~/.cache if empty OR not set
257 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
257 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
258 if xdg and _writable_dir(xdg):
258 if xdg and _writable_dir(xdg):
259 return py3compat.cast_unicode(xdg, fs_encoding)
259 return py3compat.cast_unicode(xdg, fs_encoding)
260
260
261 return None
261 return None
262
262
263
263
264 def get_ipython_dir():
264 def get_ipython_dir():
265 """Get the IPython directory for this platform and user.
265 """Get the IPython directory for this platform and user.
266
266
267 This uses the logic in `get_home_dir` to find the home directory
267 This uses the logic in `get_home_dir` to find the home directory
268 and then adds .ipython to the end of the path.
268 and then adds .ipython to the end of the path.
269 """
269 """
270
270
271 env = os.environ
271 env = os.environ
272 pjoin = os.path.join
272 pjoin = os.path.join
273
273
274
274
275 ipdir_def = '.ipython'
275 ipdir_def = '.ipython'
276 xdg_def = 'ipython'
276 xdg_def = 'ipython'
277
277
278 home_dir = get_home_dir()
278 home_dir = get_home_dir()
279 xdg_dir = get_xdg_dir()
279 xdg_dir = get_xdg_dir()
280
280
281 # import pdb; pdb.set_trace() # dbg
281 # import pdb; pdb.set_trace() # dbg
282 if 'IPYTHON_DIR' in env:
282 if 'IPYTHON_DIR' in env:
283 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
283 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
284 'Please use IPYTHONDIR instead.')
284 'Please use IPYTHONDIR instead.')
285 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
285 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
286 if ipdir is None:
286 if ipdir is None:
287 # not set explicitly, use XDG_CONFIG_HOME or HOME
287 # not set explicitly, use XDG_CONFIG_HOME or HOME
288 home_ipdir = pjoin(home_dir, ipdir_def)
288 home_ipdir = pjoin(home_dir, ipdir_def)
289 if xdg_dir:
289 if xdg_dir:
290 # use XDG, as long as the user isn't already
290 # use XDG, as long as the user isn't already
291 # using $HOME/.ipython and *not* XDG/ipython
291 # using $HOME/.ipython and *not* XDG/ipython
292
292
293 xdg_ipdir = pjoin(xdg_dir, xdg_def)
293 xdg_ipdir = pjoin(xdg_dir, xdg_def)
294
294
295 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
295 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
296 ipdir = xdg_ipdir
296 ipdir = xdg_ipdir
297
297
298 if ipdir is None:
298 if ipdir is None:
299 # not using XDG
299 # not using XDG
300 ipdir = home_ipdir
300 ipdir = home_ipdir
301
301
302 ipdir = os.path.normpath(os.path.expanduser(ipdir))
302 ipdir = os.path.normpath(os.path.expanduser(ipdir))
303
303
304 if os.path.exists(ipdir) and not _writable_dir(ipdir):
304 if os.path.exists(ipdir) and not _writable_dir(ipdir):
305 # ipdir exists, but is not writable
305 # ipdir exists, but is not writable
306 warnings.warn("IPython dir '%s' is not a writable location,"
306 warnings.warn("IPython dir '%s' is not a writable location,"
307 " using a temp directory."%ipdir)
307 " using a temp directory."%ipdir)
308 ipdir = tempfile.mkdtemp()
308 ipdir = tempfile.mkdtemp()
309 elif not os.path.exists(ipdir):
309 elif not os.path.exists(ipdir):
310 parent = os.path.dirname(ipdir)
310 parent = os.path.dirname(ipdir)
311 if not _writable_dir(parent):
311 if not _writable_dir(parent):
312 # ipdir does not exist and parent isn't writable
312 # ipdir does not exist and parent isn't writable
313 warnings.warn("IPython parent '%s' is not a writable location,"
313 warnings.warn("IPython parent '%s' is not a writable location,"
314 " using a temp directory."%parent)
314 " using a temp directory."%parent)
315 ipdir = tempfile.mkdtemp()
315 ipdir = tempfile.mkdtemp()
316
316
317 return py3compat.cast_unicode(ipdir, fs_encoding)
317 return py3compat.cast_unicode(ipdir, fs_encoding)
318
318
319
319
320 def get_ipython_cache_dir():
320 def get_ipython_cache_dir():
321 """Get the cache directory it is created if it does not exist."""
321 """Get the cache directory it is created if it does not exist."""
322 xdgdir = get_xdg_cache_dir()
322 xdgdir = get_xdg_cache_dir()
323 if xdgdir is None:
323 if xdgdir is None:
324 return get_ipython_dir()
324 return get_ipython_dir()
325 ipdir = os.path.join(xdgdir, "ipython")
325 ipdir = os.path.join(xdgdir, "ipython")
326 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
326 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
327 os.makedirs(ipdir)
327 os.makedirs(ipdir)
328 elif not _writable_dir(xdgdir):
328 elif not _writable_dir(xdgdir):
329 return get_ipython_dir()
329 return get_ipython_dir()
330
330
331 return py3compat.cast_unicode(ipdir, fs_encoding)
331 return py3compat.cast_unicode(ipdir, fs_encoding)
332
332
333
333
334 def get_ipython_package_dir():
334 def get_ipython_package_dir():
335 """Get the base directory where IPython itself is installed."""
335 """Get the base directory where IPython itself is installed."""
336 ipdir = os.path.dirname(IPython.__file__)
336 ipdir = os.path.dirname(IPython.__file__)
337 return py3compat.cast_unicode(ipdir, fs_encoding)
337 return py3compat.cast_unicode(ipdir, fs_encoding)
338
338
339
339
340 def get_ipython_module_path(module_str):
340 def get_ipython_module_path(module_str):
341 """Find the path to an IPython module in this version of IPython.
341 """Find the path to an IPython module in this version of IPython.
342
342
343 This will always find the version of the module that is in this importable
343 This will always find the version of the module that is in this importable
344 IPython package. This will always return the path to the ``.py``
344 IPython package. This will always return the path to the ``.py``
345 version of the module.
345 version of the module.
346 """
346 """
347 if module_str == 'IPython':
347 if module_str == 'IPython':
348 return os.path.join(get_ipython_package_dir(), '__init__.py')
348 return os.path.join(get_ipython_package_dir(), '__init__.py')
349 mod = import_item(module_str)
349 mod = import_item(module_str)
350 the_path = mod.__file__.replace('.pyc', '.py')
350 the_path = mod.__file__.replace('.pyc', '.py')
351 the_path = the_path.replace('.pyo', '.py')
351 the_path = the_path.replace('.pyo', '.py')
352 return py3compat.cast_unicode(the_path, fs_encoding)
352 return py3compat.cast_unicode(the_path, fs_encoding)
353
353
354 def locate_profile(profile='default'):
354 def locate_profile(profile='default'):
355 """Find the path to the folder associated with a given profile.
355 """Find the path to the folder associated with a given profile.
356
356
357 I.e. find $IPYTHONDIR/profile_whatever.
357 I.e. find $IPYTHONDIR/profile_whatever.
358 """
358 """
359 from IPython.core.profiledir import ProfileDir, ProfileDirError
359 from IPython.core.profiledir import ProfileDir, ProfileDirError
360 try:
360 try:
361 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
361 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
362 except ProfileDirError:
362 except ProfileDirError:
363 # IOError makes more sense when people are expecting a path
363 # IOError makes more sense when people are expecting a path
364 raise IOError("Couldn't find profile %r" % profile)
364 raise IOError("Couldn't find profile %r" % profile)
365 return pd.location
365 return pd.location
366
366
367 def expand_path(s):
367 def expand_path(s):
368 """Expand $VARS and ~names in a string, like a shell
368 """Expand $VARS and ~names in a string, like a shell
369
369
370 :Examples:
370 :Examples:
371
371
372 In [2]: os.environ['FOO']='test'
372 In [2]: os.environ['FOO']='test'
373
373
374 In [3]: expand_path('variable FOO is $FOO')
374 In [3]: expand_path('variable FOO is $FOO')
375 Out[3]: 'variable FOO is test'
375 Out[3]: 'variable FOO is test'
376 """
376 """
377 # This is a pretty subtle hack. When expand user is given a UNC path
377 # This is a pretty subtle hack. When expand user is given a UNC path
378 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
378 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
379 # the $ to get (\\server\share\%username%). I think it considered $
379 # the $ to get (\\server\share\%username%). I think it considered $
380 # alone an empty var. But, we need the $ to remains there (it indicates
380 # alone an empty var. But, we need the $ to remains there (it indicates
381 # a hidden share).
381 # a hidden share).
382 if os.name=='nt':
382 if os.name=='nt':
383 s = s.replace('$\\', 'IPYTHON_TEMP')
383 s = s.replace('$\\', 'IPYTHON_TEMP')
384 s = os.path.expandvars(os.path.expanduser(s))
384 s = os.path.expandvars(os.path.expanduser(s))
385 if os.name=='nt':
385 if os.name=='nt':
386 s = s.replace('IPYTHON_TEMP', '$\\')
386 s = s.replace('IPYTHON_TEMP', '$\\')
387 return s
387 return s
388
388
389
389
390 def unescape_glob(string):
390 def unescape_glob(string):
391 """Unescape glob pattern in `string`."""
391 """Unescape glob pattern in `string`."""
392 def unescape(s):
392 def unescape(s):
393 for pattern in '*[]!?':
393 for pattern in '*[]!?':
394 s = s.replace(r'\{0}'.format(pattern), pattern)
394 s = s.replace(r'\{0}'.format(pattern), pattern)
395 return s
395 return s
396 return '\\'.join(map(unescape, string.split('\\\\')))
396 return '\\'.join(map(unescape, string.split('\\\\')))
397
397
398
398
399 def shellglob(args):
399 def shellglob(args):
400 """
400 """
401 Do glob expansion for each element in `args` and return a flattened list.
401 Do glob expansion for each element in `args` and return a flattened list.
402
402
403 Unmatched glob pattern will remain as-is in the returned list.
403 Unmatched glob pattern will remain as-is in the returned list.
404
404
405 """
405 """
406 expanded = []
406 expanded = []
407 # Do not unescape backslash in Windows as it is interpreted as
407 # Do not unescape backslash in Windows as it is interpreted as
408 # path separator:
408 # path separator:
409 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
409 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
410 for a in args:
410 for a in args:
411 expanded.extend(glob.glob(a) or [unescape(a)])
411 expanded.extend(glob.glob(a) or [unescape(a)])
412 return expanded
412 return expanded
413
413
414
414
415 def target_outdated(target,deps):
415 def target_outdated(target,deps):
416 """Determine whether a target is out of date.
416 """Determine whether a target is out of date.
417
417
418 target_outdated(target,deps) -> 1/0
418 target_outdated(target,deps) -> 1/0
419
419
420 deps: list of filenames which MUST exist.
420 deps: list of filenames which MUST exist.
421 target: single filename which may or may not exist.
421 target: single filename which may or may not exist.
422
422
423 If target doesn't exist or is older than any file listed in deps, return
423 If target doesn't exist or is older than any file listed in deps, return
424 true, otherwise return false.
424 true, otherwise return false.
425 """
425 """
426 try:
426 try:
427 target_time = os.path.getmtime(target)
427 target_time = os.path.getmtime(target)
428 except os.error:
428 except os.error:
429 return 1
429 return 1
430 for dep in deps:
430 for dep in deps:
431 dep_time = os.path.getmtime(dep)
431 dep_time = os.path.getmtime(dep)
432 if dep_time > target_time:
432 if dep_time > target_time:
433 #print "For target",target,"Dep failed:",dep # dbg
433 #print "For target",target,"Dep failed:",dep # dbg
434 #print "times (dep,tar):",dep_time,target_time # dbg
434 #print "times (dep,tar):",dep_time,target_time # dbg
435 return 1
435 return 1
436 return 0
436 return 0
437
437
438
438
439 def target_update(target,deps,cmd):
439 def target_update(target,deps,cmd):
440 """Update a target with a given command given a list of dependencies.
440 """Update a target with a given command given a list of dependencies.
441
441
442 target_update(target,deps,cmd) -> runs cmd if target is outdated.
442 target_update(target,deps,cmd) -> runs cmd if target is outdated.
443
443
444 This is just a wrapper around target_outdated() which calls the given
444 This is just a wrapper around target_outdated() which calls the given
445 command if target is outdated."""
445 command if target is outdated."""
446
446
447 if target_outdated(target,deps):
447 if target_outdated(target,deps):
448 system(cmd)
448 system(cmd)
449
449
450 def filehash(path):
450 def filehash(path):
451 """Make an MD5 hash of a file, ignoring any differences in line
451 """Make an MD5 hash of a file, ignoring any differences in line
452 ending characters."""
452 ending characters."""
453 with open(path, "rU") as f:
453 with open(path, "rU") as f:
454 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
454 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
455
455
456 # If the config is unmodified from the default, we'll just delete it.
456 # If the config is unmodified from the default, we'll just delete it.
457 # These are consistent for 0.10.x, thankfully. We're not going to worry about
457 # These are consistent for 0.10.x, thankfully. We're not going to worry about
458 # older versions.
458 # older versions.
459 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
459 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
460 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
460 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
461
461
462 def check_for_old_config(ipython_dir=None):
462 def check_for_old_config(ipython_dir=None):
463 """Check for old config files, and present a warning if they exist.
463 """Check for old config files, and present a warning if they exist.
464
464
465 A link to the docs of the new config is included in the message.
465 A link to the docs of the new config is included in the message.
466
466
467 This should mitigate confusion with the transition to the new
467 This should mitigate confusion with the transition to the new
468 config system in 0.11.
468 config system in 0.11.
469 """
469 """
470 if ipython_dir is None:
470 if ipython_dir is None:
471 ipython_dir = get_ipython_dir()
471 ipython_dir = get_ipython_dir()
472
472
473 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
473 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
474 warned = False
474 warned = False
475 for cfg in old_configs:
475 for cfg in old_configs:
476 f = os.path.join(ipython_dir, cfg)
476 f = os.path.join(ipython_dir, cfg)
477 if os.path.exists(f):
477 if os.path.exists(f):
478 if filehash(f) == old_config_md5.get(cfg, ''):
478 if filehash(f) == old_config_md5.get(cfg, ''):
479 os.unlink(f)
479 os.unlink(f)
480 else:
480 else:
481 warnings.warn("Found old IPython config file %r (modified by user)"%f)
481 warnings.warn("Found old IPython config file %r (modified by user)"%f)
482 warned = True
482 warned = True
483
483
484 if warned:
484 if warned:
485 warnings.warn("""
485 warnings.warn("""
486 The IPython configuration system has changed as of 0.11, and these files will
486 The IPython configuration system has changed as of 0.11, and these files will
487 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
487 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
488 of the new config system.
488 of the new config system.
489 To start configuring IPython, do `ipython profile create`, and edit
489 To start configuring IPython, do `ipython profile create`, and edit
490 `ipython_config.py` in <ipython_dir>/profile_default.
490 `ipython_config.py` in <ipython_dir>/profile_default.
491 If you need to leave the old config files in place for an older version of
491 If you need to leave the old config files in place for an older version of
492 IPython and want to suppress this warning message, set
492 IPython and want to suppress this warning message, set
493 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
493 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
494
494
495 def get_security_file(filename, profile='default'):
495 def get_security_file(filename, profile='default'):
496 """Return the absolute path of a security file given by filename and profile
496 """Return the absolute path of a security file given by filename and profile
497
497
498 This allows users and developers to find security files without
498 This allows users and developers to find security files without
499 knowledge of the IPython directory structure. The search path
499 knowledge of the IPython directory structure. The search path
500 will be ['.', profile.security_dir]
500 will be ['.', profile.security_dir]
501
501
502 Parameters
502 Parameters
503 ----------
503 ----------
504
504
505 filename : str
505 filename : str
506 The file to be found. If it is passed as an absolute path, it will
506 The file to be found. If it is passed as an absolute path, it will
507 simply be returned.
507 simply be returned.
508 profile : str [default: 'default']
508 profile : str [default: 'default']
509 The name of the profile to search. Leaving this unspecified
509 The name of the profile to search. Leaving this unspecified
510 The file to be found. If it is passed as an absolute path, fname will
510 The file to be found. If it is passed as an absolute path, fname will
511 simply be returned.
511 simply be returned.
512
512
513 Returns
513 Returns
514 -------
514 -------
515 Raises :exc:`IOError` if file not found or returns absolute path to file.
515 Raises :exc:`IOError` if file not found or returns absolute path to file.
516 """
516 """
517 # import here, because profiledir also imports from utils.path
517 # import here, because profiledir also imports from utils.path
518 from IPython.core.profiledir import ProfileDir
518 from IPython.core.profiledir import ProfileDir
519 try:
519 try:
520 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
520 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
521 except Exception:
521 except Exception:
522 # will raise ProfileDirError if no such profile
522 # will raise ProfileDirError if no such profile
523 raise IOError("Profile %r not found")
523 raise IOError("Profile %r not found")
524 return filefind(filename, ['.', pd.security_dir])
524 return filefind(filename, ['.', pd.security_dir])
525
525
526
526
527 ENOLINK = 1998
527 ENOLINK = 1998
528
528
529 def link(src, dst):
529 def link(src, dst):
530 """Hard links ``src`` to ``dst``, returning 0 or errno.
530 """Hard links ``src`` to ``dst``, returning 0 or errno.
531
531
532 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
532 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
533 supported by the operating system.
533 supported by the operating system.
534 """
534 """
535
535
536 if not hasattr(os, "link"):
536 if not hasattr(os, "link"):
537 return ENOLINK
537 return ENOLINK
538 link_errno = 0
538 link_errno = 0
539 try:
539 try:
540 os.link(src, dst)
540 os.link(src, dst)
541 except OSError as e:
541 except OSError as e:
542 link_errno = e.errno
542 link_errno = e.errno
543 return link_errno
543 return link_errno
544
544
545
545
546 def link_or_copy(src, dst):
546 def link_or_copy(src, dst):
547 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
547 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
548
548
549 Attempts to maintain the semantics of ``shutil.copy``.
549 Attempts to maintain the semantics of ``shutil.copy``.
550
550
551 Because ``os.link`` does not overwrite files, a unique temporary file
551 Because ``os.link`` does not overwrite files, a unique temporary file
552 will be used if the target already exists, then that file will be moved
552 will be used if the target already exists, then that file will be moved
553 into place.
553 into place.
554 """
554 """
555
555
556 if os.path.isdir(dst):
556 if os.path.isdir(dst):
557 dst = os.path.join(dst, os.path.basename(src))
557 dst = os.path.join(dst, os.path.basename(src))
558
558
559 link_errno = link(src, dst)
559 link_errno = link(src, dst)
560 if link_errno == errno.EEXIST:
560 if link_errno == errno.EEXIST:
561 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
561 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
562 try:
562 try:
563 link_or_copy(src, new_dst)
563 link_or_copy(src, new_dst)
564 except:
564 except:
565 try:
565 try:
566 os.remove(new_dst)
566 os.remove(new_dst)
567 except OSError:
567 except OSError:
568 pass
568 pass
569 raise
569 raise
570 os.rename(new_dst, dst)
570 os.rename(new_dst, dst)
571 elif link_errno != 0:
571 elif link_errno != 0:
572 # Either link isn't supported, or the filesystem doesn't support
572 # Either link isn't supported, or the filesystem doesn't support
573 # linking, or 'src' and 'dst' are on different filesystems.
573 # linking, or 'src' and 'dst' are on different filesystems.
574 shutil.copy(src, dst)
574 shutil.copy(src, dst)
@@ -1,122 +1,123 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with external processes.
3 Utilities for working with external processes.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-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 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import shlex
21 import shlex
22
22
23 # Our own
23 # Our own
24 if sys.platform == 'win32':
24 if sys.platform == 'win32':
25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split
25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split
26 else:
26 else:
27 from ._process_posix import _find_cmd, system, getoutput, arg_split
27 from ._process_posix import _find_cmd, system, getoutput, arg_split
28
28
29
29
30 from ._process_common import getoutputerror, get_output_error_code
30 from ._process_common import getoutputerror, get_output_error_code
31 from . import py3compat
31
32
32 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
33 # Code
34 # Code
34 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
35
36
36
37
37 class FindCmdError(Exception):
38 class FindCmdError(Exception):
38 pass
39 pass
39
40
40
41
41 def find_cmd(cmd):
42 def find_cmd(cmd):
42 """Find absolute path to executable cmd in a cross platform manner.
43 """Find absolute path to executable cmd in a cross platform manner.
43
44
44 This function tries to determine the full path to a command line program
45 This function tries to determine the full path to a command line program
45 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 time it will use the version that is first on the users `PATH`.
47 time it will use the version that is first on the users `PATH`.
47
48
48 Warning, don't use this to find IPython command line programs as there
49 Warning, don't use this to find IPython command line programs as there
49 is a risk you will find the wrong one. Instead find those using the
50 is a risk you will find the wrong one. Instead find those using the
50 following code and looking for the application itself::
51 following code and looking for the application itself::
51
52
52 from IPython.utils.path import get_ipython_module_path
53 from IPython.utils.path import get_ipython_module_path
53 from IPython.utils.process import pycmd2argv
54 from IPython.utils.process import pycmd2argv
54 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55
56
56 Parameters
57 Parameters
57 ----------
58 ----------
58 cmd : str
59 cmd : str
59 The command line program to look for.
60 The command line program to look for.
60 """
61 """
61 try:
62 try:
62 path = _find_cmd(cmd).rstrip()
63 path = _find_cmd(cmd).rstrip()
63 except OSError:
64 except OSError:
64 raise FindCmdError('command could not be found: %s' % cmd)
65 raise FindCmdError('command could not be found: %s' % cmd)
65 # which returns empty if not found
66 # which returns empty if not found
66 if path == '':
67 if path == '':
67 raise FindCmdError('command could not be found: %s' % cmd)
68 raise FindCmdError('command could not be found: %s' % cmd)
68 return os.path.abspath(path)
69 return os.path.abspath(path)
69
70
70
71
71 def is_cmd_found(cmd):
72 def is_cmd_found(cmd):
72 """Check whether executable `cmd` exists or not and return a bool."""
73 """Check whether executable `cmd` exists or not and return a bool."""
73 try:
74 try:
74 find_cmd(cmd)
75 find_cmd(cmd)
75 return True
76 return True
76 except FindCmdError:
77 except FindCmdError:
77 return False
78 return False
78
79
79
80
80 def pycmd2argv(cmd):
81 def pycmd2argv(cmd):
81 r"""Take the path of a python command and return a list (argv-style).
82 r"""Take the path of a python command and return a list (argv-style).
82
83
83 This only works on Python based command line programs and will find the
84 This only works on Python based command line programs and will find the
84 location of the ``python`` executable using ``sys.executable`` to make
85 location of the ``python`` executable using ``sys.executable`` to make
85 sure the right version is used.
86 sure the right version is used.
86
87
87 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
88 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
88 .com or .bat, and [, cmd] otherwise.
89 .com or .bat, and [, cmd] otherwise.
89
90
90 Parameters
91 Parameters
91 ----------
92 ----------
92 cmd : string
93 cmd : string
93 The path of the command.
94 The path of the command.
94
95
95 Returns
96 Returns
96 -------
97 -------
97 argv-style list.
98 argv-style list.
98 """
99 """
99 ext = os.path.splitext(cmd)[1]
100 ext = os.path.splitext(cmd)[1]
100 if ext in ['.exe', '.com', '.bat']:
101 if ext in ['.exe', '.com', '.bat']:
101 return [cmd]
102 return [cmd]
102 else:
103 else:
103 return [sys.executable, cmd]
104 return [sys.executable, cmd]
104
105
105
106
106 def abbrev_cwd():
107 def abbrev_cwd():
107 """ Return abbreviated version of cwd, e.g. d:mydir """
108 """ Return abbreviated version of cwd, e.g. d:mydir """
108 cwd = os.getcwdu().replace('\\','/')
109 cwd = py3compat.getcwd().replace('\\','/')
109 drivepart = ''
110 drivepart = ''
110 tail = cwd
111 tail = cwd
111 if sys.platform == 'win32':
112 if sys.platform == 'win32':
112 if len(cwd) < 4:
113 if len(cwd) < 4:
113 return cwd
114 return cwd
114 drivepart,tail = os.path.splitdrive(cwd)
115 drivepart,tail = os.path.splitdrive(cwd)
115
116
116
117
117 parts = tail.split('/')
118 parts = tail.split('/')
118 if len(parts) > 2:
119 if len(parts) > 2:
119 tail = '/'.join(parts[-2:])
120 tail = '/'.join(parts[-2:])
120
121
121 return (drivepart + (
122 return (drivepart + (
122 cwd == '/' and '/' or tail))
123 cwd == '/' and '/' or tail))
@@ -1,239 +1,242 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Compatibility tricks for Python 3. Mainly to do with unicode."""
2 """Compatibility tricks for Python 3. Mainly to do with unicode."""
3 import functools
3 import functools
4 import os
4 import sys
5 import sys
5 import re
6 import re
6 import types
7 import types
7
8
8 from .encoding import DEFAULT_ENCODING
9 from .encoding import DEFAULT_ENCODING
9
10
10 orig_open = open
11 orig_open = open
11
12
12 def no_code(x, encoding=None):
13 def no_code(x, encoding=None):
13 return x
14 return x
14
15
15 def decode(s, encoding=None):
16 def decode(s, encoding=None):
16 encoding = encoding or DEFAULT_ENCODING
17 encoding = encoding or DEFAULT_ENCODING
17 return s.decode(encoding, "replace")
18 return s.decode(encoding, "replace")
18
19
19 def encode(u, encoding=None):
20 def encode(u, encoding=None):
20 encoding = encoding or DEFAULT_ENCODING
21 encoding = encoding or DEFAULT_ENCODING
21 return u.encode(encoding, "replace")
22 return u.encode(encoding, "replace")
22
23
23
24
24 def cast_unicode(s, encoding=None):
25 def cast_unicode(s, encoding=None):
25 if isinstance(s, bytes):
26 if isinstance(s, bytes):
26 return decode(s, encoding)
27 return decode(s, encoding)
27 return s
28 return s
28
29
29 def cast_bytes(s, encoding=None):
30 def cast_bytes(s, encoding=None):
30 if not isinstance(s, bytes):
31 if not isinstance(s, bytes):
31 return encode(s, encoding)
32 return encode(s, encoding)
32 return s
33 return s
33
34
34 def _modify_str_or_docstring(str_change_func):
35 def _modify_str_or_docstring(str_change_func):
35 @functools.wraps(str_change_func)
36 @functools.wraps(str_change_func)
36 def wrapper(func_or_str):
37 def wrapper(func_or_str):
37 if isinstance(func_or_str, string_types):
38 if isinstance(func_or_str, string_types):
38 func = None
39 func = None
39 doc = func_or_str
40 doc = func_or_str
40 else:
41 else:
41 func = func_or_str
42 func = func_or_str
42 doc = func.__doc__
43 doc = func.__doc__
43
44
44 doc = str_change_func(doc)
45 doc = str_change_func(doc)
45
46
46 if func:
47 if func:
47 func.__doc__ = doc
48 func.__doc__ = doc
48 return func
49 return func
49 return doc
50 return doc
50 return wrapper
51 return wrapper
51
52
52 def safe_unicode(e):
53 def safe_unicode(e):
53 """unicode(e) with various fallbacks. Used for exceptions, which may not be
54 """unicode(e) with various fallbacks. Used for exceptions, which may not be
54 safe to call unicode() on.
55 safe to call unicode() on.
55 """
56 """
56 try:
57 try:
57 return unicode_type(e)
58 return unicode_type(e)
58 except UnicodeError:
59 except UnicodeError:
59 pass
60 pass
60
61
61 try:
62 try:
62 return str_to_unicode(str(e))
63 return str_to_unicode(str(e))
63 except UnicodeError:
64 except UnicodeError:
64 pass
65 pass
65
66
66 try:
67 try:
67 return str_to_unicode(repr(e))
68 return str_to_unicode(repr(e))
68 except UnicodeError:
69 except UnicodeError:
69 pass
70 pass
70
71
71 return u'Unrecoverably corrupt evalue'
72 return u'Unrecoverably corrupt evalue'
72
73
73 if sys.version_info[0] >= 3:
74 if sys.version_info[0] >= 3:
74 PY3 = True
75 PY3 = True
75
76
76 input = input
77 input = input
77 builtin_mod_name = "builtins"
78 builtin_mod_name = "builtins"
78 import builtins as builtin_mod
79 import builtins as builtin_mod
79
80
80 str_to_unicode = no_code
81 str_to_unicode = no_code
81 unicode_to_str = no_code
82 unicode_to_str = no_code
82 str_to_bytes = encode
83 str_to_bytes = encode
83 bytes_to_str = decode
84 bytes_to_str = decode
84 cast_bytes_py2 = no_code
85 cast_bytes_py2 = no_code
85
86
86 string_types = (str,)
87 string_types = (str,)
87 unicode_type = str
88 unicode_type = str
88
89
89 def isidentifier(s, dotted=False):
90 def isidentifier(s, dotted=False):
90 if dotted:
91 if dotted:
91 return all(isidentifier(a) for a in s.split("."))
92 return all(isidentifier(a) for a in s.split("."))
92 return s.isidentifier()
93 return s.isidentifier()
93
94
94 open = orig_open
95 open = orig_open
95 xrange = range
96 xrange = range
96 def iteritems(d): return iter(d.items())
97 def iteritems(d): return iter(d.items())
97 def itervalues(d): return iter(d.values())
98 def itervalues(d): return iter(d.values())
99 getcwd = os.getcwd
98
100
99 MethodType = types.MethodType
101 MethodType = types.MethodType
100
102
101 def execfile(fname, glob, loc=None):
103 def execfile(fname, glob, loc=None):
102 loc = loc if (loc is not None) else glob
104 loc = loc if (loc is not None) else glob
103 with open(fname, 'rb') as f:
105 with open(fname, 'rb') as f:
104 exec(compile(f.read(), fname, 'exec'), glob, loc)
106 exec(compile(f.read(), fname, 'exec'), glob, loc)
105
107
106 # Refactor print statements in doctests.
108 # Refactor print statements in doctests.
107 _print_statement_re = re.compile(r"\bprint (?P<expr>.*)$", re.MULTILINE)
109 _print_statement_re = re.compile(r"\bprint (?P<expr>.*)$", re.MULTILINE)
108 def _print_statement_sub(match):
110 def _print_statement_sub(match):
109 expr = match.groups('expr')
111 expr = match.groups('expr')
110 return "print(%s)" % expr
112 return "print(%s)" % expr
111
113
112 @_modify_str_or_docstring
114 @_modify_str_or_docstring
113 def doctest_refactor_print(doc):
115 def doctest_refactor_print(doc):
114 """Refactor 'print x' statements in a doctest to print(x) style. 2to3
116 """Refactor 'print x' statements in a doctest to print(x) style. 2to3
115 unfortunately doesn't pick up on our doctests.
117 unfortunately doesn't pick up on our doctests.
116
118
117 Can accept a string or a function, so it can be used as a decorator."""
119 Can accept a string or a function, so it can be used as a decorator."""
118 return _print_statement_re.sub(_print_statement_sub, doc)
120 return _print_statement_re.sub(_print_statement_sub, doc)
119
121
120 # Abstract u'abc' syntax:
122 # Abstract u'abc' syntax:
121 @_modify_str_or_docstring
123 @_modify_str_or_docstring
122 def u_format(s):
124 def u_format(s):
123 """"{u}'abc'" --> "'abc'" (Python 3)
125 """"{u}'abc'" --> "'abc'" (Python 3)
124
126
125 Accepts a string or a function, so it can be used as a decorator."""
127 Accepts a string or a function, so it can be used as a decorator."""
126 return s.format(u='')
128 return s.format(u='')
127
129
128 else:
130 else:
129 PY3 = False
131 PY3 = False
130
132
131 input = raw_input
133 input = raw_input
132 builtin_mod_name = "__builtin__"
134 builtin_mod_name = "__builtin__"
133 import __builtin__ as builtin_mod
135 import __builtin__ as builtin_mod
134
136
135 str_to_unicode = decode
137 str_to_unicode = decode
136 unicode_to_str = encode
138 unicode_to_str = encode
137 str_to_bytes = no_code
139 str_to_bytes = no_code
138 bytes_to_str = no_code
140 bytes_to_str = no_code
139 cast_bytes_py2 = cast_bytes
141 cast_bytes_py2 = cast_bytes
140
142
141 string_types = (str, unicode)
143 string_types = (str, unicode)
142 unicode_type = unicode
144 unicode_type = unicode
143
145
144 import re
146 import re
145 _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
147 _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
146 def isidentifier(s, dotted=False):
148 def isidentifier(s, dotted=False):
147 if dotted:
149 if dotted:
148 return all(isidentifier(a) for a in s.split("."))
150 return all(isidentifier(a) for a in s.split("."))
149 return bool(_name_re.match(s))
151 return bool(_name_re.match(s))
150
152
151 class open(object):
153 class open(object):
152 """Wrapper providing key part of Python 3 open() interface."""
154 """Wrapper providing key part of Python 3 open() interface."""
153 def __init__(self, fname, mode="r", encoding="utf-8"):
155 def __init__(self, fname, mode="r", encoding="utf-8"):
154 self.f = orig_open(fname, mode)
156 self.f = orig_open(fname, mode)
155 self.enc = encoding
157 self.enc = encoding
156
158
157 def write(self, s):
159 def write(self, s):
158 return self.f.write(s.encode(self.enc))
160 return self.f.write(s.encode(self.enc))
159
161
160 def read(self, size=-1):
162 def read(self, size=-1):
161 return self.f.read(size).decode(self.enc)
163 return self.f.read(size).decode(self.enc)
162
164
163 def close(self):
165 def close(self):
164 return self.f.close()
166 return self.f.close()
165
167
166 def __enter__(self):
168 def __enter__(self):
167 return self
169 return self
168
170
169 def __exit__(self, etype, value, traceback):
171 def __exit__(self, etype, value, traceback):
170 self.f.close()
172 self.f.close()
171
173
172 xrange = xrange
174 xrange = xrange
173 def iteritems(d): return d.iteritems()
175 def iteritems(d): return d.iteritems()
174 def itervalues(d): return d.itervalues()
176 def itervalues(d): return d.itervalues()
177 getcwd = os.getcwdu
175
178
176 def MethodType(func, instance):
179 def MethodType(func, instance):
177 return types.MethodType(func, instance, type(instance))
180 return types.MethodType(func, instance, type(instance))
178
181
179 # don't override system execfile on 2.x:
182 # don't override system execfile on 2.x:
180 execfile = execfile
183 execfile = execfile
181
184
182 def doctest_refactor_print(func_or_str):
185 def doctest_refactor_print(func_or_str):
183 return func_or_str
186 return func_or_str
184
187
185
188
186 # Abstract u'abc' syntax:
189 # Abstract u'abc' syntax:
187 @_modify_str_or_docstring
190 @_modify_str_or_docstring
188 def u_format(s):
191 def u_format(s):
189 """"{u}'abc'" --> "u'abc'" (Python 2)
192 """"{u}'abc'" --> "u'abc'" (Python 2)
190
193
191 Accepts a string or a function, so it can be used as a decorator."""
194 Accepts a string or a function, so it can be used as a decorator."""
192 return s.format(u='u')
195 return s.format(u='u')
193
196
194 if sys.platform == 'win32':
197 if sys.platform == 'win32':
195 def execfile(fname, glob=None, loc=None):
198 def execfile(fname, glob=None, loc=None):
196 loc = loc if (loc is not None) else glob
199 loc = loc if (loc is not None) else glob
197 # The rstrip() is necessary b/c trailing whitespace in files will
200 # The rstrip() is necessary b/c trailing whitespace in files will
198 # cause an IndentationError in Python 2.6 (this was fixed in 2.7,
201 # cause an IndentationError in Python 2.6 (this was fixed in 2.7,
199 # but we still support 2.6). See issue 1027.
202 # but we still support 2.6). See issue 1027.
200 scripttext = builtin_mod.open(fname).read().rstrip() + '\n'
203 scripttext = builtin_mod.open(fname).read().rstrip() + '\n'
201 # compile converts unicode filename to str assuming
204 # compile converts unicode filename to str assuming
202 # ascii. Let's do the conversion before calling compile
205 # ascii. Let's do the conversion before calling compile
203 if isinstance(fname, unicode):
206 if isinstance(fname, unicode):
204 filename = unicode_to_str(fname)
207 filename = unicode_to_str(fname)
205 else:
208 else:
206 filename = fname
209 filename = fname
207 exec(compile(scripttext, filename, 'exec'), glob, loc)
210 exec(compile(scripttext, filename, 'exec'), glob, loc)
208 else:
211 else:
209 def execfile(fname, *where):
212 def execfile(fname, *where):
210 if isinstance(fname, unicode):
213 if isinstance(fname, unicode):
211 filename = fname.encode(sys.getfilesystemencoding())
214 filename = fname.encode(sys.getfilesystemencoding())
212 else:
215 else:
213 filename = fname
216 filename = fname
214 builtin_mod.execfile(filename, *where)
217 builtin_mod.execfile(filename, *where)
215
218
216 # Parts below taken from six:
219 # Parts below taken from six:
217 # Copyright (c) 2010-2013 Benjamin Peterson
220 # Copyright (c) 2010-2013 Benjamin Peterson
218 #
221 #
219 # Permission is hereby granted, free of charge, to any person obtaining a copy
222 # Permission is hereby granted, free of charge, to any person obtaining a copy
220 # of this software and associated documentation files (the "Software"), to deal
223 # of this software and associated documentation files (the "Software"), to deal
221 # in the Software without restriction, including without limitation the rights
224 # in the Software without restriction, including without limitation the rights
222 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
225 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
223 # copies of the Software, and to permit persons to whom the Software is
226 # copies of the Software, and to permit persons to whom the Software is
224 # furnished to do so, subject to the following conditions:
227 # furnished to do so, subject to the following conditions:
225 #
228 #
226 # The above copyright notice and this permission notice shall be included in all
229 # The above copyright notice and this permission notice shall be included in all
227 # copies or substantial portions of the Software.
230 # copies or substantial portions of the Software.
228 #
231 #
229 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
232 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
230 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
233 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
231 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
234 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
232 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
235 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
233 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
236 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
234 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
237 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
235 # SOFTWARE.
238 # SOFTWARE.
236
239
237 def with_metaclass(meta, *bases):
240 def with_metaclass(meta, *bases):
238 """Create a base class with a metaclass."""
241 """Create a base class with a metaclass."""
239 return meta("NewBase", bases, {})
242 return meta("NewBase", bases, {})
@@ -1,162 +1,164 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 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import struct
24 import struct
25 import sys
25 import sys
26 import warnings
26 import warnings
27
27
28 from . import py3compat
29
28 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
29 # Code
31 # Code
30 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
31
33
32 # This variable is part of the expected API of the module:
34 # This variable is part of the expected API of the module:
33 ignore_termtitle = True
35 ignore_termtitle = True
34
36
35
37
36 def _term_clear():
38 def _term_clear():
37 pass
39 pass
38
40
39
41
40 if os.name == 'posix':
42 if os.name == 'posix':
41 def _term_clear():
43 def _term_clear():
42 os.system('clear')
44 os.system('clear')
43
45
44
46
45 if sys.platform == 'win32':
47 if sys.platform == 'win32':
46 def _term_clear():
48 def _term_clear():
47 os.system('cls')
49 os.system('cls')
48
50
49
51
50 def term_clear():
52 def term_clear():
51 _term_clear()
53 _term_clear()
52
54
53
55
54 def toggle_set_term_title(val):
56 def toggle_set_term_title(val):
55 """Control whether set_term_title is active or not.
57 """Control whether set_term_title is active or not.
56
58
57 set_term_title() allows writing to the console titlebar. In embedded
59 set_term_title() allows writing to the console titlebar. In embedded
58 widgets this can cause problems, so this call can be used to toggle it on
60 widgets this can cause problems, so this call can be used to toggle it on
59 or off as needed.
61 or off as needed.
60
62
61 The default state of the module is for the function to be disabled.
63 The default state of the module is for the function to be disabled.
62
64
63 Parameters
65 Parameters
64 ----------
66 ----------
65 val : bool
67 val : bool
66 If True, set_term_title() actually writes to the terminal (using the
68 If True, set_term_title() actually writes to the terminal (using the
67 appropriate platform-specific module). If False, it is a no-op.
69 appropriate platform-specific module). If False, it is a no-op.
68 """
70 """
69 global ignore_termtitle
71 global ignore_termtitle
70 ignore_termtitle = not(val)
72 ignore_termtitle = not(val)
71
73
72
74
73 def _set_term_title(*args,**kw):
75 def _set_term_title(*args,**kw):
74 """Dummy no-op."""
76 """Dummy no-op."""
75 pass
77 pass
76
78
77
79
78 def _set_term_title_xterm(title):
80 def _set_term_title_xterm(title):
79 """ Change virtual terminal title in xterm-workalikes """
81 """ Change virtual terminal title in xterm-workalikes """
80 sys.stdout.write('\033]0;%s\007' % title)
82 sys.stdout.write('\033]0;%s\007' % title)
81
83
82 if os.name == 'posix':
84 if os.name == 'posix':
83 TERM = os.environ.get('TERM','')
85 TERM = os.environ.get('TERM','')
84 if TERM.startswith('xterm'):
86 if TERM.startswith('xterm'):
85 _set_term_title = _set_term_title_xterm
87 _set_term_title = _set_term_title_xterm
86
88
87
89
88 if sys.platform == 'win32':
90 if sys.platform == 'win32':
89 try:
91 try:
90 import ctypes
92 import ctypes
91
93
92 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
94 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
93 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
95 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
94
96
95 def _set_term_title(title):
97 def _set_term_title(title):
96 """Set terminal title using ctypes to access the Win32 APIs."""
98 """Set terminal title using ctypes to access the Win32 APIs."""
97 SetConsoleTitleW(title)
99 SetConsoleTitleW(title)
98 except ImportError:
100 except ImportError:
99 def _set_term_title(title):
101 def _set_term_title(title):
100 """Set terminal title using the 'title' command."""
102 """Set terminal title using the 'title' command."""
101 global ignore_termtitle
103 global ignore_termtitle
102
104
103 try:
105 try:
104 # Cannot be on network share when issuing system commands
106 # Cannot be on network share when issuing system commands
105 curr = os.getcwdu()
107 curr = py3compat.getcwd()
106 os.chdir("C:")
108 os.chdir("C:")
107 ret = os.system("title " + title)
109 ret = os.system("title " + title)
108 finally:
110 finally:
109 os.chdir(curr)
111 os.chdir(curr)
110 if ret:
112 if ret:
111 # non-zero return code signals error, don't try again
113 # non-zero return code signals error, don't try again
112 ignore_termtitle = True
114 ignore_termtitle = True
113
115
114
116
115 def set_term_title(title):
117 def set_term_title(title):
116 """Set terminal title using the necessary platform-dependent calls."""
118 """Set terminal title using the necessary platform-dependent calls."""
117 if ignore_termtitle:
119 if ignore_termtitle:
118 return
120 return
119 _set_term_title(title)
121 _set_term_title(title)
120
122
121
123
122 def freeze_term_title():
124 def freeze_term_title():
123 warnings.warn("This function is deprecated, use toggle_set_term_title()")
125 warnings.warn("This function is deprecated, use toggle_set_term_title()")
124 global ignore_termtitle
126 global ignore_termtitle
125 ignore_termtitle = True
127 ignore_termtitle = True
126
128
127
129
128 def get_terminal_size(defaultx=80, defaulty=25):
130 def get_terminal_size(defaultx=80, defaulty=25):
129 return defaultx, defaulty
131 return defaultx, defaulty
130
132
131
133
132 if sys.platform == 'win32':
134 if sys.platform == 'win32':
133 def get_terminal_size(defaultx=80, defaulty=25):
135 def get_terminal_size(defaultx=80, defaulty=25):
134 """Return size of current terminal console.
136 """Return size of current terminal console.
135
137
136 This function try to determine actual size of current working
138 This function try to determine actual size of current working
137 console window and return tuple (sizex, sizey) if success,
139 console window and return tuple (sizex, sizey) if success,
138 or default size (defaultx, defaulty) otherwise.
140 or default size (defaultx, defaulty) otherwise.
139
141
140 Dependencies: ctypes should be installed.
142 Dependencies: ctypes should be installed.
141
143
142 Author: Alexander Belchenko (e-mail: bialix AT ukr.net)
144 Author: Alexander Belchenko (e-mail: bialix AT ukr.net)
143 """
145 """
144 try:
146 try:
145 import ctypes
147 import ctypes
146 except ImportError:
148 except ImportError:
147 return defaultx, defaulty
149 return defaultx, defaulty
148
150
149 h = ctypes.windll.kernel32.GetStdHandle(-11)
151 h = ctypes.windll.kernel32.GetStdHandle(-11)
150 csbi = ctypes.create_string_buffer(22)
152 csbi = ctypes.create_string_buffer(22)
151 res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
153 res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
152
154
153 if res:
155 if res:
154 (bufx, bufy, curx, cury, wattr,
156 (bufx, bufy, curx, cury, wattr,
155 left, top, right, bottom, maxx, maxy) = struct.unpack(
157 left, top, right, bottom, maxx, maxy) = struct.unpack(
156 "hhhhHhhhhhh", csbi.raw)
158 "hhhhHhhhhhh", csbi.raw)
157 sizex = right - left + 1
159 sizex = right - left + 1
158 sizey = bottom - top + 1
160 sizey = bottom - top + 1
159 return (sizex, sizey)
161 return (sizex, sizey)
160 else:
162 else:
161 return (defaultx, defaulty)
163 return (defaultx, defaulty)
162
164
@@ -1,643 +1,643 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 #-----------------------------------------------------------------------------
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 from __future__ import with_statement
15 from __future__ import with_statement
16
16
17 import os
17 import os
18 import shutil
18 import shutil
19 import sys
19 import sys
20 import tempfile
20 import tempfile
21 from contextlib import contextmanager
21 from contextlib import contextmanager
22
22
23 from os.path import join, abspath, split
23 from os.path import join, abspath, split
24
24
25 import nose.tools as nt
25 import nose.tools as nt
26
26
27 from nose import with_setup
27 from nose import with_setup
28
28
29 import IPython
29 import IPython
30 from IPython.testing import decorators as dec
30 from IPython.testing import decorators as dec
31 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
31 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
32 onlyif_unicode_paths,)
32 onlyif_unicode_paths,)
33 from IPython.testing.tools import make_tempfile, AssertPrints
33 from IPython.testing.tools import make_tempfile, AssertPrints
34 from IPython.utils import path
34 from IPython.utils import path
35 from IPython.utils import py3compat
35 from IPython.utils import py3compat
36 from IPython.utils.tempdir import TemporaryDirectory
36 from IPython.utils.tempdir import TemporaryDirectory
37
37
38 # Platform-dependent imports
38 # Platform-dependent imports
39 try:
39 try:
40 import winreg as wreg # Py 3
40 import winreg as wreg # Py 3
41 except ImportError:
41 except ImportError:
42 try:
42 try:
43 import _winreg as wreg # Py 2
43 import _winreg as wreg # Py 2
44 except ImportError:
44 except ImportError:
45 #Fake _winreg module on none windows platforms
45 #Fake _winreg module on none windows platforms
46 import types
46 import types
47 wr_name = "winreg" if py3compat.PY3 else "_winreg"
47 wr_name = "winreg" if py3compat.PY3 else "_winreg"
48 sys.modules[wr_name] = types.ModuleType(wr_name)
48 sys.modules[wr_name] = types.ModuleType(wr_name)
49 try:
49 try:
50 import winreg as wreg
50 import winreg as wreg
51 except ImportError:
51 except ImportError:
52 import _winreg as wreg
52 import _winreg as wreg
53 #Add entries that needs to be stubbed by the testing code
53 #Add entries that needs to be stubbed by the testing code
54 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
54 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
55
55
56 try:
56 try:
57 reload
57 reload
58 except NameError: # Python 3
58 except NameError: # Python 3
59 from imp import reload
59 from imp import reload
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Globals
62 # Globals
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 env = os.environ
64 env = os.environ
65 TEST_FILE_PATH = split(abspath(__file__))[0]
65 TEST_FILE_PATH = split(abspath(__file__))[0]
66 TMP_TEST_DIR = tempfile.mkdtemp()
66 TMP_TEST_DIR = tempfile.mkdtemp()
67 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
67 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
68 XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir")
68 XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir")
69 XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir")
69 XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir")
70 IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython')
70 IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython')
71 #
71 #
72 # Setup/teardown functions/decorators
72 # Setup/teardown functions/decorators
73 #
73 #
74
74
75 def setup():
75 def setup():
76 """Setup testenvironment for the module:
76 """Setup testenvironment for the module:
77
77
78 - Adds dummy home dir tree
78 - Adds dummy home dir tree
79 """
79 """
80 # Do not mask exceptions here. In particular, catching WindowsError is a
80 # Do not mask exceptions here. In particular, catching WindowsError is a
81 # problem because that exception is only defined on Windows...
81 # problem because that exception is only defined on Windows...
82 os.makedirs(IP_TEST_DIR)
82 os.makedirs(IP_TEST_DIR)
83 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
83 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
84 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
84 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
85
85
86
86
87 def teardown():
87 def teardown():
88 """Teardown testenvironment for the module:
88 """Teardown testenvironment for the module:
89
89
90 - Remove dummy home dir tree
90 - Remove dummy home dir tree
91 """
91 """
92 # Note: we remove the parent test dir, which is the root of all test
92 # Note: we remove the parent test dir, which is the root of all test
93 # subdirs we may have created. Use shutil instead of os.removedirs, so
93 # subdirs we may have created. Use shutil instead of os.removedirs, so
94 # that non-empty directories are all recursively removed.
94 # that non-empty directories are all recursively removed.
95 shutil.rmtree(TMP_TEST_DIR)
95 shutil.rmtree(TMP_TEST_DIR)
96
96
97
97
98 def setup_environment():
98 def setup_environment():
99 """Setup testenvironment for some functions that are tested
99 """Setup testenvironment for some functions that are tested
100 in this module. In particular this functions stores attributes
100 in this module. In particular this functions stores attributes
101 and other things that we need to stub in some test functions.
101 and other things that we need to stub in some test functions.
102 This needs to be done on a function level and not module level because
102 This needs to be done on a function level and not module level because
103 each testfunction needs a pristine environment.
103 each testfunction needs a pristine environment.
104 """
104 """
105 global oldstuff, platformstuff
105 global oldstuff, platformstuff
106 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
106 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
107
107
108 if os.name == 'nt':
108 if os.name == 'nt':
109 platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
109 platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
110
110
111
111
112 def teardown_environment():
112 def teardown_environment():
113 """Restore things that were remembered by the setup_environment function
113 """Restore things that were remembered by the setup_environment function
114 """
114 """
115 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
115 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
116 os.chdir(old_wd)
116 os.chdir(old_wd)
117 reload(path)
117 reload(path)
118
118
119 for key in list(env):
119 for key in list(env):
120 if key not in oldenv:
120 if key not in oldenv:
121 del env[key]
121 del env[key]
122 env.update(oldenv)
122 env.update(oldenv)
123 if hasattr(sys, 'frozen'):
123 if hasattr(sys, 'frozen'):
124 del sys.frozen
124 del sys.frozen
125 if os.name == 'nt':
125 if os.name == 'nt':
126 (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff
126 (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff
127
127
128 # Build decorator that uses the setup_environment/setup_environment
128 # Build decorator that uses the setup_environment/setup_environment
129 with_environment = with_setup(setup_environment, teardown_environment)
129 with_environment = with_setup(setup_environment, teardown_environment)
130
130
131 @skip_if_not_win32
131 @skip_if_not_win32
132 @with_environment
132 @with_environment
133 def test_get_home_dir_1():
133 def test_get_home_dir_1():
134 """Testcase for py2exe logic, un-compressed lib
134 """Testcase for py2exe logic, un-compressed lib
135 """
135 """
136 unfrozen = path.get_home_dir()
136 unfrozen = path.get_home_dir()
137 sys.frozen = True
137 sys.frozen = True
138
138
139 #fake filename for IPython.__init__
139 #fake filename for IPython.__init__
140 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
140 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
141
141
142 home_dir = path.get_home_dir()
142 home_dir = path.get_home_dir()
143 nt.assert_equal(home_dir, unfrozen)
143 nt.assert_equal(home_dir, unfrozen)
144
144
145
145
146 @skip_if_not_win32
146 @skip_if_not_win32
147 @with_environment
147 @with_environment
148 def test_get_home_dir_2():
148 def test_get_home_dir_2():
149 """Testcase for py2exe logic, compressed lib
149 """Testcase for py2exe logic, compressed lib
150 """
150 """
151 unfrozen = path.get_home_dir()
151 unfrozen = path.get_home_dir()
152 sys.frozen = True
152 sys.frozen = True
153 #fake filename for IPython.__init__
153 #fake filename for IPython.__init__
154 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
154 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
155
155
156 home_dir = path.get_home_dir(True)
156 home_dir = path.get_home_dir(True)
157 nt.assert_equal(home_dir, unfrozen)
157 nt.assert_equal(home_dir, unfrozen)
158
158
159
159
160 @with_environment
160 @with_environment
161 def test_get_home_dir_3():
161 def test_get_home_dir_3():
162 """get_home_dir() uses $HOME if set"""
162 """get_home_dir() uses $HOME if set"""
163 env["HOME"] = HOME_TEST_DIR
163 env["HOME"] = HOME_TEST_DIR
164 home_dir = path.get_home_dir(True)
164 home_dir = path.get_home_dir(True)
165 # get_home_dir expands symlinks
165 # get_home_dir expands symlinks
166 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
166 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
167
167
168
168
169 @with_environment
169 @with_environment
170 def test_get_home_dir_4():
170 def test_get_home_dir_4():
171 """get_home_dir() still works if $HOME is not set"""
171 """get_home_dir() still works if $HOME is not set"""
172
172
173 if 'HOME' in env: del env['HOME']
173 if 'HOME' in env: del env['HOME']
174 # this should still succeed, but we don't care what the answer is
174 # this should still succeed, but we don't care what the answer is
175 home = path.get_home_dir(False)
175 home = path.get_home_dir(False)
176
176
177 @with_environment
177 @with_environment
178 def test_get_home_dir_5():
178 def test_get_home_dir_5():
179 """raise HomeDirError if $HOME is specified, but not a writable dir"""
179 """raise HomeDirError if $HOME is specified, but not a writable dir"""
180 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
180 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
181 # set os.name = posix, to prevent My Documents fallback on Windows
181 # set os.name = posix, to prevent My Documents fallback on Windows
182 os.name = 'posix'
182 os.name = 'posix'
183 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
183 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
184
184
185
185
186 # Should we stub wreg fully so we can run the test on all platforms?
186 # Should we stub wreg fully so we can run the test on all platforms?
187 @skip_if_not_win32
187 @skip_if_not_win32
188 @with_environment
188 @with_environment
189 def test_get_home_dir_8():
189 def test_get_home_dir_8():
190 """Using registry hack for 'My Documents', os=='nt'
190 """Using registry hack for 'My Documents', os=='nt'
191
191
192 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
192 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
193 """
193 """
194 os.name = 'nt'
194 os.name = 'nt'
195 # Remove from stub environment all keys that may be set
195 # Remove from stub environment all keys that may be set
196 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
196 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
197 env.pop(key, None)
197 env.pop(key, None)
198
198
199 #Stub windows registry functions
199 #Stub windows registry functions
200 def OpenKey(x, y):
200 def OpenKey(x, y):
201 class key:
201 class key:
202 def Close(self):
202 def Close(self):
203 pass
203 pass
204 return key()
204 return key()
205 def QueryValueEx(x, y):
205 def QueryValueEx(x, y):
206 return [abspath(HOME_TEST_DIR)]
206 return [abspath(HOME_TEST_DIR)]
207
207
208 wreg.OpenKey = OpenKey
208 wreg.OpenKey = OpenKey
209 wreg.QueryValueEx = QueryValueEx
209 wreg.QueryValueEx = QueryValueEx
210
210
211 home_dir = path.get_home_dir()
211 home_dir = path.get_home_dir()
212 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
212 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
213
213
214
214
215 @with_environment
215 @with_environment
216 def test_get_ipython_dir_1():
216 def test_get_ipython_dir_1():
217 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
217 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
218 env_ipdir = os.path.join("someplace", ".ipython")
218 env_ipdir = os.path.join("someplace", ".ipython")
219 path._writable_dir = lambda path: True
219 path._writable_dir = lambda path: True
220 env['IPYTHONDIR'] = env_ipdir
220 env['IPYTHONDIR'] = env_ipdir
221 ipdir = path.get_ipython_dir()
221 ipdir = path.get_ipython_dir()
222 nt.assert_equal(ipdir, env_ipdir)
222 nt.assert_equal(ipdir, env_ipdir)
223
223
224
224
225 @with_environment
225 @with_environment
226 def test_get_ipython_dir_2():
226 def test_get_ipython_dir_2():
227 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
227 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
228 path.get_home_dir = lambda : "someplace"
228 path.get_home_dir = lambda : "someplace"
229 path.get_xdg_dir = lambda : None
229 path.get_xdg_dir = lambda : None
230 path._writable_dir = lambda path: True
230 path._writable_dir = lambda path: True
231 os.name = "posix"
231 os.name = "posix"
232 env.pop('IPYTHON_DIR', None)
232 env.pop('IPYTHON_DIR', None)
233 env.pop('IPYTHONDIR', None)
233 env.pop('IPYTHONDIR', None)
234 env.pop('XDG_CONFIG_HOME', None)
234 env.pop('XDG_CONFIG_HOME', None)
235 ipdir = path.get_ipython_dir()
235 ipdir = path.get_ipython_dir()
236 nt.assert_equal(ipdir, os.path.join("someplace", ".ipython"))
236 nt.assert_equal(ipdir, os.path.join("someplace", ".ipython"))
237
237
238 @with_environment
238 @with_environment
239 def test_get_ipython_dir_3():
239 def test_get_ipython_dir_3():
240 """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist."""
240 """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist."""
241 path.get_home_dir = lambda : "someplace"
241 path.get_home_dir = lambda : "someplace"
242 path._writable_dir = lambda path: True
242 path._writable_dir = lambda path: True
243 os.name = "posix"
243 os.name = "posix"
244 env.pop('IPYTHON_DIR', None)
244 env.pop('IPYTHON_DIR', None)
245 env.pop('IPYTHONDIR', None)
245 env.pop('IPYTHONDIR', None)
246 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
246 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
247 ipdir = path.get_ipython_dir()
247 ipdir = path.get_ipython_dir()
248 if sys.platform == "darwin":
248 if sys.platform == "darwin":
249 expected = os.path.join("someplace", ".ipython")
249 expected = os.path.join("someplace", ".ipython")
250 else:
250 else:
251 expected = os.path.join(XDG_TEST_DIR, "ipython")
251 expected = os.path.join(XDG_TEST_DIR, "ipython")
252 nt.assert_equal(ipdir, expected)
252 nt.assert_equal(ipdir, expected)
253
253
254 @with_environment
254 @with_environment
255 def test_get_ipython_dir_4():
255 def test_get_ipython_dir_4():
256 """test_get_ipython_dir_4, use XDG if both exist."""
256 """test_get_ipython_dir_4, use XDG if both exist."""
257 path.get_home_dir = lambda : HOME_TEST_DIR
257 path.get_home_dir = lambda : HOME_TEST_DIR
258 os.name = "posix"
258 os.name = "posix"
259 env.pop('IPYTHON_DIR', None)
259 env.pop('IPYTHON_DIR', None)
260 env.pop('IPYTHONDIR', None)
260 env.pop('IPYTHONDIR', None)
261 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
261 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
262 ipdir = path.get_ipython_dir()
262 ipdir = path.get_ipython_dir()
263 if sys.platform == "darwin":
263 if sys.platform == "darwin":
264 expected = os.path.join(HOME_TEST_DIR, ".ipython")
264 expected = os.path.join(HOME_TEST_DIR, ".ipython")
265 else:
265 else:
266 expected = os.path.join(XDG_TEST_DIR, "ipython")
266 expected = os.path.join(XDG_TEST_DIR, "ipython")
267 nt.assert_equal(ipdir, expected)
267 nt.assert_equal(ipdir, expected)
268
268
269 @with_environment
269 @with_environment
270 def test_get_ipython_dir_5():
270 def test_get_ipython_dir_5():
271 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
271 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
272 path.get_home_dir = lambda : HOME_TEST_DIR
272 path.get_home_dir = lambda : HOME_TEST_DIR
273 os.name = "posix"
273 os.name = "posix"
274 env.pop('IPYTHON_DIR', None)
274 env.pop('IPYTHON_DIR', None)
275 env.pop('IPYTHONDIR', None)
275 env.pop('IPYTHONDIR', None)
276 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
276 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
277 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
277 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
278 ipdir = path.get_ipython_dir()
278 ipdir = path.get_ipython_dir()
279 nt.assert_equal(ipdir, IP_TEST_DIR)
279 nt.assert_equal(ipdir, IP_TEST_DIR)
280
280
281 @with_environment
281 @with_environment
282 def test_get_ipython_dir_6():
282 def test_get_ipython_dir_6():
283 """test_get_ipython_dir_6, use XDG if defined and neither exist."""
283 """test_get_ipython_dir_6, use XDG if defined and neither exist."""
284 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
284 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
285 os.mkdir(xdg)
285 os.mkdir(xdg)
286 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
286 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
287 path.get_home_dir = lambda : HOME_TEST_DIR
287 path.get_home_dir = lambda : HOME_TEST_DIR
288 path.get_xdg_dir = lambda : xdg
288 path.get_xdg_dir = lambda : xdg
289 os.name = "posix"
289 os.name = "posix"
290 env.pop('IPYTHON_DIR', None)
290 env.pop('IPYTHON_DIR', None)
291 env.pop('IPYTHONDIR', None)
291 env.pop('IPYTHONDIR', None)
292 env.pop('XDG_CONFIG_HOME', None)
292 env.pop('XDG_CONFIG_HOME', None)
293 xdg_ipdir = os.path.join(xdg, "ipython")
293 xdg_ipdir = os.path.join(xdg, "ipython")
294 ipdir = path.get_ipython_dir()
294 ipdir = path.get_ipython_dir()
295 nt.assert_equal(ipdir, xdg_ipdir)
295 nt.assert_equal(ipdir, xdg_ipdir)
296
296
297 @with_environment
297 @with_environment
298 def test_get_ipython_dir_7():
298 def test_get_ipython_dir_7():
299 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
299 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
300 path._writable_dir = lambda path: True
300 path._writable_dir = lambda path: True
301 home_dir = os.path.normpath(os.path.expanduser('~'))
301 home_dir = os.path.normpath(os.path.expanduser('~'))
302 env['IPYTHONDIR'] = os.path.join('~', 'somewhere')
302 env['IPYTHONDIR'] = os.path.join('~', 'somewhere')
303 ipdir = path.get_ipython_dir()
303 ipdir = path.get_ipython_dir()
304 nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere'))
304 nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere'))
305
305
306 @skip_win32
306 @skip_win32
307 @with_environment
307 @with_environment
308 def test_get_ipython_dir_8():
308 def test_get_ipython_dir_8():
309 """test_get_ipython_dir_8, test / home directory"""
309 """test_get_ipython_dir_8, test / home directory"""
310 old = path._writable_dir, path.get_xdg_dir
310 old = path._writable_dir, path.get_xdg_dir
311 try:
311 try:
312 path._writable_dir = lambda path: bool(path)
312 path._writable_dir = lambda path: bool(path)
313 path.get_xdg_dir = lambda: None
313 path.get_xdg_dir = lambda: None
314 env.pop('IPYTHON_DIR', None)
314 env.pop('IPYTHON_DIR', None)
315 env.pop('IPYTHONDIR', None)
315 env.pop('IPYTHONDIR', None)
316 env['HOME'] = '/'
316 env['HOME'] = '/'
317 nt.assert_equal(path.get_ipython_dir(), '/.ipython')
317 nt.assert_equal(path.get_ipython_dir(), '/.ipython')
318 finally:
318 finally:
319 path._writable_dir, path.get_xdg_dir = old
319 path._writable_dir, path.get_xdg_dir = old
320
320
321 @with_environment
321 @with_environment
322 def test_get_xdg_dir_0():
322 def test_get_xdg_dir_0():
323 """test_get_xdg_dir_0, check xdg_dir"""
323 """test_get_xdg_dir_0, check xdg_dir"""
324 reload(path)
324 reload(path)
325 path._writable_dir = lambda path: True
325 path._writable_dir = lambda path: True
326 path.get_home_dir = lambda : 'somewhere'
326 path.get_home_dir = lambda : 'somewhere'
327 os.name = "posix"
327 os.name = "posix"
328 sys.platform = "linux2"
328 sys.platform = "linux2"
329 env.pop('IPYTHON_DIR', None)
329 env.pop('IPYTHON_DIR', None)
330 env.pop('IPYTHONDIR', None)
330 env.pop('IPYTHONDIR', None)
331 env.pop('XDG_CONFIG_HOME', None)
331 env.pop('XDG_CONFIG_HOME', None)
332
332
333 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
333 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
334
334
335
335
336 @with_environment
336 @with_environment
337 def test_get_xdg_dir_1():
337 def test_get_xdg_dir_1():
338 """test_get_xdg_dir_1, check nonexistant xdg_dir"""
338 """test_get_xdg_dir_1, check nonexistant xdg_dir"""
339 reload(path)
339 reload(path)
340 path.get_home_dir = lambda : HOME_TEST_DIR
340 path.get_home_dir = lambda : HOME_TEST_DIR
341 os.name = "posix"
341 os.name = "posix"
342 sys.platform = "linux2"
342 sys.platform = "linux2"
343 env.pop('IPYTHON_DIR', None)
343 env.pop('IPYTHON_DIR', None)
344 env.pop('IPYTHONDIR', None)
344 env.pop('IPYTHONDIR', None)
345 env.pop('XDG_CONFIG_HOME', None)
345 env.pop('XDG_CONFIG_HOME', None)
346 nt.assert_equal(path.get_xdg_dir(), None)
346 nt.assert_equal(path.get_xdg_dir(), None)
347
347
348 @with_environment
348 @with_environment
349 def test_get_xdg_dir_2():
349 def test_get_xdg_dir_2():
350 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
350 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
351 reload(path)
351 reload(path)
352 path.get_home_dir = lambda : HOME_TEST_DIR
352 path.get_home_dir = lambda : HOME_TEST_DIR
353 os.name = "posix"
353 os.name = "posix"
354 sys.platform = "linux2"
354 sys.platform = "linux2"
355 env.pop('IPYTHON_DIR', None)
355 env.pop('IPYTHON_DIR', None)
356 env.pop('IPYTHONDIR', None)
356 env.pop('IPYTHONDIR', None)
357 env.pop('XDG_CONFIG_HOME', None)
357 env.pop('XDG_CONFIG_HOME', None)
358 cfgdir=os.path.join(path.get_home_dir(), '.config')
358 cfgdir=os.path.join(path.get_home_dir(), '.config')
359 if not os.path.exists(cfgdir):
359 if not os.path.exists(cfgdir):
360 os.makedirs(cfgdir)
360 os.makedirs(cfgdir)
361
361
362 nt.assert_equal(path.get_xdg_dir(), cfgdir)
362 nt.assert_equal(path.get_xdg_dir(), cfgdir)
363
363
364 @with_environment
364 @with_environment
365 def test_get_xdg_dir_3():
365 def test_get_xdg_dir_3():
366 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
366 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
367 reload(path)
367 reload(path)
368 path.get_home_dir = lambda : HOME_TEST_DIR
368 path.get_home_dir = lambda : HOME_TEST_DIR
369 os.name = "posix"
369 os.name = "posix"
370 sys.platform = "darwin"
370 sys.platform = "darwin"
371 env.pop('IPYTHON_DIR', None)
371 env.pop('IPYTHON_DIR', None)
372 env.pop('IPYTHONDIR', None)
372 env.pop('IPYTHONDIR', None)
373 env.pop('XDG_CONFIG_HOME', None)
373 env.pop('XDG_CONFIG_HOME', None)
374 cfgdir=os.path.join(path.get_home_dir(), '.config')
374 cfgdir=os.path.join(path.get_home_dir(), '.config')
375 if not os.path.exists(cfgdir):
375 if not os.path.exists(cfgdir):
376 os.makedirs(cfgdir)
376 os.makedirs(cfgdir)
377
377
378 nt.assert_equal(path.get_xdg_dir(), None)
378 nt.assert_equal(path.get_xdg_dir(), None)
379
379
380 def test_filefind():
380 def test_filefind():
381 """Various tests for filefind"""
381 """Various tests for filefind"""
382 f = tempfile.NamedTemporaryFile()
382 f = tempfile.NamedTemporaryFile()
383 # print 'fname:',f.name
383 # print 'fname:',f.name
384 alt_dirs = path.get_ipython_dir()
384 alt_dirs = path.get_ipython_dir()
385 t = path.filefind(f.name, alt_dirs)
385 t = path.filefind(f.name, alt_dirs)
386 # print 'found:',t
386 # print 'found:',t
387
387
388 @with_environment
388 @with_environment
389 def test_get_ipython_cache_dir():
389 def test_get_ipython_cache_dir():
390 os.environ["HOME"] = HOME_TEST_DIR
390 os.environ["HOME"] = HOME_TEST_DIR
391 if os.name == 'posix' and sys.platform != 'darwin':
391 if os.name == 'posix' and sys.platform != 'darwin':
392 # test default
392 # test default
393 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
393 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
394 os.environ.pop("XDG_CACHE_HOME", None)
394 os.environ.pop("XDG_CACHE_HOME", None)
395 ipdir = path.get_ipython_cache_dir()
395 ipdir = path.get_ipython_cache_dir()
396 nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"),
396 nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"),
397 ipdir)
397 ipdir)
398 nt.assert_true(os.path.isdir(ipdir))
398 nt.assert_true(os.path.isdir(ipdir))
399
399
400 # test env override
400 # test env override
401 os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR
401 os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR
402 ipdir = path.get_ipython_cache_dir()
402 ipdir = path.get_ipython_cache_dir()
403 nt.assert_true(os.path.isdir(ipdir))
403 nt.assert_true(os.path.isdir(ipdir))
404 nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython"))
404 nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython"))
405 else:
405 else:
406 nt.assert_equal(path.get_ipython_cache_dir(),
406 nt.assert_equal(path.get_ipython_cache_dir(),
407 path.get_ipython_dir())
407 path.get_ipython_dir())
408
408
409 def test_get_ipython_package_dir():
409 def test_get_ipython_package_dir():
410 ipdir = path.get_ipython_package_dir()
410 ipdir = path.get_ipython_package_dir()
411 nt.assert_true(os.path.isdir(ipdir))
411 nt.assert_true(os.path.isdir(ipdir))
412
412
413
413
414 def test_get_ipython_module_path():
414 def test_get_ipython_module_path():
415 ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp')
415 ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp')
416 nt.assert_true(os.path.isfile(ipapp_path))
416 nt.assert_true(os.path.isfile(ipapp_path))
417
417
418
418
419 @dec.skip_if_not_win32
419 @dec.skip_if_not_win32
420 def test_get_long_path_name_win32():
420 def test_get_long_path_name_win32():
421 with TemporaryDirectory() as tmpdir:
421 with TemporaryDirectory() as tmpdir:
422
422
423 # Make a long path.
423 # Make a long path.
424 long_path = os.path.join(tmpdir, u'this is my long path name')
424 long_path = os.path.join(tmpdir, u'this is my long path name')
425 os.makedirs(long_path)
425 os.makedirs(long_path)
426
426
427 # Test to see if the short path evaluates correctly.
427 # Test to see if the short path evaluates correctly.
428 short_path = os.path.join(tmpdir, u'THISIS~1')
428 short_path = os.path.join(tmpdir, u'THISIS~1')
429 evaluated_path = path.get_long_path_name(short_path)
429 evaluated_path = path.get_long_path_name(short_path)
430 nt.assert_equal(evaluated_path.lower(), long_path.lower())
430 nt.assert_equal(evaluated_path.lower(), long_path.lower())
431
431
432
432
433 @dec.skip_win32
433 @dec.skip_win32
434 def test_get_long_path_name():
434 def test_get_long_path_name():
435 p = path.get_long_path_name('/usr/local')
435 p = path.get_long_path_name('/usr/local')
436 nt.assert_equal(p,'/usr/local')
436 nt.assert_equal(p,'/usr/local')
437
437
438 @dec.skip_win32 # can't create not-user-writable dir on win
438 @dec.skip_win32 # can't create not-user-writable dir on win
439 @with_environment
439 @with_environment
440 def test_not_writable_ipdir():
440 def test_not_writable_ipdir():
441 tmpdir = tempfile.mkdtemp()
441 tmpdir = tempfile.mkdtemp()
442 os.name = "posix"
442 os.name = "posix"
443 env.pop('IPYTHON_DIR', None)
443 env.pop('IPYTHON_DIR', None)
444 env.pop('IPYTHONDIR', None)
444 env.pop('IPYTHONDIR', None)
445 env.pop('XDG_CONFIG_HOME', None)
445 env.pop('XDG_CONFIG_HOME', None)
446 env['HOME'] = tmpdir
446 env['HOME'] = tmpdir
447 ipdir = os.path.join(tmpdir, '.ipython')
447 ipdir = os.path.join(tmpdir, '.ipython')
448 os.mkdir(ipdir)
448 os.mkdir(ipdir)
449 os.chmod(ipdir, 600)
449 os.chmod(ipdir, 600)
450 with AssertPrints('is not a writable location', channel='stderr'):
450 with AssertPrints('is not a writable location', channel='stderr'):
451 ipdir = path.get_ipython_dir()
451 ipdir = path.get_ipython_dir()
452 env.pop('IPYTHON_DIR', None)
452 env.pop('IPYTHON_DIR', None)
453
453
454 def test_unquote_filename():
454 def test_unquote_filename():
455 for win32 in (True, False):
455 for win32 in (True, False):
456 nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py')
456 nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py')
457 nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py')
457 nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py')
458 nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py')
458 nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py')
459 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py')
459 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py')
460 nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py')
460 nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py')
461 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py')
461 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py')
462 nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"')
462 nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"')
463 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"')
463 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"')
464 nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'")
464 nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'")
465 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'")
465 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'")
466
466
467 @with_environment
467 @with_environment
468 def test_get_py_filename():
468 def test_get_py_filename():
469 os.chdir(TMP_TEST_DIR)
469 os.chdir(TMP_TEST_DIR)
470 for win32 in (True, False):
470 for win32 in (True, False):
471 with make_tempfile('foo.py'):
471 with make_tempfile('foo.py'):
472 nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py')
472 nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py')
473 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py')
473 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py')
474 with make_tempfile('foo'):
474 with make_tempfile('foo'):
475 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo')
475 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo')
476 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
476 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
477 nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32)
477 nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32)
478 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
478 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
479 true_fn = 'foo with spaces.py'
479 true_fn = 'foo with spaces.py'
480 with make_tempfile(true_fn):
480 with make_tempfile(true_fn):
481 nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn)
481 nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn)
482 nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn)
482 nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn)
483 if win32:
483 if win32:
484 nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn)
484 nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn)
485 nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn)
485 nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn)
486 else:
486 else:
487 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False)
487 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False)
488 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False)
488 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False)
489
489
490 @onlyif_unicode_paths
490 @onlyif_unicode_paths
491 def test_unicode_in_filename():
491 def test_unicode_in_filename():
492 """When a file doesn't exist, the exception raised should be safe to call
492 """When a file doesn't exist, the exception raised should be safe to call
493 str() on - i.e. in Python 2 it must only have ASCII characters.
493 str() on - i.e. in Python 2 it must only have ASCII characters.
494
494
495 https://github.com/ipython/ipython/issues/875
495 https://github.com/ipython/ipython/issues/875
496 """
496 """
497 try:
497 try:
498 # these calls should not throw unicode encode exceptions
498 # these calls should not throw unicode encode exceptions
499 path.get_py_filename(u'fooéè.py', force_win32=False)
499 path.get_py_filename(u'fooéè.py', force_win32=False)
500 except IOError as ex:
500 except IOError as ex:
501 str(ex)
501 str(ex)
502
502
503
503
504 class TestShellGlob(object):
504 class TestShellGlob(object):
505
505
506 @classmethod
506 @classmethod
507 def setUpClass(cls):
507 def setUpClass(cls):
508 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
508 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
509 cls.filenames_end_with_b = ['0b', '1b', '2b']
509 cls.filenames_end_with_b = ['0b', '1b', '2b']
510 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
510 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
511 cls.tempdir = TemporaryDirectory()
511 cls.tempdir = TemporaryDirectory()
512 td = cls.tempdir.name
512 td = cls.tempdir.name
513
513
514 with cls.in_tempdir():
514 with cls.in_tempdir():
515 # Create empty files
515 # Create empty files
516 for fname in cls.filenames:
516 for fname in cls.filenames:
517 open(os.path.join(td, fname), 'w').close()
517 open(os.path.join(td, fname), 'w').close()
518
518
519 @classmethod
519 @classmethod
520 def tearDownClass(cls):
520 def tearDownClass(cls):
521 cls.tempdir.cleanup()
521 cls.tempdir.cleanup()
522
522
523 @classmethod
523 @classmethod
524 @contextmanager
524 @contextmanager
525 def in_tempdir(cls):
525 def in_tempdir(cls):
526 save = os.getcwdu()
526 save = py3compat.getcwd()
527 try:
527 try:
528 os.chdir(cls.tempdir.name)
528 os.chdir(cls.tempdir.name)
529 yield
529 yield
530 finally:
530 finally:
531 os.chdir(save)
531 os.chdir(save)
532
532
533 def check_match(self, patterns, matches):
533 def check_match(self, patterns, matches):
534 with self.in_tempdir():
534 with self.in_tempdir():
535 # glob returns unordered list. that's why sorted is required.
535 # glob returns unordered list. that's why sorted is required.
536 nt.assert_equals(sorted(path.shellglob(patterns)),
536 nt.assert_equals(sorted(path.shellglob(patterns)),
537 sorted(matches))
537 sorted(matches))
538
538
539 def common_cases(self):
539 def common_cases(self):
540 return [
540 return [
541 (['*'], self.filenames),
541 (['*'], self.filenames),
542 (['a*'], self.filenames_start_with_a),
542 (['a*'], self.filenames_start_with_a),
543 (['*c'], ['*c']),
543 (['*c'], ['*c']),
544 (['*', 'a*', '*b', '*c'], self.filenames
544 (['*', 'a*', '*b', '*c'], self.filenames
545 + self.filenames_start_with_a
545 + self.filenames_start_with_a
546 + self.filenames_end_with_b
546 + self.filenames_end_with_b
547 + ['*c']),
547 + ['*c']),
548 (['a[012]'], self.filenames_start_with_a),
548 (['a[012]'], self.filenames_start_with_a),
549 ]
549 ]
550
550
551 @skip_win32
551 @skip_win32
552 def test_match_posix(self):
552 def test_match_posix(self):
553 for (patterns, matches) in self.common_cases() + [
553 for (patterns, matches) in self.common_cases() + [
554 ([r'\*'], ['*']),
554 ([r'\*'], ['*']),
555 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
555 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
556 ([r'a\[012]'], ['a[012]']),
556 ([r'a\[012]'], ['a[012]']),
557 ]:
557 ]:
558 yield (self.check_match, patterns, matches)
558 yield (self.check_match, patterns, matches)
559
559
560 @skip_if_not_win32
560 @skip_if_not_win32
561 def test_match_windows(self):
561 def test_match_windows(self):
562 for (patterns, matches) in self.common_cases() + [
562 for (patterns, matches) in self.common_cases() + [
563 # In windows, backslash is interpreted as path
563 # In windows, backslash is interpreted as path
564 # separator. Therefore, you can't escape glob
564 # separator. Therefore, you can't escape glob
565 # using it.
565 # using it.
566 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
566 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
567 ([r'a\[012]'], [r'a\[012]']),
567 ([r'a\[012]'], [r'a\[012]']),
568 ]:
568 ]:
569 yield (self.check_match, patterns, matches)
569 yield (self.check_match, patterns, matches)
570
570
571
571
572 def test_unescape_glob():
572 def test_unescape_glob():
573 nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
573 nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
574 nt.assert_equals(path.unescape_glob(r'\\*'), r'\*')
574 nt.assert_equals(path.unescape_glob(r'\\*'), r'\*')
575 nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*')
575 nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*')
576 nt.assert_equals(path.unescape_glob(r'\\a'), r'\a')
576 nt.assert_equals(path.unescape_glob(r'\\a'), r'\a')
577 nt.assert_equals(path.unescape_glob(r'\a'), r'\a')
577 nt.assert_equals(path.unescape_glob(r'\a'), r'\a')
578
578
579
579
580 class TestLinkOrCopy(object):
580 class TestLinkOrCopy(object):
581 def setUp(self):
581 def setUp(self):
582 self.tempdir = TemporaryDirectory()
582 self.tempdir = TemporaryDirectory()
583 self.src = self.dst("src")
583 self.src = self.dst("src")
584 with open(self.src, "w") as f:
584 with open(self.src, "w") as f:
585 f.write("Hello, world!")
585 f.write("Hello, world!")
586
586
587 def tearDown(self):
587 def tearDown(self):
588 self.tempdir.cleanup()
588 self.tempdir.cleanup()
589
589
590 def dst(self, *args):
590 def dst(self, *args):
591 return os.path.join(self.tempdir.name, *args)
591 return os.path.join(self.tempdir.name, *args)
592
592
593 def assert_inode_not_equal(self, a, b):
593 def assert_inode_not_equal(self, a, b):
594 nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino,
594 nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino,
595 "%r and %r do reference the same indoes" %(a, b))
595 "%r and %r do reference the same indoes" %(a, b))
596
596
597 def assert_inode_equal(self, a, b):
597 def assert_inode_equal(self, a, b):
598 nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino,
598 nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino,
599 "%r and %r do not reference the same indoes" %(a, b))
599 "%r and %r do not reference the same indoes" %(a, b))
600
600
601 def assert_content_equal(self, a, b):
601 def assert_content_equal(self, a, b):
602 with open(a) as a_f:
602 with open(a) as a_f:
603 with open(b) as b_f:
603 with open(b) as b_f:
604 nt.assert_equals(a_f.read(), b_f.read())
604 nt.assert_equals(a_f.read(), b_f.read())
605
605
606 @skip_win32
606 @skip_win32
607 def test_link_successful(self):
607 def test_link_successful(self):
608 dst = self.dst("target")
608 dst = self.dst("target")
609 path.link_or_copy(self.src, dst)
609 path.link_or_copy(self.src, dst)
610 self.assert_inode_equal(self.src, dst)
610 self.assert_inode_equal(self.src, dst)
611
611
612 @skip_win32
612 @skip_win32
613 def test_link_into_dir(self):
613 def test_link_into_dir(self):
614 dst = self.dst("some_dir")
614 dst = self.dst("some_dir")
615 os.mkdir(dst)
615 os.mkdir(dst)
616 path.link_or_copy(self.src, dst)
616 path.link_or_copy(self.src, dst)
617 expected_dst = self.dst("some_dir", os.path.basename(self.src))
617 expected_dst = self.dst("some_dir", os.path.basename(self.src))
618 self.assert_inode_equal(self.src, expected_dst)
618 self.assert_inode_equal(self.src, expected_dst)
619
619
620 @skip_win32
620 @skip_win32
621 def test_target_exists(self):
621 def test_target_exists(self):
622 dst = self.dst("target")
622 dst = self.dst("target")
623 open(dst, "w").close()
623 open(dst, "w").close()
624 path.link_or_copy(self.src, dst)
624 path.link_or_copy(self.src, dst)
625 self.assert_inode_equal(self.src, dst)
625 self.assert_inode_equal(self.src, dst)
626
626
627 @skip_win32
627 @skip_win32
628 def test_no_link(self):
628 def test_no_link(self):
629 real_link = os.link
629 real_link = os.link
630 try:
630 try:
631 del os.link
631 del os.link
632 dst = self.dst("target")
632 dst = self.dst("target")
633 path.link_or_copy(self.src, dst)
633 path.link_or_copy(self.src, dst)
634 self.assert_content_equal(self.src, dst)
634 self.assert_content_equal(self.src, dst)
635 self.assert_inode_not_equal(self.src, dst)
635 self.assert_inode_not_equal(self.src, dst)
636 finally:
636 finally:
637 os.link = real_link
637 os.link = real_link
638
638
639 @skip_if_not_win32
639 @skip_if_not_win32
640 def test_windows(self):
640 def test_windows(self):
641 dst = self.dst("target")
641 dst = self.dst("target")
642 path.link_or_copy(self.src, dst)
642 path.link_or_copy(self.src, dst)
643 self.assert_content_equal(self.src, dst)
643 self.assert_content_equal(self.src, dst)
General Comments 0
You need to be logged in to leave comments. Login now