Show More
The requested changes are too big and content was truncated. Show full diff
@@ -1,381 +1,382 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | An application for IPython. |
|
4 | 4 | |
|
5 | 5 | All top-level applications should use the classes in this module for |
|
6 | 6 | handling configuration and creating configurables. |
|
7 | 7 | |
|
8 | 8 | The job of an :class:`Application` is to create the master configuration |
|
9 | 9 | object and then create the configurable objects, passing the config to them. |
|
10 | 10 | |
|
11 | 11 | Authors: |
|
12 | 12 | |
|
13 | 13 | * Brian Granger |
|
14 | 14 | * Fernando Perez |
|
15 | 15 | * Min RK |
|
16 | 16 | |
|
17 | 17 | """ |
|
18 | 18 | |
|
19 | 19 | #----------------------------------------------------------------------------- |
|
20 | 20 | # Copyright (C) 2008 The IPython Development Team |
|
21 | 21 | # |
|
22 | 22 | # Distributed under the terms of the BSD License. The full license is in |
|
23 | 23 | # the file COPYING, distributed as part of this software. |
|
24 | 24 | #----------------------------------------------------------------------------- |
|
25 | 25 | |
|
26 | 26 | #----------------------------------------------------------------------------- |
|
27 | 27 | # Imports |
|
28 | 28 | #----------------------------------------------------------------------------- |
|
29 | 29 | |
|
30 | 30 | import atexit |
|
31 | 31 | import errno |
|
32 | 32 | import glob |
|
33 | 33 | import logging |
|
34 | 34 | import os |
|
35 | 35 | import shutil |
|
36 | 36 | import sys |
|
37 | 37 | |
|
38 | 38 | from IPython.config.application import Application, catch_config_error |
|
39 | 39 | from IPython.config.loader import ConfigFileNotFound |
|
40 | 40 | from IPython.core import release, crashhandler |
|
41 | 41 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
42 | 42 | from IPython.utils.path import get_ipython_dir, get_ipython_package_dir |
|
43 | from IPython.utils import py3compat | |
|
43 | 44 | from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance |
|
44 | 45 | |
|
45 | 46 | #----------------------------------------------------------------------------- |
|
46 | 47 | # Classes and functions |
|
47 | 48 | #----------------------------------------------------------------------------- |
|
48 | 49 | |
|
49 | 50 | |
|
50 | 51 | #----------------------------------------------------------------------------- |
|
51 | 52 | # Base Application Class |
|
52 | 53 | #----------------------------------------------------------------------------- |
|
53 | 54 | |
|
54 | 55 | # aliases and flags |
|
55 | 56 | |
|
56 | 57 | base_aliases = { |
|
57 | 58 | 'profile-dir' : 'ProfileDir.location', |
|
58 | 59 | 'profile' : 'BaseIPythonApplication.profile', |
|
59 | 60 | 'ipython-dir' : 'BaseIPythonApplication.ipython_dir', |
|
60 | 61 | 'log-level' : 'Application.log_level', |
|
61 | 62 | 'config' : 'BaseIPythonApplication.extra_config_file', |
|
62 | 63 | } |
|
63 | 64 | |
|
64 | 65 | base_flags = dict( |
|
65 | 66 | debug = ({'Application' : {'log_level' : logging.DEBUG}}, |
|
66 | 67 | "set log level to logging.DEBUG (maximize logging output)"), |
|
67 | 68 | quiet = ({'Application' : {'log_level' : logging.CRITICAL}}, |
|
68 | 69 | "set log level to logging.CRITICAL (minimize logging output)"), |
|
69 | 70 | init = ({'BaseIPythonApplication' : { |
|
70 | 71 | 'copy_config_files' : True, |
|
71 | 72 | 'auto_create' : True} |
|
72 | 73 | }, """Initialize profile with default config files. This is equivalent |
|
73 | 74 | to running `ipython profile create <profile>` prior to startup. |
|
74 | 75 | """) |
|
75 | 76 | ) |
|
76 | 77 | |
|
77 | 78 | |
|
78 | 79 | class BaseIPythonApplication(Application): |
|
79 | 80 | |
|
80 | 81 | name = Unicode(u'ipython') |
|
81 | 82 | description = Unicode(u'IPython: an enhanced interactive Python shell.') |
|
82 | 83 | version = Unicode(release.version) |
|
83 | 84 | |
|
84 | 85 | aliases = Dict(base_aliases) |
|
85 | 86 | flags = Dict(base_flags) |
|
86 | 87 | classes = List([ProfileDir]) |
|
87 | 88 | |
|
88 | 89 | # Track whether the config_file has changed, |
|
89 | 90 | # because some logic happens only if we aren't using the default. |
|
90 | 91 | config_file_specified = Set() |
|
91 | 92 | |
|
92 | 93 | config_file_name = Unicode() |
|
93 | 94 | def _config_file_name_default(self): |
|
94 | 95 | return self.name.replace('-','_') + u'_config.py' |
|
95 | 96 | def _config_file_name_changed(self, name, old, new): |
|
96 | 97 | if new != old: |
|
97 | 98 | self.config_file_specified.add(new) |
|
98 | 99 | |
|
99 | 100 | # The directory that contains IPython's builtin profiles. |
|
100 | 101 | builtin_profile_dir = Unicode( |
|
101 | 102 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') |
|
102 | 103 | ) |
|
103 | 104 | |
|
104 | 105 | config_file_paths = List(Unicode) |
|
105 | 106 | def _config_file_paths_default(self): |
|
106 |
return [ |
|
|
107 | return [py3compat.getcwd()] | |
|
107 | 108 | |
|
108 | 109 | extra_config_file = Unicode(config=True, |
|
109 | 110 | help="""Path to an extra config file to load. |
|
110 | 111 | |
|
111 | 112 | If specified, load this config file in addition to any other IPython config. |
|
112 | 113 | """) |
|
113 | 114 | def _extra_config_file_changed(self, name, old, new): |
|
114 | 115 | try: |
|
115 | 116 | self.config_files.remove(old) |
|
116 | 117 | except ValueError: |
|
117 | 118 | pass |
|
118 | 119 | self.config_file_specified.add(new) |
|
119 | 120 | self.config_files.append(new) |
|
120 | 121 | |
|
121 | 122 | profile = Unicode(u'default', config=True, |
|
122 | 123 | help="""The IPython profile to use.""" |
|
123 | 124 | ) |
|
124 | 125 | |
|
125 | 126 | def _profile_changed(self, name, old, new): |
|
126 | 127 | self.builtin_profile_dir = os.path.join( |
|
127 | 128 | get_ipython_package_dir(), u'config', u'profile', new |
|
128 | 129 | ) |
|
129 | 130 | |
|
130 | 131 | ipython_dir = Unicode(config=True, |
|
131 | 132 | help=""" |
|
132 | 133 | The name of the IPython directory. This directory is used for logging |
|
133 | 134 | configuration (through profiles), history storage, etc. The default |
|
134 | 135 | is usually $HOME/.ipython. This options can also be specified through |
|
135 | 136 | the environment variable IPYTHONDIR. |
|
136 | 137 | """ |
|
137 | 138 | ) |
|
138 | 139 | def _ipython_dir_default(self): |
|
139 | 140 | d = get_ipython_dir() |
|
140 | 141 | self._ipython_dir_changed('ipython_dir', d, d) |
|
141 | 142 | return d |
|
142 | 143 | |
|
143 | 144 | _in_init_profile_dir = False |
|
144 | 145 | profile_dir = Instance(ProfileDir) |
|
145 | 146 | def _profile_dir_default(self): |
|
146 | 147 | # avoid recursion |
|
147 | 148 | if self._in_init_profile_dir: |
|
148 | 149 | return |
|
149 | 150 | # profile_dir requested early, force initialization |
|
150 | 151 | self.init_profile_dir() |
|
151 | 152 | return self.profile_dir |
|
152 | 153 | |
|
153 | 154 | overwrite = Bool(False, config=True, |
|
154 | 155 | help="""Whether to overwrite existing config files when copying""") |
|
155 | 156 | auto_create = Bool(False, config=True, |
|
156 | 157 | help="""Whether to create profile dir if it doesn't exist""") |
|
157 | 158 | |
|
158 | 159 | config_files = List(Unicode) |
|
159 | 160 | def _config_files_default(self): |
|
160 | 161 | return [self.config_file_name] |
|
161 | 162 | |
|
162 | 163 | copy_config_files = Bool(False, config=True, |
|
163 | 164 | help="""Whether to install the default config files into the profile dir. |
|
164 | 165 | If a new profile is being created, and IPython contains config files for that |
|
165 | 166 | profile, then they will be staged into the new directory. Otherwise, |
|
166 | 167 | default config files will be automatically generated. |
|
167 | 168 | """) |
|
168 | 169 | |
|
169 | 170 | verbose_crash = Bool(False, config=True, |
|
170 | 171 | help="""Create a massive crash report when IPython encounters what may be an |
|
171 | 172 | internal error. The default is to append a short message to the |
|
172 | 173 | usual traceback""") |
|
173 | 174 | |
|
174 | 175 | # The class to use as the crash handler. |
|
175 | 176 | crash_handler_class = Type(crashhandler.CrashHandler) |
|
176 | 177 | |
|
177 | 178 | @catch_config_error |
|
178 | 179 | def __init__(self, **kwargs): |
|
179 | 180 | super(BaseIPythonApplication, self).__init__(**kwargs) |
|
180 | 181 | # ensure current working directory exists |
|
181 | 182 | try: |
|
182 |
directory = |
|
|
183 | directory = py3compat.getcwd() | |
|
183 | 184 | except: |
|
184 | 185 | # raise exception |
|
185 | 186 | self.log.error("Current working directory doesn't exist.") |
|
186 | 187 | raise |
|
187 | 188 | |
|
188 | 189 | #------------------------------------------------------------------------- |
|
189 | 190 | # Various stages of Application creation |
|
190 | 191 | #------------------------------------------------------------------------- |
|
191 | 192 | |
|
192 | 193 | def init_crash_handler(self): |
|
193 | 194 | """Create a crash handler, typically setting sys.excepthook to it.""" |
|
194 | 195 | self.crash_handler = self.crash_handler_class(self) |
|
195 | 196 | sys.excepthook = self.excepthook |
|
196 | 197 | def unset_crashhandler(): |
|
197 | 198 | sys.excepthook = sys.__excepthook__ |
|
198 | 199 | atexit.register(unset_crashhandler) |
|
199 | 200 | |
|
200 | 201 | def excepthook(self, etype, evalue, tb): |
|
201 | 202 | """this is sys.excepthook after init_crashhandler |
|
202 | 203 | |
|
203 | 204 | set self.verbose_crash=True to use our full crashhandler, instead of |
|
204 | 205 | a regular traceback with a short message (crash_handler_lite) |
|
205 | 206 | """ |
|
206 | 207 | |
|
207 | 208 | if self.verbose_crash: |
|
208 | 209 | return self.crash_handler(etype, evalue, tb) |
|
209 | 210 | else: |
|
210 | 211 | return crashhandler.crash_handler_lite(etype, evalue, tb) |
|
211 | 212 | |
|
212 | 213 | def _ipython_dir_changed(self, name, old, new): |
|
213 | 214 | if old in sys.path: |
|
214 | 215 | sys.path.remove(old) |
|
215 | 216 | sys.path.append(os.path.abspath(new)) |
|
216 | 217 | if not os.path.isdir(new): |
|
217 | 218 | os.makedirs(new, mode=0o777) |
|
218 | 219 | readme = os.path.join(new, 'README') |
|
219 | 220 | readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') |
|
220 | 221 | if not os.path.exists(readme) and os.path.exists(readme_src): |
|
221 | 222 | shutil.copy(readme_src, readme) |
|
222 | 223 | for d in ('extensions', 'nbextensions'): |
|
223 | 224 | path = os.path.join(new, d) |
|
224 | 225 | if not os.path.exists(path): |
|
225 | 226 | try: |
|
226 | 227 | os.mkdir(path) |
|
227 | 228 | except OSError as e: |
|
228 | 229 | if e.errno != errno.EEXIST: |
|
229 | 230 | self.log.error("couldn't create path %s: %s", path, e) |
|
230 | 231 | self.log.debug("IPYTHONDIR set to: %s" % new) |
|
231 | 232 | |
|
232 | 233 | def load_config_file(self, suppress_errors=True): |
|
233 | 234 | """Load the config file. |
|
234 | 235 | |
|
235 | 236 | By default, errors in loading config are handled, and a warning |
|
236 | 237 | printed on screen. For testing, the suppress_errors option is set |
|
237 | 238 | to False, so errors will make tests fail. |
|
238 | 239 | """ |
|
239 | 240 | self.log.debug("Searching path %s for config files", self.config_file_paths) |
|
240 | 241 | base_config = 'ipython_config.py' |
|
241 | 242 | self.log.debug("Attempting to load config file: %s" % |
|
242 | 243 | base_config) |
|
243 | 244 | try: |
|
244 | 245 | Application.load_config_file( |
|
245 | 246 | self, |
|
246 | 247 | base_config, |
|
247 | 248 | path=self.config_file_paths |
|
248 | 249 | ) |
|
249 | 250 | except ConfigFileNotFound: |
|
250 | 251 | # ignore errors loading parent |
|
251 | 252 | self.log.debug("Config file %s not found", base_config) |
|
252 | 253 | pass |
|
253 | 254 | |
|
254 | 255 | for config_file_name in self.config_files: |
|
255 | 256 | if not config_file_name or config_file_name == base_config: |
|
256 | 257 | continue |
|
257 | 258 | self.log.debug("Attempting to load config file: %s" % |
|
258 | 259 | self.config_file_name) |
|
259 | 260 | try: |
|
260 | 261 | Application.load_config_file( |
|
261 | 262 | self, |
|
262 | 263 | config_file_name, |
|
263 | 264 | path=self.config_file_paths |
|
264 | 265 | ) |
|
265 | 266 | except ConfigFileNotFound: |
|
266 | 267 | # Only warn if the default config file was NOT being used. |
|
267 | 268 | if config_file_name in self.config_file_specified: |
|
268 | 269 | msg = self.log.warn |
|
269 | 270 | else: |
|
270 | 271 | msg = self.log.debug |
|
271 | 272 | msg("Config file not found, skipping: %s", config_file_name) |
|
272 | 273 | except: |
|
273 | 274 | # For testing purposes. |
|
274 | 275 | if not suppress_errors: |
|
275 | 276 | raise |
|
276 | 277 | self.log.warn("Error loading config file: %s" % |
|
277 | 278 | self.config_file_name, exc_info=True) |
|
278 | 279 | |
|
279 | 280 | def init_profile_dir(self): |
|
280 | 281 | """initialize the profile dir""" |
|
281 | 282 | self._in_init_profile_dir = True |
|
282 | 283 | if self.profile_dir is not None: |
|
283 | 284 | # already ran |
|
284 | 285 | return |
|
285 | 286 | if 'ProfileDir.location' not in self.config: |
|
286 | 287 | # location not specified, find by profile name |
|
287 | 288 | try: |
|
288 | 289 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
289 | 290 | except ProfileDirError: |
|
290 | 291 | # not found, maybe create it (always create default profile) |
|
291 | 292 | if self.auto_create or self.profile == 'default': |
|
292 | 293 | try: |
|
293 | 294 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
294 | 295 | except ProfileDirError: |
|
295 | 296 | self.log.fatal("Could not create profile: %r"%self.profile) |
|
296 | 297 | self.exit(1) |
|
297 | 298 | else: |
|
298 | 299 | self.log.info("Created profile dir: %r"%p.location) |
|
299 | 300 | else: |
|
300 | 301 | self.log.fatal("Profile %r not found."%self.profile) |
|
301 | 302 | self.exit(1) |
|
302 | 303 | else: |
|
303 | 304 | self.log.info("Using existing profile dir: %r"%p.location) |
|
304 | 305 | else: |
|
305 | 306 | location = self.config.ProfileDir.location |
|
306 | 307 | # location is fully specified |
|
307 | 308 | try: |
|
308 | 309 | p = ProfileDir.find_profile_dir(location, self.config) |
|
309 | 310 | except ProfileDirError: |
|
310 | 311 | # not found, maybe create it |
|
311 | 312 | if self.auto_create: |
|
312 | 313 | try: |
|
313 | 314 | p = ProfileDir.create_profile_dir(location, self.config) |
|
314 | 315 | except ProfileDirError: |
|
315 | 316 | self.log.fatal("Could not create profile directory: %r"%location) |
|
316 | 317 | self.exit(1) |
|
317 | 318 | else: |
|
318 | 319 | self.log.info("Creating new profile dir: %r"%location) |
|
319 | 320 | else: |
|
320 | 321 | self.log.fatal("Profile directory %r not found."%location) |
|
321 | 322 | self.exit(1) |
|
322 | 323 | else: |
|
323 | 324 | self.log.info("Using existing profile dir: %r"%location) |
|
324 | 325 | |
|
325 | 326 | self.profile_dir = p |
|
326 | 327 | self.config_file_paths.append(p.location) |
|
327 | 328 | self._in_init_profile_dir = False |
|
328 | 329 | |
|
329 | 330 | def init_config_files(self): |
|
330 | 331 | """[optionally] copy default config files into profile dir.""" |
|
331 | 332 | # copy config files |
|
332 | 333 | path = self.builtin_profile_dir |
|
333 | 334 | if self.copy_config_files: |
|
334 | 335 | src = self.profile |
|
335 | 336 | |
|
336 | 337 | cfg = self.config_file_name |
|
337 | 338 | if path and os.path.exists(os.path.join(path, cfg)): |
|
338 | 339 | self.log.warn("Staging %r from %s into %r [overwrite=%s]"%( |
|
339 | 340 | cfg, src, self.profile_dir.location, self.overwrite) |
|
340 | 341 | ) |
|
341 | 342 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) |
|
342 | 343 | else: |
|
343 | 344 | self.stage_default_config_file() |
|
344 | 345 | else: |
|
345 | 346 | # Still stage *bundled* config files, but not generated ones |
|
346 | 347 | # This is necessary for `ipython profile=sympy` to load the profile |
|
347 | 348 | # on the first go |
|
348 | 349 | files = glob.glob(os.path.join(path, '*.py')) |
|
349 | 350 | for fullpath in files: |
|
350 | 351 | cfg = os.path.basename(fullpath) |
|
351 | 352 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): |
|
352 | 353 | # file was copied |
|
353 | 354 | self.log.warn("Staging bundled %s from %s into %r"%( |
|
354 | 355 | cfg, self.profile, self.profile_dir.location) |
|
355 | 356 | ) |
|
356 | 357 | |
|
357 | 358 | |
|
358 | 359 | def stage_default_config_file(self): |
|
359 | 360 | """auto generate default config file, and stage it into the profile.""" |
|
360 | 361 | s = self.generate_config_file() |
|
361 | 362 | fname = os.path.join(self.profile_dir.location, self.config_file_name) |
|
362 | 363 | if self.overwrite or not os.path.exists(fname): |
|
363 | 364 | self.log.warn("Generating default config file: %r"%(fname)) |
|
364 | 365 | with open(fname, 'w') as f: |
|
365 | 366 | f.write(s) |
|
366 | 367 | |
|
367 | 368 | @catch_config_error |
|
368 | 369 | def initialize(self, argv=None): |
|
369 | 370 | # don't hook up crash handler before parsing command-line |
|
370 | 371 | self.parse_command_line(argv) |
|
371 | 372 | self.init_crash_handler() |
|
372 | 373 | if self.subapp is not None: |
|
373 | 374 | # stop here if subapp is taking over |
|
374 | 375 | return |
|
375 | 376 | cl_config = self.config |
|
376 | 377 | self.init_profile_dir() |
|
377 | 378 | self.init_config_files() |
|
378 | 379 | self.load_config_file() |
|
379 | 380 | # enforce cl-opts override configfile opts: |
|
380 | 381 | self.update_config(cl_config) |
|
381 | 382 |
@@ -1,216 +1,216 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """sys.excepthook for IPython itself, leaves a detailed report on disk. |
|
3 | 3 | |
|
4 | 4 | Authors: |
|
5 | 5 | |
|
6 | 6 | * Fernando Perez |
|
7 | 7 | * Brian E. Granger |
|
8 | 8 | """ |
|
9 | 9 | |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> |
|
12 | 12 | # Copyright (C) 2008-2011 The IPython Development Team |
|
13 | 13 | # |
|
14 | 14 | # Distributed under the terms of the BSD License. The full license is in |
|
15 | 15 | # the file COPYING, distributed as part of this software. |
|
16 | 16 | #----------------------------------------------------------------------------- |
|
17 | 17 | |
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | 19 | # Imports |
|
20 | 20 | #----------------------------------------------------------------------------- |
|
21 | 21 | from __future__ import print_function |
|
22 | 22 | |
|
23 | 23 | import os |
|
24 | 24 | import sys |
|
25 | 25 | import traceback |
|
26 | 26 | from pprint import pformat |
|
27 | 27 | |
|
28 | 28 | from IPython.core import ultratb |
|
29 | 29 | from IPython.core.release import author_email |
|
30 | 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 | 34 | # Code |
|
35 | 35 | #----------------------------------------------------------------------------- |
|
36 | 36 | |
|
37 | 37 | # Template for the user message. |
|
38 | 38 | _default_message_template = """\ |
|
39 | 39 | Oops, {app_name} crashed. We do our best to make it stable, but... |
|
40 | 40 | |
|
41 | 41 | A crash report was automatically generated with the following information: |
|
42 | 42 | - A verbatim copy of the crash traceback. |
|
43 | 43 | - A copy of your input history during this session. |
|
44 | 44 | - Data on your current {app_name} configuration. |
|
45 | 45 | |
|
46 | 46 | It was left in the file named: |
|
47 | 47 | \t'{crash_report_fname}' |
|
48 | 48 | If you can email this file to the developers, the information in it will help |
|
49 | 49 | them in understanding and correcting the problem. |
|
50 | 50 | |
|
51 | 51 | You can mail it to: {contact_name} at {contact_email} |
|
52 | 52 | with the subject '{app_name} Crash Report'. |
|
53 | 53 | |
|
54 | 54 | If you want to do it now, the following command will work (under Unix): |
|
55 | 55 | mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} |
|
56 | 56 | |
|
57 | 57 | To ensure accurate tracking of this issue, please file a report about it at: |
|
58 | 58 | {bug_tracker} |
|
59 | 59 | """ |
|
60 | 60 | |
|
61 | 61 | _lite_message_template = """ |
|
62 | 62 | If you suspect this is an IPython bug, please report it at: |
|
63 | 63 | https://github.com/ipython/ipython/issues |
|
64 | 64 | or send an email to the mailing list at {email} |
|
65 | 65 | |
|
66 | 66 | You can print a more detailed traceback right now with "%tb", or use "%debug" |
|
67 | 67 | to interactively debug it. |
|
68 | 68 | |
|
69 | 69 | Extra-detailed tracebacks for bug-reporting purposes can be enabled via: |
|
70 | 70 | {config}Application.verbose_crash=True |
|
71 | 71 | """ |
|
72 | 72 | |
|
73 | 73 | |
|
74 | 74 | class CrashHandler(object): |
|
75 | 75 | """Customizable crash handlers for IPython applications. |
|
76 | 76 | |
|
77 | 77 | Instances of this class provide a :meth:`__call__` method which can be |
|
78 | 78 | used as a ``sys.excepthook``. The :meth:`__call__` signature is:: |
|
79 | 79 | |
|
80 | 80 | def __call__(self, etype, evalue, etb) |
|
81 | 81 | """ |
|
82 | 82 | |
|
83 | 83 | message_template = _default_message_template |
|
84 | 84 | section_sep = '\n\n'+'*'*75+'\n\n' |
|
85 | 85 | |
|
86 | 86 | def __init__(self, app, contact_name=None, contact_email=None, |
|
87 | 87 | bug_tracker=None, show_crash_traceback=True, call_pdb=False): |
|
88 | 88 | """Create a new crash handler |
|
89 | 89 | |
|
90 | 90 | Parameters |
|
91 | 91 | ---------- |
|
92 | 92 | app : Application |
|
93 | 93 | A running :class:`Application` instance, which will be queried at |
|
94 | 94 | crash time for internal information. |
|
95 | 95 | |
|
96 | 96 | contact_name : str |
|
97 | 97 | A string with the name of the person to contact. |
|
98 | 98 | |
|
99 | 99 | contact_email : str |
|
100 | 100 | A string with the email address of the contact. |
|
101 | 101 | |
|
102 | 102 | bug_tracker : str |
|
103 | 103 | A string with the URL for your project's bug tracker. |
|
104 | 104 | |
|
105 | 105 | show_crash_traceback : bool |
|
106 | 106 | If false, don't print the crash traceback on stderr, only generate |
|
107 | 107 | the on-disk report |
|
108 | 108 | |
|
109 | 109 | Non-argument instance attributes: |
|
110 | 110 | |
|
111 | 111 | These instances contain some non-argument attributes which allow for |
|
112 | 112 | further customization of the crash handler's behavior. Please see the |
|
113 | 113 | source for further details. |
|
114 | 114 | """ |
|
115 | 115 | self.crash_report_fname = "Crash_report_%s.txt" % app.name |
|
116 | 116 | self.app = app |
|
117 | 117 | self.call_pdb = call_pdb |
|
118 | 118 | #self.call_pdb = True # dbg |
|
119 | 119 | self.show_crash_traceback = show_crash_traceback |
|
120 | 120 | self.info = dict(app_name = app.name, |
|
121 | 121 | contact_name = contact_name, |
|
122 | 122 | contact_email = contact_email, |
|
123 | 123 | bug_tracker = bug_tracker, |
|
124 | 124 | crash_report_fname = self.crash_report_fname) |
|
125 | 125 | |
|
126 | 126 | |
|
127 | 127 | def __call__(self, etype, evalue, etb): |
|
128 | 128 | """Handle an exception, call for compatible with sys.excepthook""" |
|
129 | 129 | |
|
130 | 130 | # do not allow the crash handler to be called twice without reinstalling it |
|
131 | 131 | # this prevents unlikely errors in the crash handling from entering an |
|
132 | 132 | # infinite loop. |
|
133 | 133 | sys.excepthook = sys.__excepthook__ |
|
134 | 134 | |
|
135 | 135 | # Report tracebacks shouldn't use color in general (safer for users) |
|
136 | 136 | color_scheme = 'NoColor' |
|
137 | 137 | |
|
138 | 138 | # Use this ONLY for developer debugging (keep commented out for release) |
|
139 | 139 | #color_scheme = 'Linux' # dbg |
|
140 | 140 | try: |
|
141 | 141 | rptdir = self.app.ipython_dir |
|
142 | 142 | except: |
|
143 |
rptdir = |
|
|
143 | rptdir = getcwd() | |
|
144 | 144 | if rptdir is None or not os.path.isdir(rptdir): |
|
145 |
rptdir = |
|
|
145 | rptdir = getcwd() | |
|
146 | 146 | report_name = os.path.join(rptdir,self.crash_report_fname) |
|
147 | 147 | # write the report filename into the instance dict so it can get |
|
148 | 148 | # properly expanded out in the user message template |
|
149 | 149 | self.crash_report_fname = report_name |
|
150 | 150 | self.info['crash_report_fname'] = report_name |
|
151 | 151 | TBhandler = ultratb.VerboseTB( |
|
152 | 152 | color_scheme=color_scheme, |
|
153 | 153 | long_header=1, |
|
154 | 154 | call_pdb=self.call_pdb, |
|
155 | 155 | ) |
|
156 | 156 | if self.call_pdb: |
|
157 | 157 | TBhandler(etype,evalue,etb) |
|
158 | 158 | return |
|
159 | 159 | else: |
|
160 | 160 | traceback = TBhandler.text(etype,evalue,etb,context=31) |
|
161 | 161 | |
|
162 | 162 | # print traceback to screen |
|
163 | 163 | if self.show_crash_traceback: |
|
164 | 164 | print(traceback, file=sys.stderr) |
|
165 | 165 | |
|
166 | 166 | # and generate a complete report on disk |
|
167 | 167 | try: |
|
168 | 168 | report = open(report_name,'w') |
|
169 | 169 | except: |
|
170 | 170 | print('Could not create crash report on disk.', file=sys.stderr) |
|
171 | 171 | return |
|
172 | 172 | |
|
173 | 173 | # Inform user on stderr of what happened |
|
174 | 174 | print('\n'+'*'*70+'\n', file=sys.stderr) |
|
175 | 175 | print(self.message_template.format(**self.info), file=sys.stderr) |
|
176 | 176 | |
|
177 | 177 | # Construct report on disk |
|
178 | 178 | report.write(self.make_report(traceback)) |
|
179 | 179 | report.close() |
|
180 | 180 | input("Hit <Enter> to quit (your terminal may close):") |
|
181 | 181 | |
|
182 | 182 | def make_report(self,traceback): |
|
183 | 183 | """Return a string containing a crash report.""" |
|
184 | 184 | |
|
185 | 185 | sec_sep = self.section_sep |
|
186 | 186 | |
|
187 | 187 | report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] |
|
188 | 188 | rpt_add = report.append |
|
189 | 189 | rpt_add(sys_info()) |
|
190 | 190 | |
|
191 | 191 | try: |
|
192 | 192 | config = pformat(self.app.config) |
|
193 | 193 | rpt_add(sec_sep) |
|
194 | 194 | rpt_add('Application name: %s\n\n' % self.app_name) |
|
195 | 195 | rpt_add('Current user configuration structure:\n\n') |
|
196 | 196 | rpt_add(config) |
|
197 | 197 | except: |
|
198 | 198 | pass |
|
199 | 199 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) |
|
200 | 200 | |
|
201 | 201 | return ''.join(report) |
|
202 | 202 | |
|
203 | 203 | |
|
204 | 204 | def crash_handler_lite(etype, evalue, tb): |
|
205 | 205 | """a light excepthook, adding a small message to the usual traceback""" |
|
206 | 206 | traceback.print_exception(etype, evalue, tb) |
|
207 | 207 | |
|
208 | 208 | from IPython.core.interactiveshell import InteractiveShell |
|
209 | 209 | if InteractiveShell.initialized(): |
|
210 | 210 | # we are in a Shell environment, give %magic example |
|
211 | 211 | config = "%config " |
|
212 | 212 | else: |
|
213 | 213 | # we are not in a shell, show generic config |
|
214 | 214 | config = "c." |
|
215 | 215 | print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr) |
|
216 | 216 |
@@ -1,807 +1,808 b'' | |||
|
1 | 1 | """ History related magics and functionality """ |
|
2 | 2 | #----------------------------------------------------------------------------- |
|
3 | 3 | # Copyright (C) 2010-2011 The IPython Development Team. |
|
4 | 4 | # |
|
5 | 5 | # Distributed under the terms of the BSD License. |
|
6 | 6 | # |
|
7 | 7 | # The full license is in the file COPYING.txt, distributed with this software. |
|
8 | 8 | #----------------------------------------------------------------------------- |
|
9 | 9 | |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | # Imports |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | from __future__ import print_function |
|
14 | 14 | |
|
15 | 15 | # Stdlib imports |
|
16 | 16 | import atexit |
|
17 | 17 | import datetime |
|
18 | 18 | import os |
|
19 | 19 | import re |
|
20 | 20 | try: |
|
21 | 21 | import sqlite3 |
|
22 | 22 | except ImportError: |
|
23 | 23 | try: |
|
24 | 24 | from pysqlite2 import dbapi2 as sqlite3 |
|
25 | 25 | except ImportError: |
|
26 | 26 | sqlite3 = None |
|
27 | 27 | import threading |
|
28 | 28 | |
|
29 | 29 | # Our own packages |
|
30 | 30 | from IPython.config.configurable import Configurable |
|
31 | 31 | from IPython.external.decorator import decorator |
|
32 | 32 | from IPython.utils.path import locate_profile |
|
33 | from IPython.utils import py3compat | |
|
33 | 34 | from IPython.utils.traitlets import ( |
|
34 | 35 | Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError, |
|
35 | 36 | ) |
|
36 | 37 | from IPython.utils.warn import warn |
|
37 | 38 | |
|
38 | 39 | #----------------------------------------------------------------------------- |
|
39 | 40 | # Classes and functions |
|
40 | 41 | #----------------------------------------------------------------------------- |
|
41 | 42 | |
|
42 | 43 | class DummyDB(object): |
|
43 | 44 | """Dummy DB that will act as a black hole for history. |
|
44 | 45 | |
|
45 | 46 | Only used in the absence of sqlite""" |
|
46 | 47 | def execute(*args, **kwargs): |
|
47 | 48 | return [] |
|
48 | 49 | |
|
49 | 50 | def commit(self, *args, **kwargs): |
|
50 | 51 | pass |
|
51 | 52 | |
|
52 | 53 | def __enter__(self, *args, **kwargs): |
|
53 | 54 | pass |
|
54 | 55 | |
|
55 | 56 | def __exit__(self, *args, **kwargs): |
|
56 | 57 | pass |
|
57 | 58 | |
|
58 | 59 | |
|
59 | 60 | @decorator |
|
60 | 61 | def needs_sqlite(f, self, *a, **kw): |
|
61 | 62 | """return an empty list in the absence of sqlite""" |
|
62 | 63 | if sqlite3 is None or not self.enabled: |
|
63 | 64 | return [] |
|
64 | 65 | else: |
|
65 | 66 | return f(self, *a, **kw) |
|
66 | 67 | |
|
67 | 68 | |
|
68 | 69 | if sqlite3 is not None: |
|
69 | 70 | DatabaseError = sqlite3.DatabaseError |
|
70 | 71 | else: |
|
71 | 72 | class DatabaseError(Exception): |
|
72 | 73 | "Dummy exception when sqlite could not be imported. Should never occur." |
|
73 | 74 | |
|
74 | 75 | @decorator |
|
75 | 76 | def catch_corrupt_db(f, self, *a, **kw): |
|
76 | 77 | """A decorator which wraps HistoryAccessor method calls to catch errors from |
|
77 | 78 | a corrupt SQLite database, move the old database out of the way, and create |
|
78 | 79 | a new one. |
|
79 | 80 | """ |
|
80 | 81 | try: |
|
81 | 82 | return f(self, *a, **kw) |
|
82 | 83 | except DatabaseError: |
|
83 | 84 | if os.path.isfile(self.hist_file): |
|
84 | 85 | # Try to move the file out of the way |
|
85 | 86 | base,ext = os.path.splitext(self.hist_file) |
|
86 | 87 | newpath = base + '-corrupt' + ext |
|
87 | 88 | os.rename(self.hist_file, newpath) |
|
88 | 89 | self.init_db() |
|
89 | 90 | print("ERROR! History file wasn't a valid SQLite database.", |
|
90 | 91 | "It was moved to %s" % newpath, "and a new file created.") |
|
91 | 92 | return [] |
|
92 | 93 | |
|
93 | 94 | else: |
|
94 | 95 | # The hist_file is probably :memory: or something else. |
|
95 | 96 | raise |
|
96 | 97 | |
|
97 | 98 | |
|
98 | 99 | |
|
99 | 100 | class HistoryAccessor(Configurable): |
|
100 | 101 | """Access the history database without adding to it. |
|
101 | 102 | |
|
102 | 103 | This is intended for use by standalone history tools. IPython shells use |
|
103 | 104 | HistoryManager, below, which is a subclass of this.""" |
|
104 | 105 | |
|
105 | 106 | # String holding the path to the history file |
|
106 | 107 | hist_file = Unicode(config=True, |
|
107 | 108 | help="""Path to file to use for SQLite history database. |
|
108 | 109 | |
|
109 | 110 | By default, IPython will put the history database in the IPython |
|
110 | 111 | profile directory. If you would rather share one history among |
|
111 | 112 | profiles, you can set this value in each, so that they are consistent. |
|
112 | 113 | |
|
113 | 114 | Due to an issue with fcntl, SQLite is known to misbehave on some NFS |
|
114 | 115 | mounts. If you see IPython hanging, try setting this to something on a |
|
115 | 116 | local disk, e.g:: |
|
116 | 117 | |
|
117 | 118 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite |
|
118 | 119 | |
|
119 | 120 | """) |
|
120 | 121 | |
|
121 | 122 | enabled = Bool(True, config=True, |
|
122 | 123 | help="""enable the SQLite history |
|
123 | 124 | |
|
124 | 125 | set enabled=False to disable the SQLite history, |
|
125 | 126 | in which case there will be no stored history, no SQLite connection, |
|
126 | 127 | and no background saving thread. This may be necessary in some |
|
127 | 128 | threaded environments where IPython is embedded. |
|
128 | 129 | """ |
|
129 | 130 | ) |
|
130 | 131 | |
|
131 | 132 | connection_options = Dict(config=True, |
|
132 | 133 | help="""Options for configuring the SQLite connection |
|
133 | 134 | |
|
134 | 135 | These options are passed as keyword args to sqlite3.connect |
|
135 | 136 | when establishing database conenctions. |
|
136 | 137 | """ |
|
137 | 138 | ) |
|
138 | 139 | |
|
139 | 140 | # The SQLite database |
|
140 | 141 | db = Any() |
|
141 | 142 | def _db_changed(self, name, old, new): |
|
142 | 143 | """validate the db, since it can be an Instance of two different types""" |
|
143 | 144 | connection_types = (DummyDB,) |
|
144 | 145 | if sqlite3 is not None: |
|
145 | 146 | connection_types = (DummyDB, sqlite3.Connection) |
|
146 | 147 | if not isinstance(new, connection_types): |
|
147 | 148 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ |
|
148 | 149 | (self.__class__.__name__, new) |
|
149 | 150 | raise TraitError(msg) |
|
150 | 151 | |
|
151 | 152 | def __init__(self, profile='default', hist_file=u'', **traits): |
|
152 | 153 | """Create a new history accessor. |
|
153 | 154 | |
|
154 | 155 | Parameters |
|
155 | 156 | ---------- |
|
156 | 157 | profile : str |
|
157 | 158 | The name of the profile from which to open history. |
|
158 | 159 | hist_file : str |
|
159 | 160 | Path to an SQLite history database stored by IPython. If specified, |
|
160 | 161 | hist_file overrides profile. |
|
161 | 162 | config : |
|
162 | 163 | Config object. hist_file can also be set through this. |
|
163 | 164 | """ |
|
164 | 165 | # We need a pointer back to the shell for various tasks. |
|
165 | 166 | super(HistoryAccessor, self).__init__(**traits) |
|
166 | 167 | # defer setting hist_file from kwarg until after init, |
|
167 | 168 | # otherwise the default kwarg value would clobber any value |
|
168 | 169 | # set by config |
|
169 | 170 | if hist_file: |
|
170 | 171 | self.hist_file = hist_file |
|
171 | 172 | |
|
172 | 173 | if self.hist_file == u'': |
|
173 | 174 | # No one has set the hist_file, yet. |
|
174 | 175 | self.hist_file = self._get_hist_file_name(profile) |
|
175 | 176 | |
|
176 | 177 | if sqlite3 is None and self.enabled: |
|
177 | 178 | warn("IPython History requires SQLite, your history will not be saved") |
|
178 | 179 | self.enabled = False |
|
179 | 180 | |
|
180 | 181 | self.init_db() |
|
181 | 182 | |
|
182 | 183 | def _get_hist_file_name(self, profile='default'): |
|
183 | 184 | """Find the history file for the given profile name. |
|
184 | 185 | |
|
185 | 186 | This is overridden by the HistoryManager subclass, to use the shell's |
|
186 | 187 | active profile. |
|
187 | 188 | |
|
188 | 189 | Parameters |
|
189 | 190 | ---------- |
|
190 | 191 | profile : str |
|
191 | 192 | The name of a profile which has a history file. |
|
192 | 193 | """ |
|
193 | 194 | return os.path.join(locate_profile(profile), 'history.sqlite') |
|
194 | 195 | |
|
195 | 196 | @catch_corrupt_db |
|
196 | 197 | def init_db(self): |
|
197 | 198 | """Connect to the database, and create tables if necessary.""" |
|
198 | 199 | if not self.enabled: |
|
199 | 200 | self.db = DummyDB() |
|
200 | 201 | return |
|
201 | 202 | |
|
202 | 203 | # use detect_types so that timestamps return datetime objects |
|
203 | 204 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) |
|
204 | 205 | kwargs.update(self.connection_options) |
|
205 | 206 | self.db = sqlite3.connect(self.hist_file, **kwargs) |
|
206 | 207 | self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer |
|
207 | 208 | primary key autoincrement, start timestamp, |
|
208 | 209 | end timestamp, num_cmds integer, remark text)""") |
|
209 | 210 | self.db.execute("""CREATE TABLE IF NOT EXISTS history |
|
210 | 211 | (session integer, line integer, source text, source_raw text, |
|
211 | 212 | PRIMARY KEY (session, line))""") |
|
212 | 213 | # Output history is optional, but ensure the table's there so it can be |
|
213 | 214 | # enabled later. |
|
214 | 215 | self.db.execute("""CREATE TABLE IF NOT EXISTS output_history |
|
215 | 216 | (session integer, line integer, output text, |
|
216 | 217 | PRIMARY KEY (session, line))""") |
|
217 | 218 | self.db.commit() |
|
218 | 219 | |
|
219 | 220 | def writeout_cache(self): |
|
220 | 221 | """Overridden by HistoryManager to dump the cache before certain |
|
221 | 222 | database lookups.""" |
|
222 | 223 | pass |
|
223 | 224 | |
|
224 | 225 | ## ------------------------------- |
|
225 | 226 | ## Methods for retrieving history: |
|
226 | 227 | ## ------------------------------- |
|
227 | 228 | def _run_sql(self, sql, params, raw=True, output=False): |
|
228 | 229 | """Prepares and runs an SQL query for the history database. |
|
229 | 230 | |
|
230 | 231 | Parameters |
|
231 | 232 | ---------- |
|
232 | 233 | sql : str |
|
233 | 234 | Any filtering expressions to go after SELECT ... FROM ... |
|
234 | 235 | params : tuple |
|
235 | 236 | Parameters passed to the SQL query (to replace "?") |
|
236 | 237 | raw, output : bool |
|
237 | 238 | See :meth:`get_range` |
|
238 | 239 | |
|
239 | 240 | Returns |
|
240 | 241 | ------- |
|
241 | 242 | Tuples as :meth:`get_range` |
|
242 | 243 | """ |
|
243 | 244 | toget = 'source_raw' if raw else 'source' |
|
244 | 245 | sqlfrom = "history" |
|
245 | 246 | if output: |
|
246 | 247 | sqlfrom = "history LEFT JOIN output_history USING (session, line)" |
|
247 | 248 | toget = "history.%s, output_history.output" % toget |
|
248 | 249 | cur = self.db.execute("SELECT session, line, %s FROM %s " %\ |
|
249 | 250 | (toget, sqlfrom) + sql, params) |
|
250 | 251 | if output: # Regroup into 3-tuples, and parse JSON |
|
251 | 252 | return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) |
|
252 | 253 | return cur |
|
253 | 254 | |
|
254 | 255 | @needs_sqlite |
|
255 | 256 | @catch_corrupt_db |
|
256 | 257 | def get_session_info(self, session=0): |
|
257 | 258 | """get info about a session |
|
258 | 259 | |
|
259 | 260 | Parameters |
|
260 | 261 | ---------- |
|
261 | 262 | |
|
262 | 263 | session : int |
|
263 | 264 | Session number to retrieve. The current session is 0, and negative |
|
264 | 265 | numbers count back from current session, so -1 is previous session. |
|
265 | 266 | |
|
266 | 267 | Returns |
|
267 | 268 | ------- |
|
268 | 269 | |
|
269 | 270 | (session_id [int], start [datetime], end [datetime], num_cmds [int], |
|
270 | 271 | remark [unicode]) |
|
271 | 272 | |
|
272 | 273 | Sessions that are running or did not exit cleanly will have `end=None` |
|
273 | 274 | and `num_cmds=None`. |
|
274 | 275 | |
|
275 | 276 | """ |
|
276 | 277 | |
|
277 | 278 | if session <= 0: |
|
278 | 279 | session += self.session_number |
|
279 | 280 | |
|
280 | 281 | query = "SELECT * from sessions where session == ?" |
|
281 | 282 | return self.db.execute(query, (session,)).fetchone() |
|
282 | 283 | |
|
283 | 284 | @catch_corrupt_db |
|
284 | 285 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): |
|
285 | 286 | """Get the last n lines from the history database. |
|
286 | 287 | |
|
287 | 288 | Parameters |
|
288 | 289 | ---------- |
|
289 | 290 | n : int |
|
290 | 291 | The number of lines to get |
|
291 | 292 | raw, output : bool |
|
292 | 293 | See :meth:`get_range` |
|
293 | 294 | include_latest : bool |
|
294 | 295 | If False (default), n+1 lines are fetched, and the latest one |
|
295 | 296 | is discarded. This is intended to be used where the function |
|
296 | 297 | is called by a user command, which it should not return. |
|
297 | 298 | |
|
298 | 299 | Returns |
|
299 | 300 | ------- |
|
300 | 301 | Tuples as :meth:`get_range` |
|
301 | 302 | """ |
|
302 | 303 | self.writeout_cache() |
|
303 | 304 | if not include_latest: |
|
304 | 305 | n += 1 |
|
305 | 306 | cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?", |
|
306 | 307 | (n,), raw=raw, output=output) |
|
307 | 308 | if not include_latest: |
|
308 | 309 | return reversed(list(cur)[1:]) |
|
309 | 310 | return reversed(list(cur)) |
|
310 | 311 | |
|
311 | 312 | @catch_corrupt_db |
|
312 | 313 | def search(self, pattern="*", raw=True, search_raw=True, |
|
313 | 314 | output=False, n=None, unique=False): |
|
314 | 315 | """Search the database using unix glob-style matching (wildcards |
|
315 | 316 | * and ?). |
|
316 | 317 | |
|
317 | 318 | Parameters |
|
318 | 319 | ---------- |
|
319 | 320 | pattern : str |
|
320 | 321 | The wildcarded pattern to match when searching |
|
321 | 322 | search_raw : bool |
|
322 | 323 | If True, search the raw input, otherwise, the parsed input |
|
323 | 324 | raw, output : bool |
|
324 | 325 | See :meth:`get_range` |
|
325 | 326 | n : None or int |
|
326 | 327 | If an integer is given, it defines the limit of |
|
327 | 328 | returned entries. |
|
328 | 329 | unique : bool |
|
329 | 330 | When it is true, return only unique entries. |
|
330 | 331 | |
|
331 | 332 | Returns |
|
332 | 333 | ------- |
|
333 | 334 | Tuples as :meth:`get_range` |
|
334 | 335 | """ |
|
335 | 336 | tosearch = "source_raw" if search_raw else "source" |
|
336 | 337 | if output: |
|
337 | 338 | tosearch = "history." + tosearch |
|
338 | 339 | self.writeout_cache() |
|
339 | 340 | sqlform = "WHERE %s GLOB ?" % tosearch |
|
340 | 341 | params = (pattern,) |
|
341 | 342 | if unique: |
|
342 | 343 | sqlform += ' GROUP BY {0}'.format(tosearch) |
|
343 | 344 | if n is not None: |
|
344 | 345 | sqlform += " ORDER BY session DESC, line DESC LIMIT ?" |
|
345 | 346 | params += (n,) |
|
346 | 347 | elif unique: |
|
347 | 348 | sqlform += " ORDER BY session, line" |
|
348 | 349 | cur = self._run_sql(sqlform, params, raw=raw, output=output) |
|
349 | 350 | if n is not None: |
|
350 | 351 | return reversed(list(cur)) |
|
351 | 352 | return cur |
|
352 | 353 | |
|
353 | 354 | @catch_corrupt_db |
|
354 | 355 | def get_range(self, session, start=1, stop=None, raw=True,output=False): |
|
355 | 356 | """Retrieve input by session. |
|
356 | 357 | |
|
357 | 358 | Parameters |
|
358 | 359 | ---------- |
|
359 | 360 | session : int |
|
360 | 361 | Session number to retrieve. |
|
361 | 362 | start : int |
|
362 | 363 | First line to retrieve. |
|
363 | 364 | stop : int |
|
364 | 365 | End of line range (excluded from output itself). If None, retrieve |
|
365 | 366 | to the end of the session. |
|
366 | 367 | raw : bool |
|
367 | 368 | If True, return untranslated input |
|
368 | 369 | output : bool |
|
369 | 370 | If True, attempt to include output. This will be 'real' Python |
|
370 | 371 | objects for the current session, or text reprs from previous |
|
371 | 372 | sessions if db_log_output was enabled at the time. Where no output |
|
372 | 373 | is found, None is used. |
|
373 | 374 | |
|
374 | 375 | Returns |
|
375 | 376 | ------- |
|
376 | 377 | An iterator over the desired lines. Each line is a 3-tuple, either |
|
377 | 378 | (session, line, input) if output is False, or |
|
378 | 379 | (session, line, (input, output)) if output is True. |
|
379 | 380 | """ |
|
380 | 381 | if stop: |
|
381 | 382 | lineclause = "line >= ? AND line < ?" |
|
382 | 383 | params = (session, start, stop) |
|
383 | 384 | else: |
|
384 | 385 | lineclause = "line>=?" |
|
385 | 386 | params = (session, start) |
|
386 | 387 | |
|
387 | 388 | return self._run_sql("WHERE session==? AND %s" % lineclause, |
|
388 | 389 | params, raw=raw, output=output) |
|
389 | 390 | |
|
390 | 391 | def get_range_by_str(self, rangestr, raw=True, output=False): |
|
391 | 392 | """Get lines of history from a string of ranges, as used by magic |
|
392 | 393 | commands %hist, %save, %macro, etc. |
|
393 | 394 | |
|
394 | 395 | Parameters |
|
395 | 396 | ---------- |
|
396 | 397 | rangestr : str |
|
397 | 398 | A string specifying ranges, e.g. "5 ~2/1-4". See |
|
398 | 399 | :func:`magic_history` for full details. |
|
399 | 400 | raw, output : bool |
|
400 | 401 | As :meth:`get_range` |
|
401 | 402 | |
|
402 | 403 | Returns |
|
403 | 404 | ------- |
|
404 | 405 | Tuples as :meth:`get_range` |
|
405 | 406 | """ |
|
406 | 407 | for sess, s, e in extract_hist_ranges(rangestr): |
|
407 | 408 | for line in self.get_range(sess, s, e, raw=raw, output=output): |
|
408 | 409 | yield line |
|
409 | 410 | |
|
410 | 411 | |
|
411 | 412 | class HistoryManager(HistoryAccessor): |
|
412 | 413 | """A class to organize all history-related functionality in one place. |
|
413 | 414 | """ |
|
414 | 415 | # Public interface |
|
415 | 416 | |
|
416 | 417 | # An instance of the IPython shell we are attached to |
|
417 | 418 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') |
|
418 | 419 | # Lists to hold processed and raw history. These start with a blank entry |
|
419 | 420 | # so that we can index them starting from 1 |
|
420 | 421 | input_hist_parsed = List([""]) |
|
421 | 422 | input_hist_raw = List([""]) |
|
422 | 423 | # A list of directories visited during session |
|
423 | 424 | dir_hist = List() |
|
424 | 425 | def _dir_hist_default(self): |
|
425 | 426 | try: |
|
426 |
return [ |
|
|
427 | return [py3compat.getcwd()] | |
|
427 | 428 | except OSError: |
|
428 | 429 | return [] |
|
429 | 430 | |
|
430 | 431 | # A dict of output history, keyed with ints from the shell's |
|
431 | 432 | # execution count. |
|
432 | 433 | output_hist = Dict() |
|
433 | 434 | # The text/plain repr of outputs. |
|
434 | 435 | output_hist_reprs = Dict() |
|
435 | 436 | |
|
436 | 437 | # The number of the current session in the history database |
|
437 | 438 | session_number = Integer() |
|
438 | 439 | # Should we log output to the database? (default no) |
|
439 | 440 | db_log_output = Bool(False, config=True) |
|
440 | 441 | # Write to database every x commands (higher values save disk access & power) |
|
441 | 442 | # Values of 1 or less effectively disable caching. |
|
442 | 443 | db_cache_size = Integer(0, config=True) |
|
443 | 444 | # The input and output caches |
|
444 | 445 | db_input_cache = List() |
|
445 | 446 | db_output_cache = List() |
|
446 | 447 | |
|
447 | 448 | # History saving in separate thread |
|
448 | 449 | save_thread = Instance('IPython.core.history.HistorySavingThread') |
|
449 | 450 | try: # Event is a function returning an instance of _Event... |
|
450 | 451 | save_flag = Instance(threading._Event) |
|
451 | 452 | except AttributeError: # ...until Python 3.3, when it's a class. |
|
452 | 453 | save_flag = Instance(threading.Event) |
|
453 | 454 | |
|
454 | 455 | # Private interface |
|
455 | 456 | # Variables used to store the three last inputs from the user. On each new |
|
456 | 457 | # history update, we populate the user's namespace with these, shifted as |
|
457 | 458 | # necessary. |
|
458 | 459 | _i00 = Unicode(u'') |
|
459 | 460 | _i = Unicode(u'') |
|
460 | 461 | _ii = Unicode(u'') |
|
461 | 462 | _iii = Unicode(u'') |
|
462 | 463 | |
|
463 | 464 | # A regex matching all forms of the exit command, so that we don't store |
|
464 | 465 | # them in the history (it's annoying to rewind the first entry and land on |
|
465 | 466 | # an exit call). |
|
466 | 467 | _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$") |
|
467 | 468 | |
|
468 | 469 | def __init__(self, shell=None, config=None, **traits): |
|
469 | 470 | """Create a new history manager associated with a shell instance. |
|
470 | 471 | """ |
|
471 | 472 | # We need a pointer back to the shell for various tasks. |
|
472 | 473 | super(HistoryManager, self).__init__(shell=shell, config=config, |
|
473 | 474 | **traits) |
|
474 | 475 | self.save_flag = threading.Event() |
|
475 | 476 | self.db_input_cache_lock = threading.Lock() |
|
476 | 477 | self.db_output_cache_lock = threading.Lock() |
|
477 | 478 | if self.enabled and self.hist_file != ':memory:': |
|
478 | 479 | self.save_thread = HistorySavingThread(self) |
|
479 | 480 | self.save_thread.start() |
|
480 | 481 | |
|
481 | 482 | self.new_session() |
|
482 | 483 | |
|
483 | 484 | def _get_hist_file_name(self, profile=None): |
|
484 | 485 | """Get default history file name based on the Shell's profile. |
|
485 | 486 | |
|
486 | 487 | The profile parameter is ignored, but must exist for compatibility with |
|
487 | 488 | the parent class.""" |
|
488 | 489 | profile_dir = self.shell.profile_dir.location |
|
489 | 490 | return os.path.join(profile_dir, 'history.sqlite') |
|
490 | 491 | |
|
491 | 492 | @needs_sqlite |
|
492 | 493 | def new_session(self, conn=None): |
|
493 | 494 | """Get a new session number.""" |
|
494 | 495 | if conn is None: |
|
495 | 496 | conn = self.db |
|
496 | 497 | |
|
497 | 498 | with conn: |
|
498 | 499 | cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, |
|
499 | 500 | NULL, "") """, (datetime.datetime.now(),)) |
|
500 | 501 | self.session_number = cur.lastrowid |
|
501 | 502 | |
|
502 | 503 | def end_session(self): |
|
503 | 504 | """Close the database session, filling in the end time and line count.""" |
|
504 | 505 | self.writeout_cache() |
|
505 | 506 | with self.db: |
|
506 | 507 | self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE |
|
507 | 508 | session==?""", (datetime.datetime.now(), |
|
508 | 509 | len(self.input_hist_parsed)-1, self.session_number)) |
|
509 | 510 | self.session_number = 0 |
|
510 | 511 | |
|
511 | 512 | def name_session(self, name): |
|
512 | 513 | """Give the current session a name in the history database.""" |
|
513 | 514 | with self.db: |
|
514 | 515 | self.db.execute("UPDATE sessions SET remark=? WHERE session==?", |
|
515 | 516 | (name, self.session_number)) |
|
516 | 517 | |
|
517 | 518 | def reset(self, new_session=True): |
|
518 | 519 | """Clear the session history, releasing all object references, and |
|
519 | 520 | optionally open a new session.""" |
|
520 | 521 | self.output_hist.clear() |
|
521 | 522 | # The directory history can't be completely empty |
|
522 |
self.dir_hist[:] = [ |
|
|
523 | self.dir_hist[:] = [py3compat.getcwd()] | |
|
523 | 524 | |
|
524 | 525 | if new_session: |
|
525 | 526 | if self.session_number: |
|
526 | 527 | self.end_session() |
|
527 | 528 | self.input_hist_parsed[:] = [""] |
|
528 | 529 | self.input_hist_raw[:] = [""] |
|
529 | 530 | self.new_session() |
|
530 | 531 | |
|
531 | 532 | # ------------------------------ |
|
532 | 533 | # Methods for retrieving history |
|
533 | 534 | # ------------------------------ |
|
534 | 535 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): |
|
535 | 536 | """Get input and output history from the current session. Called by |
|
536 | 537 | get_range, and takes similar parameters.""" |
|
537 | 538 | input_hist = self.input_hist_raw if raw else self.input_hist_parsed |
|
538 | 539 | |
|
539 | 540 | n = len(input_hist) |
|
540 | 541 | if start < 0: |
|
541 | 542 | start += n |
|
542 | 543 | if not stop or (stop > n): |
|
543 | 544 | stop = n |
|
544 | 545 | elif stop < 0: |
|
545 | 546 | stop += n |
|
546 | 547 | |
|
547 | 548 | for i in range(start, stop): |
|
548 | 549 | if output: |
|
549 | 550 | line = (input_hist[i], self.output_hist_reprs.get(i)) |
|
550 | 551 | else: |
|
551 | 552 | line = input_hist[i] |
|
552 | 553 | yield (0, i, line) |
|
553 | 554 | |
|
554 | 555 | def get_range(self, session=0, start=1, stop=None, raw=True,output=False): |
|
555 | 556 | """Retrieve input by session. |
|
556 | 557 | |
|
557 | 558 | Parameters |
|
558 | 559 | ---------- |
|
559 | 560 | session : int |
|
560 | 561 | Session number to retrieve. The current session is 0, and negative |
|
561 | 562 | numbers count back from current session, so -1 is previous session. |
|
562 | 563 | start : int |
|
563 | 564 | First line to retrieve. |
|
564 | 565 | stop : int |
|
565 | 566 | End of line range (excluded from output itself). If None, retrieve |
|
566 | 567 | to the end of the session. |
|
567 | 568 | raw : bool |
|
568 | 569 | If True, return untranslated input |
|
569 | 570 | output : bool |
|
570 | 571 | If True, attempt to include output. This will be 'real' Python |
|
571 | 572 | objects for the current session, or text reprs from previous |
|
572 | 573 | sessions if db_log_output was enabled at the time. Where no output |
|
573 | 574 | is found, None is used. |
|
574 | 575 | |
|
575 | 576 | Returns |
|
576 | 577 | ------- |
|
577 | 578 | An iterator over the desired lines. Each line is a 3-tuple, either |
|
578 | 579 | (session, line, input) if output is False, or |
|
579 | 580 | (session, line, (input, output)) if output is True. |
|
580 | 581 | """ |
|
581 | 582 | if session <= 0: |
|
582 | 583 | session += self.session_number |
|
583 | 584 | if session==self.session_number: # Current session |
|
584 | 585 | return self._get_range_session(start, stop, raw, output) |
|
585 | 586 | return super(HistoryManager, self).get_range(session, start, stop, raw, |
|
586 | 587 | output) |
|
587 | 588 | |
|
588 | 589 | ## ---------------------------- |
|
589 | 590 | ## Methods for storing history: |
|
590 | 591 | ## ---------------------------- |
|
591 | 592 | def store_inputs(self, line_num, source, source_raw=None): |
|
592 | 593 | """Store source and raw input in history and create input cache |
|
593 | 594 | variables _i*. |
|
594 | 595 | |
|
595 | 596 | Parameters |
|
596 | 597 | ---------- |
|
597 | 598 | line_num : int |
|
598 | 599 | The prompt number of this input. |
|
599 | 600 | |
|
600 | 601 | source : str |
|
601 | 602 | Python input. |
|
602 | 603 | |
|
603 | 604 | source_raw : str, optional |
|
604 | 605 | If given, this is the raw input without any IPython transformations |
|
605 | 606 | applied to it. If not given, ``source`` is used. |
|
606 | 607 | """ |
|
607 | 608 | if source_raw is None: |
|
608 | 609 | source_raw = source |
|
609 | 610 | source = source.rstrip('\n') |
|
610 | 611 | source_raw = source_raw.rstrip('\n') |
|
611 | 612 | |
|
612 | 613 | # do not store exit/quit commands |
|
613 | 614 | if self._exit_re.match(source_raw.strip()): |
|
614 | 615 | return |
|
615 | 616 | |
|
616 | 617 | self.input_hist_parsed.append(source) |
|
617 | 618 | self.input_hist_raw.append(source_raw) |
|
618 | 619 | |
|
619 | 620 | with self.db_input_cache_lock: |
|
620 | 621 | self.db_input_cache.append((line_num, source, source_raw)) |
|
621 | 622 | # Trigger to flush cache and write to DB. |
|
622 | 623 | if len(self.db_input_cache) >= self.db_cache_size: |
|
623 | 624 | self.save_flag.set() |
|
624 | 625 | |
|
625 | 626 | # update the auto _i variables |
|
626 | 627 | self._iii = self._ii |
|
627 | 628 | self._ii = self._i |
|
628 | 629 | self._i = self._i00 |
|
629 | 630 | self._i00 = source_raw |
|
630 | 631 | |
|
631 | 632 | # hackish access to user namespace to create _i1,_i2... dynamically |
|
632 | 633 | new_i = '_i%s' % line_num |
|
633 | 634 | to_main = {'_i': self._i, |
|
634 | 635 | '_ii': self._ii, |
|
635 | 636 | '_iii': self._iii, |
|
636 | 637 | new_i : self._i00 } |
|
637 | 638 | |
|
638 | 639 | if self.shell is not None: |
|
639 | 640 | self.shell.push(to_main, interactive=False) |
|
640 | 641 | |
|
641 | 642 | def store_output(self, line_num): |
|
642 | 643 | """If database output logging is enabled, this saves all the |
|
643 | 644 | outputs from the indicated prompt number to the database. It's |
|
644 | 645 | called by run_cell after code has been executed. |
|
645 | 646 | |
|
646 | 647 | Parameters |
|
647 | 648 | ---------- |
|
648 | 649 | line_num : int |
|
649 | 650 | The line number from which to save outputs |
|
650 | 651 | """ |
|
651 | 652 | if (not self.db_log_output) or (line_num not in self.output_hist_reprs): |
|
652 | 653 | return |
|
653 | 654 | output = self.output_hist_reprs[line_num] |
|
654 | 655 | |
|
655 | 656 | with self.db_output_cache_lock: |
|
656 | 657 | self.db_output_cache.append((line_num, output)) |
|
657 | 658 | if self.db_cache_size <= 1: |
|
658 | 659 | self.save_flag.set() |
|
659 | 660 | |
|
660 | 661 | def _writeout_input_cache(self, conn): |
|
661 | 662 | with conn: |
|
662 | 663 | for line in self.db_input_cache: |
|
663 | 664 | conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)", |
|
664 | 665 | (self.session_number,)+line) |
|
665 | 666 | |
|
666 | 667 | def _writeout_output_cache(self, conn): |
|
667 | 668 | with conn: |
|
668 | 669 | for line in self.db_output_cache: |
|
669 | 670 | conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", |
|
670 | 671 | (self.session_number,)+line) |
|
671 | 672 | |
|
672 | 673 | @needs_sqlite |
|
673 | 674 | def writeout_cache(self, conn=None): |
|
674 | 675 | """Write any entries in the cache to the database.""" |
|
675 | 676 | if conn is None: |
|
676 | 677 | conn = self.db |
|
677 | 678 | |
|
678 | 679 | with self.db_input_cache_lock: |
|
679 | 680 | try: |
|
680 | 681 | self._writeout_input_cache(conn) |
|
681 | 682 | except sqlite3.IntegrityError: |
|
682 | 683 | self.new_session(conn) |
|
683 | 684 | print("ERROR! Session/line number was not unique in", |
|
684 | 685 | "database. History logging moved to new session", |
|
685 | 686 | self.session_number) |
|
686 | 687 | try: |
|
687 | 688 | # Try writing to the new session. If this fails, don't |
|
688 | 689 | # recurse |
|
689 | 690 | self._writeout_input_cache(conn) |
|
690 | 691 | except sqlite3.IntegrityError: |
|
691 | 692 | pass |
|
692 | 693 | finally: |
|
693 | 694 | self.db_input_cache = [] |
|
694 | 695 | |
|
695 | 696 | with self.db_output_cache_lock: |
|
696 | 697 | try: |
|
697 | 698 | self._writeout_output_cache(conn) |
|
698 | 699 | except sqlite3.IntegrityError: |
|
699 | 700 | print("!! Session/line number for output was not unique", |
|
700 | 701 | "in database. Output will not be stored.") |
|
701 | 702 | finally: |
|
702 | 703 | self.db_output_cache = [] |
|
703 | 704 | |
|
704 | 705 | |
|
705 | 706 | class HistorySavingThread(threading.Thread): |
|
706 | 707 | """This thread takes care of writing history to the database, so that |
|
707 | 708 | the UI isn't held up while that happens. |
|
708 | 709 | |
|
709 | 710 | It waits for the HistoryManager's save_flag to be set, then writes out |
|
710 | 711 | the history cache. The main thread is responsible for setting the flag when |
|
711 | 712 | the cache size reaches a defined threshold.""" |
|
712 | 713 | daemon = True |
|
713 | 714 | stop_now = False |
|
714 | 715 | enabled = True |
|
715 | 716 | def __init__(self, history_manager): |
|
716 | 717 | super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread") |
|
717 | 718 | self.history_manager = history_manager |
|
718 | 719 | self.enabled = history_manager.enabled |
|
719 | 720 | atexit.register(self.stop) |
|
720 | 721 | |
|
721 | 722 | @needs_sqlite |
|
722 | 723 | def run(self): |
|
723 | 724 | # We need a separate db connection per thread: |
|
724 | 725 | try: |
|
725 | 726 | self.db = sqlite3.connect(self.history_manager.hist_file, |
|
726 | 727 | **self.history_manager.connection_options |
|
727 | 728 | ) |
|
728 | 729 | while True: |
|
729 | 730 | self.history_manager.save_flag.wait() |
|
730 | 731 | if self.stop_now: |
|
731 | 732 | return |
|
732 | 733 | self.history_manager.save_flag.clear() |
|
733 | 734 | self.history_manager.writeout_cache(self.db) |
|
734 | 735 | except Exception as e: |
|
735 | 736 | print(("The history saving thread hit an unexpected error (%s)." |
|
736 | 737 | "History will not be written to the database.") % repr(e)) |
|
737 | 738 | |
|
738 | 739 | def stop(self): |
|
739 | 740 | """This can be called from the main thread to safely stop this thread. |
|
740 | 741 | |
|
741 | 742 | Note that it does not attempt to write out remaining history before |
|
742 | 743 | exiting. That should be done by calling the HistoryManager's |
|
743 | 744 | end_session method.""" |
|
744 | 745 | self.stop_now = True |
|
745 | 746 | self.history_manager.save_flag.set() |
|
746 | 747 | self.join() |
|
747 | 748 | |
|
748 | 749 | |
|
749 | 750 | # To match, e.g. ~5/8-~2/3 |
|
750 | 751 | range_re = re.compile(r""" |
|
751 | 752 | ((?P<startsess>~?\d+)/)? |
|
752 | 753 | (?P<start>\d+)? |
|
753 | 754 | ((?P<sep>[\-:]) |
|
754 | 755 | ((?P<endsess>~?\d+)/)? |
|
755 | 756 | (?P<end>\d+))? |
|
756 | 757 | $""", re.VERBOSE) |
|
757 | 758 | |
|
758 | 759 | |
|
759 | 760 | def extract_hist_ranges(ranges_str): |
|
760 | 761 | """Turn a string of history ranges into 3-tuples of (session, start, stop). |
|
761 | 762 | |
|
762 | 763 | Examples |
|
763 | 764 | -------- |
|
764 | 765 | list(extract_input_ranges("~8/5-~7/4 2")) |
|
765 | 766 | [(-8, 5, None), (-7, 1, 4), (0, 2, 3)] |
|
766 | 767 | """ |
|
767 | 768 | for range_str in ranges_str.split(): |
|
768 | 769 | rmatch = range_re.match(range_str) |
|
769 | 770 | if not rmatch: |
|
770 | 771 | continue |
|
771 | 772 | start = rmatch.group("start") |
|
772 | 773 | if start: |
|
773 | 774 | start = int(start) |
|
774 | 775 | end = rmatch.group("end") |
|
775 | 776 | # If no end specified, get (a, a + 1) |
|
776 | 777 | end = int(end) if end else start + 1 |
|
777 | 778 | else: # start not specified |
|
778 | 779 | if not rmatch.group('startsess'): # no startsess |
|
779 | 780 | continue |
|
780 | 781 | start = 1 |
|
781 | 782 | end = None # provide the entire session hist |
|
782 | 783 | |
|
783 | 784 | if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3] |
|
784 | 785 | end += 1 |
|
785 | 786 | startsess = rmatch.group("startsess") or "0" |
|
786 | 787 | endsess = rmatch.group("endsess") or startsess |
|
787 | 788 | startsess = int(startsess.replace("~","-")) |
|
788 | 789 | endsess = int(endsess.replace("~","-")) |
|
789 | 790 | assert endsess >= startsess, "start session must be earlier than end session" |
|
790 | 791 | |
|
791 | 792 | if endsess == startsess: |
|
792 | 793 | yield (startsess, start, end) |
|
793 | 794 | continue |
|
794 | 795 | # Multiple sessions in one range: |
|
795 | 796 | yield (startsess, start, None) |
|
796 | 797 | for sess in range(startsess+1, endsess): |
|
797 | 798 | yield (sess, 1, None) |
|
798 | 799 | yield (endsess, 1, end) |
|
799 | 800 | |
|
800 | 801 | |
|
801 | 802 | def _format_lineno(session, line): |
|
802 | 803 | """Helper function to format line numbers properly.""" |
|
803 | 804 | if session == 0: |
|
804 | 805 | return str(line) |
|
805 | 806 | return "%s#%s" % (session, line) |
|
806 | 807 | |
|
807 | 808 |
|
1 | 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 | 1 | """Implementation of magic functions for interaction with the OS. |
|
2 | 2 | |
|
3 | 3 | Note: this module is named 'osm' instead of 'os' to avoid a collision with the |
|
4 | 4 | builtin. |
|
5 | 5 | """ |
|
6 | 6 | from __future__ import print_function |
|
7 | 7 | #----------------------------------------------------------------------------- |
|
8 | 8 | # Copyright (c) 2012 The IPython Development Team. |
|
9 | 9 | # |
|
10 | 10 | # Distributed under the terms of the Modified BSD License. |
|
11 | 11 | # |
|
12 | 12 | # The full license is in the file COPYING.txt, distributed with this software. |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | # Imports |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | 19 | # Stdlib |
|
20 | 20 | import io |
|
21 | 21 | import os |
|
22 | 22 | import re |
|
23 | 23 | import sys |
|
24 | 24 | from pprint import pformat |
|
25 | 25 | |
|
26 | 26 | # Our own packages |
|
27 | 27 | from IPython.core import magic_arguments |
|
28 | 28 | from IPython.core import oinspect |
|
29 | 29 | from IPython.core import page |
|
30 | 30 | from IPython.core.alias import AliasError, Alias |
|
31 | 31 | from IPython.core.error import UsageError |
|
32 | 32 | from IPython.core.magic import ( |
|
33 | 33 | Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic |
|
34 | 34 | ) |
|
35 | 35 | from IPython.testing.skipdoctest import skip_doctest |
|
36 | 36 | from IPython.utils.openpy import source_to_unicode |
|
37 | 37 | from IPython.utils.path import unquote_filename |
|
38 | 38 | from IPython.utils.process import abbrev_cwd |
|
39 | from IPython.utils import py3compat | |
|
39 | 40 | from IPython.utils.py3compat import unicode_type |
|
40 | 41 | from IPython.utils.terminal import set_term_title |
|
41 | 42 | |
|
42 | 43 | #----------------------------------------------------------------------------- |
|
43 | 44 | # Magic implementation classes |
|
44 | 45 | #----------------------------------------------------------------------------- |
|
45 | 46 | @magics_class |
|
46 | 47 | class OSMagics(Magics): |
|
47 | 48 | """Magics to interact with the underlying OS (shell-type functionality). |
|
48 | 49 | """ |
|
49 | 50 | |
|
50 | 51 | @skip_doctest |
|
51 | 52 | @line_magic |
|
52 | 53 | def alias(self, parameter_s=''): |
|
53 | 54 | """Define an alias for a system command. |
|
54 | 55 | |
|
55 | 56 | '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd' |
|
56 | 57 | |
|
57 | 58 | Then, typing 'alias_name params' will execute the system command 'cmd |
|
58 | 59 | params' (from your underlying operating system). |
|
59 | 60 | |
|
60 | 61 | Aliases have lower precedence than magic functions and Python normal |
|
61 | 62 | variables, so if 'foo' is both a Python variable and an alias, the |
|
62 | 63 | alias can not be executed until 'del foo' removes the Python variable. |
|
63 | 64 | |
|
64 | 65 | You can use the %l specifier in an alias definition to represent the |
|
65 | 66 | whole line when the alias is called. For example:: |
|
66 | 67 | |
|
67 | 68 | In [2]: alias bracket echo "Input in brackets: <%l>" |
|
68 | 69 | In [3]: bracket hello world |
|
69 | 70 | Input in brackets: <hello world> |
|
70 | 71 | |
|
71 | 72 | You can also define aliases with parameters using %s specifiers (one |
|
72 | 73 | per parameter):: |
|
73 | 74 | |
|
74 | 75 | In [1]: alias parts echo first %s second %s |
|
75 | 76 | In [2]: %parts A B |
|
76 | 77 | first A second B |
|
77 | 78 | In [3]: %parts A |
|
78 | 79 | Incorrect number of arguments: 2 expected. |
|
79 | 80 | parts is an alias to: 'echo first %s second %s' |
|
80 | 81 | |
|
81 | 82 | Note that %l and %s are mutually exclusive. You can only use one or |
|
82 | 83 | the other in your aliases. |
|
83 | 84 | |
|
84 | 85 | Aliases expand Python variables just like system calls using ! or !! |
|
85 | 86 | do: all expressions prefixed with '$' get expanded. For details of |
|
86 | 87 | the semantic rules, see PEP-215: |
|
87 | 88 | http://www.python.org/peps/pep-0215.html. This is the library used by |
|
88 | 89 | IPython for variable expansion. If you want to access a true shell |
|
89 | 90 | variable, an extra $ is necessary to prevent its expansion by |
|
90 | 91 | IPython:: |
|
91 | 92 | |
|
92 | 93 | In [6]: alias show echo |
|
93 | 94 | In [7]: PATH='A Python string' |
|
94 | 95 | In [8]: show $PATH |
|
95 | 96 | A Python string |
|
96 | 97 | In [9]: show $$PATH |
|
97 | 98 | /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... |
|
98 | 99 | |
|
99 | 100 | You can use the alias facility to acess all of $PATH. See the %rehash |
|
100 | 101 | and %rehashx functions, which automatically create aliases for the |
|
101 | 102 | contents of your $PATH. |
|
102 | 103 | |
|
103 | 104 | If called with no parameters, %alias prints the current alias table.""" |
|
104 | 105 | |
|
105 | 106 | par = parameter_s.strip() |
|
106 | 107 | if not par: |
|
107 | 108 | aliases = sorted(self.shell.alias_manager.aliases) |
|
108 | 109 | # stored = self.shell.db.get('stored_aliases', {} ) |
|
109 | 110 | # for k, v in stored: |
|
110 | 111 | # atab.append(k, v[0]) |
|
111 | 112 | |
|
112 | 113 | print("Total number of aliases:", len(aliases)) |
|
113 | 114 | sys.stdout.flush() |
|
114 | 115 | return aliases |
|
115 | 116 | |
|
116 | 117 | # Now try to define a new one |
|
117 | 118 | try: |
|
118 | 119 | alias,cmd = par.split(None, 1) |
|
119 | 120 | except TypeError: |
|
120 | 121 | print(oinspect.getdoc(self.alias)) |
|
121 | 122 | return |
|
122 | 123 | |
|
123 | 124 | try: |
|
124 | 125 | self.shell.alias_manager.define_alias(alias, cmd) |
|
125 | 126 | except AliasError as e: |
|
126 | 127 | print(e) |
|
127 | 128 | # end magic_alias |
|
128 | 129 | |
|
129 | 130 | @line_magic |
|
130 | 131 | def unalias(self, parameter_s=''): |
|
131 | 132 | """Remove an alias""" |
|
132 | 133 | |
|
133 | 134 | aname = parameter_s.strip() |
|
134 | 135 | try: |
|
135 | 136 | self.shell.alias_manager.undefine_alias(aname) |
|
136 | 137 | except ValueError as e: |
|
137 | 138 | print(e) |
|
138 | 139 | return |
|
139 | 140 | |
|
140 | 141 | stored = self.shell.db.get('stored_aliases', {} ) |
|
141 | 142 | if aname in stored: |
|
142 | 143 | print("Removing %stored alias",aname) |
|
143 | 144 | del stored[aname] |
|
144 | 145 | self.shell.db['stored_aliases'] = stored |
|
145 | 146 | |
|
146 | 147 | @line_magic |
|
147 | 148 | def rehashx(self, parameter_s=''): |
|
148 | 149 | """Update the alias table with all executable files in $PATH. |
|
149 | 150 | |
|
150 | 151 | This version explicitly checks that every entry in $PATH is a file |
|
151 | 152 | with execute access (os.X_OK), so it is much slower than %rehash. |
|
152 | 153 | |
|
153 | 154 | Under Windows, it checks executability as a match against a |
|
154 | 155 | '|'-separated string of extensions, stored in the IPython config |
|
155 | 156 | variable win_exec_ext. This defaults to 'exe|com|bat'. |
|
156 | 157 | |
|
157 | 158 | This function also resets the root module cache of module completer, |
|
158 | 159 | used on slow filesystems. |
|
159 | 160 | """ |
|
160 | 161 | from IPython.core.alias import InvalidAliasError |
|
161 | 162 | |
|
162 | 163 | # for the benefit of module completer in ipy_completers.py |
|
163 | 164 | del self.shell.db['rootmodules_cache'] |
|
164 | 165 | |
|
165 | 166 | path = [os.path.abspath(os.path.expanduser(p)) for p in |
|
166 | 167 | os.environ.get('PATH','').split(os.pathsep)] |
|
167 | 168 | path = filter(os.path.isdir,path) |
|
168 | 169 | |
|
169 | 170 | syscmdlist = [] |
|
170 | 171 | # Now define isexec in a cross platform manner. |
|
171 | 172 | if os.name == 'posix': |
|
172 | 173 | isexec = lambda fname:os.path.isfile(fname) and \ |
|
173 | 174 | os.access(fname,os.X_OK) |
|
174 | 175 | else: |
|
175 | 176 | try: |
|
176 | 177 | winext = os.environ['pathext'].replace(';','|').replace('.','') |
|
177 | 178 | except KeyError: |
|
178 | 179 | winext = 'exe|com|bat|py' |
|
179 | 180 | if 'py' not in winext: |
|
180 | 181 | winext += '|py' |
|
181 | 182 | execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) |
|
182 | 183 | isexec = lambda fname:os.path.isfile(fname) and execre.match(fname) |
|
183 |
savedir = |
|
|
184 | savedir = py3compat.getcwd() | |
|
184 | 185 | |
|
185 | 186 | # Now walk the paths looking for executables to alias. |
|
186 | 187 | try: |
|
187 | 188 | # write the whole loop for posix/Windows so we don't have an if in |
|
188 | 189 | # the innermost part |
|
189 | 190 | if os.name == 'posix': |
|
190 | 191 | for pdir in path: |
|
191 | 192 | os.chdir(pdir) |
|
192 | 193 | for ff in os.listdir(pdir): |
|
193 | 194 | if isexec(ff): |
|
194 | 195 | try: |
|
195 | 196 | # Removes dots from the name since ipython |
|
196 | 197 | # will assume names with dots to be python. |
|
197 | 198 | if not self.shell.alias_manager.is_alias(ff): |
|
198 | 199 | self.shell.alias_manager.define_alias( |
|
199 | 200 | ff.replace('.',''), ff) |
|
200 | 201 | except InvalidAliasError: |
|
201 | 202 | pass |
|
202 | 203 | else: |
|
203 | 204 | syscmdlist.append(ff) |
|
204 | 205 | else: |
|
205 | 206 | no_alias = Alias.blacklist |
|
206 | 207 | for pdir in path: |
|
207 | 208 | os.chdir(pdir) |
|
208 | 209 | for ff in os.listdir(pdir): |
|
209 | 210 | base, ext = os.path.splitext(ff) |
|
210 | 211 | if isexec(ff) and base.lower() not in no_alias: |
|
211 | 212 | if ext.lower() == '.exe': |
|
212 | 213 | ff = base |
|
213 | 214 | try: |
|
214 | 215 | # Removes dots from the name since ipython |
|
215 | 216 | # will assume names with dots to be python. |
|
216 | 217 | self.shell.alias_manager.define_alias( |
|
217 | 218 | base.lower().replace('.',''), ff) |
|
218 | 219 | except InvalidAliasError: |
|
219 | 220 | pass |
|
220 | 221 | syscmdlist.append(ff) |
|
221 | 222 | self.shell.db['syscmdlist'] = syscmdlist |
|
222 | 223 | finally: |
|
223 | 224 | os.chdir(savedir) |
|
224 | 225 | |
|
225 | 226 | @skip_doctest |
|
226 | 227 | @line_magic |
|
227 | 228 | def pwd(self, parameter_s=''): |
|
228 | 229 | """Return the current working directory path. |
|
229 | 230 | |
|
230 | 231 | Examples |
|
231 | 232 | -------- |
|
232 | 233 | :: |
|
233 | 234 | |
|
234 | 235 | In [9]: pwd |
|
235 | 236 | Out[9]: '/home/tsuser/sprint/ipython' |
|
236 | 237 | """ |
|
237 |
return |
|
|
238 | return py3compat.getcwd() | |
|
238 | 239 | |
|
239 | 240 | @skip_doctest |
|
240 | 241 | @line_magic |
|
241 | 242 | def cd(self, parameter_s=''): |
|
242 | 243 | """Change the current working directory. |
|
243 | 244 | |
|
244 | 245 | This command automatically maintains an internal list of directories |
|
245 | 246 | you visit during your IPython session, in the variable _dh. The |
|
246 | 247 | command %dhist shows this history nicely formatted. You can also |
|
247 | 248 | do 'cd -<tab>' to see directory history conveniently. |
|
248 | 249 | |
|
249 | 250 | Usage: |
|
250 | 251 | |
|
251 | 252 | cd 'dir': changes to directory 'dir'. |
|
252 | 253 | |
|
253 | 254 | cd -: changes to the last visited directory. |
|
254 | 255 | |
|
255 | 256 | cd -<n>: changes to the n-th directory in the directory history. |
|
256 | 257 | |
|
257 | 258 | cd --foo: change to directory that matches 'foo' in history |
|
258 | 259 | |
|
259 | 260 | cd -b <bookmark_name>: jump to a bookmark set by %bookmark |
|
260 | 261 | (note: cd <bookmark_name> is enough if there is no |
|
261 | 262 | directory <bookmark_name>, but a bookmark with the name exists.) |
|
262 | 263 | 'cd -b <tab>' allows you to tab-complete bookmark names. |
|
263 | 264 | |
|
264 | 265 | Options: |
|
265 | 266 | |
|
266 | 267 | -q: quiet. Do not print the working directory after the cd command is |
|
267 | 268 | executed. By default IPython's cd command does print this directory, |
|
268 | 269 | since the default prompts do not display path information. |
|
269 | 270 | |
|
270 | 271 | Note that !cd doesn't work for this purpose because the shell where |
|
271 | 272 | !command runs is immediately discarded after executing 'command'. |
|
272 | 273 | |
|
273 | 274 | Examples |
|
274 | 275 | -------- |
|
275 | 276 | :: |
|
276 | 277 | |
|
277 | 278 | In [10]: cd parent/child |
|
278 | 279 | /home/tsuser/parent/child |
|
279 | 280 | """ |
|
280 | 281 | |
|
281 |
oldcwd = |
|
|
282 | oldcwd = py3compat.getcwd() | |
|
282 | 283 | numcd = re.match(r'(-)(\d+)$',parameter_s) |
|
283 | 284 | # jump in directory history by number |
|
284 | 285 | if numcd: |
|
285 | 286 | nn = int(numcd.group(2)) |
|
286 | 287 | try: |
|
287 | 288 | ps = self.shell.user_ns['_dh'][nn] |
|
288 | 289 | except IndexError: |
|
289 | 290 | print('The requested directory does not exist in history.') |
|
290 | 291 | return |
|
291 | 292 | else: |
|
292 | 293 | opts = {} |
|
293 | 294 | elif parameter_s.startswith('--'): |
|
294 | 295 | ps = None |
|
295 | 296 | fallback = None |
|
296 | 297 | pat = parameter_s[2:] |
|
297 | 298 | dh = self.shell.user_ns['_dh'] |
|
298 | 299 | # first search only by basename (last component) |
|
299 | 300 | for ent in reversed(dh): |
|
300 | 301 | if pat in os.path.basename(ent) and os.path.isdir(ent): |
|
301 | 302 | ps = ent |
|
302 | 303 | break |
|
303 | 304 | |
|
304 | 305 | if fallback is None and pat in ent and os.path.isdir(ent): |
|
305 | 306 | fallback = ent |
|
306 | 307 | |
|
307 | 308 | # if we have no last part match, pick the first full path match |
|
308 | 309 | if ps is None: |
|
309 | 310 | ps = fallback |
|
310 | 311 | |
|
311 | 312 | if ps is None: |
|
312 | 313 | print("No matching entry in directory history") |
|
313 | 314 | return |
|
314 | 315 | else: |
|
315 | 316 | opts = {} |
|
316 | 317 | |
|
317 | 318 | |
|
318 | 319 | else: |
|
319 | 320 | #turn all non-space-escaping backslashes to slashes, |
|
320 | 321 | # for c:\windows\directory\names\ |
|
321 | 322 | parameter_s = re.sub(r'\\(?! )','/', parameter_s) |
|
322 | 323 | opts,ps = self.parse_options(parameter_s,'qb',mode='string') |
|
323 | 324 | # jump to previous |
|
324 | 325 | if ps == '-': |
|
325 | 326 | try: |
|
326 | 327 | ps = self.shell.user_ns['_dh'][-2] |
|
327 | 328 | except IndexError: |
|
328 | 329 | raise UsageError('%cd -: No previous directory to change to.') |
|
329 | 330 | # jump to bookmark if needed |
|
330 | 331 | else: |
|
331 | 332 | if not os.path.isdir(ps) or 'b' in opts: |
|
332 | 333 | bkms = self.shell.db.get('bookmarks', {}) |
|
333 | 334 | |
|
334 | 335 | if ps in bkms: |
|
335 | 336 | target = bkms[ps] |
|
336 | 337 | print('(bookmark:%s) -> %s' % (ps, target)) |
|
337 | 338 | ps = target |
|
338 | 339 | else: |
|
339 | 340 | if 'b' in opts: |
|
340 | 341 | raise UsageError("Bookmark '%s' not found. " |
|
341 | 342 | "Use '%%bookmark -l' to see your bookmarks." % ps) |
|
342 | 343 | |
|
343 | 344 | # strip extra quotes on Windows, because os.chdir doesn't like them |
|
344 | 345 | ps = unquote_filename(ps) |
|
345 | 346 | # at this point ps should point to the target dir |
|
346 | 347 | if ps: |
|
347 | 348 | try: |
|
348 | 349 | os.chdir(os.path.expanduser(ps)) |
|
349 | 350 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
350 | 351 | set_term_title('IPython: ' + abbrev_cwd()) |
|
351 | 352 | except OSError: |
|
352 | 353 | print(sys.exc_info()[1]) |
|
353 | 354 | else: |
|
354 |
cwd = |
|
|
355 | cwd = py3compat.getcwd() | |
|
355 | 356 | dhist = self.shell.user_ns['_dh'] |
|
356 | 357 | if oldcwd != cwd: |
|
357 | 358 | dhist.append(cwd) |
|
358 | 359 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
359 | 360 | |
|
360 | 361 | else: |
|
361 | 362 | os.chdir(self.shell.home_dir) |
|
362 | 363 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
363 | 364 | set_term_title('IPython: ' + '~') |
|
364 |
cwd = |
|
|
365 | cwd = py3compat.getcwd() | |
|
365 | 366 | dhist = self.shell.user_ns['_dh'] |
|
366 | 367 | |
|
367 | 368 | if oldcwd != cwd: |
|
368 | 369 | dhist.append(cwd) |
|
369 | 370 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
370 | 371 | if not 'q' in opts and self.shell.user_ns['_dh']: |
|
371 | 372 | print(self.shell.user_ns['_dh'][-1]) |
|
372 | 373 | |
|
373 | 374 | |
|
374 | 375 | @line_magic |
|
375 | 376 | def env(self, parameter_s=''): |
|
376 | 377 | """List environment variables.""" |
|
377 | 378 | |
|
378 | 379 | return dict(os.environ) |
|
379 | 380 | |
|
380 | 381 | @line_magic |
|
381 | 382 | def pushd(self, parameter_s=''): |
|
382 | 383 | """Place the current dir on stack and change directory. |
|
383 | 384 | |
|
384 | 385 | Usage:\\ |
|
385 | 386 | %pushd ['dirname'] |
|
386 | 387 | """ |
|
387 | 388 | |
|
388 | 389 | dir_s = self.shell.dir_stack |
|
389 | 390 | tgt = os.path.expanduser(unquote_filename(parameter_s)) |
|
390 |
cwd = |
|
|
391 | cwd = py3compat.getcwd().replace(self.shell.home_dir,'~') | |
|
391 | 392 | if tgt: |
|
392 | 393 | self.cd(parameter_s) |
|
393 | 394 | dir_s.insert(0,cwd) |
|
394 | 395 | return self.shell.magic('dirs') |
|
395 | 396 | |
|
396 | 397 | @line_magic |
|
397 | 398 | def popd(self, parameter_s=''): |
|
398 | 399 | """Change to directory popped off the top of the stack. |
|
399 | 400 | """ |
|
400 | 401 | if not self.shell.dir_stack: |
|
401 | 402 | raise UsageError("%popd on empty stack") |
|
402 | 403 | top = self.shell.dir_stack.pop(0) |
|
403 | 404 | self.cd(top) |
|
404 | 405 | print("popd ->",top) |
|
405 | 406 | |
|
406 | 407 | @line_magic |
|
407 | 408 | def dirs(self, parameter_s=''): |
|
408 | 409 | """Return the current directory stack.""" |
|
409 | 410 | |
|
410 | 411 | return self.shell.dir_stack |
|
411 | 412 | |
|
412 | 413 | @line_magic |
|
413 | 414 | def dhist(self, parameter_s=''): |
|
414 | 415 | """Print your history of visited directories. |
|
415 | 416 | |
|
416 | 417 | %dhist -> print full history\\ |
|
417 | 418 | %dhist n -> print last n entries only\\ |
|
418 | 419 | %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\ |
|
419 | 420 | |
|
420 | 421 | This history is automatically maintained by the %cd command, and |
|
421 | 422 | always available as the global list variable _dh. You can use %cd -<n> |
|
422 | 423 | to go to directory number <n>. |
|
423 | 424 | |
|
424 | 425 | Note that most of time, you should view directory history by entering |
|
425 | 426 | cd -<TAB>. |
|
426 | 427 | |
|
427 | 428 | """ |
|
428 | 429 | |
|
429 | 430 | dh = self.shell.user_ns['_dh'] |
|
430 | 431 | if parameter_s: |
|
431 | 432 | try: |
|
432 | 433 | args = map(int,parameter_s.split()) |
|
433 | 434 | except: |
|
434 | 435 | self.arg_err(self.dhist) |
|
435 | 436 | return |
|
436 | 437 | if len(args) == 1: |
|
437 | 438 | ini,fin = max(len(dh)-(args[0]),0),len(dh) |
|
438 | 439 | elif len(args) == 2: |
|
439 | 440 | ini,fin = args |
|
440 | 441 | fin = min(fin, len(dh)) |
|
441 | 442 | else: |
|
442 | 443 | self.arg_err(self.dhist) |
|
443 | 444 | return |
|
444 | 445 | else: |
|
445 | 446 | ini,fin = 0,len(dh) |
|
446 | 447 | print('Directory history (kept in _dh)') |
|
447 | 448 | for i in range(ini, fin): |
|
448 | 449 | print("%d: %s" % (i, dh[i])) |
|
449 | 450 | |
|
450 | 451 | @skip_doctest |
|
451 | 452 | @line_magic |
|
452 | 453 | def sc(self, parameter_s=''): |
|
453 | 454 | """Shell capture - run shell command and capture output (DEPRECATED use !). |
|
454 | 455 | |
|
455 | 456 | DEPRECATED. Suboptimal, retained for backwards compatibility. |
|
456 | 457 | |
|
457 | 458 | You should use the form 'var = !command' instead. Example: |
|
458 | 459 | |
|
459 | 460 | "%sc -l myfiles = ls ~" should now be written as |
|
460 | 461 | |
|
461 | 462 | "myfiles = !ls ~" |
|
462 | 463 | |
|
463 | 464 | myfiles.s, myfiles.l and myfiles.n still apply as documented |
|
464 | 465 | below. |
|
465 | 466 | |
|
466 | 467 | -- |
|
467 | 468 | %sc [options] varname=command |
|
468 | 469 | |
|
469 | 470 | IPython will run the given command using commands.getoutput(), and |
|
470 | 471 | will then update the user's interactive namespace with a variable |
|
471 | 472 | called varname, containing the value of the call. Your command can |
|
472 | 473 | contain shell wildcards, pipes, etc. |
|
473 | 474 | |
|
474 | 475 | The '=' sign in the syntax is mandatory, and the variable name you |
|
475 | 476 | supply must follow Python's standard conventions for valid names. |
|
476 | 477 | |
|
477 | 478 | (A special format without variable name exists for internal use) |
|
478 | 479 | |
|
479 | 480 | Options: |
|
480 | 481 | |
|
481 | 482 | -l: list output. Split the output on newlines into a list before |
|
482 | 483 | assigning it to the given variable. By default the output is stored |
|
483 | 484 | as a single string. |
|
484 | 485 | |
|
485 | 486 | -v: verbose. Print the contents of the variable. |
|
486 | 487 | |
|
487 | 488 | In most cases you should not need to split as a list, because the |
|
488 | 489 | returned value is a special type of string which can automatically |
|
489 | 490 | provide its contents either as a list (split on newlines) or as a |
|
490 | 491 | space-separated string. These are convenient, respectively, either |
|
491 | 492 | for sequential processing or to be passed to a shell command. |
|
492 | 493 | |
|
493 | 494 | For example:: |
|
494 | 495 | |
|
495 | 496 | # Capture into variable a |
|
496 | 497 | In [1]: sc a=ls *py |
|
497 | 498 | |
|
498 | 499 | # a is a string with embedded newlines |
|
499 | 500 | In [2]: a |
|
500 | 501 | Out[2]: 'setup.py\\nwin32_manual_post_install.py' |
|
501 | 502 | |
|
502 | 503 | # which can be seen as a list: |
|
503 | 504 | In [3]: a.l |
|
504 | 505 | Out[3]: ['setup.py', 'win32_manual_post_install.py'] |
|
505 | 506 | |
|
506 | 507 | # or as a whitespace-separated string: |
|
507 | 508 | In [4]: a.s |
|
508 | 509 | Out[4]: 'setup.py win32_manual_post_install.py' |
|
509 | 510 | |
|
510 | 511 | # a.s is useful to pass as a single command line: |
|
511 | 512 | In [5]: !wc -l $a.s |
|
512 | 513 | 146 setup.py |
|
513 | 514 | 130 win32_manual_post_install.py |
|
514 | 515 | 276 total |
|
515 | 516 | |
|
516 | 517 | # while the list form is useful to loop over: |
|
517 | 518 | In [6]: for f in a.l: |
|
518 | 519 | ...: !wc -l $f |
|
519 | 520 | ...: |
|
520 | 521 | 146 setup.py |
|
521 | 522 | 130 win32_manual_post_install.py |
|
522 | 523 | |
|
523 | 524 | Similarly, the lists returned by the -l option are also special, in |
|
524 | 525 | the sense that you can equally invoke the .s attribute on them to |
|
525 | 526 | automatically get a whitespace-separated string from their contents:: |
|
526 | 527 | |
|
527 | 528 | In [7]: sc -l b=ls *py |
|
528 | 529 | |
|
529 | 530 | In [8]: b |
|
530 | 531 | Out[8]: ['setup.py', 'win32_manual_post_install.py'] |
|
531 | 532 | |
|
532 | 533 | In [9]: b.s |
|
533 | 534 | Out[9]: 'setup.py win32_manual_post_install.py' |
|
534 | 535 | |
|
535 | 536 | In summary, both the lists and strings used for output capture have |
|
536 | 537 | the following special attributes:: |
|
537 | 538 | |
|
538 | 539 | .l (or .list) : value as list. |
|
539 | 540 | .n (or .nlstr): value as newline-separated string. |
|
540 | 541 | .s (or .spstr): value as space-separated string. |
|
541 | 542 | """ |
|
542 | 543 | |
|
543 | 544 | opts,args = self.parse_options(parameter_s, 'lv') |
|
544 | 545 | # Try to get a variable name and command to run |
|
545 | 546 | try: |
|
546 | 547 | # the variable name must be obtained from the parse_options |
|
547 | 548 | # output, which uses shlex.split to strip options out. |
|
548 | 549 | var,_ = args.split('=', 1) |
|
549 | 550 | var = var.strip() |
|
550 | 551 | # But the command has to be extracted from the original input |
|
551 | 552 | # parameter_s, not on what parse_options returns, to avoid the |
|
552 | 553 | # quote stripping which shlex.split performs on it. |
|
553 | 554 | _,cmd = parameter_s.split('=', 1) |
|
554 | 555 | except ValueError: |
|
555 | 556 | var,cmd = '','' |
|
556 | 557 | # If all looks ok, proceed |
|
557 | 558 | split = 'l' in opts |
|
558 | 559 | out = self.shell.getoutput(cmd, split=split) |
|
559 | 560 | if 'v' in opts: |
|
560 | 561 | print('%s ==\n%s' % (var, pformat(out))) |
|
561 | 562 | if var: |
|
562 | 563 | self.shell.user_ns.update({var:out}) |
|
563 | 564 | else: |
|
564 | 565 | return out |
|
565 | 566 | |
|
566 | 567 | @line_cell_magic |
|
567 | 568 | def sx(self, line='', cell=None): |
|
568 | 569 | """Shell execute - run shell command and capture output (!! is short-hand). |
|
569 | 570 | |
|
570 | 571 | %sx command |
|
571 | 572 | |
|
572 | 573 | IPython will run the given command using commands.getoutput(), and |
|
573 | 574 | return the result formatted as a list (split on '\\n'). Since the |
|
574 | 575 | output is _returned_, it will be stored in ipython's regular output |
|
575 | 576 | cache Out[N] and in the '_N' automatic variables. |
|
576 | 577 | |
|
577 | 578 | Notes: |
|
578 | 579 | |
|
579 | 580 | 1) If an input line begins with '!!', then %sx is automatically |
|
580 | 581 | invoked. That is, while:: |
|
581 | 582 | |
|
582 | 583 | !ls |
|
583 | 584 | |
|
584 | 585 | causes ipython to simply issue system('ls'), typing:: |
|
585 | 586 | |
|
586 | 587 | !!ls |
|
587 | 588 | |
|
588 | 589 | is a shorthand equivalent to:: |
|
589 | 590 | |
|
590 | 591 | %sx ls |
|
591 | 592 | |
|
592 | 593 | 2) %sx differs from %sc in that %sx automatically splits into a list, |
|
593 | 594 | like '%sc -l'. The reason for this is to make it as easy as possible |
|
594 | 595 | to process line-oriented shell output via further python commands. |
|
595 | 596 | %sc is meant to provide much finer control, but requires more |
|
596 | 597 | typing. |
|
597 | 598 | |
|
598 | 599 | 3) Just like %sc -l, this is a list with special attributes: |
|
599 | 600 | :: |
|
600 | 601 | |
|
601 | 602 | .l (or .list) : value as list. |
|
602 | 603 | .n (or .nlstr): value as newline-separated string. |
|
603 | 604 | .s (or .spstr): value as whitespace-separated string. |
|
604 | 605 | |
|
605 | 606 | This is very useful when trying to use such lists as arguments to |
|
606 | 607 | system commands.""" |
|
607 | 608 | |
|
608 | 609 | if cell is None: |
|
609 | 610 | # line magic |
|
610 | 611 | return self.shell.getoutput(line) |
|
611 | 612 | else: |
|
612 | 613 | opts,args = self.parse_options(line, '', 'out=') |
|
613 | 614 | output = self.shell.getoutput(cell) |
|
614 | 615 | out_name = opts.get('out', opts.get('o')) |
|
615 | 616 | if out_name: |
|
616 | 617 | self.shell.user_ns[out_name] = output |
|
617 | 618 | else: |
|
618 | 619 | return output |
|
619 | 620 | |
|
620 | 621 | system = line_cell_magic('system')(sx) |
|
621 | 622 | bang = cell_magic('!')(sx) |
|
622 | 623 | |
|
623 | 624 | @line_magic |
|
624 | 625 | def bookmark(self, parameter_s=''): |
|
625 | 626 | """Manage IPython's bookmark system. |
|
626 | 627 | |
|
627 | 628 | %bookmark <name> - set bookmark to current dir |
|
628 | 629 | %bookmark <name> <dir> - set bookmark to <dir> |
|
629 | 630 | %bookmark -l - list all bookmarks |
|
630 | 631 | %bookmark -d <name> - remove bookmark |
|
631 | 632 | %bookmark -r - remove all bookmarks |
|
632 | 633 | |
|
633 | 634 | You can later on access a bookmarked folder with:: |
|
634 | 635 | |
|
635 | 636 | %cd -b <name> |
|
636 | 637 | |
|
637 | 638 | or simply '%cd <name>' if there is no directory called <name> AND |
|
638 | 639 | there is such a bookmark defined. |
|
639 | 640 | |
|
640 | 641 | Your bookmarks persist through IPython sessions, but they are |
|
641 | 642 | associated with each profile.""" |
|
642 | 643 | |
|
643 | 644 | opts,args = self.parse_options(parameter_s,'drl',mode='list') |
|
644 | 645 | if len(args) > 2: |
|
645 | 646 | raise UsageError("%bookmark: too many arguments") |
|
646 | 647 | |
|
647 | 648 | bkms = self.shell.db.get('bookmarks',{}) |
|
648 | 649 | |
|
649 | 650 | if 'd' in opts: |
|
650 | 651 | try: |
|
651 | 652 | todel = args[0] |
|
652 | 653 | except IndexError: |
|
653 | 654 | raise UsageError( |
|
654 | 655 | "%bookmark -d: must provide a bookmark to delete") |
|
655 | 656 | else: |
|
656 | 657 | try: |
|
657 | 658 | del bkms[todel] |
|
658 | 659 | except KeyError: |
|
659 | 660 | raise UsageError( |
|
660 | 661 | "%%bookmark -d: Can't delete bookmark '%s'" % todel) |
|
661 | 662 | |
|
662 | 663 | elif 'r' in opts: |
|
663 | 664 | bkms = {} |
|
664 | 665 | elif 'l' in opts: |
|
665 | 666 | bks = bkms.keys() |
|
666 | 667 | bks.sort() |
|
667 | 668 | if bks: |
|
668 | 669 | size = max(map(len, bks)) |
|
669 | 670 | else: |
|
670 | 671 | size = 0 |
|
671 | 672 | fmt = '%-'+str(size)+'s -> %s' |
|
672 | 673 | print('Current bookmarks:') |
|
673 | 674 | for bk in bks: |
|
674 | 675 | print(fmt % (bk, bkms[bk])) |
|
675 | 676 | else: |
|
676 | 677 | if not args: |
|
677 | 678 | raise UsageError("%bookmark: You must specify the bookmark name") |
|
678 | 679 | elif len(args)==1: |
|
679 |
bkms[args[0]] = |
|
|
680 | bkms[args[0]] = py3compat.getcwd() | |
|
680 | 681 | elif len(args)==2: |
|
681 | 682 | bkms[args[0]] = args[1] |
|
682 | 683 | self.shell.db['bookmarks'] = bkms |
|
683 | 684 | |
|
684 | 685 | @line_magic |
|
685 | 686 | def pycat(self, parameter_s=''): |
|
686 | 687 | """Show a syntax-highlighted file through a pager. |
|
687 | 688 | |
|
688 | 689 | This magic is similar to the cat utility, but it will assume the file |
|
689 | 690 | to be Python source and will show it with syntax highlighting. |
|
690 | 691 | |
|
691 | 692 | This magic command can either take a local filename, an url, |
|
692 | 693 | an history range (see %history) or a macro as argument :: |
|
693 | 694 | |
|
694 | 695 | %pycat myscript.py |
|
695 | 696 | %pycat 7-27 |
|
696 | 697 | %pycat myMacro |
|
697 | 698 | %pycat http://www.example.com/myscript.py |
|
698 | 699 | """ |
|
699 | 700 | if not parameter_s: |
|
700 | 701 | raise UsageError('Missing filename, URL, input history range, ' |
|
701 | 702 | 'or macro.') |
|
702 | 703 | |
|
703 | 704 | try : |
|
704 | 705 | cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False) |
|
705 | 706 | except (ValueError, IOError): |
|
706 | 707 | print("Error: no such file, variable, URL, history range or macro") |
|
707 | 708 | return |
|
708 | 709 | |
|
709 | 710 | page.page(self.shell.pycolorize(source_to_unicode(cont))) |
|
710 | 711 | |
|
711 | 712 | @magic_arguments.magic_arguments() |
|
712 | 713 | @magic_arguments.argument( |
|
713 | 714 | '-a', '--append', action='store_true', default=False, |
|
714 | 715 | help='Append contents of the cell to an existing file. ' |
|
715 | 716 | 'The file will be created if it does not exist.' |
|
716 | 717 | ) |
|
717 | 718 | @magic_arguments.argument( |
|
718 | 719 | 'filename', type=unicode_type, |
|
719 | 720 | help='file to write' |
|
720 | 721 | ) |
|
721 | 722 | @cell_magic |
|
722 | 723 | def writefile(self, line, cell): |
|
723 | 724 | """Write the contents of the cell to a file. |
|
724 | 725 | |
|
725 | 726 | The file will be overwritten unless the -a (--append) flag is specified. |
|
726 | 727 | """ |
|
727 | 728 | args = magic_arguments.parse_argstring(self.writefile, line) |
|
728 | 729 | filename = os.path.expanduser(unquote_filename(args.filename)) |
|
729 | 730 | |
|
730 | 731 | if os.path.exists(filename): |
|
731 | 732 | if args.append: |
|
732 | 733 | print("Appending to %s" % filename) |
|
733 | 734 | else: |
|
734 | 735 | print("Overwriting %s" % filename) |
|
735 | 736 | else: |
|
736 | 737 | print("Writing %s" % filename) |
|
737 | 738 | |
|
738 | 739 | mode = 'a' if args.append else 'w' |
|
739 | 740 | with io.open(filename, mode, encoding='utf-8') as f: |
|
740 | 741 | f.write(cell) |
@@ -1,314 +1,315 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | An application for managing IPython profiles. |
|
4 | 4 | |
|
5 | 5 | To be invoked as the `ipython profile` subcommand. |
|
6 | 6 | |
|
7 | 7 | Authors: |
|
8 | 8 | |
|
9 | 9 | * Min RK |
|
10 | 10 | |
|
11 | 11 | """ |
|
12 | 12 | from __future__ import print_function |
|
13 | 13 | |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | # Copyright (C) 2008 The IPython Development Team |
|
16 | 16 | # |
|
17 | 17 | # Distributed under the terms of the BSD License. The full license is in |
|
18 | 18 | # the file COPYING, distributed as part of this software. |
|
19 | 19 | #----------------------------------------------------------------------------- |
|
20 | 20 | |
|
21 | 21 | #----------------------------------------------------------------------------- |
|
22 | 22 | # Imports |
|
23 | 23 | #----------------------------------------------------------------------------- |
|
24 | 24 | |
|
25 | 25 | import os |
|
26 | 26 | |
|
27 | 27 | from IPython.config.application import Application |
|
28 | 28 | from IPython.core.application import ( |
|
29 | 29 | BaseIPythonApplication, base_flags |
|
30 | 30 | ) |
|
31 | 31 | from IPython.core.profiledir import ProfileDir |
|
32 | 32 | from IPython.utils.importstring import import_item |
|
33 | 33 | from IPython.utils.path import get_ipython_dir, get_ipython_package_dir |
|
34 | from IPython.utils import py3compat | |
|
34 | 35 | from IPython.utils.traitlets import Unicode, Bool, Dict |
|
35 | 36 | |
|
36 | 37 | #----------------------------------------------------------------------------- |
|
37 | 38 | # Constants |
|
38 | 39 | #----------------------------------------------------------------------------- |
|
39 | 40 | |
|
40 | 41 | create_help = """Create an IPython profile by name |
|
41 | 42 | |
|
42 | 43 | Create an ipython profile directory by its name or |
|
43 | 44 | profile directory path. Profile directories contain |
|
44 | 45 | configuration, log and security related files and are named |
|
45 | 46 | using the convention 'profile_<name>'. By default they are |
|
46 | 47 | located in your ipython directory. Once created, you will |
|
47 | 48 | can edit the configuration files in the profile |
|
48 | 49 | directory to configure IPython. Most users will create a |
|
49 | 50 | profile directory by name, |
|
50 | 51 | `ipython profile create myprofile`, which will put the directory |
|
51 | 52 | in `<ipython_dir>/profile_myprofile`. |
|
52 | 53 | """ |
|
53 | 54 | list_help = """List available IPython profiles |
|
54 | 55 | |
|
55 | 56 | List all available profiles, by profile location, that can |
|
56 | 57 | be found in the current working directly or in the ipython |
|
57 | 58 | directory. Profile directories are named using the convention |
|
58 | 59 | 'profile_<profile>'. |
|
59 | 60 | """ |
|
60 | 61 | profile_help = """Manage IPython profiles |
|
61 | 62 | |
|
62 | 63 | Profile directories contain |
|
63 | 64 | configuration, log and security related files and are named |
|
64 | 65 | using the convention 'profile_<name>'. By default they are |
|
65 | 66 | located in your ipython directory. You can create profiles |
|
66 | 67 | with `ipython profile create <name>`, or see the profiles you |
|
67 | 68 | already have with `ipython profile list` |
|
68 | 69 | |
|
69 | 70 | To get started configuring IPython, simply do: |
|
70 | 71 | |
|
71 | 72 | $> ipython profile create |
|
72 | 73 | |
|
73 | 74 | and IPython will create the default profile in <ipython_dir>/profile_default, |
|
74 | 75 | where you can edit ipython_config.py to start configuring IPython. |
|
75 | 76 | |
|
76 | 77 | """ |
|
77 | 78 | |
|
78 | 79 | _list_examples = "ipython profile list # list all profiles" |
|
79 | 80 | |
|
80 | 81 | _create_examples = """ |
|
81 | 82 | ipython profile create foo # create profile foo w/ default config files |
|
82 | 83 | ipython profile create foo --reset # restage default config files over current |
|
83 | 84 | ipython profile create foo --parallel # also stage parallel config files |
|
84 | 85 | """ |
|
85 | 86 | |
|
86 | 87 | _main_examples = """ |
|
87 | 88 | ipython profile create -h # show the help string for the create subcommand |
|
88 | 89 | ipython profile list -h # show the help string for the list subcommand |
|
89 | 90 | |
|
90 | 91 | ipython locate profile foo # print the path to the directory for profile 'foo' |
|
91 | 92 | """ |
|
92 | 93 | |
|
93 | 94 | #----------------------------------------------------------------------------- |
|
94 | 95 | # Profile Application Class (for `ipython profile` subcommand) |
|
95 | 96 | #----------------------------------------------------------------------------- |
|
96 | 97 | |
|
97 | 98 | |
|
98 | 99 | def list_profiles_in(path): |
|
99 | 100 | """list profiles in a given root directory""" |
|
100 | 101 | files = os.listdir(path) |
|
101 | 102 | profiles = [] |
|
102 | 103 | for f in files: |
|
103 | 104 | try: |
|
104 | 105 | full_path = os.path.join(path, f) |
|
105 | 106 | except UnicodeError: |
|
106 | 107 | continue |
|
107 | 108 | if os.path.isdir(full_path) and f.startswith('profile_'): |
|
108 | 109 | profiles.append(f.split('_',1)[-1]) |
|
109 | 110 | return profiles |
|
110 | 111 | |
|
111 | 112 | |
|
112 | 113 | def list_bundled_profiles(): |
|
113 | 114 | """list profiles that are bundled with IPython.""" |
|
114 | 115 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile') |
|
115 | 116 | files = os.listdir(path) |
|
116 | 117 | profiles = [] |
|
117 | 118 | for profile in files: |
|
118 | 119 | full_path = os.path.join(path, profile) |
|
119 | 120 | if os.path.isdir(full_path) and profile != "__pycache__": |
|
120 | 121 | profiles.append(profile) |
|
121 | 122 | return profiles |
|
122 | 123 | |
|
123 | 124 | |
|
124 | 125 | class ProfileLocate(BaseIPythonApplication): |
|
125 | 126 | description = """print the path to an IPython profile dir""" |
|
126 | 127 | |
|
127 | 128 | def parse_command_line(self, argv=None): |
|
128 | 129 | super(ProfileLocate, self).parse_command_line(argv) |
|
129 | 130 | if self.extra_args: |
|
130 | 131 | self.profile = self.extra_args[0] |
|
131 | 132 | |
|
132 | 133 | def start(self): |
|
133 | 134 | print(self.profile_dir.location) |
|
134 | 135 | |
|
135 | 136 | |
|
136 | 137 | class ProfileList(Application): |
|
137 | 138 | name = u'ipython-profile' |
|
138 | 139 | description = list_help |
|
139 | 140 | examples = _list_examples |
|
140 | 141 | |
|
141 | 142 | aliases = Dict({ |
|
142 | 143 | 'ipython-dir' : 'ProfileList.ipython_dir', |
|
143 | 144 | 'log-level' : 'Application.log_level', |
|
144 | 145 | }) |
|
145 | 146 | flags = Dict(dict( |
|
146 | 147 | debug = ({'Application' : {'log_level' : 0}}, |
|
147 | 148 | "Set Application.log_level to 0, maximizing log output." |
|
148 | 149 | ) |
|
149 | 150 | )) |
|
150 | 151 | |
|
151 | 152 | ipython_dir = Unicode(get_ipython_dir(), config=True, |
|
152 | 153 | help=""" |
|
153 | 154 | The name of the IPython directory. This directory is used for logging |
|
154 | 155 | configuration (through profiles), history storage, etc. The default |
|
155 | 156 | is usually $HOME/.ipython. This options can also be specified through |
|
156 | 157 | the environment variable IPYTHONDIR. |
|
157 | 158 | """ |
|
158 | 159 | ) |
|
159 | 160 | |
|
160 | 161 | |
|
161 | 162 | def _print_profiles(self, profiles): |
|
162 | 163 | """print list of profiles, indented.""" |
|
163 | 164 | for profile in profiles: |
|
164 | 165 | print(' %s' % profile) |
|
165 | 166 | |
|
166 | 167 | def list_profile_dirs(self): |
|
167 | 168 | profiles = list_bundled_profiles() |
|
168 | 169 | if profiles: |
|
169 | 170 | print() |
|
170 | 171 | print("Available profiles in IPython:") |
|
171 | 172 | self._print_profiles(profiles) |
|
172 | 173 | print() |
|
173 | 174 | print(" The first request for a bundled profile will copy it") |
|
174 | 175 | print(" into your IPython directory (%s)," % self.ipython_dir) |
|
175 | 176 | print(" where you can customize it.") |
|
176 | 177 | |
|
177 | 178 | profiles = list_profiles_in(self.ipython_dir) |
|
178 | 179 | if profiles: |
|
179 | 180 | print() |
|
180 | 181 | print("Available profiles in %s:" % self.ipython_dir) |
|
181 | 182 | self._print_profiles(profiles) |
|
182 | 183 | |
|
183 |
profiles = list_profiles_in( |
|
|
184 | profiles = list_profiles_in(py3compat.getcwd()) | |
|
184 | 185 | if profiles: |
|
185 | 186 | print() |
|
186 |
print("Available profiles in current directory (%s):" % |
|
|
187 | print("Available profiles in current directory (%s):" % py3compat.getcwd()) | |
|
187 | 188 | self._print_profiles(profiles) |
|
188 | 189 | |
|
189 | 190 | print() |
|
190 | 191 | print("To use any of the above profiles, start IPython with:") |
|
191 | 192 | print(" ipython --profile=<name>") |
|
192 | 193 | print() |
|
193 | 194 | |
|
194 | 195 | def start(self): |
|
195 | 196 | self.list_profile_dirs() |
|
196 | 197 | |
|
197 | 198 | |
|
198 | 199 | create_flags = {} |
|
199 | 200 | create_flags.update(base_flags) |
|
200 | 201 | # don't include '--init' flag, which implies running profile create in other apps |
|
201 | 202 | create_flags.pop('init') |
|
202 | 203 | create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}}, |
|
203 | 204 | "reset config files in this profile to the defaults.") |
|
204 | 205 | create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}}, |
|
205 | 206 | "Include the config files for parallel " |
|
206 | 207 | "computing apps (ipengine, ipcontroller, etc.)") |
|
207 | 208 | |
|
208 | 209 | |
|
209 | 210 | class ProfileCreate(BaseIPythonApplication): |
|
210 | 211 | name = u'ipython-profile' |
|
211 | 212 | description = create_help |
|
212 | 213 | examples = _create_examples |
|
213 | 214 | auto_create = Bool(True, config=False) |
|
214 | 215 | def _log_format_default(self): |
|
215 | 216 | return "[%(name)s] %(message)s" |
|
216 | 217 | |
|
217 | 218 | def _copy_config_files_default(self): |
|
218 | 219 | return True |
|
219 | 220 | |
|
220 | 221 | parallel = Bool(False, config=True, |
|
221 | 222 | help="whether to include parallel computing config files") |
|
222 | 223 | def _parallel_changed(self, name, old, new): |
|
223 | 224 | parallel_files = [ 'ipcontroller_config.py', |
|
224 | 225 | 'ipengine_config.py', |
|
225 | 226 | 'ipcluster_config.py' |
|
226 | 227 | ] |
|
227 | 228 | if new: |
|
228 | 229 | for cf in parallel_files: |
|
229 | 230 | self.config_files.append(cf) |
|
230 | 231 | else: |
|
231 | 232 | for cf in parallel_files: |
|
232 | 233 | if cf in self.config_files: |
|
233 | 234 | self.config_files.remove(cf) |
|
234 | 235 | |
|
235 | 236 | def parse_command_line(self, argv): |
|
236 | 237 | super(ProfileCreate, self).parse_command_line(argv) |
|
237 | 238 | # accept positional arg as profile name |
|
238 | 239 | if self.extra_args: |
|
239 | 240 | self.profile = self.extra_args[0] |
|
240 | 241 | |
|
241 | 242 | flags = Dict(create_flags) |
|
242 | 243 | |
|
243 | 244 | classes = [ProfileDir] |
|
244 | 245 | |
|
245 | 246 | def _import_app(self, app_path): |
|
246 | 247 | """import an app class""" |
|
247 | 248 | app = None |
|
248 | 249 | name = app_path.rsplit('.', 1)[-1] |
|
249 | 250 | try: |
|
250 | 251 | app = import_item(app_path) |
|
251 |
except ImportError |
|
|
252 | except ImportError: | |
|
252 | 253 | self.log.info("Couldn't import %s, config file will be excluded", name) |
|
253 | 254 | except Exception: |
|
254 | 255 | self.log.warn('Unexpected error importing %s', name, exc_info=True) |
|
255 | 256 | return app |
|
256 | 257 | |
|
257 | 258 | def init_config_files(self): |
|
258 | 259 | super(ProfileCreate, self).init_config_files() |
|
259 | 260 | # use local imports, since these classes may import from here |
|
260 | 261 | from IPython.terminal.ipapp import TerminalIPythonApp |
|
261 | 262 | apps = [TerminalIPythonApp] |
|
262 | 263 | for app_path in ( |
|
263 | 264 | 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp', |
|
264 | 265 | 'IPython.html.notebookapp.NotebookApp', |
|
265 | 266 | 'IPython.nbconvert.nbconvertapp.NbConvertApp', |
|
266 | 267 | ): |
|
267 | 268 | app = self._import_app(app_path) |
|
268 | 269 | if app is not None: |
|
269 | 270 | apps.append(app) |
|
270 | 271 | if self.parallel: |
|
271 | 272 | from IPython.parallel.apps.ipcontrollerapp import IPControllerApp |
|
272 | 273 | from IPython.parallel.apps.ipengineapp import IPEngineApp |
|
273 | 274 | from IPython.parallel.apps.ipclusterapp import IPClusterStart |
|
274 | 275 | from IPython.parallel.apps.iploggerapp import IPLoggerApp |
|
275 | 276 | apps.extend([ |
|
276 | 277 | IPControllerApp, |
|
277 | 278 | IPEngineApp, |
|
278 | 279 | IPClusterStart, |
|
279 | 280 | IPLoggerApp, |
|
280 | 281 | ]) |
|
281 | 282 | for App in apps: |
|
282 | 283 | app = App() |
|
283 | 284 | app.config.update(self.config) |
|
284 | 285 | app.log = self.log |
|
285 | 286 | app.overwrite = self.overwrite |
|
286 | 287 | app.copy_config_files=True |
|
287 | 288 | app.profile = self.profile |
|
288 | 289 | app.init_profile_dir() |
|
289 | 290 | app.init_config_files() |
|
290 | 291 | |
|
291 | 292 | def stage_default_config_file(self): |
|
292 | 293 | pass |
|
293 | 294 | |
|
294 | 295 | |
|
295 | 296 | class ProfileApp(Application): |
|
296 | 297 | name = u'ipython-profile' |
|
297 | 298 | description = profile_help |
|
298 | 299 | examples = _main_examples |
|
299 | 300 | |
|
300 | 301 | subcommands = Dict(dict( |
|
301 | 302 | create = (ProfileCreate, ProfileCreate.description.splitlines()[0]), |
|
302 | 303 | list = (ProfileList, ProfileList.description.splitlines()[0]), |
|
303 | 304 | locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]), |
|
304 | 305 | )) |
|
305 | 306 | |
|
306 | 307 | def start(self): |
|
307 | 308 | if self.subapp is None: |
|
308 | 309 | print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())) |
|
309 | 310 | print() |
|
310 | 311 | self.print_description() |
|
311 | 312 | self.print_subcommands() |
|
312 | 313 | self.exit(1) |
|
313 | 314 | else: |
|
314 | 315 | return self.subapp.start() |
@@ -1,273 +1,274 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | An object for managing IPython profile directories. |
|
4 | 4 | |
|
5 | 5 | Authors: |
|
6 | 6 | |
|
7 | 7 | * Brian Granger |
|
8 | 8 | * Fernando Perez |
|
9 | 9 | * Min RK |
|
10 | 10 | |
|
11 | 11 | """ |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Copyright (C) 2011 The IPython Development Team |
|
15 | 15 | # |
|
16 | 16 | # Distributed under the terms of the BSD License. The full license is in |
|
17 | 17 | # the file COPYING, distributed as part of this software. |
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | 19 | |
|
20 | 20 | #----------------------------------------------------------------------------- |
|
21 | 21 | # Imports |
|
22 | 22 | #----------------------------------------------------------------------------- |
|
23 | 23 | |
|
24 | 24 | import os |
|
25 | 25 | import shutil |
|
26 | 26 | import errno |
|
27 | 27 | |
|
28 | 28 | from IPython.config.configurable import LoggingConfigurable |
|
29 | 29 | from IPython.utils.path import get_ipython_package_dir, expand_path |
|
30 | from IPython.utils import py3compat | |
|
30 | 31 | from IPython.utils.traitlets import Unicode, Bool |
|
31 | 32 | |
|
32 | 33 | #----------------------------------------------------------------------------- |
|
33 | 34 | # Classes and functions |
|
34 | 35 | #----------------------------------------------------------------------------- |
|
35 | 36 | |
|
36 | 37 | |
|
37 | 38 | #----------------------------------------------------------------------------- |
|
38 | 39 | # Module errors |
|
39 | 40 | #----------------------------------------------------------------------------- |
|
40 | 41 | |
|
41 | 42 | class ProfileDirError(Exception): |
|
42 | 43 | pass |
|
43 | 44 | |
|
44 | 45 | |
|
45 | 46 | #----------------------------------------------------------------------------- |
|
46 | 47 | # Class for managing profile directories |
|
47 | 48 | #----------------------------------------------------------------------------- |
|
48 | 49 | |
|
49 | 50 | class ProfileDir(LoggingConfigurable): |
|
50 | 51 | """An object to manage the profile directory and its resources. |
|
51 | 52 | |
|
52 | 53 | The profile directory is used by all IPython applications, to manage |
|
53 | 54 | configuration, logging and security. |
|
54 | 55 | |
|
55 | 56 | This object knows how to find, create and manage these directories. This |
|
56 | 57 | should be used by any code that wants to handle profiles. |
|
57 | 58 | """ |
|
58 | 59 | |
|
59 | 60 | security_dir_name = Unicode('security') |
|
60 | 61 | log_dir_name = Unicode('log') |
|
61 | 62 | startup_dir_name = Unicode('startup') |
|
62 | 63 | pid_dir_name = Unicode('pid') |
|
63 | 64 | static_dir_name = Unicode('static') |
|
64 | 65 | security_dir = Unicode(u'') |
|
65 | 66 | log_dir = Unicode(u'') |
|
66 | 67 | startup_dir = Unicode(u'') |
|
67 | 68 | pid_dir = Unicode(u'') |
|
68 | 69 | static_dir = Unicode(u'') |
|
69 | 70 | |
|
70 | 71 | location = Unicode(u'', config=True, |
|
71 | 72 | help="""Set the profile location directly. This overrides the logic used by the |
|
72 | 73 | `profile` option.""", |
|
73 | 74 | ) |
|
74 | 75 | |
|
75 | 76 | _location_isset = Bool(False) # flag for detecting multiply set location |
|
76 | 77 | |
|
77 | 78 | def _location_changed(self, name, old, new): |
|
78 | 79 | if self._location_isset: |
|
79 | 80 | raise RuntimeError("Cannot set profile location more than once.") |
|
80 | 81 | self._location_isset = True |
|
81 | 82 | if not os.path.isdir(new): |
|
82 | 83 | os.makedirs(new) |
|
83 | 84 | |
|
84 | 85 | # ensure config files exist: |
|
85 | 86 | self.security_dir = os.path.join(new, self.security_dir_name) |
|
86 | 87 | self.log_dir = os.path.join(new, self.log_dir_name) |
|
87 | 88 | self.startup_dir = os.path.join(new, self.startup_dir_name) |
|
88 | 89 | self.pid_dir = os.path.join(new, self.pid_dir_name) |
|
89 | 90 | self.static_dir = os.path.join(new, self.static_dir_name) |
|
90 | 91 | self.check_dirs() |
|
91 | 92 | |
|
92 | 93 | def _log_dir_changed(self, name, old, new): |
|
93 | 94 | self.check_log_dir() |
|
94 | 95 | |
|
95 | 96 | def _mkdir(self, path, mode=None): |
|
96 | 97 | """ensure a directory exists at a given path |
|
97 | 98 | |
|
98 | 99 | This is a version of os.mkdir, with the following differences: |
|
99 | 100 | |
|
100 | 101 | - returns True if it created the directory, False otherwise |
|
101 | 102 | - ignores EEXIST, protecting against race conditions where |
|
102 | 103 | the dir may have been created in between the check and |
|
103 | 104 | the creation |
|
104 | 105 | - sets permissions if requested and the dir already exists |
|
105 | 106 | """ |
|
106 | 107 | if os.path.exists(path): |
|
107 | 108 | if mode and os.stat(path).st_mode != mode: |
|
108 | 109 | try: |
|
109 | 110 | os.chmod(path, mode) |
|
110 | 111 | except OSError: |
|
111 | 112 | self.log.warn( |
|
112 | 113 | "Could not set permissions on %s", |
|
113 | 114 | path |
|
114 | 115 | ) |
|
115 | 116 | return False |
|
116 | 117 | try: |
|
117 | 118 | if mode: |
|
118 | 119 | os.mkdir(path, mode) |
|
119 | 120 | else: |
|
120 | 121 | os.mkdir(path) |
|
121 | 122 | except OSError as e: |
|
122 | 123 | if e.errno == errno.EEXIST: |
|
123 | 124 | return False |
|
124 | 125 | else: |
|
125 | 126 | raise |
|
126 | 127 | |
|
127 | 128 | return True |
|
128 | 129 | |
|
129 | 130 | def check_log_dir(self): |
|
130 | 131 | self._mkdir(self.log_dir) |
|
131 | 132 | |
|
132 | 133 | def _startup_dir_changed(self, name, old, new): |
|
133 | 134 | self.check_startup_dir() |
|
134 | 135 | |
|
135 | 136 | def check_startup_dir(self): |
|
136 | 137 | self._mkdir(self.startup_dir) |
|
137 | 138 | |
|
138 | 139 | readme = os.path.join(self.startup_dir, 'README') |
|
139 | 140 | src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP') |
|
140 | 141 | |
|
141 | 142 | if not os.path.exists(src): |
|
142 | 143 | self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src) |
|
143 | 144 | |
|
144 | 145 | if os.path.exists(src) and not os.path.exists(readme): |
|
145 | 146 | shutil.copy(src, readme) |
|
146 | 147 | |
|
147 | 148 | def _security_dir_changed(self, name, old, new): |
|
148 | 149 | self.check_security_dir() |
|
149 | 150 | |
|
150 | 151 | def check_security_dir(self): |
|
151 | 152 | self._mkdir(self.security_dir, 0o40700) |
|
152 | 153 | |
|
153 | 154 | def _pid_dir_changed(self, name, old, new): |
|
154 | 155 | self.check_pid_dir() |
|
155 | 156 | |
|
156 | 157 | def check_pid_dir(self): |
|
157 | 158 | self._mkdir(self.pid_dir, 0o40700) |
|
158 | 159 | |
|
159 | 160 | def _static_dir_changed(self, name, old, new): |
|
160 | 161 | self.check_startup_dir() |
|
161 | 162 | |
|
162 | 163 | def check_static_dir(self): |
|
163 | 164 | self._mkdir(self.static_dir) |
|
164 | 165 | custom = os.path.join(self.static_dir, 'custom') |
|
165 | 166 | self._mkdir(custom) |
|
166 | 167 | from IPython.html import DEFAULT_STATIC_FILES_PATH |
|
167 | 168 | for fname in ('custom.js', 'custom.css'): |
|
168 | 169 | src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname) |
|
169 | 170 | dest = os.path.join(custom, fname) |
|
170 | 171 | if not os.path.exists(src): |
|
171 | 172 | self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src) |
|
172 | 173 | continue |
|
173 | 174 | if not os.path.exists(dest): |
|
174 | 175 | shutil.copy(src, dest) |
|
175 | 176 | |
|
176 | 177 | def check_dirs(self): |
|
177 | 178 | self.check_security_dir() |
|
178 | 179 | self.check_log_dir() |
|
179 | 180 | self.check_pid_dir() |
|
180 | 181 | self.check_startup_dir() |
|
181 | 182 | self.check_static_dir() |
|
182 | 183 | |
|
183 | 184 | def copy_config_file(self, config_file, path=None, overwrite=False): |
|
184 | 185 | """Copy a default config file into the active profile directory. |
|
185 | 186 | |
|
186 | 187 | Default configuration files are kept in :mod:`IPython.config.default`. |
|
187 | 188 | This function moves these from that location to the working profile |
|
188 | 189 | directory. |
|
189 | 190 | """ |
|
190 | 191 | dst = os.path.join(self.location, config_file) |
|
191 | 192 | if os.path.isfile(dst) and not overwrite: |
|
192 | 193 | return False |
|
193 | 194 | if path is None: |
|
194 | 195 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') |
|
195 | 196 | src = os.path.join(path, config_file) |
|
196 | 197 | shutil.copy(src, dst) |
|
197 | 198 | return True |
|
198 | 199 | |
|
199 | 200 | @classmethod |
|
200 | 201 | def create_profile_dir(cls, profile_dir, config=None): |
|
201 | 202 | """Create a new profile directory given a full path. |
|
202 | 203 | |
|
203 | 204 | Parameters |
|
204 | 205 | ---------- |
|
205 | 206 | profile_dir : str |
|
206 | 207 | The full path to the profile directory. If it does exist, it will |
|
207 | 208 | be used. If not, it will be created. |
|
208 | 209 | """ |
|
209 | 210 | return cls(location=profile_dir, config=config) |
|
210 | 211 | |
|
211 | 212 | @classmethod |
|
212 | 213 | def create_profile_dir_by_name(cls, path, name=u'default', config=None): |
|
213 | 214 | """Create a profile dir by profile name and path. |
|
214 | 215 | |
|
215 | 216 | Parameters |
|
216 | 217 | ---------- |
|
217 | 218 | path : unicode |
|
218 | 219 | The path (directory) to put the profile directory in. |
|
219 | 220 | name : unicode |
|
220 | 221 | The name of the profile. The name of the profile directory will |
|
221 | 222 | be "profile_<profile>". |
|
222 | 223 | """ |
|
223 | 224 | if not os.path.isdir(path): |
|
224 | 225 | raise ProfileDirError('Directory not found: %s' % path) |
|
225 | 226 | profile_dir = os.path.join(path, u'profile_' + name) |
|
226 | 227 | return cls(location=profile_dir, config=config) |
|
227 | 228 | |
|
228 | 229 | @classmethod |
|
229 | 230 | def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): |
|
230 | 231 | """Find an existing profile dir by profile name, return its ProfileDir. |
|
231 | 232 | |
|
232 | 233 | This searches through a sequence of paths for a profile dir. If it |
|
233 | 234 | is not found, a :class:`ProfileDirError` exception will be raised. |
|
234 | 235 | |
|
235 | 236 | The search path algorithm is: |
|
236 |
1. `` |
|
|
237 | 1. ``py3compat.getcwd()`` | |
|
237 | 238 | 2. ``ipython_dir`` |
|
238 | 239 | |
|
239 | 240 | Parameters |
|
240 | 241 | ---------- |
|
241 | 242 | ipython_dir : unicode or str |
|
242 | 243 | The IPython directory to use. |
|
243 | 244 | name : unicode or str |
|
244 | 245 | The name of the profile. The name of the profile directory |
|
245 | 246 | will be "profile_<profile>". |
|
246 | 247 | """ |
|
247 | 248 | dirname = u'profile_' + name |
|
248 |
paths = [ |
|
|
249 | paths = [py3compat.getcwd(), ipython_dir] | |
|
249 | 250 | for p in paths: |
|
250 | 251 | profile_dir = os.path.join(p, dirname) |
|
251 | 252 | if os.path.isdir(profile_dir): |
|
252 | 253 | return cls(location=profile_dir, config=config) |
|
253 | 254 | else: |
|
254 | 255 | raise ProfileDirError('Profile directory not found in paths: %s' % dirname) |
|
255 | 256 | |
|
256 | 257 | @classmethod |
|
257 | 258 | def find_profile_dir(cls, profile_dir, config=None): |
|
258 | 259 | """Find/create a profile dir and return its ProfileDir. |
|
259 | 260 | |
|
260 | 261 | This will create the profile directory if it doesn't exist. |
|
261 | 262 | |
|
262 | 263 | Parameters |
|
263 | 264 | ---------- |
|
264 | 265 | profile_dir : unicode or str |
|
265 | 266 | The path of the profile directory. This is expanded using |
|
266 | 267 | :func:`IPython.utils.genutils.expand_path`. |
|
267 | 268 | """ |
|
268 | 269 | profile_dir = expand_path(profile_dir) |
|
269 | 270 | if not os.path.isdir(profile_dir): |
|
270 | 271 | raise ProfileDirError('Profile directory not found: %s' % profile_dir) |
|
271 | 272 | return cls(location=profile_dir, config=config) |
|
272 | 273 | |
|
273 | 274 |
@@ -1,439 +1,439 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Classes for handling input/output prompts. |
|
3 | 3 | |
|
4 | 4 | Authors: |
|
5 | 5 | |
|
6 | 6 | * Fernando Perez |
|
7 | 7 | * Brian Granger |
|
8 | 8 | * Thomas Kluyver |
|
9 | 9 | """ |
|
10 | 10 | |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | # Copyright (C) 2008-2011 The IPython Development Team |
|
13 | 13 | # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> |
|
14 | 14 | # |
|
15 | 15 | # Distributed under the terms of the BSD License. The full license is in |
|
16 | 16 | # the file COPYING, distributed as part of this software. |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | 19 | #----------------------------------------------------------------------------- |
|
20 | 20 | # Imports |
|
21 | 21 | #----------------------------------------------------------------------------- |
|
22 | 22 | |
|
23 | 23 | import os |
|
24 | 24 | import re |
|
25 | 25 | import socket |
|
26 | 26 | import sys |
|
27 | 27 | import time |
|
28 | 28 | |
|
29 | 29 | from string import Formatter |
|
30 | 30 | |
|
31 | 31 | from IPython.config.configurable import Configurable |
|
32 | 32 | from IPython.core import release |
|
33 | 33 | from IPython.utils import coloransi, py3compat |
|
34 | 34 | from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int) |
|
35 | 35 | |
|
36 | 36 | #----------------------------------------------------------------------------- |
|
37 | 37 | # Color schemes for prompts |
|
38 | 38 | #----------------------------------------------------------------------------- |
|
39 | 39 | |
|
40 | 40 | InputColors = coloransi.InputTermColors # just a shorthand |
|
41 | 41 | Colors = coloransi.TermColors # just a shorthand |
|
42 | 42 | |
|
43 | 43 | color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors()) |
|
44 | 44 | |
|
45 | 45 | PColNoColors = coloransi.ColorScheme( |
|
46 | 46 | 'NoColor', |
|
47 | 47 | in_prompt = InputColors.NoColor, # Input prompt |
|
48 | 48 | in_number = InputColors.NoColor, # Input prompt number |
|
49 | 49 | in_prompt2 = InputColors.NoColor, # Continuation prompt |
|
50 | 50 | in_normal = InputColors.NoColor, # color off (usu. Colors.Normal) |
|
51 | 51 | |
|
52 | 52 | out_prompt = Colors.NoColor, # Output prompt |
|
53 | 53 | out_number = Colors.NoColor, # Output prompt number |
|
54 | 54 | |
|
55 | 55 | normal = Colors.NoColor # color off (usu. Colors.Normal) |
|
56 | 56 | ) |
|
57 | 57 | |
|
58 | 58 | # make some schemes as instances so we can copy them for modification easily: |
|
59 | 59 | PColLinux = coloransi.ColorScheme( |
|
60 | 60 | 'Linux', |
|
61 | 61 | in_prompt = InputColors.Green, |
|
62 | 62 | in_number = InputColors.LightGreen, |
|
63 | 63 | in_prompt2 = InputColors.Green, |
|
64 | 64 | in_normal = InputColors.Normal, # color off (usu. Colors.Normal) |
|
65 | 65 | |
|
66 | 66 | out_prompt = Colors.Red, |
|
67 | 67 | out_number = Colors.LightRed, |
|
68 | 68 | |
|
69 | 69 | normal = Colors.Normal |
|
70 | 70 | ) |
|
71 | 71 | |
|
72 | 72 | # Slightly modified Linux for light backgrounds |
|
73 | 73 | PColLightBG = PColLinux.copy('LightBG') |
|
74 | 74 | |
|
75 | 75 | PColLightBG.colors.update( |
|
76 | 76 | in_prompt = InputColors.Blue, |
|
77 | 77 | in_number = InputColors.LightBlue, |
|
78 | 78 | in_prompt2 = InputColors.Blue |
|
79 | 79 | ) |
|
80 | 80 | |
|
81 | 81 | #----------------------------------------------------------------------------- |
|
82 | 82 | # Utilities |
|
83 | 83 | #----------------------------------------------------------------------------- |
|
84 | 84 | |
|
85 | 85 | class LazyEvaluate(object): |
|
86 | 86 | """This is used for formatting strings with values that need to be updated |
|
87 | 87 | at that time, such as the current time or working directory.""" |
|
88 | 88 | def __init__(self, func, *args, **kwargs): |
|
89 | 89 | self.func = func |
|
90 | 90 | self.args = args |
|
91 | 91 | self.kwargs = kwargs |
|
92 | 92 | |
|
93 | 93 | def __call__(self, **kwargs): |
|
94 | 94 | self.kwargs.update(kwargs) |
|
95 | 95 | return self.func(*self.args, **self.kwargs) |
|
96 | 96 | |
|
97 | 97 | def __str__(self): |
|
98 | 98 | return str(self()) |
|
99 | 99 | |
|
100 | 100 | def __unicode__(self): |
|
101 | 101 | return py3compat.unicode_type(self()) |
|
102 | 102 | |
|
103 | 103 | def __format__(self, format_spec): |
|
104 | 104 | return format(self(), format_spec) |
|
105 | 105 | |
|
106 | 106 | def multiple_replace(dict, text): |
|
107 | 107 | """ Replace in 'text' all occurences of any key in the given |
|
108 | 108 | dictionary by its corresponding value. Returns the new string.""" |
|
109 | 109 | |
|
110 | 110 | # Function by Xavier Defrang, originally found at: |
|
111 | 111 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330 |
|
112 | 112 | |
|
113 | 113 | # Create a regular expression from the dictionary keys |
|
114 | 114 | regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys()))) |
|
115 | 115 | # For each match, look-up corresponding value in dictionary |
|
116 | 116 | return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) |
|
117 | 117 | |
|
118 | 118 | #----------------------------------------------------------------------------- |
|
119 | 119 | # Special characters that can be used in prompt templates, mainly bash-like |
|
120 | 120 | #----------------------------------------------------------------------------- |
|
121 | 121 | |
|
122 | 122 | # If $HOME isn't defined (Windows), make it an absurd string so that it can |
|
123 | 123 | # never be expanded out into '~'. Basically anything which can never be a |
|
124 | 124 | # reasonable directory name will do, we just want the $HOME -> '~' operation |
|
125 | 125 | # to become a no-op. We pre-compute $HOME here so it's not done on every |
|
126 | 126 | # prompt call. |
|
127 | 127 | |
|
128 | 128 | # FIXME: |
|
129 | 129 | |
|
130 | 130 | # - This should be turned into a class which does proper namespace management, |
|
131 | 131 | # since the prompt specials need to be evaluated in a certain namespace. |
|
132 | 132 | # Currently it's just globals, which need to be managed manually by code |
|
133 | 133 | # below. |
|
134 | 134 | |
|
135 | 135 | # - I also need to split up the color schemes from the prompt specials |
|
136 | 136 | # somehow. I don't have a clean design for that quite yet. |
|
137 | 137 | |
|
138 | 138 | HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")) |
|
139 | 139 | |
|
140 | 140 | # This is needed on FreeBSD, and maybe other systems which symlink /home to |
|
141 | 141 | # /usr/home, but retain the $HOME variable as pointing to /home |
|
142 | 142 | HOME = os.path.realpath(HOME) |
|
143 | 143 | |
|
144 | 144 | # We precompute a few more strings here for the prompt_specials, which are |
|
145 | 145 | # fixed once ipython starts. This reduces the runtime overhead of computing |
|
146 | 146 | # prompt strings. |
|
147 | 147 | USER = py3compat.str_to_unicode(os.environ.get("USER",'')) |
|
148 | 148 | HOSTNAME = py3compat.str_to_unicode(socket.gethostname()) |
|
149 | 149 | HOSTNAME_SHORT = HOSTNAME.split(".")[0] |
|
150 | 150 | ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$" |
|
151 | 151 | |
|
152 | 152 | prompt_abbreviations = { |
|
153 | 153 | # Prompt/history count |
|
154 | 154 | '%n' : '{color.number}' '{count}' '{color.prompt}', |
|
155 | 155 | r'\#': '{color.number}' '{count}' '{color.prompt}', |
|
156 | 156 | # Just the prompt counter number, WITHOUT any coloring wrappers, so users |
|
157 | 157 | # can get numbers displayed in whatever color they want. |
|
158 | 158 | r'\N': '{count}', |
|
159 | 159 | |
|
160 | 160 | # Prompt/history count, with the actual digits replaced by dots. Used |
|
161 | 161 | # mainly in continuation prompts (prompt_in2) |
|
162 | 162 | r'\D': '{dots}', |
|
163 | 163 | |
|
164 | 164 | # Current time |
|
165 | 165 | r'\T' : '{time}', |
|
166 | 166 | # Current working directory |
|
167 | 167 | r'\w': '{cwd}', |
|
168 | 168 | # Basename of current working directory. |
|
169 | 169 | # (use os.sep to make this portable across OSes) |
|
170 | 170 | r'\W' : '{cwd_last}', |
|
171 | 171 | # These X<N> are an extension to the normal bash prompts. They return |
|
172 | 172 | # N terms of the path, after replacing $HOME with '~' |
|
173 | 173 | r'\X0': '{cwd_x[0]}', |
|
174 | 174 | r'\X1': '{cwd_x[1]}', |
|
175 | 175 | r'\X2': '{cwd_x[2]}', |
|
176 | 176 | r'\X3': '{cwd_x[3]}', |
|
177 | 177 | r'\X4': '{cwd_x[4]}', |
|
178 | 178 | r'\X5': '{cwd_x[5]}', |
|
179 | 179 | # Y<N> are similar to X<N>, but they show '~' if it's the directory |
|
180 | 180 | # N+1 in the list. Somewhat like %cN in tcsh. |
|
181 | 181 | r'\Y0': '{cwd_y[0]}', |
|
182 | 182 | r'\Y1': '{cwd_y[1]}', |
|
183 | 183 | r'\Y2': '{cwd_y[2]}', |
|
184 | 184 | r'\Y3': '{cwd_y[3]}', |
|
185 | 185 | r'\Y4': '{cwd_y[4]}', |
|
186 | 186 | r'\Y5': '{cwd_y[5]}', |
|
187 | 187 | # Hostname up to first . |
|
188 | 188 | r'\h': HOSTNAME_SHORT, |
|
189 | 189 | # Full hostname |
|
190 | 190 | r'\H': HOSTNAME, |
|
191 | 191 | # Username of current user |
|
192 | 192 | r'\u': USER, |
|
193 | 193 | # Escaped '\' |
|
194 | 194 | '\\\\': '\\', |
|
195 | 195 | # Newline |
|
196 | 196 | r'\n': '\n', |
|
197 | 197 | # Carriage return |
|
198 | 198 | r'\r': '\r', |
|
199 | 199 | # Release version |
|
200 | 200 | r'\v': release.version, |
|
201 | 201 | # Root symbol ($ or #) |
|
202 | 202 | r'\$': ROOT_SYMBOL, |
|
203 | 203 | } |
|
204 | 204 | |
|
205 | 205 | #----------------------------------------------------------------------------- |
|
206 | 206 | # More utilities |
|
207 | 207 | #----------------------------------------------------------------------------- |
|
208 | 208 | |
|
209 | 209 | def cwd_filt(depth): |
|
210 | 210 | """Return the last depth elements of the current working directory. |
|
211 | 211 | |
|
212 | 212 | $HOME is always replaced with '~'. |
|
213 | 213 | If depth==0, the full path is returned.""" |
|
214 | 214 | |
|
215 |
cwd = |
|
|
215 | cwd = py3compat.getcwd().replace(HOME,"~") | |
|
216 | 216 | out = os.sep.join(cwd.split(os.sep)[-depth:]) |
|
217 | 217 | return out or os.sep |
|
218 | 218 | |
|
219 | 219 | def cwd_filt2(depth): |
|
220 | 220 | """Return the last depth elements of the current working directory. |
|
221 | 221 | |
|
222 | 222 | $HOME is always replaced with '~'. |
|
223 | 223 | If depth==0, the full path is returned.""" |
|
224 | 224 | |
|
225 |
full_cwd = |
|
|
225 | full_cwd = py3compat.getcwd() | |
|
226 | 226 | cwd = full_cwd.replace(HOME,"~").split(os.sep) |
|
227 | 227 | if '~' in cwd and len(cwd) == depth+1: |
|
228 | 228 | depth += 1 |
|
229 | 229 | drivepart = '' |
|
230 | 230 | if sys.platform == 'win32' and len(cwd) > depth: |
|
231 | 231 | drivepart = os.path.splitdrive(full_cwd)[0] |
|
232 | 232 | out = drivepart + '/'.join(cwd[-depth:]) |
|
233 | 233 | |
|
234 | 234 | return out or os.sep |
|
235 | 235 | |
|
236 | 236 | #----------------------------------------------------------------------------- |
|
237 | 237 | # Prompt classes |
|
238 | 238 | #----------------------------------------------------------------------------- |
|
239 | 239 | |
|
240 | 240 | lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"), |
|
241 |
'cwd': LazyEvaluate( |
|
|
242 |
'cwd_last': LazyEvaluate(lambda: |
|
|
243 |
'cwd_x': [LazyEvaluate(lambda: |
|
|
241 | 'cwd': LazyEvaluate(py3compat.getcwd), | |
|
242 | 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]), | |
|
243 | 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\ | |
|
244 | 244 | [LazyEvaluate(cwd_filt, x) for x in range(1,6)], |
|
245 | 245 | 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)] |
|
246 | 246 | } |
|
247 | 247 | |
|
248 | 248 | def _lenlastline(s): |
|
249 | 249 | """Get the length of the last line. More intelligent than |
|
250 | 250 | len(s.splitlines()[-1]). |
|
251 | 251 | """ |
|
252 | 252 | if not s or s.endswith(('\n', '\r')): |
|
253 | 253 | return 0 |
|
254 | 254 | return len(s.splitlines()[-1]) |
|
255 | 255 | |
|
256 | 256 | |
|
257 | 257 | class UserNSFormatter(Formatter): |
|
258 | 258 | """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution""" |
|
259 | 259 | def __init__(self, shell): |
|
260 | 260 | self.shell = shell |
|
261 | 261 | |
|
262 | 262 | def get_value(self, key, args, kwargs): |
|
263 | 263 | # try regular formatting first: |
|
264 | 264 | try: |
|
265 | 265 | return Formatter.get_value(self, key, args, kwargs) |
|
266 | 266 | except Exception: |
|
267 | 267 | pass |
|
268 | 268 | # next, look in user_ns and builtins: |
|
269 | 269 | for container in (self.shell.user_ns, __builtins__): |
|
270 | 270 | if key in container: |
|
271 | 271 | return container[key] |
|
272 | 272 | # nothing found, put error message in its place |
|
273 | 273 | return "<ERROR: '%s' not found>" % key |
|
274 | 274 | |
|
275 | 275 | |
|
276 | 276 | class PromptManager(Configurable): |
|
277 | 277 | """This is the primary interface for producing IPython's prompts.""" |
|
278 | 278 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') |
|
279 | 279 | |
|
280 | 280 | color_scheme_table = Instance(coloransi.ColorSchemeTable) |
|
281 | 281 | color_scheme = Unicode('Linux', config=True) |
|
282 | 282 | def _color_scheme_changed(self, name, new_value): |
|
283 | 283 | self.color_scheme_table.set_active_scheme(new_value) |
|
284 | 284 | for pname in ['in', 'in2', 'out', 'rewrite']: |
|
285 | 285 | # We need to recalculate the number of invisible characters |
|
286 | 286 | self.update_prompt(pname) |
|
287 | 287 | |
|
288 | 288 | lazy_evaluate_fields = Dict(help=""" |
|
289 | 289 | This maps field names used in the prompt templates to functions which |
|
290 | 290 | will be called when the prompt is rendered. This allows us to include |
|
291 | 291 | things like the current time in the prompts. Functions are only called |
|
292 | 292 | if they are used in the prompt. |
|
293 | 293 | """) |
|
294 | 294 | def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy() |
|
295 | 295 | |
|
296 | 296 | in_template = Unicode('In [\\#]: ', config=True, |
|
297 | 297 | help="Input prompt. '\\#' will be transformed to the prompt number") |
|
298 | 298 | in2_template = Unicode(' .\\D.: ', config=True, |
|
299 | 299 | help="Continuation prompt.") |
|
300 | 300 | out_template = Unicode('Out[\\#]: ', config=True, |
|
301 | 301 | help="Output prompt. '\\#' will be transformed to the prompt number") |
|
302 | 302 | |
|
303 | 303 | justify = Bool(True, config=True, help=""" |
|
304 | 304 | If True (default), each prompt will be right-aligned with the |
|
305 | 305 | preceding one. |
|
306 | 306 | """) |
|
307 | 307 | |
|
308 | 308 | # We actually store the expanded templates here: |
|
309 | 309 | templates = Dict() |
|
310 | 310 | |
|
311 | 311 | # The number of characters in the last prompt rendered, not including |
|
312 | 312 | # colour characters. |
|
313 | 313 | width = Int() |
|
314 | 314 | txtwidth = Int() # Not including right-justification |
|
315 | 315 | |
|
316 | 316 | # The number of characters in each prompt which don't contribute to width |
|
317 | 317 | invisible_chars = Dict() |
|
318 | 318 | def _invisible_chars_default(self): |
|
319 | 319 | return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0} |
|
320 | 320 | |
|
321 | 321 | def __init__(self, shell, **kwargs): |
|
322 | 322 | super(PromptManager, self).__init__(shell=shell, **kwargs) |
|
323 | 323 | |
|
324 | 324 | # Prepare colour scheme table |
|
325 | 325 | self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors, |
|
326 | 326 | PColLinux, PColLightBG], self.color_scheme) |
|
327 | 327 | |
|
328 | 328 | self._formatter = UserNSFormatter(shell) |
|
329 | 329 | # Prepare templates & numbers of invisible characters |
|
330 | 330 | self.update_prompt('in', self.in_template) |
|
331 | 331 | self.update_prompt('in2', self.in2_template) |
|
332 | 332 | self.update_prompt('out', self.out_template) |
|
333 | 333 | self.update_prompt('rewrite') |
|
334 | 334 | self.on_trait_change(self._update_prompt_trait, ['in_template', |
|
335 | 335 | 'in2_template', 'out_template']) |
|
336 | 336 | |
|
337 | 337 | def update_prompt(self, name, new_template=None): |
|
338 | 338 | """This is called when a prompt template is updated. It processes |
|
339 | 339 | abbreviations used in the prompt template (like \#) and calculates how |
|
340 | 340 | many invisible characters (ANSI colour escapes) the resulting prompt |
|
341 | 341 | contains. |
|
342 | 342 | |
|
343 | 343 | It is also called for each prompt on changing the colour scheme. In both |
|
344 | 344 | cases, traitlets should take care of calling this automatically. |
|
345 | 345 | """ |
|
346 | 346 | if new_template is not None: |
|
347 | 347 | self.templates[name] = multiple_replace(prompt_abbreviations, new_template) |
|
348 | 348 | # We count invisible characters (colour escapes) on the last line of the |
|
349 | 349 | # prompt, to calculate the width for lining up subsequent prompts. |
|
350 | 350 | invis_chars = _lenlastline(self._render(name, color=True)) - \ |
|
351 | 351 | _lenlastline(self._render(name, color=False)) |
|
352 | 352 | self.invisible_chars[name] = invis_chars |
|
353 | 353 | |
|
354 | 354 | def _update_prompt_trait(self, traitname, new_template): |
|
355 | 355 | name = traitname[:-9] # Cut off '_template' |
|
356 | 356 | self.update_prompt(name, new_template) |
|
357 | 357 | |
|
358 | 358 | def _render(self, name, color=True, **kwargs): |
|
359 | 359 | """Render but don't justify, or update the width or txtwidth attributes. |
|
360 | 360 | """ |
|
361 | 361 | if name == 'rewrite': |
|
362 | 362 | return self._render_rewrite(color=color) |
|
363 | 363 | |
|
364 | 364 | if color: |
|
365 | 365 | scheme = self.color_scheme_table.active_colors |
|
366 | 366 | if name=='out': |
|
367 | 367 | colors = color_lists['normal'] |
|
368 | 368 | colors.number, colors.prompt, colors.normal = \ |
|
369 | 369 | scheme.out_number, scheme.out_prompt, scheme.normal |
|
370 | 370 | else: |
|
371 | 371 | colors = color_lists['inp'] |
|
372 | 372 | colors.number, colors.prompt, colors.normal = \ |
|
373 | 373 | scheme.in_number, scheme.in_prompt, scheme.in_normal |
|
374 | 374 | if name=='in2': |
|
375 | 375 | colors.prompt = scheme.in_prompt2 |
|
376 | 376 | else: |
|
377 | 377 | # No color |
|
378 | 378 | colors = color_lists['nocolor'] |
|
379 | 379 | colors.number, colors.prompt, colors.normal = '', '', '' |
|
380 | 380 | |
|
381 | 381 | count = self.shell.execution_count # Shorthand |
|
382 | 382 | # Build the dictionary to be passed to string formatting |
|
383 | 383 | fmtargs = dict(color=colors, count=count, |
|
384 | 384 | dots="."*len(str(count)), |
|
385 | 385 | width=self.width, txtwidth=self.txtwidth ) |
|
386 | 386 | fmtargs.update(self.lazy_evaluate_fields) |
|
387 | 387 | fmtargs.update(kwargs) |
|
388 | 388 | |
|
389 | 389 | # Prepare the prompt |
|
390 | 390 | prompt = colors.prompt + self.templates[name] + colors.normal |
|
391 | 391 | |
|
392 | 392 | # Fill in required fields |
|
393 | 393 | return self._formatter.format(prompt, **fmtargs) |
|
394 | 394 | |
|
395 | 395 | def _render_rewrite(self, color=True): |
|
396 | 396 | """Render the ---> rewrite prompt.""" |
|
397 | 397 | if color: |
|
398 | 398 | scheme = self.color_scheme_table.active_colors |
|
399 | 399 | # We need a non-input version of these escapes |
|
400 | 400 | color_prompt = scheme.in_prompt.replace("\001","").replace("\002","") |
|
401 | 401 | color_normal = scheme.normal |
|
402 | 402 | else: |
|
403 | 403 | color_prompt, color_normal = '', '' |
|
404 | 404 | |
|
405 | 405 | return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal |
|
406 | 406 | |
|
407 | 407 | def render(self, name, color=True, just=None, **kwargs): |
|
408 | 408 | """ |
|
409 | 409 | Render the selected prompt. |
|
410 | 410 | |
|
411 | 411 | Parameters |
|
412 | 412 | ---------- |
|
413 | 413 | name : str |
|
414 | 414 | Which prompt to render. One of 'in', 'in2', 'out', 'rewrite' |
|
415 | 415 | color : bool |
|
416 | 416 | If True (default), include ANSI escape sequences for a coloured prompt. |
|
417 | 417 | just : bool |
|
418 | 418 | If True, justify the prompt to the width of the last prompt. The |
|
419 | 419 | default is stored in self.justify. |
|
420 | 420 | **kwargs : |
|
421 | 421 | Additional arguments will be passed to the string formatting operation, |
|
422 | 422 | so they can override the values that would otherwise fill in the |
|
423 | 423 | template. |
|
424 | 424 | |
|
425 | 425 | Returns |
|
426 | 426 | ------- |
|
427 | 427 | A string containing the rendered prompt. |
|
428 | 428 | """ |
|
429 | 429 | res = self._render(name, color=color, **kwargs) |
|
430 | 430 | |
|
431 | 431 | # Handle justification of prompt |
|
432 | 432 | invis_chars = self.invisible_chars[name] if color else 0 |
|
433 | 433 | self.txtwidth = _lenlastline(res) - invis_chars |
|
434 | 434 | just = self.justify if (just is None) else just |
|
435 | 435 | # If the prompt spans more than one line, don't try to justify it: |
|
436 | 436 | if just and name != 'in' and ('\n' not in res) and ('\r' not in res): |
|
437 | 437 | res = res.rjust(self.width + invis_chars) |
|
438 | 438 | self.width = _lenlastline(res) - invis_chars |
|
439 | 439 | return res |
@@ -1,50 +1,50 b'' | |||
|
1 | 1 | # coding: utf-8 |
|
2 | 2 | """Tests for IPython.core.application""" |
|
3 | 3 | |
|
4 | 4 | import os |
|
5 | 5 | import tempfile |
|
6 | 6 | |
|
7 | 7 | from IPython.core.application import BaseIPythonApplication |
|
8 | 8 | from IPython.testing import decorators as dec |
|
9 | 9 | from IPython.utils import py3compat |
|
10 | 10 | |
|
11 | 11 | @dec.onlyif_unicode_paths |
|
12 | 12 | def test_unicode_cwd(): |
|
13 | 13 | """Check that IPython starts with non-ascii characters in the path.""" |
|
14 | 14 | wd = tempfile.mkdtemp(suffix=u"β¬") |
|
15 | 15 | |
|
16 |
old_wd = |
|
|
16 | old_wd = py3compat.getcwd() | |
|
17 | 17 | os.chdir(wd) |
|
18 |
#raise Exception(repr( |
|
|
18 | #raise Exception(repr(py3compat.getcwd())) | |
|
19 | 19 | try: |
|
20 | 20 | app = BaseIPythonApplication() |
|
21 | 21 | # The lines below are copied from Application.initialize() |
|
22 | 22 | app.init_profile_dir() |
|
23 | 23 | app.init_config_files() |
|
24 | 24 | app.load_config_file(suppress_errors=False) |
|
25 | 25 | finally: |
|
26 | 26 | os.chdir(old_wd) |
|
27 | 27 | |
|
28 | 28 | @dec.onlyif_unicode_paths |
|
29 | 29 | def test_unicode_ipdir(): |
|
30 | 30 | """Check that IPython starts with non-ascii characters in the IP dir.""" |
|
31 | 31 | ipdir = tempfile.mkdtemp(suffix=u"β¬") |
|
32 | 32 | |
|
33 | 33 | # Create the config file, so it tries to load it. |
|
34 | 34 | with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f: |
|
35 | 35 | pass |
|
36 | 36 | |
|
37 | 37 | old_ipdir1 = os.environ.pop("IPYTHONDIR", None) |
|
38 | 38 | old_ipdir2 = os.environ.pop("IPYTHON_DIR", None) |
|
39 | 39 | os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8") |
|
40 | 40 | try: |
|
41 | 41 | app = BaseIPythonApplication() |
|
42 | 42 | # The lines below are copied from Application.initialize() |
|
43 | 43 | app.init_profile_dir() |
|
44 | 44 | app.init_config_files() |
|
45 | 45 | app.load_config_file(suppress_errors=False) |
|
46 | 46 | finally: |
|
47 | 47 | if old_ipdir1: |
|
48 | 48 | os.environ["IPYTHONDIR"] = old_ipdir1 |
|
49 | 49 | if old_ipdir2: |
|
50 | 50 | os.environ["IPYTHONDIR"] = old_ipdir2 |
@@ -1,393 +1,394 b'' | |||
|
1 | 1 | """Tests for the IPython tab-completion machinery. |
|
2 | 2 | """ |
|
3 | 3 | #----------------------------------------------------------------------------- |
|
4 | 4 | # Module imports |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | |
|
7 | 7 | # stdlib |
|
8 | 8 | import os |
|
9 | 9 | import sys |
|
10 | 10 | import unittest |
|
11 | 11 | |
|
12 | 12 | # third party |
|
13 | 13 | import nose.tools as nt |
|
14 | 14 | |
|
15 | 15 | # our own packages |
|
16 | 16 | from IPython.config.loader import Config |
|
17 | 17 | from IPython.core import completer |
|
18 | 18 | from IPython.external.decorators import knownfailureif |
|
19 | 19 | from IPython.utils.tempdir import TemporaryDirectory |
|
20 | 20 | from IPython.utils.generics import complete_object |
|
21 | from IPython.utils import py3compat | |
|
21 | 22 | from IPython.utils.py3compat import string_types, unicode_type |
|
22 | 23 | |
|
23 | 24 | #----------------------------------------------------------------------------- |
|
24 | 25 | # Test functions |
|
25 | 26 | #----------------------------------------------------------------------------- |
|
26 | 27 | def test_protect_filename(): |
|
27 | 28 | pairs = [ ('abc','abc'), |
|
28 | 29 | (' abc',r'\ abc'), |
|
29 | 30 | ('a bc',r'a\ bc'), |
|
30 | 31 | ('a bc',r'a\ \ bc'), |
|
31 | 32 | (' bc',r'\ \ bc'), |
|
32 | 33 | ] |
|
33 | 34 | # On posix, we also protect parens and other special characters |
|
34 | 35 | if sys.platform != 'win32': |
|
35 | 36 | pairs.extend( [('a(bc',r'a\(bc'), |
|
36 | 37 | ('a)bc',r'a\)bc'), |
|
37 | 38 | ('a( )bc',r'a\(\ \)bc'), |
|
38 | 39 | ('a[1]bc', r'a\[1\]bc'), |
|
39 | 40 | ('a{1}bc', r'a\{1\}bc'), |
|
40 | 41 | ('a#bc', r'a\#bc'), |
|
41 | 42 | ('a?bc', r'a\?bc'), |
|
42 | 43 | ('a=bc', r'a\=bc'), |
|
43 | 44 | ('a\\bc', r'a\\bc'), |
|
44 | 45 | ('a|bc', r'a\|bc'), |
|
45 | 46 | ('a;bc', r'a\;bc'), |
|
46 | 47 | ('a:bc', r'a\:bc'), |
|
47 | 48 | ("a'bc", r"a\'bc"), |
|
48 | 49 | ('a*bc', r'a\*bc'), |
|
49 | 50 | ('a"bc', r'a\"bc'), |
|
50 | 51 | ('a^bc', r'a\^bc'), |
|
51 | 52 | ('a&bc', r'a\&bc'), |
|
52 | 53 | ] ) |
|
53 | 54 | # run the actual tests |
|
54 | 55 | for s1, s2 in pairs: |
|
55 | 56 | s1p = completer.protect_filename(s1) |
|
56 | 57 | nt.assert_equal(s1p, s2) |
|
57 | 58 | |
|
58 | 59 | |
|
59 | 60 | def check_line_split(splitter, test_specs): |
|
60 | 61 | for part1, part2, split in test_specs: |
|
61 | 62 | cursor_pos = len(part1) |
|
62 | 63 | line = part1+part2 |
|
63 | 64 | out = splitter.split_line(line, cursor_pos) |
|
64 | 65 | nt.assert_equal(out, split) |
|
65 | 66 | |
|
66 | 67 | |
|
67 | 68 | def test_line_split(): |
|
68 | 69 | """Basic line splitter test with default specs.""" |
|
69 | 70 | sp = completer.CompletionSplitter() |
|
70 | 71 | # The format of the test specs is: part1, part2, expected answer. Parts 1 |
|
71 | 72 | # and 2 are joined into the 'line' sent to the splitter, as if the cursor |
|
72 | 73 | # was at the end of part1. So an empty part2 represents someone hitting |
|
73 | 74 | # tab at the end of the line, the most common case. |
|
74 | 75 | t = [('run some/scrip', '', 'some/scrip'), |
|
75 | 76 | ('run scripts/er', 'ror.py foo', 'scripts/er'), |
|
76 | 77 | ('echo $HOM', '', 'HOM'), |
|
77 | 78 | ('print sys.pa', '', 'sys.pa'), |
|
78 | 79 | ('print(sys.pa', '', 'sys.pa'), |
|
79 | 80 | ("execfile('scripts/er", '', 'scripts/er'), |
|
80 | 81 | ('a[x.', '', 'x.'), |
|
81 | 82 | ('a[x.', 'y', 'x.'), |
|
82 | 83 | ('cd "some_file/', '', 'some_file/'), |
|
83 | 84 | ] |
|
84 | 85 | check_line_split(sp, t) |
|
85 | 86 | # Ensure splitting works OK with unicode by re-running the tests with |
|
86 | 87 | # all inputs turned into unicode |
|
87 | 88 | check_line_split(sp, [ map(unicode_type, p) for p in t] ) |
|
88 | 89 | |
|
89 | 90 | |
|
90 | 91 | def test_custom_completion_error(): |
|
91 | 92 | """Test that errors from custom attribute completers are silenced.""" |
|
92 | 93 | ip = get_ipython() |
|
93 | 94 | class A(object): pass |
|
94 | 95 | ip.user_ns['a'] = A() |
|
95 | 96 | |
|
96 | 97 | @complete_object.when_type(A) |
|
97 | 98 | def complete_A(a, existing_completions): |
|
98 | 99 | raise TypeError("this should be silenced") |
|
99 | 100 | |
|
100 | 101 | ip.complete("a.") |
|
101 | 102 | |
|
102 | 103 | |
|
103 | 104 | def test_unicode_completions(): |
|
104 | 105 | ip = get_ipython() |
|
105 | 106 | # Some strings that trigger different types of completion. Check them both |
|
106 | 107 | # in str and unicode forms |
|
107 | 108 | s = ['ru', '%ru', 'cd /', 'floa', 'float(x)/'] |
|
108 | 109 | for t in s + list(map(unicode_type, s)): |
|
109 | 110 | # We don't need to check exact completion values (they may change |
|
110 | 111 | # depending on the state of the namespace, but at least no exceptions |
|
111 | 112 | # should be thrown and the return value should be a pair of text, list |
|
112 | 113 | # values. |
|
113 | 114 | text, matches = ip.complete(t) |
|
114 | 115 | nt.assert_true(isinstance(text, string_types)) |
|
115 | 116 | nt.assert_true(isinstance(matches, list)) |
|
116 | 117 | |
|
117 | 118 | |
|
118 | 119 | class CompletionSplitterTestCase(unittest.TestCase): |
|
119 | 120 | def setUp(self): |
|
120 | 121 | self.sp = completer.CompletionSplitter() |
|
121 | 122 | |
|
122 | 123 | def test_delim_setting(self): |
|
123 | 124 | self.sp.delims = ' ' |
|
124 | 125 | nt.assert_equal(self.sp.delims, ' ') |
|
125 | 126 | nt.assert_equal(self.sp._delim_expr, '[\ ]') |
|
126 | 127 | |
|
127 | 128 | def test_spaces(self): |
|
128 | 129 | """Test with only spaces as split chars.""" |
|
129 | 130 | self.sp.delims = ' ' |
|
130 | 131 | t = [('foo', '', 'foo'), |
|
131 | 132 | ('run foo', '', 'foo'), |
|
132 | 133 | ('run foo', 'bar', 'foo'), |
|
133 | 134 | ] |
|
134 | 135 | check_line_split(self.sp, t) |
|
135 | 136 | |
|
136 | 137 | |
|
137 | 138 | def test_has_open_quotes1(): |
|
138 | 139 | for s in ["'", "'''", "'hi' '"]: |
|
139 | 140 | nt.assert_equal(completer.has_open_quotes(s), "'") |
|
140 | 141 | |
|
141 | 142 | |
|
142 | 143 | def test_has_open_quotes2(): |
|
143 | 144 | for s in ['"', '"""', '"hi" "']: |
|
144 | 145 | nt.assert_equal(completer.has_open_quotes(s), '"') |
|
145 | 146 | |
|
146 | 147 | |
|
147 | 148 | def test_has_open_quotes3(): |
|
148 | 149 | for s in ["''", "''' '''", "'hi' 'ipython'"]: |
|
149 | 150 | nt.assert_false(completer.has_open_quotes(s)) |
|
150 | 151 | |
|
151 | 152 | |
|
152 | 153 | def test_has_open_quotes4(): |
|
153 | 154 | for s in ['""', '""" """', '"hi" "ipython"']: |
|
154 | 155 | nt.assert_false(completer.has_open_quotes(s)) |
|
155 | 156 | |
|
156 | 157 | |
|
157 | 158 | @knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows") |
|
158 | 159 | def test_abspath_file_completions(): |
|
159 | 160 | ip = get_ipython() |
|
160 | 161 | with TemporaryDirectory() as tmpdir: |
|
161 | 162 | prefix = os.path.join(tmpdir, 'foo') |
|
162 | 163 | suffixes = ['1', '2'] |
|
163 | 164 | names = [prefix+s for s in suffixes] |
|
164 | 165 | for n in names: |
|
165 | 166 | open(n, 'w').close() |
|
166 | 167 | |
|
167 | 168 | # Check simple completion |
|
168 | 169 | c = ip.complete(prefix)[1] |
|
169 | 170 | nt.assert_equal(c, names) |
|
170 | 171 | |
|
171 | 172 | # Now check with a function call |
|
172 | 173 | cmd = 'a = f("%s' % prefix |
|
173 | 174 | c = ip.complete(prefix, cmd)[1] |
|
174 | 175 | comp = [prefix+s for s in suffixes] |
|
175 | 176 | nt.assert_equal(c, comp) |
|
176 | 177 | |
|
177 | 178 | |
|
178 | 179 | def test_local_file_completions(): |
|
179 | 180 | ip = get_ipython() |
|
180 |
cwd = |
|
|
181 | cwd = py3compat.getcwd() | |
|
181 | 182 | try: |
|
182 | 183 | with TemporaryDirectory() as tmpdir: |
|
183 | 184 | os.chdir(tmpdir) |
|
184 | 185 | prefix = './foo' |
|
185 | 186 | suffixes = ['1', '2'] |
|
186 | 187 | names = [prefix+s for s in suffixes] |
|
187 | 188 | for n in names: |
|
188 | 189 | open(n, 'w').close() |
|
189 | 190 | |
|
190 | 191 | # Check simple completion |
|
191 | 192 | c = ip.complete(prefix)[1] |
|
192 | 193 | nt.assert_equal(c, names) |
|
193 | 194 | |
|
194 | 195 | # Now check with a function call |
|
195 | 196 | cmd = 'a = f("%s' % prefix |
|
196 | 197 | c = ip.complete(prefix, cmd)[1] |
|
197 | 198 | comp = [prefix+s for s in suffixes] |
|
198 | 199 | nt.assert_equal(c, comp) |
|
199 | 200 | finally: |
|
200 | 201 | # prevent failures from making chdir stick |
|
201 | 202 | os.chdir(cwd) |
|
202 | 203 | |
|
203 | 204 | |
|
204 | 205 | def test_greedy_completions(): |
|
205 | 206 | ip = get_ipython() |
|
206 | 207 | greedy_original = ip.Completer.greedy |
|
207 | 208 | try: |
|
208 | 209 | ip.Completer.greedy = False |
|
209 | 210 | ip.ex('a=list(range(5))') |
|
210 | 211 | _,c = ip.complete('.',line='a[0].') |
|
211 | 212 | nt.assert_false('a[0].real' in c, |
|
212 | 213 | "Shouldn't have completed on a[0]: %s"%c) |
|
213 | 214 | ip.Completer.greedy = True |
|
214 | 215 | _,c = ip.complete('.',line='a[0].') |
|
215 | 216 | nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c) |
|
216 | 217 | finally: |
|
217 | 218 | ip.Completer.greedy = greedy_original |
|
218 | 219 | |
|
219 | 220 | |
|
220 | 221 | def test_omit__names(): |
|
221 | 222 | # also happens to test IPCompleter as a configurable |
|
222 | 223 | ip = get_ipython() |
|
223 | 224 | ip._hidden_attr = 1 |
|
224 | 225 | c = ip.Completer |
|
225 | 226 | ip.ex('ip=get_ipython()') |
|
226 | 227 | cfg = Config() |
|
227 | 228 | cfg.IPCompleter.omit__names = 0 |
|
228 | 229 | c.update_config(cfg) |
|
229 | 230 | s,matches = c.complete('ip.') |
|
230 | 231 | nt.assert_in('ip.__str__', matches) |
|
231 | 232 | nt.assert_in('ip._hidden_attr', matches) |
|
232 | 233 | cfg.IPCompleter.omit__names = 1 |
|
233 | 234 | c.update_config(cfg) |
|
234 | 235 | s,matches = c.complete('ip.') |
|
235 | 236 | nt.assert_not_in('ip.__str__', matches) |
|
236 | 237 | nt.assert_in('ip._hidden_attr', matches) |
|
237 | 238 | cfg.IPCompleter.omit__names = 2 |
|
238 | 239 | c.update_config(cfg) |
|
239 | 240 | s,matches = c.complete('ip.') |
|
240 | 241 | nt.assert_not_in('ip.__str__', matches) |
|
241 | 242 | nt.assert_not_in('ip._hidden_attr', matches) |
|
242 | 243 | del ip._hidden_attr |
|
243 | 244 | |
|
244 | 245 | |
|
245 | 246 | def test_limit_to__all__False_ok(): |
|
246 | 247 | ip = get_ipython() |
|
247 | 248 | c = ip.Completer |
|
248 | 249 | ip.ex('class D: x=24') |
|
249 | 250 | ip.ex('d=D()') |
|
250 | 251 | cfg = Config() |
|
251 | 252 | cfg.IPCompleter.limit_to__all__ = False |
|
252 | 253 | c.update_config(cfg) |
|
253 | 254 | s, matches = c.complete('d.') |
|
254 | 255 | nt.assert_in('d.x', matches) |
|
255 | 256 | |
|
256 | 257 | |
|
257 | 258 | def test_limit_to__all__True_ok(): |
|
258 | 259 | ip = get_ipython() |
|
259 | 260 | c = ip.Completer |
|
260 | 261 | ip.ex('class D: x=24') |
|
261 | 262 | ip.ex('d=D()') |
|
262 | 263 | ip.ex("d.__all__=['z']") |
|
263 | 264 | cfg = Config() |
|
264 | 265 | cfg.IPCompleter.limit_to__all__ = True |
|
265 | 266 | c.update_config(cfg) |
|
266 | 267 | s, matches = c.complete('d.') |
|
267 | 268 | nt.assert_in('d.z', matches) |
|
268 | 269 | nt.assert_not_in('d.x', matches) |
|
269 | 270 | |
|
270 | 271 | |
|
271 | 272 | def test_get__all__entries_ok(): |
|
272 | 273 | class A(object): |
|
273 | 274 | __all__ = ['x', 1] |
|
274 | 275 | words = completer.get__all__entries(A()) |
|
275 | 276 | nt.assert_equal(words, ['x']) |
|
276 | 277 | |
|
277 | 278 | |
|
278 | 279 | def test_get__all__entries_no__all__ok(): |
|
279 | 280 | class A(object): |
|
280 | 281 | pass |
|
281 | 282 | words = completer.get__all__entries(A()) |
|
282 | 283 | nt.assert_equal(words, []) |
|
283 | 284 | |
|
284 | 285 | |
|
285 | 286 | def test_func_kw_completions(): |
|
286 | 287 | ip = get_ipython() |
|
287 | 288 | c = ip.Completer |
|
288 | 289 | ip.ex('def myfunc(a=1,b=2): return a+b') |
|
289 | 290 | s, matches = c.complete(None, 'myfunc(1,b') |
|
290 | 291 | nt.assert_in('b=', matches) |
|
291 | 292 | # Simulate completing with cursor right after b (pos==10): |
|
292 | 293 | s, matches = c.complete(None, 'myfunc(1,b)', 10) |
|
293 | 294 | nt.assert_in('b=', matches) |
|
294 | 295 | s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b') |
|
295 | 296 | nt.assert_in('b=', matches) |
|
296 | 297 | #builtin function |
|
297 | 298 | s, matches = c.complete(None, 'min(k, k') |
|
298 | 299 | nt.assert_in('key=', matches) |
|
299 | 300 | |
|
300 | 301 | |
|
301 | 302 | def test_default_arguments_from_docstring(): |
|
302 | 303 | doc = min.__doc__ |
|
303 | 304 | ip = get_ipython() |
|
304 | 305 | c = ip.Completer |
|
305 | 306 | kwd = c._default_arguments_from_docstring( |
|
306 | 307 | 'min(iterable[, key=func]) -> value') |
|
307 | 308 | nt.assert_equal(kwd, ['key']) |
|
308 | 309 | #with cython type etc |
|
309 | 310 | kwd = c._default_arguments_from_docstring( |
|
310 | 311 | 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n') |
|
311 | 312 | nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit']) |
|
312 | 313 | #white spaces |
|
313 | 314 | kwd = c._default_arguments_from_docstring( |
|
314 | 315 | '\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n') |
|
315 | 316 | nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit']) |
|
316 | 317 | |
|
317 | 318 | def test_line_magics(): |
|
318 | 319 | ip = get_ipython() |
|
319 | 320 | c = ip.Completer |
|
320 | 321 | s, matches = c.complete(None, 'lsmag') |
|
321 | 322 | nt.assert_in('%lsmagic', matches) |
|
322 | 323 | s, matches = c.complete(None, '%lsmag') |
|
323 | 324 | nt.assert_in('%lsmagic', matches) |
|
324 | 325 | |
|
325 | 326 | |
|
326 | 327 | def test_cell_magics(): |
|
327 | 328 | from IPython.core.magic import register_cell_magic |
|
328 | 329 | |
|
329 | 330 | @register_cell_magic |
|
330 | 331 | def _foo_cellm(line, cell): |
|
331 | 332 | pass |
|
332 | 333 | |
|
333 | 334 | ip = get_ipython() |
|
334 | 335 | c = ip.Completer |
|
335 | 336 | |
|
336 | 337 | s, matches = c.complete(None, '_foo_ce') |
|
337 | 338 | nt.assert_in('%%_foo_cellm', matches) |
|
338 | 339 | s, matches = c.complete(None, '%%_foo_ce') |
|
339 | 340 | nt.assert_in('%%_foo_cellm', matches) |
|
340 | 341 | |
|
341 | 342 | |
|
342 | 343 | def test_line_cell_magics(): |
|
343 | 344 | from IPython.core.magic import register_line_cell_magic |
|
344 | 345 | |
|
345 | 346 | @register_line_cell_magic |
|
346 | 347 | def _bar_cellm(line, cell): |
|
347 | 348 | pass |
|
348 | 349 | |
|
349 | 350 | ip = get_ipython() |
|
350 | 351 | c = ip.Completer |
|
351 | 352 | |
|
352 | 353 | # The policy here is trickier, see comments in completion code. The |
|
353 | 354 | # returned values depend on whether the user passes %% or not explicitly, |
|
354 | 355 | # and this will show a difference if the same name is both a line and cell |
|
355 | 356 | # magic. |
|
356 | 357 | s, matches = c.complete(None, '_bar_ce') |
|
357 | 358 | nt.assert_in('%_bar_cellm', matches) |
|
358 | 359 | nt.assert_in('%%_bar_cellm', matches) |
|
359 | 360 | s, matches = c.complete(None, '%_bar_ce') |
|
360 | 361 | nt.assert_in('%_bar_cellm', matches) |
|
361 | 362 | nt.assert_in('%%_bar_cellm', matches) |
|
362 | 363 | s, matches = c.complete(None, '%%_bar_ce') |
|
363 | 364 | nt.assert_not_in('%_bar_cellm', matches) |
|
364 | 365 | nt.assert_in('%%_bar_cellm', matches) |
|
365 | 366 | |
|
366 | 367 | |
|
367 | 368 | def test_magic_completion_order(): |
|
368 | 369 | |
|
369 | 370 | ip = get_ipython() |
|
370 | 371 | c = ip.Completer |
|
371 | 372 | |
|
372 | 373 | # Test ordering of magics and non-magics with the same name |
|
373 | 374 | # We want the non-magic first |
|
374 | 375 | |
|
375 | 376 | # Before importing matplotlib, there should only be one option: |
|
376 | 377 | |
|
377 | 378 | text, matches = c.complete('mat') |
|
378 | 379 | nt.assert_equal(matches, ["%matplotlib"]) |
|
379 | 380 | |
|
380 | 381 | |
|
381 | 382 | ip.run_cell("matplotlib = 1") # introduce name into namespace |
|
382 | 383 | |
|
383 | 384 | # After the import, there should be two options, ordered like this: |
|
384 | 385 | text, matches = c.complete('mat') |
|
385 | 386 | nt.assert_equal(matches, ["matplotlib", "%matplotlib"]) |
|
386 | 387 | |
|
387 | 388 | |
|
388 | 389 | ip.run_cell("timeit = 1") # define a user variable called 'timeit' |
|
389 | 390 | |
|
390 | 391 | # Order of user variable and line and cell magics with same name: |
|
391 | 392 | text, matches = c.complete('timeit') |
|
392 | 393 | nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"]) |
|
393 | 394 |
@@ -1,120 +1,121 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Tests for completerlib. |
|
3 | 3 | |
|
4 | 4 | """ |
|
5 | 5 | from __future__ import absolute_import |
|
6 | 6 | |
|
7 | 7 | #----------------------------------------------------------------------------- |
|
8 | 8 | # Imports |
|
9 | 9 | #----------------------------------------------------------------------------- |
|
10 | 10 | |
|
11 | 11 | import os |
|
12 | 12 | import shutil |
|
13 | 13 | import sys |
|
14 | 14 | import tempfile |
|
15 | 15 | import unittest |
|
16 | 16 | from os.path import join |
|
17 | 17 | |
|
18 | 18 | from IPython.core.completerlib import magic_run_completer, module_completion |
|
19 | from IPython.utils import py3compat | |
|
19 | 20 | from IPython.utils.tempdir import TemporaryDirectory |
|
20 | 21 | from IPython.testing.decorators import onlyif_unicode_paths |
|
21 | 22 | |
|
22 | 23 | |
|
23 | 24 | class MockEvent(object): |
|
24 | 25 | def __init__(self, line): |
|
25 | 26 | self.line = line |
|
26 | 27 | |
|
27 | 28 | #----------------------------------------------------------------------------- |
|
28 | 29 | # Test functions begin |
|
29 | 30 | #----------------------------------------------------------------------------- |
|
30 | 31 | class Test_magic_run_completer(unittest.TestCase): |
|
31 | 32 | def setUp(self): |
|
32 | 33 | self.BASETESTDIR = tempfile.mkdtemp() |
|
33 | 34 | for fil in [u"aao.py", u"a.py", u"b.py"]: |
|
34 | 35 | with open(join(self.BASETESTDIR, fil), "w") as sfile: |
|
35 | 36 | sfile.write("pass\n") |
|
36 |
self.oldpath = |
|
|
37 | self.oldpath = py3compat.getcwd() | |
|
37 | 38 | os.chdir(self.BASETESTDIR) |
|
38 | 39 | |
|
39 | 40 | def tearDown(self): |
|
40 | 41 | os.chdir(self.oldpath) |
|
41 | 42 | shutil.rmtree(self.BASETESTDIR) |
|
42 | 43 | |
|
43 | 44 | def test_1(self): |
|
44 | 45 | """Test magic_run_completer, should match two alterntives |
|
45 | 46 | """ |
|
46 | 47 | event = MockEvent(u"%run a") |
|
47 | 48 | mockself = None |
|
48 | 49 | match = set(magic_run_completer(mockself, event)) |
|
49 | 50 | self.assertEqual(match, set([u"a.py", u"aao.py"])) |
|
50 | 51 | |
|
51 | 52 | def test_2(self): |
|
52 | 53 | """Test magic_run_completer, should match one alterntive |
|
53 | 54 | """ |
|
54 | 55 | event = MockEvent(u"%run aa") |
|
55 | 56 | mockself = None |
|
56 | 57 | match = set(magic_run_completer(mockself, event)) |
|
57 | 58 | self.assertEqual(match, set([u"aao.py"])) |
|
58 | 59 | |
|
59 | 60 | def test_3(self): |
|
60 | 61 | """Test magic_run_completer with unterminated " """ |
|
61 | 62 | event = MockEvent(u'%run "a') |
|
62 | 63 | mockself = None |
|
63 | 64 | match = set(magic_run_completer(mockself, event)) |
|
64 | 65 | self.assertEqual(match, set([u"a.py", u"aao.py"])) |
|
65 | 66 | |
|
66 | 67 | def test_import_invalid_module(self): |
|
67 | 68 | """Testing of issue https://github.com/ipython/ipython/issues/1107""" |
|
68 | 69 | invalid_module_names = set(['foo-bar', 'foo:bar', '10foo']) |
|
69 | 70 | valid_module_names = set(['foobar']) |
|
70 | 71 | with TemporaryDirectory() as tmpdir: |
|
71 | 72 | sys.path.insert( 0, tmpdir ) |
|
72 | 73 | for name in invalid_module_names | valid_module_names: |
|
73 | 74 | filename = os.path.join(tmpdir, name + '.py') |
|
74 | 75 | open(filename, 'w').close() |
|
75 | 76 | |
|
76 | 77 | s = set( module_completion('import foo') ) |
|
77 | 78 | intersection = s.intersection(invalid_module_names) |
|
78 | 79 | self.assertFalse(intersection, intersection) |
|
79 | 80 | |
|
80 | 81 | assert valid_module_names.issubset(s), valid_module_names.intersection(s) |
|
81 | 82 | |
|
82 | 83 | class Test_magic_run_completer_nonascii(unittest.TestCase): |
|
83 | 84 | @onlyif_unicode_paths |
|
84 | 85 | def setUp(self): |
|
85 | 86 | self.BASETESTDIR = tempfile.mkdtemp() |
|
86 | 87 | for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]: |
|
87 | 88 | with open(join(self.BASETESTDIR, fil), "w") as sfile: |
|
88 | 89 | sfile.write("pass\n") |
|
89 |
self.oldpath = |
|
|
90 | self.oldpath = py3compat.getcwd() | |
|
90 | 91 | os.chdir(self.BASETESTDIR) |
|
91 | 92 | |
|
92 | 93 | def tearDown(self): |
|
93 | 94 | os.chdir(self.oldpath) |
|
94 | 95 | shutil.rmtree(self.BASETESTDIR) |
|
95 | 96 | |
|
96 | 97 | @onlyif_unicode_paths |
|
97 | 98 | def test_1(self): |
|
98 | 99 | """Test magic_run_completer, should match two alterntives |
|
99 | 100 | """ |
|
100 | 101 | event = MockEvent(u"%run a") |
|
101 | 102 | mockself = None |
|
102 | 103 | match = set(magic_run_completer(mockself, event)) |
|
103 | 104 | self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"])) |
|
104 | 105 | |
|
105 | 106 | @onlyif_unicode_paths |
|
106 | 107 | def test_2(self): |
|
107 | 108 | """Test magic_run_completer, should match one alterntive |
|
108 | 109 | """ |
|
109 | 110 | event = MockEvent(u"%run aa") |
|
110 | 111 | mockself = None |
|
111 | 112 | match = set(magic_run_completer(mockself, event)) |
|
112 | 113 | self.assertEqual(match, set([u"aaΓΈ.py"])) |
|
113 | 114 | |
|
114 | 115 | @onlyif_unicode_paths |
|
115 | 116 | def test_3(self): |
|
116 | 117 | """Test magic_run_completer with unterminated " """ |
|
117 | 118 | event = MockEvent(u'%run "a') |
|
118 | 119 | mockself = None |
|
119 | 120 | match = set(magic_run_completer(mockself, event)) |
|
120 | 121 | self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"])) |
@@ -1,676 +1,677 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Tests for the key interactiveshell module. |
|
3 | 3 | |
|
4 | 4 | Historically the main classes in interactiveshell have been under-tested. This |
|
5 | 5 | module should grow as many single-method tests as possible to trap many of the |
|
6 | 6 | recurring bugs we seem to encounter with high-level interaction. |
|
7 | 7 | |
|
8 | 8 | Authors |
|
9 | 9 | ------- |
|
10 | 10 | * Fernando Perez |
|
11 | 11 | """ |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | # Copyright (C) 2011 The IPython Development Team |
|
14 | 14 | # |
|
15 | 15 | # Distributed under the terms of the BSD License. The full license is in |
|
16 | 16 | # the file COPYING, distributed as part of this software. |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | 19 | #----------------------------------------------------------------------------- |
|
20 | 20 | # Imports |
|
21 | 21 | #----------------------------------------------------------------------------- |
|
22 | 22 | # stdlib |
|
23 | 23 | import ast |
|
24 | 24 | import os |
|
25 | 25 | import signal |
|
26 | 26 | import shutil |
|
27 | 27 | import sys |
|
28 | 28 | import tempfile |
|
29 | 29 | import unittest |
|
30 | 30 | from os.path import join |
|
31 | 31 | |
|
32 | 32 | # third-party |
|
33 | 33 | import nose.tools as nt |
|
34 | 34 | |
|
35 | 35 | # Our own |
|
36 | 36 | from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths |
|
37 | 37 | from IPython.testing import tools as tt |
|
38 | 38 | from IPython.utils import io |
|
39 | from IPython.utils import py3compat | |
|
39 | 40 | from IPython.utils.py3compat import unicode_type, PY3 |
|
40 | 41 | |
|
41 | 42 | if PY3: |
|
42 | 43 | from io import StringIO |
|
43 | 44 | else: |
|
44 | 45 | from StringIO import StringIO |
|
45 | 46 | |
|
46 | 47 | #----------------------------------------------------------------------------- |
|
47 | 48 | # Globals |
|
48 | 49 | #----------------------------------------------------------------------------- |
|
49 | 50 | # This is used by every single test, no point repeating it ad nauseam |
|
50 | 51 | ip = get_ipython() |
|
51 | 52 | |
|
52 | 53 | #----------------------------------------------------------------------------- |
|
53 | 54 | # Tests |
|
54 | 55 | #----------------------------------------------------------------------------- |
|
55 | 56 | |
|
56 | 57 | class InteractiveShellTestCase(unittest.TestCase): |
|
57 | 58 | def test_naked_string_cells(self): |
|
58 | 59 | """Test that cells with only naked strings are fully executed""" |
|
59 | 60 | # First, single-line inputs |
|
60 | 61 | ip.run_cell('"a"\n') |
|
61 | 62 | self.assertEqual(ip.user_ns['_'], 'a') |
|
62 | 63 | # And also multi-line cells |
|
63 | 64 | ip.run_cell('"""a\nb"""\n') |
|
64 | 65 | self.assertEqual(ip.user_ns['_'], 'a\nb') |
|
65 | 66 | |
|
66 | 67 | def test_run_empty_cell(self): |
|
67 | 68 | """Just make sure we don't get a horrible error with a blank |
|
68 | 69 | cell of input. Yes, I did overlook that.""" |
|
69 | 70 | old_xc = ip.execution_count |
|
70 | 71 | ip.run_cell('') |
|
71 | 72 | self.assertEqual(ip.execution_count, old_xc) |
|
72 | 73 | |
|
73 | 74 | def test_run_cell_multiline(self): |
|
74 | 75 | """Multi-block, multi-line cells must execute correctly. |
|
75 | 76 | """ |
|
76 | 77 | src = '\n'.join(["x=1", |
|
77 | 78 | "y=2", |
|
78 | 79 | "if 1:", |
|
79 | 80 | " x += 1", |
|
80 | 81 | " y += 1",]) |
|
81 | 82 | ip.run_cell(src) |
|
82 | 83 | self.assertEqual(ip.user_ns['x'], 2) |
|
83 | 84 | self.assertEqual(ip.user_ns['y'], 3) |
|
84 | 85 | |
|
85 | 86 | def test_multiline_string_cells(self): |
|
86 | 87 | "Code sprinkled with multiline strings should execute (GH-306)" |
|
87 | 88 | ip.run_cell('tmp=0') |
|
88 | 89 | self.assertEqual(ip.user_ns['tmp'], 0) |
|
89 | 90 | ip.run_cell('tmp=1;"""a\nb"""\n') |
|
90 | 91 | self.assertEqual(ip.user_ns['tmp'], 1) |
|
91 | 92 | |
|
92 | 93 | def test_dont_cache_with_semicolon(self): |
|
93 | 94 | "Ending a line with semicolon should not cache the returned object (GH-307)" |
|
94 | 95 | oldlen = len(ip.user_ns['Out']) |
|
95 | 96 | a = ip.run_cell('1;', store_history=True) |
|
96 | 97 | newlen = len(ip.user_ns['Out']) |
|
97 | 98 | self.assertEqual(oldlen, newlen) |
|
98 | 99 | #also test the default caching behavior |
|
99 | 100 | ip.run_cell('1', store_history=True) |
|
100 | 101 | newlen = len(ip.user_ns['Out']) |
|
101 | 102 | self.assertEqual(oldlen+1, newlen) |
|
102 | 103 | |
|
103 | 104 | def test_In_variable(self): |
|
104 | 105 | "Verify that In variable grows with user input (GH-284)" |
|
105 | 106 | oldlen = len(ip.user_ns['In']) |
|
106 | 107 | ip.run_cell('1;', store_history=True) |
|
107 | 108 | newlen = len(ip.user_ns['In']) |
|
108 | 109 | self.assertEqual(oldlen+1, newlen) |
|
109 | 110 | self.assertEqual(ip.user_ns['In'][-1],'1;') |
|
110 | 111 | |
|
111 | 112 | def test_magic_names_in_string(self): |
|
112 | 113 | ip.run_cell('a = """\n%exit\n"""') |
|
113 | 114 | self.assertEqual(ip.user_ns['a'], '\n%exit\n') |
|
114 | 115 | |
|
115 | 116 | def test_trailing_newline(self): |
|
116 | 117 | """test that running !(command) does not raise a SyntaxError""" |
|
117 | 118 | ip.run_cell('!(true)\n', False) |
|
118 | 119 | ip.run_cell('!(true)\n\n\n', False) |
|
119 | 120 | |
|
120 | 121 | def test_gh_597(self): |
|
121 | 122 | """Pretty-printing lists of objects with non-ascii reprs may cause |
|
122 | 123 | problems.""" |
|
123 | 124 | class Spam(object): |
|
124 | 125 | def __repr__(self): |
|
125 | 126 | return "\xe9"*50 |
|
126 | 127 | import IPython.core.formatters |
|
127 | 128 | f = IPython.core.formatters.PlainTextFormatter() |
|
128 | 129 | f([Spam(),Spam()]) |
|
129 | 130 | |
|
130 | 131 | |
|
131 | 132 | def test_future_flags(self): |
|
132 | 133 | """Check that future flags are used for parsing code (gh-777)""" |
|
133 | 134 | ip.run_cell('from __future__ import print_function') |
|
134 | 135 | try: |
|
135 | 136 | ip.run_cell('prfunc_return_val = print(1,2, sep=" ")') |
|
136 | 137 | assert 'prfunc_return_val' in ip.user_ns |
|
137 | 138 | finally: |
|
138 | 139 | # Reset compiler flags so we don't mess up other tests. |
|
139 | 140 | ip.compile.reset_compiler_flags() |
|
140 | 141 | |
|
141 | 142 | def test_future_unicode(self): |
|
142 | 143 | """Check that unicode_literals is imported from __future__ (gh #786)""" |
|
143 | 144 | try: |
|
144 | 145 | ip.run_cell(u'byte_str = "a"') |
|
145 | 146 | assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default |
|
146 | 147 | ip.run_cell('from __future__ import unicode_literals') |
|
147 | 148 | ip.run_cell(u'unicode_str = "a"') |
|
148 | 149 | assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode |
|
149 | 150 | finally: |
|
150 | 151 | # Reset compiler flags so we don't mess up other tests. |
|
151 | 152 | ip.compile.reset_compiler_flags() |
|
152 | 153 | |
|
153 | 154 | def test_can_pickle(self): |
|
154 | 155 | "Can we pickle objects defined interactively (GH-29)" |
|
155 | 156 | ip = get_ipython() |
|
156 | 157 | ip.reset() |
|
157 | 158 | ip.run_cell(("class Mylist(list):\n" |
|
158 | 159 | " def __init__(self,x=[]):\n" |
|
159 | 160 | " list.__init__(self,x)")) |
|
160 | 161 | ip.run_cell("w=Mylist([1,2,3])") |
|
161 | 162 | |
|
162 | 163 | from pickle import dumps |
|
163 | 164 | |
|
164 | 165 | # We need to swap in our main module - this is only necessary |
|
165 | 166 | # inside the test framework, because IPython puts the interactive module |
|
166 | 167 | # in place (but the test framework undoes this). |
|
167 | 168 | _main = sys.modules['__main__'] |
|
168 | 169 | sys.modules['__main__'] = ip.user_module |
|
169 | 170 | try: |
|
170 | 171 | res = dumps(ip.user_ns["w"]) |
|
171 | 172 | finally: |
|
172 | 173 | sys.modules['__main__'] = _main |
|
173 | 174 | self.assertTrue(isinstance(res, bytes)) |
|
174 | 175 | |
|
175 | 176 | def test_global_ns(self): |
|
176 | 177 | "Code in functions must be able to access variables outside them." |
|
177 | 178 | ip = get_ipython() |
|
178 | 179 | ip.run_cell("a = 10") |
|
179 | 180 | ip.run_cell(("def f(x):\n" |
|
180 | 181 | " return x + a")) |
|
181 | 182 | ip.run_cell("b = f(12)") |
|
182 | 183 | self.assertEqual(ip.user_ns["b"], 22) |
|
183 | 184 | |
|
184 | 185 | def test_bad_custom_tb(self): |
|
185 | 186 | """Check that InteractiveShell is protected from bad custom exception handlers""" |
|
186 | 187 | from IPython.utils import io |
|
187 | 188 | save_stderr = io.stderr |
|
188 | 189 | try: |
|
189 | 190 | # capture stderr |
|
190 | 191 | io.stderr = StringIO() |
|
191 | 192 | ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0) |
|
192 | 193 | self.assertEqual(ip.custom_exceptions, (IOError,)) |
|
193 | 194 | ip.run_cell(u'raise IOError("foo")') |
|
194 | 195 | self.assertEqual(ip.custom_exceptions, ()) |
|
195 | 196 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) |
|
196 | 197 | finally: |
|
197 | 198 | io.stderr = save_stderr |
|
198 | 199 | |
|
199 | 200 | def test_bad_custom_tb_return(self): |
|
200 | 201 | """Check that InteractiveShell is protected from bad return types in custom exception handlers""" |
|
201 | 202 | from IPython.utils import io |
|
202 | 203 | save_stderr = io.stderr |
|
203 | 204 | try: |
|
204 | 205 | # capture stderr |
|
205 | 206 | io.stderr = StringIO() |
|
206 | 207 | ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1) |
|
207 | 208 | self.assertEqual(ip.custom_exceptions, (NameError,)) |
|
208 | 209 | ip.run_cell(u'a=abracadabra') |
|
209 | 210 | self.assertEqual(ip.custom_exceptions, ()) |
|
210 | 211 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) |
|
211 | 212 | finally: |
|
212 | 213 | io.stderr = save_stderr |
|
213 | 214 | |
|
214 | 215 | def test_drop_by_id(self): |
|
215 | 216 | myvars = {"a":object(), "b":object(), "c": object()} |
|
216 | 217 | ip.push(myvars, interactive=False) |
|
217 | 218 | for name in myvars: |
|
218 | 219 | assert name in ip.user_ns, name |
|
219 | 220 | assert name in ip.user_ns_hidden, name |
|
220 | 221 | ip.user_ns['b'] = 12 |
|
221 | 222 | ip.drop_by_id(myvars) |
|
222 | 223 | for name in ["a", "c"]: |
|
223 | 224 | assert name not in ip.user_ns, name |
|
224 | 225 | assert name not in ip.user_ns_hidden, name |
|
225 | 226 | assert ip.user_ns['b'] == 12 |
|
226 | 227 | ip.reset() |
|
227 | 228 | |
|
228 | 229 | def test_var_expand(self): |
|
229 | 230 | ip.user_ns['f'] = u'Ca\xf1o' |
|
230 | 231 | self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o') |
|
231 | 232 | self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o') |
|
232 | 233 | self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1') |
|
233 | 234 | self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2') |
|
234 | 235 | |
|
235 | 236 | ip.user_ns['f'] = b'Ca\xc3\xb1o' |
|
236 | 237 | # This should not raise any exception: |
|
237 | 238 | ip.var_expand(u'echo $f') |
|
238 | 239 | |
|
239 | 240 | def test_var_expand_local(self): |
|
240 | 241 | """Test local variable expansion in !system and %magic calls""" |
|
241 | 242 | # !system |
|
242 | 243 | ip.run_cell('def test():\n' |
|
243 | 244 | ' lvar = "ttt"\n' |
|
244 | 245 | ' ret = !echo {lvar}\n' |
|
245 | 246 | ' return ret[0]\n') |
|
246 | 247 | res = ip.user_ns['test']() |
|
247 | 248 | nt.assert_in('ttt', res) |
|
248 | 249 | |
|
249 | 250 | # %magic |
|
250 | 251 | ip.run_cell('def makemacro():\n' |
|
251 | 252 | ' macroname = "macro_var_expand_locals"\n' |
|
252 | 253 | ' %macro {macroname} codestr\n') |
|
253 | 254 | ip.user_ns['codestr'] = "str(12)" |
|
254 | 255 | ip.run_cell('makemacro()') |
|
255 | 256 | nt.assert_in('macro_var_expand_locals', ip.user_ns) |
|
256 | 257 | |
|
257 | 258 | def test_var_expand_self(self): |
|
258 | 259 | """Test variable expansion with the name 'self', which was failing. |
|
259 | 260 | |
|
260 | 261 | See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218 |
|
261 | 262 | """ |
|
262 | 263 | ip.run_cell('class cTest:\n' |
|
263 | 264 | ' classvar="see me"\n' |
|
264 | 265 | ' def test(self):\n' |
|
265 | 266 | ' res = !echo Variable: {self.classvar}\n' |
|
266 | 267 | ' return res[0]\n') |
|
267 | 268 | nt.assert_in('see me', ip.user_ns['cTest']().test()) |
|
268 | 269 | |
|
269 | 270 | def test_bad_var_expand(self): |
|
270 | 271 | """var_expand on invalid formats shouldn't raise""" |
|
271 | 272 | # SyntaxError |
|
272 | 273 | self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}") |
|
273 | 274 | # NameError |
|
274 | 275 | self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}") |
|
275 | 276 | # ZeroDivisionError |
|
276 | 277 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") |
|
277 | 278 | |
|
278 | 279 | def test_silent_nopostexec(self): |
|
279 | 280 | """run_cell(silent=True) doesn't invoke post-exec funcs""" |
|
280 | 281 | d = dict(called=False) |
|
281 | 282 | def set_called(): |
|
282 | 283 | d['called'] = True |
|
283 | 284 | |
|
284 | 285 | ip.register_post_execute(set_called) |
|
285 | 286 | ip.run_cell("1", silent=True) |
|
286 | 287 | self.assertFalse(d['called']) |
|
287 | 288 | # double-check that non-silent exec did what we expected |
|
288 | 289 | # silent to avoid |
|
289 | 290 | ip.run_cell("1") |
|
290 | 291 | self.assertTrue(d['called']) |
|
291 | 292 | # remove post-exec |
|
292 | 293 | ip._post_execute.pop(set_called) |
|
293 | 294 | |
|
294 | 295 | def test_silent_noadvance(self): |
|
295 | 296 | """run_cell(silent=True) doesn't advance execution_count""" |
|
296 | 297 | ec = ip.execution_count |
|
297 | 298 | # silent should force store_history=False |
|
298 | 299 | ip.run_cell("1", store_history=True, silent=True) |
|
299 | 300 | |
|
300 | 301 | self.assertEqual(ec, ip.execution_count) |
|
301 | 302 | # double-check that non-silent exec did what we expected |
|
302 | 303 | # silent to avoid |
|
303 | 304 | ip.run_cell("1", store_history=True) |
|
304 | 305 | self.assertEqual(ec+1, ip.execution_count) |
|
305 | 306 | |
|
306 | 307 | def test_silent_nodisplayhook(self): |
|
307 | 308 | """run_cell(silent=True) doesn't trigger displayhook""" |
|
308 | 309 | d = dict(called=False) |
|
309 | 310 | |
|
310 | 311 | trap = ip.display_trap |
|
311 | 312 | save_hook = trap.hook |
|
312 | 313 | |
|
313 | 314 | def failing_hook(*args, **kwargs): |
|
314 | 315 | d['called'] = True |
|
315 | 316 | |
|
316 | 317 | try: |
|
317 | 318 | trap.hook = failing_hook |
|
318 | 319 | ip.run_cell("1", silent=True) |
|
319 | 320 | self.assertFalse(d['called']) |
|
320 | 321 | # double-check that non-silent exec did what we expected |
|
321 | 322 | # silent to avoid |
|
322 | 323 | ip.run_cell("1") |
|
323 | 324 | self.assertTrue(d['called']) |
|
324 | 325 | finally: |
|
325 | 326 | trap.hook = save_hook |
|
326 | 327 | |
|
327 | 328 | @skipif(sys.version_info[0] >= 3, "softspace removed in py3") |
|
328 | 329 | def test_print_softspace(self): |
|
329 | 330 | """Verify that softspace is handled correctly when executing multiple |
|
330 | 331 | statements. |
|
331 | 332 | |
|
332 | 333 | In [1]: print 1; print 2 |
|
333 | 334 | 1 |
|
334 | 335 | 2 |
|
335 | 336 | |
|
336 | 337 | In [2]: print 1,; print 2 |
|
337 | 338 | 1 2 |
|
338 | 339 | """ |
|
339 | 340 | |
|
340 | 341 | def test_ofind_line_magic(self): |
|
341 | 342 | from IPython.core.magic import register_line_magic |
|
342 | 343 | |
|
343 | 344 | @register_line_magic |
|
344 | 345 | def lmagic(line): |
|
345 | 346 | "A line magic" |
|
346 | 347 | |
|
347 | 348 | # Get info on line magic |
|
348 | 349 | lfind = ip._ofind('lmagic') |
|
349 | 350 | info = dict(found=True, isalias=False, ismagic=True, |
|
350 | 351 | namespace = 'IPython internal', obj= lmagic.__wrapped__, |
|
351 | 352 | parent = None) |
|
352 | 353 | nt.assert_equal(lfind, info) |
|
353 | 354 | |
|
354 | 355 | def test_ofind_cell_magic(self): |
|
355 | 356 | from IPython.core.magic import register_cell_magic |
|
356 | 357 | |
|
357 | 358 | @register_cell_magic |
|
358 | 359 | def cmagic(line, cell): |
|
359 | 360 | "A cell magic" |
|
360 | 361 | |
|
361 | 362 | # Get info on cell magic |
|
362 | 363 | find = ip._ofind('cmagic') |
|
363 | 364 | info = dict(found=True, isalias=False, ismagic=True, |
|
364 | 365 | namespace = 'IPython internal', obj= cmagic.__wrapped__, |
|
365 | 366 | parent = None) |
|
366 | 367 | nt.assert_equal(find, info) |
|
367 | 368 | |
|
368 | 369 | def test_custom_exception(self): |
|
369 | 370 | called = [] |
|
370 | 371 | def my_handler(shell, etype, value, tb, tb_offset=None): |
|
371 | 372 | called.append(etype) |
|
372 | 373 | shell.showtraceback((etype, value, tb), tb_offset=tb_offset) |
|
373 | 374 | |
|
374 | 375 | ip.set_custom_exc((ValueError,), my_handler) |
|
375 | 376 | try: |
|
376 | 377 | ip.run_cell("raise ValueError('test')") |
|
377 | 378 | # Check that this was called, and only once. |
|
378 | 379 | self.assertEqual(called, [ValueError]) |
|
379 | 380 | finally: |
|
380 | 381 | # Reset the custom exception hook |
|
381 | 382 | ip.set_custom_exc((), None) |
|
382 | 383 | |
|
383 | 384 | @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3") |
|
384 | 385 | def test_future_environment(self): |
|
385 | 386 | "Can we run code with & without the shell's __future__ imports?" |
|
386 | 387 | ip.run_cell("from __future__ import division") |
|
387 | 388 | ip.run_cell("a = 1/2", shell_futures=True) |
|
388 | 389 | self.assertEqual(ip.user_ns['a'], 0.5) |
|
389 | 390 | ip.run_cell("b = 1/2", shell_futures=False) |
|
390 | 391 | self.assertEqual(ip.user_ns['b'], 0) |
|
391 | 392 | |
|
392 | 393 | ip.compile.reset_compiler_flags() |
|
393 | 394 | # This shouldn't leak to the shell's compiler |
|
394 | 395 | ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False) |
|
395 | 396 | self.assertEqual(ip.user_ns['c'], 0.5) |
|
396 | 397 | ip.run_cell("d = 1/2", shell_futures=True) |
|
397 | 398 | self.assertEqual(ip.user_ns['d'], 0) |
|
398 | 399 | |
|
399 | 400 | |
|
400 | 401 | class TestSafeExecfileNonAsciiPath(unittest.TestCase): |
|
401 | 402 | |
|
402 | 403 | @onlyif_unicode_paths |
|
403 | 404 | def setUp(self): |
|
404 | 405 | self.BASETESTDIR = tempfile.mkdtemp() |
|
405 | 406 | self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ") |
|
406 | 407 | os.mkdir(self.TESTDIR) |
|
407 | 408 | with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile: |
|
408 | 409 | sfile.write("pass\n") |
|
409 |
self.oldpath = |
|
|
410 | self.oldpath = py3compat.getcwd() | |
|
410 | 411 | os.chdir(self.TESTDIR) |
|
411 | 412 | self.fname = u"Γ₯Àâtestscript.py" |
|
412 | 413 | |
|
413 | 414 | def tearDown(self): |
|
414 | 415 | os.chdir(self.oldpath) |
|
415 | 416 | shutil.rmtree(self.BASETESTDIR) |
|
416 | 417 | |
|
417 | 418 | @onlyif_unicode_paths |
|
418 | 419 | def test_1(self): |
|
419 | 420 | """Test safe_execfile with non-ascii path |
|
420 | 421 | """ |
|
421 | 422 | ip.safe_execfile(self.fname, {}, raise_exceptions=True) |
|
422 | 423 | |
|
423 | 424 | class ExitCodeChecks(tt.TempFileMixin): |
|
424 | 425 | def test_exit_code_ok(self): |
|
425 | 426 | self.system('exit 0') |
|
426 | 427 | self.assertEqual(ip.user_ns['_exit_code'], 0) |
|
427 | 428 | |
|
428 | 429 | def test_exit_code_error(self): |
|
429 | 430 | self.system('exit 1') |
|
430 | 431 | self.assertEqual(ip.user_ns['_exit_code'], 1) |
|
431 | 432 | |
|
432 | 433 | @skipif(not hasattr(signal, 'SIGALRM')) |
|
433 | 434 | def test_exit_code_signal(self): |
|
434 | 435 | self.mktmp("import signal, time\n" |
|
435 | 436 | "signal.setitimer(signal.ITIMER_REAL, 0.1)\n" |
|
436 | 437 | "time.sleep(1)\n") |
|
437 | 438 | self.system("%s %s" % (sys.executable, self.fname)) |
|
438 | 439 | self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM) |
|
439 | 440 | |
|
440 | 441 | class TestSystemRaw(unittest.TestCase, ExitCodeChecks): |
|
441 | 442 | system = ip.system_raw |
|
442 | 443 | |
|
443 | 444 | @onlyif_unicode_paths |
|
444 | 445 | def test_1(self): |
|
445 | 446 | """Test system_raw with non-ascii cmd |
|
446 | 447 | """ |
|
447 | 448 | cmd = u'''python -c "'Γ₯Àâ'" ''' |
|
448 | 449 | ip.system_raw(cmd) |
|
449 | 450 | |
|
450 | 451 | # TODO: Exit codes are currently ignored on Windows. |
|
451 | 452 | class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks): |
|
452 | 453 | system = ip.system_piped |
|
453 | 454 | |
|
454 | 455 | @skip_win32 |
|
455 | 456 | def test_exit_code_ok(self): |
|
456 | 457 | ExitCodeChecks.test_exit_code_ok(self) |
|
457 | 458 | |
|
458 | 459 | @skip_win32 |
|
459 | 460 | def test_exit_code_error(self): |
|
460 | 461 | ExitCodeChecks.test_exit_code_error(self) |
|
461 | 462 | |
|
462 | 463 | @skip_win32 |
|
463 | 464 | def test_exit_code_signal(self): |
|
464 | 465 | ExitCodeChecks.test_exit_code_signal(self) |
|
465 | 466 | |
|
466 | 467 | class TestModules(unittest.TestCase, tt.TempFileMixin): |
|
467 | 468 | def test_extraneous_loads(self): |
|
468 | 469 | """Test we're not loading modules on startup that we shouldn't. |
|
469 | 470 | """ |
|
470 | 471 | self.mktmp("import sys\n" |
|
471 | 472 | "print('numpy' in sys.modules)\n" |
|
472 | 473 | "print('IPython.parallel' in sys.modules)\n" |
|
473 | 474 | "print('IPython.kernel.zmq' in sys.modules)\n" |
|
474 | 475 | ) |
|
475 | 476 | out = "False\nFalse\nFalse\n" |
|
476 | 477 | tt.ipexec_validate(self.fname, out) |
|
477 | 478 | |
|
478 | 479 | class Negator(ast.NodeTransformer): |
|
479 | 480 | """Negates all number literals in an AST.""" |
|
480 | 481 | def visit_Num(self, node): |
|
481 | 482 | node.n = -node.n |
|
482 | 483 | return node |
|
483 | 484 | |
|
484 | 485 | class TestAstTransform(unittest.TestCase): |
|
485 | 486 | def setUp(self): |
|
486 | 487 | self.negator = Negator() |
|
487 | 488 | ip.ast_transformers.append(self.negator) |
|
488 | 489 | |
|
489 | 490 | def tearDown(self): |
|
490 | 491 | ip.ast_transformers.remove(self.negator) |
|
491 | 492 | |
|
492 | 493 | def test_run_cell(self): |
|
493 | 494 | with tt.AssertPrints('-34'): |
|
494 | 495 | ip.run_cell('print (12 + 22)') |
|
495 | 496 | |
|
496 | 497 | # A named reference to a number shouldn't be transformed. |
|
497 | 498 | ip.user_ns['n'] = 55 |
|
498 | 499 | with tt.AssertNotPrints('-55'): |
|
499 | 500 | ip.run_cell('print (n)') |
|
500 | 501 | |
|
501 | 502 | def test_timeit(self): |
|
502 | 503 | called = set() |
|
503 | 504 | def f(x): |
|
504 | 505 | called.add(x) |
|
505 | 506 | ip.push({'f':f}) |
|
506 | 507 | |
|
507 | 508 | with tt.AssertPrints("best of "): |
|
508 | 509 | ip.run_line_magic("timeit", "-n1 f(1)") |
|
509 | 510 | self.assertEqual(called, set([-1])) |
|
510 | 511 | called.clear() |
|
511 | 512 | |
|
512 | 513 | with tt.AssertPrints("best of "): |
|
513 | 514 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") |
|
514 | 515 | self.assertEqual(called, set([-2, -3])) |
|
515 | 516 | |
|
516 | 517 | def test_time(self): |
|
517 | 518 | called = [] |
|
518 | 519 | def f(x): |
|
519 | 520 | called.append(x) |
|
520 | 521 | ip.push({'f':f}) |
|
521 | 522 | |
|
522 | 523 | # Test with an expression |
|
523 | 524 | with tt.AssertPrints("Wall time: "): |
|
524 | 525 | ip.run_line_magic("time", "f(5+9)") |
|
525 | 526 | self.assertEqual(called, [-14]) |
|
526 | 527 | called[:] = [] |
|
527 | 528 | |
|
528 | 529 | # Test with a statement (different code path) |
|
529 | 530 | with tt.AssertPrints("Wall time: "): |
|
530 | 531 | ip.run_line_magic("time", "a = f(-3 + -2)") |
|
531 | 532 | self.assertEqual(called, [5]) |
|
532 | 533 | |
|
533 | 534 | def test_macro(self): |
|
534 | 535 | ip.push({'a':10}) |
|
535 | 536 | # The AST transformation makes this do a+=-1 |
|
536 | 537 | ip.define_macro("amacro", "a+=1\nprint(a)") |
|
537 | 538 | |
|
538 | 539 | with tt.AssertPrints("9"): |
|
539 | 540 | ip.run_cell("amacro") |
|
540 | 541 | with tt.AssertPrints("8"): |
|
541 | 542 | ip.run_cell("amacro") |
|
542 | 543 | |
|
543 | 544 | class IntegerWrapper(ast.NodeTransformer): |
|
544 | 545 | """Wraps all integers in a call to Integer()""" |
|
545 | 546 | def visit_Num(self, node): |
|
546 | 547 | if isinstance(node.n, int): |
|
547 | 548 | return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()), |
|
548 | 549 | args=[node], keywords=[]) |
|
549 | 550 | return node |
|
550 | 551 | |
|
551 | 552 | class TestAstTransform2(unittest.TestCase): |
|
552 | 553 | def setUp(self): |
|
553 | 554 | self.intwrapper = IntegerWrapper() |
|
554 | 555 | ip.ast_transformers.append(self.intwrapper) |
|
555 | 556 | |
|
556 | 557 | self.calls = [] |
|
557 | 558 | def Integer(*args): |
|
558 | 559 | self.calls.append(args) |
|
559 | 560 | return args |
|
560 | 561 | ip.push({"Integer": Integer}) |
|
561 | 562 | |
|
562 | 563 | def tearDown(self): |
|
563 | 564 | ip.ast_transformers.remove(self.intwrapper) |
|
564 | 565 | del ip.user_ns['Integer'] |
|
565 | 566 | |
|
566 | 567 | def test_run_cell(self): |
|
567 | 568 | ip.run_cell("n = 2") |
|
568 | 569 | self.assertEqual(self.calls, [(2,)]) |
|
569 | 570 | |
|
570 | 571 | # This shouldn't throw an error |
|
571 | 572 | ip.run_cell("o = 2.0") |
|
572 | 573 | self.assertEqual(ip.user_ns['o'], 2.0) |
|
573 | 574 | |
|
574 | 575 | def test_timeit(self): |
|
575 | 576 | called = set() |
|
576 | 577 | def f(x): |
|
577 | 578 | called.add(x) |
|
578 | 579 | ip.push({'f':f}) |
|
579 | 580 | |
|
580 | 581 | with tt.AssertPrints("best of "): |
|
581 | 582 | ip.run_line_magic("timeit", "-n1 f(1)") |
|
582 | 583 | self.assertEqual(called, set([(1,)])) |
|
583 | 584 | called.clear() |
|
584 | 585 | |
|
585 | 586 | with tt.AssertPrints("best of "): |
|
586 | 587 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") |
|
587 | 588 | self.assertEqual(called, set([(2,), (3,)])) |
|
588 | 589 | |
|
589 | 590 | class ErrorTransformer(ast.NodeTransformer): |
|
590 | 591 | """Throws an error when it sees a number.""" |
|
591 | 592 | def visit_Num(self): |
|
592 | 593 | raise ValueError("test") |
|
593 | 594 | |
|
594 | 595 | class TestAstTransformError(unittest.TestCase): |
|
595 | 596 | def test_unregistering(self): |
|
596 | 597 | err_transformer = ErrorTransformer() |
|
597 | 598 | ip.ast_transformers.append(err_transformer) |
|
598 | 599 | |
|
599 | 600 | with tt.AssertPrints("unregister", channel='stderr'): |
|
600 | 601 | ip.run_cell("1 + 2") |
|
601 | 602 | |
|
602 | 603 | # This should have been removed. |
|
603 | 604 | nt.assert_not_in(err_transformer, ip.ast_transformers) |
|
604 | 605 | |
|
605 | 606 | def test__IPYTHON__(): |
|
606 | 607 | # This shouldn't raise a NameError, that's all |
|
607 | 608 | __IPYTHON__ |
|
608 | 609 | |
|
609 | 610 | |
|
610 | 611 | class DummyRepr(object): |
|
611 | 612 | def __repr__(self): |
|
612 | 613 | return "DummyRepr" |
|
613 | 614 | |
|
614 | 615 | def _repr_html_(self): |
|
615 | 616 | return "<b>dummy</b>" |
|
616 | 617 | |
|
617 | 618 | def _repr_javascript_(self): |
|
618 | 619 | return "console.log('hi');", {'key': 'value'} |
|
619 | 620 | |
|
620 | 621 | |
|
621 | 622 | def test_user_variables(): |
|
622 | 623 | # enable all formatters |
|
623 | 624 | ip.display_formatter.active_types = ip.display_formatter.format_types |
|
624 | 625 | |
|
625 | 626 | ip.user_ns['dummy'] = d = DummyRepr() |
|
626 | 627 | keys = set(['dummy', 'doesnotexist']) |
|
627 | 628 | r = ip.user_variables(keys) |
|
628 | 629 | |
|
629 | 630 | nt.assert_equal(keys, set(r.keys())) |
|
630 | 631 | dummy = r['dummy'] |
|
631 | 632 | nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys())) |
|
632 | 633 | nt.assert_equal(dummy['status'], 'ok') |
|
633 | 634 | data = dummy['data'] |
|
634 | 635 | metadata = dummy['metadata'] |
|
635 | 636 | nt.assert_equal(data.get('text/html'), d._repr_html_()) |
|
636 | 637 | js, jsmd = d._repr_javascript_() |
|
637 | 638 | nt.assert_equal(data.get('application/javascript'), js) |
|
638 | 639 | nt.assert_equal(metadata.get('application/javascript'), jsmd) |
|
639 | 640 | |
|
640 | 641 | dne = r['doesnotexist'] |
|
641 | 642 | nt.assert_equal(dne['status'], 'error') |
|
642 | 643 | nt.assert_equal(dne['ename'], 'KeyError') |
|
643 | 644 | |
|
644 | 645 | # back to text only |
|
645 | 646 | ip.display_formatter.active_types = ['text/plain'] |
|
646 | 647 | |
|
647 | 648 | def test_user_expression(): |
|
648 | 649 | # enable all formatters |
|
649 | 650 | ip.display_formatter.active_types = ip.display_formatter.format_types |
|
650 | 651 | query = { |
|
651 | 652 | 'a' : '1 + 2', |
|
652 | 653 | 'b' : '1/0', |
|
653 | 654 | } |
|
654 | 655 | r = ip.user_expressions(query) |
|
655 | 656 | import pprint |
|
656 | 657 | pprint.pprint(r) |
|
657 | 658 | nt.assert_equal(r.keys(), query.keys()) |
|
658 | 659 | a = r['a'] |
|
659 | 660 | nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys())) |
|
660 | 661 | nt.assert_equal(a['status'], 'ok') |
|
661 | 662 | data = a['data'] |
|
662 | 663 | metadata = a['metadata'] |
|
663 | 664 | nt.assert_equal(data.get('text/plain'), '3') |
|
664 | 665 | |
|
665 | 666 | b = r['b'] |
|
666 | 667 | nt.assert_equal(b['status'], 'error') |
|
667 | 668 | nt.assert_equal(b['ename'], 'ZeroDivisionError') |
|
668 | 669 | |
|
669 | 670 | # back to text only |
|
670 | 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 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Tests for various magic functions. |
|
3 | 3 | |
|
4 | 4 | Needs to be run by nose (to make ipython session available). |
|
5 | 5 | """ |
|
6 | 6 | from __future__ import absolute_import |
|
7 | 7 | |
|
8 | 8 | #----------------------------------------------------------------------------- |
|
9 | 9 | # Imports |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | |
|
12 | 12 | import io |
|
13 | 13 | import os |
|
14 | 14 | import sys |
|
15 | 15 | from unittest import TestCase |
|
16 | 16 | |
|
17 | 17 | try: |
|
18 | 18 | from importlib import invalidate_caches # Required from Python 3.3 |
|
19 | 19 | except ImportError: |
|
20 | 20 | def invalidate_caches(): |
|
21 | 21 | pass |
|
22 | 22 | |
|
23 | 23 | import nose.tools as nt |
|
24 | 24 | |
|
25 | 25 | from IPython.core import magic |
|
26 | 26 | from IPython.core.magic import (Magics, magics_class, line_magic, |
|
27 | 27 | cell_magic, line_cell_magic, |
|
28 | 28 | register_line_magic, register_cell_magic, |
|
29 | 29 | register_line_cell_magic) |
|
30 | 30 | from IPython.core.magics import execution, script, code |
|
31 | 31 | from IPython.nbformat.v3.tests.nbexamples import nb0 |
|
32 | 32 | from IPython.nbformat import current |
|
33 | 33 | from IPython.testing import decorators as dec |
|
34 | 34 | from IPython.testing import tools as tt |
|
35 | 35 | from IPython.utils import py3compat |
|
36 | 36 | from IPython.utils.io import capture_output |
|
37 | 37 | from IPython.utils.tempdir import TemporaryDirectory |
|
38 | 38 | from IPython.utils.process import find_cmd |
|
39 | 39 | |
|
40 | 40 | if py3compat.PY3: |
|
41 | 41 | from io import StringIO |
|
42 | 42 | else: |
|
43 | 43 | from StringIO import StringIO |
|
44 | 44 | |
|
45 | 45 | #----------------------------------------------------------------------------- |
|
46 | 46 | # Test functions begin |
|
47 | 47 | #----------------------------------------------------------------------------- |
|
48 | 48 | |
|
49 | 49 | @magic.magics_class |
|
50 | 50 | class DummyMagics(magic.Magics): pass |
|
51 | 51 | |
|
52 | 52 | def test_extract_code_ranges(): |
|
53 | 53 | instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" |
|
54 | 54 | expected = [(0, 1), |
|
55 | 55 | (2, 3), |
|
56 | 56 | (4, 6), |
|
57 | 57 | (6, 9), |
|
58 | 58 | (9, 14), |
|
59 | 59 | (16, None), |
|
60 | 60 | (None, 9), |
|
61 | 61 | (9, None), |
|
62 | 62 | (None, 13), |
|
63 | 63 | (None, None)] |
|
64 | 64 | actual = list(code.extract_code_ranges(instr)) |
|
65 | 65 | nt.assert_equal(actual, expected) |
|
66 | 66 | |
|
67 | 67 | def test_extract_symbols(): |
|
68 | 68 | source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" |
|
69 | 69 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] |
|
70 | 70 | expected = [([], ['a']), |
|
71 | 71 | (["def b():\n return 42\n"], []), |
|
72 | 72 | (["class A: pass\n"], []), |
|
73 | 73 | (["class A: pass\n", "def b():\n return 42\n"], []), |
|
74 | 74 | (["class A: pass\n"], ['a']), |
|
75 | 75 | ([], ['z'])] |
|
76 | 76 | for symbols, exp in zip(symbols_args, expected): |
|
77 | 77 | nt.assert_equal(code.extract_symbols(source, symbols), exp) |
|
78 | 78 | |
|
79 | 79 | |
|
80 | 80 | def test_extract_symbols_raises_exception_with_non_python_code(): |
|
81 | 81 | source = ("=begin A Ruby program :)=end\n" |
|
82 | 82 | "def hello\n" |
|
83 | 83 | "puts 'Hello world'\n" |
|
84 | 84 | "end") |
|
85 | 85 | with nt.assert_raises(SyntaxError): |
|
86 | 86 | code.extract_symbols(source, "hello") |
|
87 | 87 | |
|
88 | 88 | def test_config(): |
|
89 | 89 | """ test that config magic does not raise |
|
90 | 90 | can happen if Configurable init is moved too early into |
|
91 | 91 | Magics.__init__ as then a Config object will be registerd as a |
|
92 | 92 | magic. |
|
93 | 93 | """ |
|
94 | 94 | ## should not raise. |
|
95 | 95 | _ip.magic('config') |
|
96 | 96 | |
|
97 | 97 | def test_rehashx(): |
|
98 | 98 | # clear up everything |
|
99 | 99 | _ip = get_ipython() |
|
100 | 100 | _ip.alias_manager.clear_aliases() |
|
101 | 101 | del _ip.db['syscmdlist'] |
|
102 | 102 | |
|
103 | 103 | _ip.magic('rehashx') |
|
104 | 104 | # Practically ALL ipython development systems will have more than 10 aliases |
|
105 | 105 | |
|
106 | 106 | nt.assert_true(len(_ip.alias_manager.aliases) > 10) |
|
107 | 107 | for name, cmd in _ip.alias_manager.aliases: |
|
108 | 108 | # we must strip dots from alias names |
|
109 | 109 | nt.assert_not_in('.', name) |
|
110 | 110 | |
|
111 | 111 | # rehashx must fill up syscmdlist |
|
112 | 112 | scoms = _ip.db['syscmdlist'] |
|
113 | 113 | nt.assert_true(len(scoms) > 10) |
|
114 | 114 | |
|
115 | 115 | |
|
116 | 116 | def test_magic_parse_options(): |
|
117 | 117 | """Test that we don't mangle paths when parsing magic options.""" |
|
118 | 118 | ip = get_ipython() |
|
119 | 119 | path = 'c:\\x' |
|
120 | 120 | m = DummyMagics(ip) |
|
121 | 121 | opts = m.parse_options('-f %s' % path,'f:')[0] |
|
122 | 122 | # argv splitting is os-dependent |
|
123 | 123 | if os.name == 'posix': |
|
124 | 124 | expected = 'c:x' |
|
125 | 125 | else: |
|
126 | 126 | expected = path |
|
127 | 127 | nt.assert_equal(opts['f'], expected) |
|
128 | 128 | |
|
129 | 129 | def test_magic_parse_long_options(): |
|
130 | 130 | """Magic.parse_options can handle --foo=bar long options""" |
|
131 | 131 | ip = get_ipython() |
|
132 | 132 | m = DummyMagics(ip) |
|
133 | 133 | opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=') |
|
134 | 134 | nt.assert_in('foo', opts) |
|
135 | 135 | nt.assert_in('bar', opts) |
|
136 | 136 | nt.assert_equal(opts['bar'], "bubble") |
|
137 | 137 | |
|
138 | 138 | |
|
139 | 139 | @dec.skip_without('sqlite3') |
|
140 | 140 | def doctest_hist_f(): |
|
141 | 141 | """Test %hist -f with temporary filename. |
|
142 | 142 | |
|
143 | 143 | In [9]: import tempfile |
|
144 | 144 | |
|
145 | 145 | In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') |
|
146 | 146 | |
|
147 | 147 | In [11]: %hist -nl -f $tfile 3 |
|
148 | 148 | |
|
149 | 149 | In [13]: import os; os.unlink(tfile) |
|
150 | 150 | """ |
|
151 | 151 | |
|
152 | 152 | |
|
153 | 153 | @dec.skip_without('sqlite3') |
|
154 | 154 | def doctest_hist_r(): |
|
155 | 155 | """Test %hist -r |
|
156 | 156 | |
|
157 | 157 | XXX - This test is not recording the output correctly. For some reason, in |
|
158 | 158 | testing mode the raw history isn't getting populated. No idea why. |
|
159 | 159 | Disabling the output checking for now, though at least we do run it. |
|
160 | 160 | |
|
161 | 161 | In [1]: 'hist' in _ip.lsmagic() |
|
162 | 162 | Out[1]: True |
|
163 | 163 | |
|
164 | 164 | In [2]: x=1 |
|
165 | 165 | |
|
166 | 166 | In [3]: %hist -rl 2 |
|
167 | 167 | x=1 # random |
|
168 | 168 | %hist -r 2 |
|
169 | 169 | """ |
|
170 | 170 | |
|
171 | 171 | |
|
172 | 172 | @dec.skip_without('sqlite3') |
|
173 | 173 | def doctest_hist_op(): |
|
174 | 174 | """Test %hist -op |
|
175 | 175 | |
|
176 | 176 | In [1]: class b(float): |
|
177 | 177 | ...: pass |
|
178 | 178 | ...: |
|
179 | 179 | |
|
180 | 180 | In [2]: class s(object): |
|
181 | 181 | ...: def __str__(self): |
|
182 | 182 | ...: return 's' |
|
183 | 183 | ...: |
|
184 | 184 | |
|
185 | 185 | In [3]: |
|
186 | 186 | |
|
187 | 187 | In [4]: class r(b): |
|
188 | 188 | ...: def __repr__(self): |
|
189 | 189 | ...: return 'r' |
|
190 | 190 | ...: |
|
191 | 191 | |
|
192 | 192 | In [5]: class sr(s,r): pass |
|
193 | 193 | ...: |
|
194 | 194 | |
|
195 | 195 | In [6]: |
|
196 | 196 | |
|
197 | 197 | In [7]: bb=b() |
|
198 | 198 | |
|
199 | 199 | In [8]: ss=s() |
|
200 | 200 | |
|
201 | 201 | In [9]: rr=r() |
|
202 | 202 | |
|
203 | 203 | In [10]: ssrr=sr() |
|
204 | 204 | |
|
205 | 205 | In [11]: 4.5 |
|
206 | 206 | Out[11]: 4.5 |
|
207 | 207 | |
|
208 | 208 | In [12]: str(ss) |
|
209 | 209 | Out[12]: 's' |
|
210 | 210 | |
|
211 | 211 | In [13]: |
|
212 | 212 | |
|
213 | 213 | In [14]: %hist -op |
|
214 | 214 | >>> class b: |
|
215 | 215 | ... pass |
|
216 | 216 | ... |
|
217 | 217 | >>> class s(b): |
|
218 | 218 | ... def __str__(self): |
|
219 | 219 | ... return 's' |
|
220 | 220 | ... |
|
221 | 221 | >>> |
|
222 | 222 | >>> class r(b): |
|
223 | 223 | ... def __repr__(self): |
|
224 | 224 | ... return 'r' |
|
225 | 225 | ... |
|
226 | 226 | >>> class sr(s,r): pass |
|
227 | 227 | >>> |
|
228 | 228 | >>> bb=b() |
|
229 | 229 | >>> ss=s() |
|
230 | 230 | >>> rr=r() |
|
231 | 231 | >>> ssrr=sr() |
|
232 | 232 | >>> 4.5 |
|
233 | 233 | 4.5 |
|
234 | 234 | >>> str(ss) |
|
235 | 235 | 's' |
|
236 | 236 | >>> |
|
237 | 237 | """ |
|
238 | 238 | |
|
239 | 239 | |
|
240 | 240 | @dec.skip_without('sqlite3') |
|
241 | 241 | def test_macro(): |
|
242 | 242 | ip = get_ipython() |
|
243 | 243 | ip.history_manager.reset() # Clear any existing history. |
|
244 | 244 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] |
|
245 | 245 | for i, cmd in enumerate(cmds, start=1): |
|
246 | 246 | ip.history_manager.store_inputs(i, cmd) |
|
247 | 247 | ip.magic("macro test 1-3") |
|
248 | 248 | nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n") |
|
249 | 249 | |
|
250 | 250 | # List macros |
|
251 | 251 | nt.assert_in("test", ip.magic("macro")) |
|
252 | 252 | |
|
253 | 253 | |
|
254 | 254 | @dec.skip_without('sqlite3') |
|
255 | 255 | def test_macro_run(): |
|
256 | 256 | """Test that we can run a multi-line macro successfully.""" |
|
257 | 257 | ip = get_ipython() |
|
258 | 258 | ip.history_manager.reset() |
|
259 | 259 | cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"), |
|
260 | 260 | "%macro test 2-3"] |
|
261 | 261 | for cmd in cmds: |
|
262 | 262 | ip.run_cell(cmd, store_history=True) |
|
263 | 263 | nt.assert_equal(ip.user_ns["test"].value, |
|
264 | 264 | py3compat.doctest_refactor_print("a+=1\nprint a\n")) |
|
265 | 265 | with tt.AssertPrints("12"): |
|
266 | 266 | ip.run_cell("test") |
|
267 | 267 | with tt.AssertPrints("13"): |
|
268 | 268 | ip.run_cell("test") |
|
269 | 269 | |
|
270 | 270 | |
|
271 | 271 | def test_magic_magic(): |
|
272 | 272 | """Test %magic""" |
|
273 | 273 | ip = get_ipython() |
|
274 | 274 | with capture_output() as captured: |
|
275 | 275 | ip.magic("magic") |
|
276 | 276 | |
|
277 | 277 | stdout = captured.stdout |
|
278 | 278 | nt.assert_in('%magic', stdout) |
|
279 | 279 | nt.assert_in('IPython', stdout) |
|
280 | 280 | nt.assert_in('Available', stdout) |
|
281 | 281 | |
|
282 | 282 | |
|
283 | 283 | @dec.skipif_not_numpy |
|
284 | 284 | def test_numpy_reset_array_undec(): |
|
285 | 285 | "Test '%reset array' functionality" |
|
286 | 286 | _ip.ex('import numpy as np') |
|
287 | 287 | _ip.ex('a = np.empty(2)') |
|
288 | 288 | nt.assert_in('a', _ip.user_ns) |
|
289 | 289 | _ip.magic('reset -f array') |
|
290 | 290 | nt.assert_not_in('a', _ip.user_ns) |
|
291 | 291 | |
|
292 | 292 | def test_reset_out(): |
|
293 | 293 | "Test '%reset out' magic" |
|
294 | 294 | _ip.run_cell("parrot = 'dead'", store_history=True) |
|
295 | 295 | # test '%reset -f out', make an Out prompt |
|
296 | 296 | _ip.run_cell("parrot", store_history=True) |
|
297 | 297 | nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) |
|
298 | 298 | _ip.magic('reset -f out') |
|
299 | 299 | nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) |
|
300 | 300 | nt.assert_equal(len(_ip.user_ns['Out']), 0) |
|
301 | 301 | |
|
302 | 302 | def test_reset_in(): |
|
303 | 303 | "Test '%reset in' magic" |
|
304 | 304 | # test '%reset -f in' |
|
305 | 305 | _ip.run_cell("parrot", store_history=True) |
|
306 | 306 | nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) |
|
307 | 307 | _ip.magic('%reset -f in') |
|
308 | 308 | nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) |
|
309 | 309 | nt.assert_equal(len(set(_ip.user_ns['In'])), 1) |
|
310 | 310 | |
|
311 | 311 | def test_reset_dhist(): |
|
312 | 312 | "Test '%reset dhist' magic" |
|
313 | 313 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing |
|
314 | 314 | _ip.magic('cd ' + os.path.dirname(nt.__file__)) |
|
315 | 315 | _ip.magic('cd -') |
|
316 | 316 | nt.assert_true(len(_ip.user_ns['_dh']) > 0) |
|
317 | 317 | _ip.magic('reset -f dhist') |
|
318 | 318 | nt.assert_equal(len(_ip.user_ns['_dh']), 0) |
|
319 | 319 | _ip.run_cell("_dh = [d for d in tmp]") #restore |
|
320 | 320 | |
|
321 | 321 | def test_reset_in_length(): |
|
322 | 322 | "Test that '%reset in' preserves In[] length" |
|
323 | 323 | _ip.run_cell("print 'foo'") |
|
324 | 324 | _ip.run_cell("reset -f in") |
|
325 | 325 | nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1) |
|
326 | 326 | |
|
327 | 327 | def test_tb_syntaxerror(): |
|
328 | 328 | """test %tb after a SyntaxError""" |
|
329 | 329 | ip = get_ipython() |
|
330 | 330 | ip.run_cell("for") |
|
331 | 331 | |
|
332 | 332 | # trap and validate stdout |
|
333 | 333 | save_stdout = sys.stdout |
|
334 | 334 | try: |
|
335 | 335 | sys.stdout = StringIO() |
|
336 | 336 | ip.run_cell("%tb") |
|
337 | 337 | out = sys.stdout.getvalue() |
|
338 | 338 | finally: |
|
339 | 339 | sys.stdout = save_stdout |
|
340 | 340 | # trim output, and only check the last line |
|
341 | 341 | last_line = out.rstrip().splitlines()[-1].strip() |
|
342 | 342 | nt.assert_equal(last_line, "SyntaxError: invalid syntax") |
|
343 | 343 | |
|
344 | 344 | |
|
345 | 345 | def test_time(): |
|
346 | 346 | ip = get_ipython() |
|
347 | 347 | |
|
348 | 348 | with tt.AssertPrints("Wall time: "): |
|
349 | 349 | ip.run_cell("%time None") |
|
350 | 350 | |
|
351 | 351 | ip.run_cell("def f(kmjy):\n" |
|
352 | 352 | " %time print (2*kmjy)") |
|
353 | 353 | |
|
354 | 354 | with tt.AssertPrints("Wall time: "): |
|
355 | 355 | with tt.AssertPrints("hihi", suppress=False): |
|
356 | 356 | ip.run_cell("f('hi')") |
|
357 | 357 | |
|
358 | 358 | |
|
359 | 359 | @dec.skip_win32 |
|
360 | 360 | def test_time2(): |
|
361 | 361 | ip = get_ipython() |
|
362 | 362 | |
|
363 | 363 | with tt.AssertPrints("CPU times: user "): |
|
364 | 364 | ip.run_cell("%time None") |
|
365 | 365 | |
|
366 | 366 | def test_time3(): |
|
367 | 367 | """Erroneous magic function calls, issue gh-3334""" |
|
368 | 368 | ip = get_ipython() |
|
369 | 369 | ip.user_ns.pop('run', None) |
|
370 | 370 | |
|
371 | 371 | with tt.AssertNotPrints("not found", channel='stderr'): |
|
372 | 372 | ip.run_cell("%%time\n" |
|
373 | 373 | "run = 0\n" |
|
374 | 374 | "run += 1") |
|
375 | 375 | |
|
376 | 376 | def test_doctest_mode(): |
|
377 | 377 | "Toggle doctest_mode twice, it should be a no-op and run without error" |
|
378 | 378 | _ip.magic('doctest_mode') |
|
379 | 379 | _ip.magic('doctest_mode') |
|
380 | 380 | |
|
381 | 381 | |
|
382 | 382 | def test_parse_options(): |
|
383 | 383 | """Tests for basic options parsing in magics.""" |
|
384 | 384 | # These are only the most minimal of tests, more should be added later. At |
|
385 | 385 | # the very least we check that basic text/unicode calls work OK. |
|
386 | 386 | m = DummyMagics(_ip) |
|
387 | 387 | nt.assert_equal(m.parse_options('foo', '')[1], 'foo') |
|
388 | 388 | nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo') |
|
389 | 389 | |
|
390 | 390 | |
|
391 | 391 | def test_dirops(): |
|
392 | 392 | """Test various directory handling operations.""" |
|
393 |
# curpath = lambda :os.path.splitdrive( |
|
|
394 |
curpath = |
|
|
395 |
startdir = |
|
|
393 | # curpath = lambda :os.path.splitdrive(py3compat.getcwd())[1].replace('\\','/') | |
|
394 | curpath = py3compat.getcwd | |
|
395 | startdir = py3compat.getcwd() | |
|
396 | 396 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
397 | 397 | try: |
|
398 | 398 | _ip.magic('cd "%s"' % ipdir) |
|
399 | 399 | nt.assert_equal(curpath(), ipdir) |
|
400 | 400 | _ip.magic('cd -') |
|
401 | 401 | nt.assert_equal(curpath(), startdir) |
|
402 | 402 | _ip.magic('pushd "%s"' % ipdir) |
|
403 | 403 | nt.assert_equal(curpath(), ipdir) |
|
404 | 404 | _ip.magic('popd') |
|
405 | 405 | nt.assert_equal(curpath(), startdir) |
|
406 | 406 | finally: |
|
407 | 407 | os.chdir(startdir) |
|
408 | 408 | |
|
409 | 409 | |
|
410 | 410 | def test_xmode(): |
|
411 | 411 | # Calling xmode three times should be a no-op |
|
412 | 412 | xmode = _ip.InteractiveTB.mode |
|
413 | 413 | for i in range(3): |
|
414 | 414 | _ip.magic("xmode") |
|
415 | 415 | nt.assert_equal(_ip.InteractiveTB.mode, xmode) |
|
416 | 416 | |
|
417 | 417 | def test_reset_hard(): |
|
418 | 418 | monitor = [] |
|
419 | 419 | class A(object): |
|
420 | 420 | def __del__(self): |
|
421 | 421 | monitor.append(1) |
|
422 | 422 | def __repr__(self): |
|
423 | 423 | return "<A instance>" |
|
424 | 424 | |
|
425 | 425 | _ip.user_ns["a"] = A() |
|
426 | 426 | _ip.run_cell("a") |
|
427 | 427 | |
|
428 | 428 | nt.assert_equal(monitor, []) |
|
429 | 429 | _ip.magic("reset -f") |
|
430 | 430 | nt.assert_equal(monitor, [1]) |
|
431 | 431 | |
|
432 | 432 | class TestXdel(tt.TempFileMixin): |
|
433 | 433 | def test_xdel(self): |
|
434 | 434 | """Test that references from %run are cleared by xdel.""" |
|
435 | 435 | src = ("class A(object):\n" |
|
436 | 436 | " monitor = []\n" |
|
437 | 437 | " def __del__(self):\n" |
|
438 | 438 | " self.monitor.append(1)\n" |
|
439 | 439 | "a = A()\n") |
|
440 | 440 | self.mktmp(src) |
|
441 | 441 | # %run creates some hidden references... |
|
442 | 442 | _ip.magic("run %s" % self.fname) |
|
443 | 443 | # ... as does the displayhook. |
|
444 | 444 | _ip.run_cell("a") |
|
445 | 445 | |
|
446 | 446 | monitor = _ip.user_ns["A"].monitor |
|
447 | 447 | nt.assert_equal(monitor, []) |
|
448 | 448 | |
|
449 | 449 | _ip.magic("xdel a") |
|
450 | 450 | |
|
451 | 451 | # Check that a's __del__ method has been called. |
|
452 | 452 | nt.assert_equal(monitor, [1]) |
|
453 | 453 | |
|
454 | 454 | def doctest_who(): |
|
455 | 455 | """doctest for %who |
|
456 | 456 | |
|
457 | 457 | In [1]: %reset -f |
|
458 | 458 | |
|
459 | 459 | In [2]: alpha = 123 |
|
460 | 460 | |
|
461 | 461 | In [3]: beta = 'beta' |
|
462 | 462 | |
|
463 | 463 | In [4]: %who int |
|
464 | 464 | alpha |
|
465 | 465 | |
|
466 | 466 | In [5]: %who str |
|
467 | 467 | beta |
|
468 | 468 | |
|
469 | 469 | In [6]: %whos |
|
470 | 470 | Variable Type Data/Info |
|
471 | 471 | ---------------------------- |
|
472 | 472 | alpha int 123 |
|
473 | 473 | beta str beta |
|
474 | 474 | |
|
475 | 475 | In [7]: %who_ls |
|
476 | 476 | Out[7]: ['alpha', 'beta'] |
|
477 | 477 | """ |
|
478 | 478 | |
|
479 | 479 | def test_whos(): |
|
480 | 480 | """Check that whos is protected against objects where repr() fails.""" |
|
481 | 481 | class A(object): |
|
482 | 482 | def __repr__(self): |
|
483 | 483 | raise Exception() |
|
484 | 484 | _ip.user_ns['a'] = A() |
|
485 | 485 | _ip.magic("whos") |
|
486 | 486 | |
|
487 | 487 | @py3compat.u_format |
|
488 | 488 | def doctest_precision(): |
|
489 | 489 | """doctest for %precision |
|
490 | 490 | |
|
491 | 491 | In [1]: f = get_ipython().display_formatter.formatters['text/plain'] |
|
492 | 492 | |
|
493 | 493 | In [2]: %precision 5 |
|
494 | 494 | Out[2]: {u}'%.5f' |
|
495 | 495 | |
|
496 | 496 | In [3]: f.float_format |
|
497 | 497 | Out[3]: {u}'%.5f' |
|
498 | 498 | |
|
499 | 499 | In [4]: %precision %e |
|
500 | 500 | Out[4]: {u}'%e' |
|
501 | 501 | |
|
502 | 502 | In [5]: f(3.1415927) |
|
503 | 503 | Out[5]: {u}'3.141593e+00' |
|
504 | 504 | """ |
|
505 | 505 | |
|
506 | 506 | def test_psearch(): |
|
507 | 507 | with tt.AssertPrints("dict.fromkeys"): |
|
508 | 508 | _ip.run_cell("dict.fr*?") |
|
509 | 509 | |
|
510 | 510 | def test_timeit_shlex(): |
|
511 | 511 | """test shlex issues with timeit (#1109)""" |
|
512 | 512 | _ip.ex("def f(*a,**kw): pass") |
|
513 | 513 | _ip.magic('timeit -n1 "this is a bug".count(" ")') |
|
514 | 514 | _ip.magic('timeit -r1 -n1 f(" ", 1)') |
|
515 | 515 | _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")') |
|
516 | 516 | _ip.magic('timeit -r1 -n1 ("a " + "b")') |
|
517 | 517 | _ip.magic('timeit -r1 -n1 f("a " + "b")') |
|
518 | 518 | _ip.magic('timeit -r1 -n1 f("a " + "b ")') |
|
519 | 519 | |
|
520 | 520 | |
|
521 | 521 | def test_timeit_arguments(): |
|
522 | 522 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" |
|
523 | 523 | _ip.magic("timeit ('#')") |
|
524 | 524 | |
|
525 | 525 | |
|
526 | 526 | def test_timeit_special_syntax(): |
|
527 | 527 | "Test %%timeit with IPython special syntax" |
|
528 | 528 | @register_line_magic |
|
529 | 529 | def lmagic(line): |
|
530 | 530 | ip = get_ipython() |
|
531 | 531 | ip.user_ns['lmagic_out'] = line |
|
532 | 532 | |
|
533 | 533 | # line mode test |
|
534 | 534 | _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line') |
|
535 | 535 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') |
|
536 | 536 | # cell mode test |
|
537 | 537 | _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2') |
|
538 | 538 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') |
|
539 | 539 | |
|
540 | 540 | def test_timeit_return(): |
|
541 | 541 | """ |
|
542 | 542 | test wether timeit -o return object |
|
543 | 543 | """ |
|
544 | 544 | |
|
545 | 545 | res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') |
|
546 | 546 | assert(res is not None) |
|
547 | 547 | |
|
548 | 548 | def test_timeit_quiet(): |
|
549 | 549 | """ |
|
550 | 550 | test quiet option of timeit magic |
|
551 | 551 | """ |
|
552 | 552 | with tt.AssertNotPrints("loops"): |
|
553 | 553 | _ip.run_cell("%timeit -n1 -r1 -q 1") |
|
554 | 554 | |
|
555 | 555 | @dec.skipif(execution.profile is None) |
|
556 | 556 | def test_prun_special_syntax(): |
|
557 | 557 | "Test %%prun with IPython special syntax" |
|
558 | 558 | @register_line_magic |
|
559 | 559 | def lmagic(line): |
|
560 | 560 | ip = get_ipython() |
|
561 | 561 | ip.user_ns['lmagic_out'] = line |
|
562 | 562 | |
|
563 | 563 | # line mode test |
|
564 | 564 | _ip.run_line_magic('prun', '-q %lmagic my line') |
|
565 | 565 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') |
|
566 | 566 | # cell mode test |
|
567 | 567 | _ip.run_cell_magic('prun', '-q', '%lmagic my line2') |
|
568 | 568 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') |
|
569 | 569 | |
|
570 | 570 | @dec.skipif(execution.profile is None) |
|
571 | 571 | def test_prun_quotes(): |
|
572 | 572 | "Test that prun does not clobber string escapes (GH #1302)" |
|
573 | 573 | _ip.magic(r"prun -q x = '\t'") |
|
574 | 574 | nt.assert_equal(_ip.user_ns['x'], '\t') |
|
575 | 575 | |
|
576 | 576 | def test_extension(): |
|
577 | 577 | tmpdir = TemporaryDirectory() |
|
578 | 578 | orig_ipython_dir = _ip.ipython_dir |
|
579 | 579 | try: |
|
580 | 580 | _ip.ipython_dir = tmpdir.name |
|
581 | 581 | nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension") |
|
582 | 582 | url = os.path.join(os.path.dirname(__file__), "daft_extension.py") |
|
583 | 583 | _ip.magic("install_ext %s" % url) |
|
584 | 584 | _ip.user_ns.pop('arq', None) |
|
585 | 585 | invalidate_caches() # Clear import caches |
|
586 | 586 | _ip.magic("load_ext daft_extension") |
|
587 | 587 | nt.assert_equal(_ip.user_ns['arq'], 185) |
|
588 | 588 | _ip.magic("unload_ext daft_extension") |
|
589 | 589 | assert 'arq' not in _ip.user_ns |
|
590 | 590 | finally: |
|
591 | 591 | _ip.ipython_dir = orig_ipython_dir |
|
592 | 592 | tmpdir.cleanup() |
|
593 | 593 | |
|
594 | 594 | def test_notebook_export_json(): |
|
595 | 595 | with TemporaryDirectory() as td: |
|
596 | 596 | outfile = os.path.join(td, "nb.ipynb") |
|
597 | 597 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
598 | 598 | _ip.magic("notebook -e %s" % outfile) |
|
599 | 599 | |
|
600 | 600 | def test_notebook_export_py(): |
|
601 | 601 | with TemporaryDirectory() as td: |
|
602 | 602 | outfile = os.path.join(td, "nb.py") |
|
603 | 603 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
604 | 604 | _ip.magic("notebook -e %s" % outfile) |
|
605 | 605 | |
|
606 | 606 | def test_notebook_reformat_py(): |
|
607 | 607 | with TemporaryDirectory() as td: |
|
608 | 608 | infile = os.path.join(td, "nb.ipynb") |
|
609 | 609 | with io.open(infile, 'w', encoding='utf-8') as f: |
|
610 | 610 | current.write(nb0, f, 'json') |
|
611 | 611 | |
|
612 | 612 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
613 | 613 | _ip.magic("notebook -f py %s" % infile) |
|
614 | 614 | |
|
615 | 615 | def test_notebook_reformat_json(): |
|
616 | 616 | with TemporaryDirectory() as td: |
|
617 | 617 | infile = os.path.join(td, "nb.py") |
|
618 | 618 | with io.open(infile, 'w', encoding='utf-8') as f: |
|
619 | 619 | current.write(nb0, f, 'py') |
|
620 | 620 | |
|
621 | 621 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
622 | 622 | _ip.magic("notebook -f ipynb %s" % infile) |
|
623 | 623 | _ip.magic("notebook -f json %s" % infile) |
|
624 | 624 | |
|
625 | 625 | def test_env(): |
|
626 | 626 | env = _ip.magic("env") |
|
627 | 627 | assert isinstance(env, dict), type(env) |
|
628 | 628 | |
|
629 | 629 | |
|
630 | 630 | class CellMagicTestCase(TestCase): |
|
631 | 631 | |
|
632 | 632 | def check_ident(self, magic): |
|
633 | 633 | # Manually called, we get the result |
|
634 | 634 | out = _ip.run_cell_magic(magic, 'a', 'b') |
|
635 | 635 | nt.assert_equal(out, ('a','b')) |
|
636 | 636 | # Via run_cell, it goes into the user's namespace via displayhook |
|
637 | 637 | _ip.run_cell('%%' + magic +' c\nd') |
|
638 | 638 | nt.assert_equal(_ip.user_ns['_'], ('c','d')) |
|
639 | 639 | |
|
640 | 640 | def test_cell_magic_func_deco(self): |
|
641 | 641 | "Cell magic using simple decorator" |
|
642 | 642 | @register_cell_magic |
|
643 | 643 | def cellm(line, cell): |
|
644 | 644 | return line, cell |
|
645 | 645 | |
|
646 | 646 | self.check_ident('cellm') |
|
647 | 647 | |
|
648 | 648 | def test_cell_magic_reg(self): |
|
649 | 649 | "Cell magic manually registered" |
|
650 | 650 | def cellm(line, cell): |
|
651 | 651 | return line, cell |
|
652 | 652 | |
|
653 | 653 | _ip.register_magic_function(cellm, 'cell', 'cellm2') |
|
654 | 654 | self.check_ident('cellm2') |
|
655 | 655 | |
|
656 | 656 | def test_cell_magic_class(self): |
|
657 | 657 | "Cell magics declared via a class" |
|
658 | 658 | @magics_class |
|
659 | 659 | class MyMagics(Magics): |
|
660 | 660 | |
|
661 | 661 | @cell_magic |
|
662 | 662 | def cellm3(self, line, cell): |
|
663 | 663 | return line, cell |
|
664 | 664 | |
|
665 | 665 | _ip.register_magics(MyMagics) |
|
666 | 666 | self.check_ident('cellm3') |
|
667 | 667 | |
|
668 | 668 | def test_cell_magic_class2(self): |
|
669 | 669 | "Cell magics declared via a class, #2" |
|
670 | 670 | @magics_class |
|
671 | 671 | class MyMagics2(Magics): |
|
672 | 672 | |
|
673 | 673 | @cell_magic('cellm4') |
|
674 | 674 | def cellm33(self, line, cell): |
|
675 | 675 | return line, cell |
|
676 | 676 | |
|
677 | 677 | _ip.register_magics(MyMagics2) |
|
678 | 678 | self.check_ident('cellm4') |
|
679 | 679 | # Check that nothing is registered as 'cellm33' |
|
680 | 680 | c33 = _ip.find_cell_magic('cellm33') |
|
681 | 681 | nt.assert_equal(c33, None) |
|
682 | 682 | |
|
683 | 683 | def test_file(): |
|
684 | 684 | """Basic %%file""" |
|
685 | 685 | ip = get_ipython() |
|
686 | 686 | with TemporaryDirectory() as td: |
|
687 | 687 | fname = os.path.join(td, 'file1') |
|
688 | 688 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
689 | 689 | 'line1', |
|
690 | 690 | 'line2', |
|
691 | 691 | ])) |
|
692 | 692 | with open(fname) as f: |
|
693 | 693 | s = f.read() |
|
694 | 694 | nt.assert_in('line1\n', s) |
|
695 | 695 | nt.assert_in('line2', s) |
|
696 | 696 | |
|
697 | 697 | def test_file_var_expand(): |
|
698 | 698 | """%%file $filename""" |
|
699 | 699 | ip = get_ipython() |
|
700 | 700 | with TemporaryDirectory() as td: |
|
701 | 701 | fname = os.path.join(td, 'file1') |
|
702 | 702 | ip.user_ns['filename'] = fname |
|
703 | 703 | ip.run_cell_magic("file", '$filename', u'\n'.join([ |
|
704 | 704 | 'line1', |
|
705 | 705 | 'line2', |
|
706 | 706 | ])) |
|
707 | 707 | with open(fname) as f: |
|
708 | 708 | s = f.read() |
|
709 | 709 | nt.assert_in('line1\n', s) |
|
710 | 710 | nt.assert_in('line2', s) |
|
711 | 711 | |
|
712 | 712 | def test_file_unicode(): |
|
713 | 713 | """%%file with unicode cell""" |
|
714 | 714 | ip = get_ipython() |
|
715 | 715 | with TemporaryDirectory() as td: |
|
716 | 716 | fname = os.path.join(td, 'file1') |
|
717 | 717 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
718 | 718 | u'linΓ©1', |
|
719 | 719 | u'linΓ©2', |
|
720 | 720 | ])) |
|
721 | 721 | with io.open(fname, encoding='utf-8') as f: |
|
722 | 722 | s = f.read() |
|
723 | 723 | nt.assert_in(u'linΓ©1\n', s) |
|
724 | 724 | nt.assert_in(u'linΓ©2', s) |
|
725 | 725 | |
|
726 | 726 | def test_file_amend(): |
|
727 | 727 | """%%file -a amends files""" |
|
728 | 728 | ip = get_ipython() |
|
729 | 729 | with TemporaryDirectory() as td: |
|
730 | 730 | fname = os.path.join(td, 'file2') |
|
731 | 731 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
732 | 732 | 'line1', |
|
733 | 733 | 'line2', |
|
734 | 734 | ])) |
|
735 | 735 | ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([ |
|
736 | 736 | 'line3', |
|
737 | 737 | 'line4', |
|
738 | 738 | ])) |
|
739 | 739 | with open(fname) as f: |
|
740 | 740 | s = f.read() |
|
741 | 741 | nt.assert_in('line1\n', s) |
|
742 | 742 | nt.assert_in('line3\n', s) |
|
743 | 743 | |
|
744 | 744 | |
|
745 | 745 | def test_script_config(): |
|
746 | 746 | ip = get_ipython() |
|
747 | 747 | ip.config.ScriptMagics.script_magics = ['whoda'] |
|
748 | 748 | sm = script.ScriptMagics(shell=ip) |
|
749 | 749 | nt.assert_in('whoda', sm.magics['cell']) |
|
750 | 750 | |
|
751 | 751 | @dec.skip_win32 |
|
752 | 752 | def test_script_out(): |
|
753 | 753 | ip = get_ipython() |
|
754 | 754 | ip.run_cell_magic("script", "--out output sh", "echo 'hi'") |
|
755 | 755 | nt.assert_equal(ip.user_ns['output'], 'hi\n') |
|
756 | 756 | |
|
757 | 757 | @dec.skip_win32 |
|
758 | 758 | def test_script_err(): |
|
759 | 759 | ip = get_ipython() |
|
760 | 760 | ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2") |
|
761 | 761 | nt.assert_equal(ip.user_ns['error'], 'hello\n') |
|
762 | 762 | |
|
763 | 763 | @dec.skip_win32 |
|
764 | 764 | def test_script_out_err(): |
|
765 | 765 | ip = get_ipython() |
|
766 | 766 | ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2") |
|
767 | 767 | nt.assert_equal(ip.user_ns['output'], 'hi\n') |
|
768 | 768 | nt.assert_equal(ip.user_ns['error'], 'hello\n') |
|
769 | 769 | |
|
770 | 770 | @dec.skip_win32 |
|
771 | 771 | def test_script_bg_out(): |
|
772 | 772 | ip = get_ipython() |
|
773 | 773 | ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") |
|
774 | 774 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') |
|
775 | 775 | |
|
776 | 776 | @dec.skip_win32 |
|
777 | 777 | def test_script_bg_err(): |
|
778 | 778 | ip = get_ipython() |
|
779 | 779 | ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2") |
|
780 | 780 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') |
|
781 | 781 | |
|
782 | 782 | @dec.skip_win32 |
|
783 | 783 | def test_script_bg_out_err(): |
|
784 | 784 | ip = get_ipython() |
|
785 | 785 | ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2") |
|
786 | 786 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') |
|
787 | 787 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') |
|
788 | 788 | |
|
789 | 789 | def test_script_defaults(): |
|
790 | 790 | ip = get_ipython() |
|
791 | 791 | for cmd in ['sh', 'bash', 'perl', 'ruby']: |
|
792 | 792 | try: |
|
793 | 793 | find_cmd(cmd) |
|
794 | 794 | except Exception: |
|
795 | 795 | pass |
|
796 | 796 | else: |
|
797 | 797 | nt.assert_in(cmd, ip.magics_manager.magics['cell']) |
|
798 | 798 | |
|
799 | 799 | |
|
800 | 800 | @magics_class |
|
801 | 801 | class FooFoo(Magics): |
|
802 | 802 | """class with both %foo and %%foo magics""" |
|
803 | 803 | @line_magic('foo') |
|
804 | 804 | def line_foo(self, line): |
|
805 | 805 | "I am line foo" |
|
806 | 806 | pass |
|
807 | 807 | |
|
808 | 808 | @cell_magic("foo") |
|
809 | 809 | def cell_foo(self, line, cell): |
|
810 | 810 | "I am cell foo, not line foo" |
|
811 | 811 | pass |
|
812 | 812 | |
|
813 | 813 | def test_line_cell_info(): |
|
814 | 814 | """%%foo and %foo magics are distinguishable to inspect""" |
|
815 | 815 | ip = get_ipython() |
|
816 | 816 | ip.magics_manager.register(FooFoo) |
|
817 | 817 | oinfo = ip.object_inspect('foo') |
|
818 | 818 | nt.assert_true(oinfo['found']) |
|
819 | 819 | nt.assert_true(oinfo['ismagic']) |
|
820 | 820 | |
|
821 | 821 | oinfo = ip.object_inspect('%%foo') |
|
822 | 822 | nt.assert_true(oinfo['found']) |
|
823 | 823 | nt.assert_true(oinfo['ismagic']) |
|
824 | 824 | nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__) |
|
825 | 825 | |
|
826 | 826 | oinfo = ip.object_inspect('%foo') |
|
827 | 827 | nt.assert_true(oinfo['found']) |
|
828 | 828 | nt.assert_true(oinfo['ismagic']) |
|
829 | 829 | nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__) |
|
830 | 830 | |
|
831 | 831 | def test_multiple_magics(): |
|
832 | 832 | ip = get_ipython() |
|
833 | 833 | foo1 = FooFoo(ip) |
|
834 | 834 | foo2 = FooFoo(ip) |
|
835 | 835 | mm = ip.magics_manager |
|
836 | 836 | mm.register(foo1) |
|
837 | 837 | nt.assert_true(mm.magics['line']['foo'].__self__ is foo1) |
|
838 | 838 | mm.register(foo2) |
|
839 | 839 | nt.assert_true(mm.magics['line']['foo'].__self__ is foo2) |
|
840 | 840 | |
|
841 | 841 | def test_alias_magic(): |
|
842 | 842 | """Test %alias_magic.""" |
|
843 | 843 | ip = get_ipython() |
|
844 | 844 | mm = ip.magics_manager |
|
845 | 845 | |
|
846 | 846 | # Basic operation: both cell and line magics are created, if possible. |
|
847 | 847 | ip.run_line_magic('alias_magic', 'timeit_alias timeit') |
|
848 | 848 | nt.assert_in('timeit_alias', mm.magics['line']) |
|
849 | 849 | nt.assert_in('timeit_alias', mm.magics['cell']) |
|
850 | 850 | |
|
851 | 851 | # --cell is specified, line magic not created. |
|
852 | 852 | ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit') |
|
853 | 853 | nt.assert_not_in('timeit_cell_alias', mm.magics['line']) |
|
854 | 854 | nt.assert_in('timeit_cell_alias', mm.magics['cell']) |
|
855 | 855 | |
|
856 | 856 | # Test that line alias is created successfully. |
|
857 | 857 | ip.run_line_magic('alias_magic', '--line env_alias env') |
|
858 | 858 | nt.assert_equal(ip.run_line_magic('env', ''), |
|
859 | 859 | ip.run_line_magic('env_alias', '')) |
|
860 | 860 | |
|
861 | 861 | def test_save(): |
|
862 | 862 | """Test %save.""" |
|
863 | 863 | ip = get_ipython() |
|
864 | 864 | ip.history_manager.reset() # Clear any existing history. |
|
865 | 865 | cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"] |
|
866 | 866 | for i, cmd in enumerate(cmds, start=1): |
|
867 | 867 | ip.history_manager.store_inputs(i, cmd) |
|
868 | 868 | with TemporaryDirectory() as tmpdir: |
|
869 | 869 | file = os.path.join(tmpdir, "testsave.py") |
|
870 | 870 | ip.run_line_magic("save", "%s 1-10" % file) |
|
871 | 871 | with open(file) as f: |
|
872 | 872 | content = f.read() |
|
873 | 873 | nt.assert_equal(content.count(cmds[0]), 1) |
|
874 | 874 | nt.assert_in('coding: utf-8', content) |
|
875 | 875 | ip.run_line_magic("save", "-a %s 1-10" % file) |
|
876 | 876 | with open(file) as f: |
|
877 | 877 | content = f.read() |
|
878 | 878 | nt.assert_equal(content.count(cmds[0]), 2) |
|
879 | 879 | nt.assert_in('coding: utf-8', content) |
|
880 | 880 | |
|
881 | 881 | |
|
882 | 882 | def test_store(): |
|
883 | 883 | """Test %store.""" |
|
884 | 884 | ip = get_ipython() |
|
885 | 885 | ip.run_line_magic('load_ext', 'storemagic') |
|
886 | 886 | |
|
887 | 887 | # make sure the storage is empty |
|
888 | 888 | ip.run_line_magic('store', '-z') |
|
889 | 889 | ip.user_ns['var'] = 42 |
|
890 | 890 | ip.run_line_magic('store', 'var') |
|
891 | 891 | ip.user_ns['var'] = 39 |
|
892 | 892 | ip.run_line_magic('store', '-r') |
|
893 | 893 | nt.assert_equal(ip.user_ns['var'], 42) |
|
894 | 894 | |
|
895 | 895 | ip.run_line_magic('store', '-d var') |
|
896 | 896 | ip.user_ns['var'] = 39 |
|
897 | 897 | ip.run_line_magic('store' , '-r') |
|
898 | 898 | nt.assert_equal(ip.user_ns['var'], 39) |
|
899 | 899 | |
|
900 | 900 | |
|
901 | 901 | def _run_edit_test(arg_s, exp_filename=None, |
|
902 | 902 | exp_lineno=-1, |
|
903 | 903 | exp_contents=None, |
|
904 | 904 | exp_is_temp=None): |
|
905 | 905 | ip = get_ipython() |
|
906 | 906 | M = code.CodeMagics(ip) |
|
907 | 907 | last_call = ['',''] |
|
908 | 908 | opts,args = M.parse_options(arg_s,'prxn:') |
|
909 | 909 | filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) |
|
910 | 910 | |
|
911 | 911 | if exp_filename is not None: |
|
912 | 912 | nt.assert_equal(exp_filename, filename) |
|
913 | 913 | if exp_contents is not None: |
|
914 | 914 | with io.open(filename, 'r') as f: |
|
915 | 915 | contents = f.read() |
|
916 | 916 | nt.assert_equal(exp_contents, contents) |
|
917 | 917 | if exp_lineno != -1: |
|
918 | 918 | nt.assert_equal(exp_lineno, lineno) |
|
919 | 919 | if exp_is_temp is not None: |
|
920 | 920 | nt.assert_equal(exp_is_temp, is_temp) |
|
921 | 921 | |
|
922 | 922 | |
|
923 | 923 | def test_edit_interactive(): |
|
924 | 924 | """%edit on interactively defined objects""" |
|
925 | 925 | ip = get_ipython() |
|
926 | 926 | n = ip.execution_count |
|
927 | 927 | ip.run_cell(u"def foo(): return 1", store_history=True) |
|
928 | 928 | |
|
929 | 929 | try: |
|
930 | 930 | _run_edit_test("foo") |
|
931 | 931 | except code.InteractivelyDefined as e: |
|
932 | 932 | nt.assert_equal(e.index, n) |
|
933 | 933 | else: |
|
934 | 934 | raise AssertionError("Should have raised InteractivelyDefined") |
|
935 | 935 | |
|
936 | 936 | |
|
937 | 937 | def test_edit_cell(): |
|
938 | 938 | """%edit [cell id]""" |
|
939 | 939 | ip = get_ipython() |
|
940 | 940 | |
|
941 | 941 | ip.run_cell(u"def foo(): return 1", store_history=True) |
|
942 | 942 | |
|
943 | 943 | # test |
|
944 | 944 | _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) |
@@ -1,111 +1,112 b'' | |||
|
1 | 1 | # -*- coding: utf-8 |
|
2 | 2 | """Tests for prompt generation.""" |
|
3 | 3 | |
|
4 | 4 | import unittest |
|
5 | 5 | |
|
6 | 6 | import os |
|
7 | 7 | |
|
8 | 8 | from IPython.testing import tools as tt, decorators as dec |
|
9 | 9 | from IPython.core.prompts import PromptManager, LazyEvaluate |
|
10 | 10 | from IPython.testing.globalipapp import get_ipython |
|
11 | 11 | from IPython.utils.tempdir import TemporaryDirectory |
|
12 | from IPython.utils import py3compat | |
|
12 | 13 | from IPython.utils.py3compat import unicode_type |
|
13 | 14 | |
|
14 | 15 | ip = get_ipython() |
|
15 | 16 | |
|
16 | 17 | |
|
17 | 18 | class PromptTests(unittest.TestCase): |
|
18 | 19 | def setUp(self): |
|
19 | 20 | self.pm = PromptManager(shell=ip, config=ip.config) |
|
20 | 21 | |
|
21 | 22 | def test_multiline_prompt(self): |
|
22 | 23 | self.pm.in_template = "[In]\n>>>" |
|
23 | 24 | self.pm.render('in') |
|
24 | 25 | self.assertEqual(self.pm.width, 3) |
|
25 | 26 | self.assertEqual(self.pm.txtwidth, 3) |
|
26 | 27 | |
|
27 | 28 | self.pm.in_template = '[In]\n' |
|
28 | 29 | self.pm.render('in') |
|
29 | 30 | self.assertEqual(self.pm.width, 0) |
|
30 | 31 | self.assertEqual(self.pm.txtwidth, 0) |
|
31 | 32 | |
|
32 | 33 | def test_translate_abbreviations(self): |
|
33 | 34 | def do_translate(template): |
|
34 | 35 | self.pm.in_template = template |
|
35 | 36 | return self.pm.templates['in'] |
|
36 | 37 | |
|
37 | 38 | pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'), |
|
38 | 39 | (r'\T', '{time}'), |
|
39 | 40 | (r'\n', '\n') |
|
40 | 41 | ] |
|
41 | 42 | |
|
42 | 43 | tt.check_pairs(do_translate, pairs) |
|
43 | 44 | |
|
44 | 45 | def test_user_ns(self): |
|
45 | 46 | self.pm.color_scheme = 'NoColor' |
|
46 | 47 | ip.ex("foo='bar'") |
|
47 | 48 | self.pm.in_template = "In [{foo}]" |
|
48 | 49 | prompt = self.pm.render('in') |
|
49 | 50 | self.assertEqual(prompt, u'In [bar]') |
|
50 | 51 | |
|
51 | 52 | def test_builtins(self): |
|
52 | 53 | self.pm.color_scheme = 'NoColor' |
|
53 | 54 | self.pm.in_template = "In [{int}]" |
|
54 | 55 | prompt = self.pm.render('in') |
|
55 | 56 | self.assertEqual(prompt, u"In [%r]" % int) |
|
56 | 57 | |
|
57 | 58 | def test_undefined(self): |
|
58 | 59 | self.pm.color_scheme = 'NoColor' |
|
59 | 60 | self.pm.in_template = "In [{foo_dne}]" |
|
60 | 61 | prompt = self.pm.render('in') |
|
61 | 62 | self.assertEqual(prompt, u"In [<ERROR: 'foo_dne' not found>]") |
|
62 | 63 | |
|
63 | 64 | def test_render(self): |
|
64 | 65 | self.pm.in_template = r'\#>' |
|
65 | 66 | self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count) |
|
66 | 67 | |
|
67 | 68 | @dec.onlyif_unicode_paths |
|
68 | 69 | def test_render_unicode_cwd(self): |
|
69 |
save = |
|
|
70 | save = py3compat.getcwd() | |
|
70 | 71 | with TemporaryDirectory(u'ΓΌnicΓΈdΓ©') as td: |
|
71 | 72 | os.chdir(td) |
|
72 | 73 | self.pm.in_template = r'\w [\#]' |
|
73 | 74 | p = self.pm.render('in', color=False) |
|
74 |
self.assertEqual(p, u"%s [%i]" % ( |
|
|
75 | self.assertEqual(p, u"%s [%i]" % (py3compat.getcwd(), ip.execution_count)) | |
|
75 | 76 | os.chdir(save) |
|
76 | 77 | |
|
77 | 78 | def test_lazy_eval_unicode(self): |
|
78 | 79 | u = u'ΓΌnicΓΈdΓ©' |
|
79 | 80 | lz = LazyEvaluate(lambda : u) |
|
80 | 81 | # str(lz) would fail |
|
81 | 82 | self.assertEqual(unicode_type(lz), u) |
|
82 | 83 | self.assertEqual(format(lz), u) |
|
83 | 84 | |
|
84 | 85 | def test_lazy_eval_nonascii_bytes(self): |
|
85 | 86 | u = u'ΓΌnicΓΈdΓ©' |
|
86 | 87 | b = u.encode('utf8') |
|
87 | 88 | lz = LazyEvaluate(lambda : b) |
|
88 | 89 | # unicode(lz) would fail |
|
89 | 90 | self.assertEqual(str(lz), str(b)) |
|
90 | 91 | self.assertEqual(format(lz), str(b)) |
|
91 | 92 | |
|
92 | 93 | def test_lazy_eval_float(self): |
|
93 | 94 | f = 0.503 |
|
94 | 95 | lz = LazyEvaluate(lambda : f) |
|
95 | 96 | |
|
96 | 97 | self.assertEqual(str(lz), str(f)) |
|
97 | 98 | self.assertEqual(unicode_type(lz), unicode_type(f)) |
|
98 | 99 | self.assertEqual(format(lz), str(f)) |
|
99 | 100 | self.assertEqual(format(lz, '.1'), '0.5') |
|
100 | 101 | |
|
101 | 102 | @dec.skip_win32 |
|
102 | 103 | def test_cwd_x(self): |
|
103 | 104 | self.pm.in_template = r"\X0" |
|
104 |
save = |
|
|
105 | save = py3compat.getcwd() | |
|
105 | 106 | os.chdir(os.path.expanduser('~')) |
|
106 | 107 | p = self.pm.render('in', color=False) |
|
107 | 108 | try: |
|
108 | 109 | self.assertEqual(p, '~') |
|
109 | 110 | finally: |
|
110 | 111 | os.chdir(save) |
|
111 | 112 |
@@ -1,458 +1,458 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """Tests for code execution (%run and related), which is particularly tricky. |
|
3 | 3 | |
|
4 | 4 | Because of how %run manages namespaces, and the fact that we are trying here to |
|
5 | 5 | verify subtle object deletion and reference counting issues, the %run tests |
|
6 | 6 | will be kept in this separate file. This makes it easier to aggregate in one |
|
7 | 7 | place the tricks needed to handle it; most other magics are much easier to test |
|
8 | 8 | and we do so in a common test_magic file. |
|
9 | 9 | """ |
|
10 | 10 | from __future__ import absolute_import |
|
11 | 11 | |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | # Imports |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | |
|
16 | 16 | import functools |
|
17 | 17 | import os |
|
18 | 18 | from os.path import join as pjoin |
|
19 | 19 | import random |
|
20 | 20 | import sys |
|
21 | 21 | import tempfile |
|
22 | 22 | import textwrap |
|
23 | 23 | import unittest |
|
24 | 24 | |
|
25 | 25 | import nose.tools as nt |
|
26 | 26 | from nose import SkipTest |
|
27 | 27 | |
|
28 | 28 | from IPython.testing import decorators as dec |
|
29 | 29 | from IPython.testing import tools as tt |
|
30 | 30 | from IPython.utils import py3compat |
|
31 | 31 | from IPython.utils.tempdir import TemporaryDirectory |
|
32 | 32 | from IPython.core import debugger |
|
33 | 33 | |
|
34 | 34 | #----------------------------------------------------------------------------- |
|
35 | 35 | # Test functions begin |
|
36 | 36 | #----------------------------------------------------------------------------- |
|
37 | 37 | |
|
38 | 38 | def doctest_refbug(): |
|
39 | 39 | """Very nasty problem with references held by multiple runs of a script. |
|
40 | 40 | See: https://github.com/ipython/ipython/issues/141 |
|
41 | 41 | |
|
42 | 42 | In [1]: _ip.clear_main_mod_cache() |
|
43 | 43 | # random |
|
44 | 44 | |
|
45 | 45 | In [2]: %run refbug |
|
46 | 46 | |
|
47 | 47 | In [3]: call_f() |
|
48 | 48 | lowercased: hello |
|
49 | 49 | |
|
50 | 50 | In [4]: %run refbug |
|
51 | 51 | |
|
52 | 52 | In [5]: call_f() |
|
53 | 53 | lowercased: hello |
|
54 | 54 | lowercased: hello |
|
55 | 55 | """ |
|
56 | 56 | |
|
57 | 57 | |
|
58 | 58 | def doctest_run_builtins(): |
|
59 | 59 | r"""Check that %run doesn't damage __builtins__. |
|
60 | 60 | |
|
61 | 61 | In [1]: import tempfile |
|
62 | 62 | |
|
63 | 63 | In [2]: bid1 = id(__builtins__) |
|
64 | 64 | |
|
65 | 65 | In [3]: fname = tempfile.mkstemp('.py')[1] |
|
66 | 66 | |
|
67 | 67 | In [3]: f = open(fname,'w') |
|
68 | 68 | |
|
69 | 69 | In [4]: dummy= f.write('pass\n') |
|
70 | 70 | |
|
71 | 71 | In [5]: f.flush() |
|
72 | 72 | |
|
73 | 73 | In [6]: t1 = type(__builtins__) |
|
74 | 74 | |
|
75 | 75 | In [7]: %run $fname |
|
76 | 76 | |
|
77 | 77 | In [7]: f.close() |
|
78 | 78 | |
|
79 | 79 | In [8]: bid2 = id(__builtins__) |
|
80 | 80 | |
|
81 | 81 | In [9]: t2 = type(__builtins__) |
|
82 | 82 | |
|
83 | 83 | In [10]: t1 == t2 |
|
84 | 84 | Out[10]: True |
|
85 | 85 | |
|
86 | 86 | In [10]: bid1 == bid2 |
|
87 | 87 | Out[10]: True |
|
88 | 88 | |
|
89 | 89 | In [12]: try: |
|
90 | 90 | ....: os.unlink(fname) |
|
91 | 91 | ....: except: |
|
92 | 92 | ....: pass |
|
93 | 93 | ....: |
|
94 | 94 | """ |
|
95 | 95 | |
|
96 | 96 | |
|
97 | 97 | def doctest_run_option_parser(): |
|
98 | 98 | r"""Test option parser in %run. |
|
99 | 99 | |
|
100 | 100 | In [1]: %run print_argv.py |
|
101 | 101 | [] |
|
102 | 102 | |
|
103 | 103 | In [2]: %run print_argv.py print*.py |
|
104 | 104 | ['print_argv.py'] |
|
105 | 105 | |
|
106 | 106 | In [3]: %run -G print_argv.py print*.py |
|
107 | 107 | ['print*.py'] |
|
108 | 108 | |
|
109 | 109 | """ |
|
110 | 110 | |
|
111 | 111 | |
|
112 | 112 | @dec.skip_win32 |
|
113 | 113 | def doctest_run_option_parser_for_posix(): |
|
114 | 114 | r"""Test option parser in %run (Linux/OSX specific). |
|
115 | 115 | |
|
116 | 116 | You need double quote to escape glob in POSIX systems: |
|
117 | 117 | |
|
118 | 118 | In [1]: %run print_argv.py print\\*.py |
|
119 | 119 | ['print*.py'] |
|
120 | 120 | |
|
121 | 121 | You can't use quote to escape glob in POSIX systems: |
|
122 | 122 | |
|
123 | 123 | In [2]: %run print_argv.py 'print*.py' |
|
124 | 124 | ['print_argv.py'] |
|
125 | 125 | |
|
126 | 126 | """ |
|
127 | 127 | |
|
128 | 128 | |
|
129 | 129 | @dec.skip_if_not_win32 |
|
130 | 130 | def doctest_run_option_parser_for_windows(): |
|
131 | 131 | r"""Test option parser in %run (Windows specific). |
|
132 | 132 | |
|
133 | 133 | In Windows, you can't escape ``*` `by backslash: |
|
134 | 134 | |
|
135 | 135 | In [1]: %run print_argv.py print\\*.py |
|
136 | 136 | ['print\\*.py'] |
|
137 | 137 | |
|
138 | 138 | You can use quote to escape glob: |
|
139 | 139 | |
|
140 | 140 | In [2]: %run print_argv.py 'print*.py' |
|
141 | 141 | ['print*.py'] |
|
142 | 142 | |
|
143 | 143 | """ |
|
144 | 144 | |
|
145 | 145 | |
|
146 | 146 | @py3compat.doctest_refactor_print |
|
147 | 147 | def doctest_reset_del(): |
|
148 | 148 | """Test that resetting doesn't cause errors in __del__ methods. |
|
149 | 149 | |
|
150 | 150 | In [2]: class A(object): |
|
151 | 151 | ...: def __del__(self): |
|
152 | 152 | ...: print str("Hi") |
|
153 | 153 | ...: |
|
154 | 154 | |
|
155 | 155 | In [3]: a = A() |
|
156 | 156 | |
|
157 | 157 | In [4]: get_ipython().reset() |
|
158 | 158 | Hi |
|
159 | 159 | |
|
160 | 160 | In [5]: 1+1 |
|
161 | 161 | Out[5]: 2 |
|
162 | 162 | """ |
|
163 | 163 | |
|
164 | 164 | # For some tests, it will be handy to organize them in a class with a common |
|
165 | 165 | # setup that makes a temp file |
|
166 | 166 | |
|
167 | 167 | class TestMagicRunPass(tt.TempFileMixin): |
|
168 | 168 | |
|
169 | 169 | def setup(self): |
|
170 | 170 | """Make a valid python temp file.""" |
|
171 | 171 | self.mktmp('pass\n') |
|
172 | 172 | |
|
173 | 173 | def run_tmpfile(self): |
|
174 | 174 | _ip = get_ipython() |
|
175 | 175 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. |
|
176 | 176 | # See below and ticket https://bugs.launchpad.net/bugs/366353 |
|
177 | 177 | _ip.magic('run %s' % self.fname) |
|
178 | 178 | |
|
179 | 179 | def run_tmpfile_p(self): |
|
180 | 180 | _ip = get_ipython() |
|
181 | 181 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. |
|
182 | 182 | # See below and ticket https://bugs.launchpad.net/bugs/366353 |
|
183 | 183 | _ip.magic('run -p %s' % self.fname) |
|
184 | 184 | |
|
185 | 185 | def test_builtins_id(self): |
|
186 | 186 | """Check that %run doesn't damage __builtins__ """ |
|
187 | 187 | _ip = get_ipython() |
|
188 | 188 | # Test that the id of __builtins__ is not modified by %run |
|
189 | 189 | bid1 = id(_ip.user_ns['__builtins__']) |
|
190 | 190 | self.run_tmpfile() |
|
191 | 191 | bid2 = id(_ip.user_ns['__builtins__']) |
|
192 | 192 | nt.assert_equal(bid1, bid2) |
|
193 | 193 | |
|
194 | 194 | def test_builtins_type(self): |
|
195 | 195 | """Check that the type of __builtins__ doesn't change with %run. |
|
196 | 196 | |
|
197 | 197 | However, the above could pass if __builtins__ was already modified to |
|
198 | 198 | be a dict (it should be a module) by a previous use of %run. So we |
|
199 | 199 | also check explicitly that it really is a module: |
|
200 | 200 | """ |
|
201 | 201 | _ip = get_ipython() |
|
202 | 202 | self.run_tmpfile() |
|
203 | 203 | nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys)) |
|
204 | 204 | |
|
205 | 205 | def test_prompts(self): |
|
206 | 206 | """Test that prompts correctly generate after %run""" |
|
207 | 207 | self.run_tmpfile() |
|
208 | 208 | _ip = get_ipython() |
|
209 | 209 | p2 = _ip.prompt_manager.render('in2').strip() |
|
210 | 210 | nt.assert_equal(p2[:3], '...') |
|
211 | 211 | |
|
212 | 212 | def test_run_profile( self ): |
|
213 | 213 | """Test that the option -p, which invokes the profiler, do not |
|
214 | 214 | crash by invoking execfile""" |
|
215 | 215 | _ip = get_ipython() |
|
216 | 216 | self.run_tmpfile_p() |
|
217 | 217 | |
|
218 | 218 | |
|
219 | 219 | class TestMagicRunSimple(tt.TempFileMixin): |
|
220 | 220 | |
|
221 | 221 | def test_simpledef(self): |
|
222 | 222 | """Test that simple class definitions work.""" |
|
223 | 223 | src = ("class foo: pass\n" |
|
224 | 224 | "def f(): return foo()") |
|
225 | 225 | self.mktmp(src) |
|
226 | 226 | _ip.magic('run %s' % self.fname) |
|
227 | 227 | _ip.run_cell('t = isinstance(f(), foo)') |
|
228 | 228 | nt.assert_true(_ip.user_ns['t']) |
|
229 | 229 | |
|
230 | 230 | def test_obj_del(self): |
|
231 | 231 | """Test that object's __del__ methods are called on exit.""" |
|
232 | 232 | if sys.platform == 'win32': |
|
233 | 233 | try: |
|
234 | 234 | import win32api |
|
235 | 235 | except ImportError: |
|
236 | 236 | raise SkipTest("Test requires pywin32") |
|
237 | 237 | src = ("class A(object):\n" |
|
238 | 238 | " def __del__(self):\n" |
|
239 | 239 | " print 'object A deleted'\n" |
|
240 | 240 | "a = A()\n") |
|
241 | 241 | self.mktmp(py3compat.doctest_refactor_print(src)) |
|
242 | 242 | if dec.module_not_available('sqlite3'): |
|
243 | 243 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' |
|
244 | 244 | else: |
|
245 | 245 | err = None |
|
246 | 246 | tt.ipexec_validate(self.fname, 'object A deleted', err) |
|
247 | 247 | |
|
248 | 248 | def test_aggressive_namespace_cleanup(self): |
|
249 | 249 | """Test that namespace cleanup is not too aggressive GH-238 |
|
250 | 250 | |
|
251 | 251 | Returning from another run magic deletes the namespace""" |
|
252 | 252 | # see ticket https://github.com/ipython/ipython/issues/238 |
|
253 | 253 | class secondtmp(tt.TempFileMixin): pass |
|
254 | 254 | empty = secondtmp() |
|
255 | 255 | empty.mktmp('') |
|
256 | 256 | # On Windows, the filename will have \users in it, so we need to use the |
|
257 | 257 | # repr so that the \u becomes \\u. |
|
258 | 258 | src = ("ip = get_ipython()\n" |
|
259 | 259 | "for i in range(5):\n" |
|
260 | 260 | " try:\n" |
|
261 | 261 | " ip.magic(%r)\n" |
|
262 | 262 | " except NameError as e:\n" |
|
263 | 263 | " print(i)\n" |
|
264 | 264 | " break\n" % ('run ' + empty.fname)) |
|
265 | 265 | self.mktmp(src) |
|
266 | 266 | _ip.magic('run %s' % self.fname) |
|
267 | 267 | _ip.run_cell('ip == get_ipython()') |
|
268 | 268 | nt.assert_equal(_ip.user_ns['i'], 4) |
|
269 | 269 | |
|
270 | 270 | def test_run_second(self): |
|
271 | 271 | """Test that running a second file doesn't clobber the first, gh-3547 |
|
272 | 272 | """ |
|
273 | 273 | self.mktmp("avar = 1\n" |
|
274 | 274 | "def afunc():\n" |
|
275 | 275 | " return avar\n") |
|
276 | 276 | |
|
277 | 277 | empty = tt.TempFileMixin() |
|
278 | 278 | empty.mktmp("") |
|
279 | 279 | |
|
280 | 280 | _ip.magic('run %s' % self.fname) |
|
281 | 281 | _ip.magic('run %s' % empty.fname) |
|
282 | 282 | nt.assert_equal(_ip.user_ns['afunc'](), 1) |
|
283 | 283 | |
|
284 | 284 | @dec.skip_win32 |
|
285 | 285 | def test_tclass(self): |
|
286 | 286 | mydir = os.path.dirname(__file__) |
|
287 | 287 | tc = os.path.join(mydir, 'tclass') |
|
288 | 288 | src = ("%%run '%s' C-first\n" |
|
289 | 289 | "%%run '%s' C-second\n" |
|
290 | 290 | "%%run '%s' C-third\n") % (tc, tc, tc) |
|
291 | 291 | self.mktmp(src, '.ipy') |
|
292 | 292 | out = """\ |
|
293 | 293 | ARGV 1-: ['C-first'] |
|
294 | 294 | ARGV 1-: ['C-second'] |
|
295 | 295 | tclass.py: deleting object: C-first |
|
296 | 296 | ARGV 1-: ['C-third'] |
|
297 | 297 | tclass.py: deleting object: C-second |
|
298 | 298 | tclass.py: deleting object: C-third |
|
299 | 299 | """ |
|
300 | 300 | if dec.module_not_available('sqlite3'): |
|
301 | 301 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' |
|
302 | 302 | else: |
|
303 | 303 | err = None |
|
304 | 304 | tt.ipexec_validate(self.fname, out, err) |
|
305 | 305 | |
|
306 | 306 | def test_run_i_after_reset(self): |
|
307 | 307 | """Check that %run -i still works after %reset (gh-693)""" |
|
308 | 308 | src = "yy = zz\n" |
|
309 | 309 | self.mktmp(src) |
|
310 | 310 | _ip.run_cell("zz = 23") |
|
311 | 311 | _ip.magic('run -i %s' % self.fname) |
|
312 | 312 | nt.assert_equal(_ip.user_ns['yy'], 23) |
|
313 | 313 | _ip.magic('reset -f') |
|
314 | 314 | _ip.run_cell("zz = 23") |
|
315 | 315 | _ip.magic('run -i %s' % self.fname) |
|
316 | 316 | nt.assert_equal(_ip.user_ns['yy'], 23) |
|
317 | 317 | |
|
318 | 318 | def test_unicode(self): |
|
319 | 319 | """Check that files in odd encodings are accepted.""" |
|
320 | 320 | mydir = os.path.dirname(__file__) |
|
321 | 321 | na = os.path.join(mydir, 'nonascii.py') |
|
322 | 322 | _ip.magic('run "%s"' % na) |
|
323 | 323 | nt.assert_equal(_ip.user_ns['u'], u'ΠΡβΠ€') |
|
324 | 324 | |
|
325 | 325 | def test_run_py_file_attribute(self): |
|
326 | 326 | """Test handling of `__file__` attribute in `%run <file>.py`.""" |
|
327 | 327 | src = "t = __file__\n" |
|
328 | 328 | self.mktmp(src) |
|
329 | 329 | _missing = object() |
|
330 | 330 | file1 = _ip.user_ns.get('__file__', _missing) |
|
331 | 331 | _ip.magic('run %s' % self.fname) |
|
332 | 332 | file2 = _ip.user_ns.get('__file__', _missing) |
|
333 | 333 | |
|
334 | 334 | # Check that __file__ was equal to the filename in the script's |
|
335 | 335 | # namespace. |
|
336 | 336 | nt.assert_equal(_ip.user_ns['t'], self.fname) |
|
337 | 337 | |
|
338 | 338 | # Check that __file__ was not leaked back into user_ns. |
|
339 | 339 | nt.assert_equal(file1, file2) |
|
340 | 340 | |
|
341 | 341 | def test_run_ipy_file_attribute(self): |
|
342 | 342 | """Test handling of `__file__` attribute in `%run <file.ipy>`.""" |
|
343 | 343 | src = "t = __file__\n" |
|
344 | 344 | self.mktmp(src, ext='.ipy') |
|
345 | 345 | _missing = object() |
|
346 | 346 | file1 = _ip.user_ns.get('__file__', _missing) |
|
347 | 347 | _ip.magic('run %s' % self.fname) |
|
348 | 348 | file2 = _ip.user_ns.get('__file__', _missing) |
|
349 | 349 | |
|
350 | 350 | # Check that __file__ was equal to the filename in the script's |
|
351 | 351 | # namespace. |
|
352 | 352 | nt.assert_equal(_ip.user_ns['t'], self.fname) |
|
353 | 353 | |
|
354 | 354 | # Check that __file__ was not leaked back into user_ns. |
|
355 | 355 | nt.assert_equal(file1, file2) |
|
356 | 356 | |
|
357 | 357 | def test_run_formatting(self): |
|
358 | 358 | """ Test that %run -t -N<N> does not raise a TypeError for N > 1.""" |
|
359 | 359 | src = "pass" |
|
360 | 360 | self.mktmp(src) |
|
361 | 361 | _ip.magic('run -t -N 1 %s' % self.fname) |
|
362 | 362 | _ip.magic('run -t -N 10 %s' % self.fname) |
|
363 | 363 | |
|
364 | 364 | def test_ignore_sys_exit(self): |
|
365 | 365 | """Test the -e option to ignore sys.exit()""" |
|
366 | 366 | src = "import sys; sys.exit(1)" |
|
367 | 367 | self.mktmp(src) |
|
368 | 368 | with tt.AssertPrints('SystemExit'): |
|
369 | 369 | _ip.magic('run %s' % self.fname) |
|
370 | 370 | |
|
371 | 371 | with tt.AssertNotPrints('SystemExit'): |
|
372 | 372 | _ip.magic('run -e %s' % self.fname) |
|
373 | 373 | |
|
374 | 374 | |
|
375 | 375 | |
|
376 | 376 | class TestMagicRunWithPackage(unittest.TestCase): |
|
377 | 377 | |
|
378 | 378 | def writefile(self, name, content): |
|
379 | 379 | path = os.path.join(self.tempdir.name, name) |
|
380 | 380 | d = os.path.dirname(path) |
|
381 | 381 | if not os.path.isdir(d): |
|
382 | 382 | os.makedirs(d) |
|
383 | 383 | with open(path, 'w') as f: |
|
384 | 384 | f.write(textwrap.dedent(content)) |
|
385 | 385 | |
|
386 | 386 | def setUp(self): |
|
387 | 387 | self.package = package = 'tmp{0}'.format(repr(random.random())[2:]) |
|
388 | 388 | """Temporary valid python package name.""" |
|
389 | 389 | |
|
390 | 390 | self.value = int(random.random() * 10000) |
|
391 | 391 | |
|
392 | 392 | self.tempdir = TemporaryDirectory() |
|
393 |
self.__orig_cwd = |
|
|
393 | self.__orig_cwd = py3compat.getcwd() | |
|
394 | 394 | sys.path.insert(0, self.tempdir.name) |
|
395 | 395 | |
|
396 | 396 | self.writefile(os.path.join(package, '__init__.py'), '') |
|
397 | 397 | self.writefile(os.path.join(package, 'sub.py'), """ |
|
398 | 398 | x = {0!r} |
|
399 | 399 | """.format(self.value)) |
|
400 | 400 | self.writefile(os.path.join(package, 'relative.py'), """ |
|
401 | 401 | from .sub import x |
|
402 | 402 | """) |
|
403 | 403 | self.writefile(os.path.join(package, 'absolute.py'), """ |
|
404 | 404 | from {0}.sub import x |
|
405 | 405 | """.format(package)) |
|
406 | 406 | |
|
407 | 407 | def tearDown(self): |
|
408 | 408 | os.chdir(self.__orig_cwd) |
|
409 | 409 | sys.path[:] = [p for p in sys.path if p != self.tempdir.name] |
|
410 | 410 | self.tempdir.cleanup() |
|
411 | 411 | |
|
412 | 412 | def check_run_submodule(self, submodule, opts=''): |
|
413 | 413 | _ip.user_ns.pop('x', None) |
|
414 | 414 | _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts)) |
|
415 | 415 | self.assertEqual(_ip.user_ns['x'], self.value, |
|
416 | 416 | 'Variable `x` is not loaded from module `{0}`.' |
|
417 | 417 | .format(submodule)) |
|
418 | 418 | |
|
419 | 419 | def test_run_submodule_with_absolute_import(self): |
|
420 | 420 | self.check_run_submodule('absolute') |
|
421 | 421 | |
|
422 | 422 | def test_run_submodule_with_relative_import(self): |
|
423 | 423 | """Run submodule that has a relative import statement (#2727).""" |
|
424 | 424 | self.check_run_submodule('relative') |
|
425 | 425 | |
|
426 | 426 | def test_prun_submodule_with_absolute_import(self): |
|
427 | 427 | self.check_run_submodule('absolute', '-p') |
|
428 | 428 | |
|
429 | 429 | def test_prun_submodule_with_relative_import(self): |
|
430 | 430 | self.check_run_submodule('relative', '-p') |
|
431 | 431 | |
|
432 | 432 | def with_fake_debugger(func): |
|
433 | 433 | @functools.wraps(func) |
|
434 | 434 | def wrapper(*args, **kwds): |
|
435 | 435 | with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)): |
|
436 | 436 | return func(*args, **kwds) |
|
437 | 437 | return wrapper |
|
438 | 438 | |
|
439 | 439 | @with_fake_debugger |
|
440 | 440 | def test_debug_run_submodule_with_absolute_import(self): |
|
441 | 441 | self.check_run_submodule('absolute', '-d') |
|
442 | 442 | |
|
443 | 443 | @with_fake_debugger |
|
444 | 444 | def test_debug_run_submodule_with_relative_import(self): |
|
445 | 445 | self.check_run_submodule('relative', '-d') |
|
446 | 446 | |
|
447 | 447 | def test_run__name__(): |
|
448 | 448 | with TemporaryDirectory() as td: |
|
449 | 449 | path = pjoin(td, 'foo.py') |
|
450 | 450 | with open(path, 'w') as f: |
|
451 | 451 | f.write("q = __name__") |
|
452 | 452 | |
|
453 | 453 | _ip.user_ns.pop('q', None) |
|
454 | 454 | _ip.magic('run {}'.format(path)) |
|
455 | 455 | nt.assert_equal(_ip.user_ns.pop('q'), '__main__') |
|
456 | 456 | |
|
457 | 457 | _ip.magic('run -n {}'.format(path)) |
|
458 | 458 | nt.assert_equal(_ip.user_ns.pop('q'), 'foo') |
@@ -1,172 +1,171 b'' | |||
|
1 | 1 | """Manage IPython.parallel clusters in the notebook. |
|
2 | 2 | |
|
3 | 3 | Authors: |
|
4 | 4 | |
|
5 | 5 | * Brian Granger |
|
6 | 6 | """ |
|
7 | 7 | |
|
8 | 8 | #----------------------------------------------------------------------------- |
|
9 | 9 | # Copyright (C) 2008-2011 The IPython Development Team |
|
10 | 10 | # |
|
11 | 11 | # Distributed under the terms of the BSD License. The full license is in |
|
12 | 12 | # the file COPYING, distributed as part of this software. |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | # Imports |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | import os | |
|
20 | ||
|
21 | 19 | from tornado import web |
|
22 | 20 | from zmq.eventloop import ioloop |
|
23 | 21 | |
|
24 | 22 | from IPython.config.configurable import LoggingConfigurable |
|
25 | 23 | from IPython.utils.traitlets import Dict, Instance, CFloat |
|
26 | 24 | from IPython.parallel.apps.ipclusterapp import IPClusterStart |
|
27 | 25 | from IPython.core.profileapp import list_profiles_in |
|
28 | 26 | from IPython.core.profiledir import ProfileDir |
|
27 | from IPython.utils import py3compat | |
|
29 | 28 | from IPython.utils.path import get_ipython_dir |
|
30 | 29 | |
|
31 | 30 | |
|
32 | 31 | #----------------------------------------------------------------------------- |
|
33 | 32 | # Classes |
|
34 | 33 | #----------------------------------------------------------------------------- |
|
35 | 34 | |
|
36 | 35 | |
|
37 | 36 | class DummyIPClusterStart(IPClusterStart): |
|
38 | 37 | """Dummy subclass to skip init steps that conflict with global app. |
|
39 | 38 | |
|
40 | 39 | Instantiating and initializing this class should result in fully configured |
|
41 | 40 | launchers, but no other side effects or state. |
|
42 | 41 | """ |
|
43 | 42 | |
|
44 | 43 | def init_signal(self): |
|
45 | 44 | pass |
|
46 | 45 | def reinit_logging(self): |
|
47 | 46 | pass |
|
48 | 47 | |
|
49 | 48 | |
|
50 | 49 | class ClusterManager(LoggingConfigurable): |
|
51 | 50 | |
|
52 | 51 | profiles = Dict() |
|
53 | 52 | |
|
54 | 53 | delay = CFloat(1., config=True, |
|
55 | 54 | help="delay (in s) between starting the controller and the engines") |
|
56 | 55 | |
|
57 | 56 | loop = Instance('zmq.eventloop.ioloop.IOLoop') |
|
58 | 57 | def _loop_default(self): |
|
59 | 58 | from zmq.eventloop.ioloop import IOLoop |
|
60 | 59 | return IOLoop.instance() |
|
61 | 60 | |
|
62 | 61 | def build_launchers(self, profile_dir): |
|
63 | 62 | starter = DummyIPClusterStart(log=self.log) |
|
64 | 63 | starter.initialize(['--profile-dir', profile_dir]) |
|
65 | 64 | cl = starter.controller_launcher |
|
66 | 65 | esl = starter.engine_launcher |
|
67 | 66 | n = starter.n |
|
68 | 67 | return cl, esl, n |
|
69 | 68 | |
|
70 | 69 | def get_profile_dir(self, name, path): |
|
71 | 70 | p = ProfileDir.find_profile_dir_by_name(path,name=name) |
|
72 | 71 | return p.location |
|
73 | 72 | |
|
74 | 73 | def update_profiles(self): |
|
75 | 74 | """List all profiles in the ipython_dir and cwd. |
|
76 | 75 | """ |
|
77 |
for path in [get_ipython_dir(), |
|
|
76 | for path in [get_ipython_dir(), py3compat.getcwd()]: | |
|
78 | 77 | for profile in list_profiles_in(path): |
|
79 | 78 | pd = self.get_profile_dir(profile, path) |
|
80 | 79 | if profile not in self.profiles: |
|
81 | 80 | self.log.debug("Adding cluster profile '%s'" % profile) |
|
82 | 81 | self.profiles[profile] = { |
|
83 | 82 | 'profile': profile, |
|
84 | 83 | 'profile_dir': pd, |
|
85 | 84 | 'status': 'stopped' |
|
86 | 85 | } |
|
87 | 86 | |
|
88 | 87 | def list_profiles(self): |
|
89 | 88 | self.update_profiles() |
|
90 | 89 | # sorted list, but ensure that 'default' always comes first |
|
91 | 90 | default_first = lambda name: name if name != 'default' else '' |
|
92 | 91 | result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)] |
|
93 | 92 | return result |
|
94 | 93 | |
|
95 | 94 | def check_profile(self, profile): |
|
96 | 95 | if profile not in self.profiles: |
|
97 | 96 | raise web.HTTPError(404, u'profile not found') |
|
98 | 97 | |
|
99 | 98 | def profile_info(self, profile): |
|
100 | 99 | self.check_profile(profile) |
|
101 | 100 | result = {} |
|
102 | 101 | data = self.profiles.get(profile) |
|
103 | 102 | result['profile'] = profile |
|
104 | 103 | result['profile_dir'] = data['profile_dir'] |
|
105 | 104 | result['status'] = data['status'] |
|
106 | 105 | if 'n' in data: |
|
107 | 106 | result['n'] = data['n'] |
|
108 | 107 | return result |
|
109 | 108 | |
|
110 | 109 | def start_cluster(self, profile, n=None): |
|
111 | 110 | """Start a cluster for a given profile.""" |
|
112 | 111 | self.check_profile(profile) |
|
113 | 112 | data = self.profiles[profile] |
|
114 | 113 | if data['status'] == 'running': |
|
115 | 114 | raise web.HTTPError(409, u'cluster already running') |
|
116 | 115 | cl, esl, default_n = self.build_launchers(data['profile_dir']) |
|
117 | 116 | n = n if n is not None else default_n |
|
118 | 117 | def clean_data(): |
|
119 | 118 | data.pop('controller_launcher',None) |
|
120 | 119 | data.pop('engine_set_launcher',None) |
|
121 | 120 | data.pop('n',None) |
|
122 | 121 | data['status'] = 'stopped' |
|
123 | 122 | def engines_stopped(r): |
|
124 | 123 | self.log.debug('Engines stopped') |
|
125 | 124 | if cl.running: |
|
126 | 125 | cl.stop() |
|
127 | 126 | clean_data() |
|
128 | 127 | esl.on_stop(engines_stopped) |
|
129 | 128 | def controller_stopped(r): |
|
130 | 129 | self.log.debug('Controller stopped') |
|
131 | 130 | if esl.running: |
|
132 | 131 | esl.stop() |
|
133 | 132 | clean_data() |
|
134 | 133 | cl.on_stop(controller_stopped) |
|
135 | 134 | |
|
136 | 135 | dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop) |
|
137 | 136 | dc.start() |
|
138 | 137 | dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop) |
|
139 | 138 | dc.start() |
|
140 | 139 | |
|
141 | 140 | self.log.debug('Cluster started') |
|
142 | 141 | data['controller_launcher'] = cl |
|
143 | 142 | data['engine_set_launcher'] = esl |
|
144 | 143 | data['n'] = n |
|
145 | 144 | data['status'] = 'running' |
|
146 | 145 | return self.profile_info(profile) |
|
147 | 146 | |
|
148 | 147 | def stop_cluster(self, profile): |
|
149 | 148 | """Stop a cluster for a given profile.""" |
|
150 | 149 | self.check_profile(profile) |
|
151 | 150 | data = self.profiles[profile] |
|
152 | 151 | if data['status'] == 'stopped': |
|
153 | 152 | raise web.HTTPError(409, u'cluster not running') |
|
154 | 153 | data = self.profiles[profile] |
|
155 | 154 | cl = data['controller_launcher'] |
|
156 | 155 | esl = data['engine_set_launcher'] |
|
157 | 156 | if cl.running: |
|
158 | 157 | cl.stop() |
|
159 | 158 | if esl.running: |
|
160 | 159 | esl.stop() |
|
161 | 160 | # Return a temp info dict, the real one is updated in the on_stop |
|
162 | 161 | # logic above. |
|
163 | 162 | result = { |
|
164 | 163 | 'profile': data['profile'], |
|
165 | 164 | 'profile_dir': data['profile_dir'], |
|
166 | 165 | 'status': 'stopped' |
|
167 | 166 | } |
|
168 | 167 | return result |
|
169 | 168 | |
|
170 | 169 | def stop_all_clusters(self): |
|
171 | 170 | for p in self.profiles.keys(): |
|
172 | 171 | self.stop_cluster(p) |
@@ -1,173 +1,174 b'' | |||
|
1 | 1 | """A base class notebook manager. |
|
2 | 2 | |
|
3 | 3 | Authors: |
|
4 | 4 | |
|
5 | 5 | * Brian Granger |
|
6 | 6 | * Zach Sailer |
|
7 | 7 | """ |
|
8 | 8 | |
|
9 | 9 | #----------------------------------------------------------------------------- |
|
10 | 10 | # Copyright (C) 2011 The IPython Development Team |
|
11 | 11 | # |
|
12 | 12 | # Distributed under the terms of the BSD License. The full license is in |
|
13 | 13 | # the file COPYING, distributed as part of this software. |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | |
|
16 | 16 | #----------------------------------------------------------------------------- |
|
17 | 17 | # Imports |
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | 19 | |
|
20 | 20 | import os |
|
21 | 21 | |
|
22 | 22 | from IPython.config.configurable import LoggingConfigurable |
|
23 | 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 | 28 | # Classes |
|
28 | 29 | #----------------------------------------------------------------------------- |
|
29 | 30 | |
|
30 | 31 | class NotebookManager(LoggingConfigurable): |
|
31 | 32 | |
|
32 | 33 | # Todo: |
|
33 | 34 | # The notebook_dir attribute is used to mean a couple of different things: |
|
34 | 35 | # 1. Where the notebooks are stored if FileNotebookManager is used. |
|
35 | 36 | # 2. The cwd of the kernel for a project. |
|
36 | 37 | # Right now we use this attribute in a number of different places and |
|
37 | 38 | # we are going to have to disentangle all of this. |
|
38 |
notebook_dir = Unicode( |
|
|
39 | notebook_dir = Unicode(py3compat.getcwd(), config=True, help=""" | |
|
39 | 40 | The directory to use for notebooks. |
|
40 | 41 | """) |
|
41 | 42 | |
|
42 | 43 | filename_ext = Unicode(u'.ipynb') |
|
43 | 44 | |
|
44 | 45 | def path_exists(self, path): |
|
45 | 46 | """Does the API-style path (directory) actually exist? |
|
46 | 47 | |
|
47 | 48 | Override this method in subclasses. |
|
48 | 49 | |
|
49 | 50 | Parameters |
|
50 | 51 | ---------- |
|
51 | 52 | path : string |
|
52 | 53 | The |
|
53 | 54 | |
|
54 | 55 | Returns |
|
55 | 56 | ------- |
|
56 | 57 | exists : bool |
|
57 | 58 | Whether the path does indeed exist. |
|
58 | 59 | """ |
|
59 | 60 | raise NotImplementedError |
|
60 | 61 | |
|
61 | 62 | def _notebook_dir_changed(self, name, old, new): |
|
62 | 63 | """Do a bit of validation of the notebook dir.""" |
|
63 | 64 | if not os.path.isabs(new): |
|
64 | 65 | # If we receive a non-absolute path, make it absolute. |
|
65 | 66 | self.notebook_dir = os.path.abspath(new) |
|
66 | 67 | return |
|
67 | 68 | if os.path.exists(new) and not os.path.isdir(new): |
|
68 | 69 | raise TraitError("notebook dir %r is not a directory" % new) |
|
69 | 70 | if not os.path.exists(new): |
|
70 | 71 | self.log.info("Creating notebook dir %s", new) |
|
71 | 72 | try: |
|
72 | 73 | os.mkdir(new) |
|
73 | 74 | except: |
|
74 | 75 | raise TraitError("Couldn't create notebook dir %r" % new) |
|
75 | 76 | |
|
76 | 77 | # Main notebook API |
|
77 | 78 | |
|
78 | 79 | def increment_filename(self, basename, path=''): |
|
79 | 80 | """Increment a notebook filename without the .ipynb to make it unique. |
|
80 | 81 | |
|
81 | 82 | Parameters |
|
82 | 83 | ---------- |
|
83 | 84 | basename : unicode |
|
84 | 85 | The name of a notebook without the ``.ipynb`` file extension. |
|
85 | 86 | path : unicode |
|
86 | 87 | The URL path of the notebooks directory |
|
87 | 88 | """ |
|
88 | 89 | return basename |
|
89 | 90 | |
|
90 | 91 | def list_notebooks(self, path=''): |
|
91 | 92 | """Return a list of notebook dicts without content. |
|
92 | 93 | |
|
93 | 94 | This returns a list of dicts, each of the form:: |
|
94 | 95 | |
|
95 | 96 | dict(notebook_id=notebook,name=name) |
|
96 | 97 | |
|
97 | 98 | This list of dicts should be sorted by name:: |
|
98 | 99 | |
|
99 | 100 | data = sorted(data, key=lambda item: item['name']) |
|
100 | 101 | """ |
|
101 | 102 | raise NotImplementedError('must be implemented in a subclass') |
|
102 | 103 | |
|
103 | 104 | def get_notebook_model(self, name, path='', content=True): |
|
104 | 105 | """Get the notebook model with or without content.""" |
|
105 | 106 | raise NotImplementedError('must be implemented in a subclass') |
|
106 | 107 | |
|
107 | 108 | def save_notebook_model(self, model, name, path=''): |
|
108 | 109 | """Save the notebook model and return the model with no content.""" |
|
109 | 110 | raise NotImplementedError('must be implemented in a subclass') |
|
110 | 111 | |
|
111 | 112 | def update_notebook_model(self, model, name, path=''): |
|
112 | 113 | """Update the notebook model and return the model with no content.""" |
|
113 | 114 | raise NotImplementedError('must be implemented in a subclass') |
|
114 | 115 | |
|
115 | 116 | def delete_notebook_model(self, name, path=''): |
|
116 | 117 | """Delete notebook by name and path.""" |
|
117 | 118 | raise NotImplementedError('must be implemented in a subclass') |
|
118 | 119 | |
|
119 | 120 | def create_notebook_model(self, model=None, path=''): |
|
120 | 121 | """Create a new notebook and return its model with no content.""" |
|
121 | 122 | path = path.strip('/') |
|
122 | 123 | if model is None: |
|
123 | 124 | model = {} |
|
124 | 125 | if 'content' not in model: |
|
125 | 126 | metadata = current.new_metadata(name=u'') |
|
126 | 127 | model['content'] = current.new_notebook(metadata=metadata) |
|
127 | 128 | if 'name' not in model: |
|
128 | 129 | model['name'] = self.increment_filename('Untitled', path) |
|
129 | 130 | |
|
130 | 131 | model['path'] = path |
|
131 | 132 | model = self.save_notebook_model(model, model['name'], model['path']) |
|
132 | 133 | return model |
|
133 | 134 | |
|
134 | 135 | def copy_notebook(self, from_name, to_name=None, path=''): |
|
135 | 136 | """Copy an existing notebook and return its new model. |
|
136 | 137 | |
|
137 | 138 | If to_name not specified, increment `from_name-Copy#.ipynb`. |
|
138 | 139 | """ |
|
139 | 140 | path = path.strip('/') |
|
140 | 141 | model = self.get_notebook_model(from_name, path) |
|
141 | 142 | if not to_name: |
|
142 | 143 | base = os.path.splitext(from_name)[0] + '-Copy' |
|
143 | 144 | to_name = self.increment_filename(base, path) |
|
144 | 145 | model['name'] = to_name |
|
145 | 146 | model = self.save_notebook_model(model, to_name, path) |
|
146 | 147 | return model |
|
147 | 148 | |
|
148 | 149 | # Checkpoint-related |
|
149 | 150 | |
|
150 | 151 | def create_checkpoint(self, name, path=''): |
|
151 | 152 | """Create a checkpoint of the current state of a notebook |
|
152 | 153 | |
|
153 | 154 | Returns a checkpoint_id for the new checkpoint. |
|
154 | 155 | """ |
|
155 | 156 | raise NotImplementedError("must be implemented in a subclass") |
|
156 | 157 | |
|
157 | 158 | def list_checkpoints(self, name, path=''): |
|
158 | 159 | """Return a list of checkpoints for a given notebook""" |
|
159 | 160 | return [] |
|
160 | 161 | |
|
161 | 162 | def restore_checkpoint(self, checkpoint_id, name, path=''): |
|
162 | 163 | """Restore a notebook from one of its checkpoints""" |
|
163 | 164 | raise NotImplementedError("must be implemented in a subclass") |
|
164 | 165 | |
|
165 | 166 | def delete_checkpoint(self, checkpoint_id, name, path=''): |
|
166 | 167 | """delete a checkpoint for a notebook""" |
|
167 | 168 | raise NotImplementedError("must be implemented in a subclass") |
|
168 | 169 | |
|
169 | 170 | def log_info(self): |
|
170 | 171 | self.log.info(self.info_string()) |
|
171 | 172 | |
|
172 | 173 | def info_string(self): |
|
173 | 174 | return "Serving notebooks" |
@@ -1,275 +1,276 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | The Base Application class for IPython.parallel apps |
|
4 | 4 | |
|
5 | 5 | Authors: |
|
6 | 6 | |
|
7 | 7 | * Brian Granger |
|
8 | 8 | * Min RK |
|
9 | 9 | |
|
10 | 10 | """ |
|
11 | 11 | |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | # Copyright (C) 2008-2011 The IPython Development Team |
|
14 | 14 | # |
|
15 | 15 | # Distributed under the terms of the BSD License. The full license is in |
|
16 | 16 | # the file COPYING, distributed as part of this software. |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | 19 | #----------------------------------------------------------------------------- |
|
20 | 20 | # Imports |
|
21 | 21 | #----------------------------------------------------------------------------- |
|
22 | 22 | |
|
23 | 23 | import os |
|
24 | 24 | import logging |
|
25 | 25 | import re |
|
26 | 26 | import sys |
|
27 | 27 | |
|
28 | 28 | from subprocess import Popen, PIPE |
|
29 | 29 | |
|
30 | 30 | from IPython.config.application import catch_config_error, LevelFormatter |
|
31 | 31 | from IPython.core import release |
|
32 | 32 | from IPython.core.crashhandler import CrashHandler |
|
33 | 33 | from IPython.core.application import ( |
|
34 | 34 | BaseIPythonApplication, |
|
35 | 35 | base_aliases as base_ip_aliases, |
|
36 | 36 | base_flags as base_ip_flags |
|
37 | 37 | ) |
|
38 | 38 | from IPython.utils.path import expand_path |
|
39 | from IPython.utils import py3compat | |
|
39 | 40 | from IPython.utils.py3compat import unicode_type |
|
40 | 41 | |
|
41 | 42 | from IPython.utils.traitlets import Unicode, Bool, Instance, Dict |
|
42 | 43 | |
|
43 | 44 | #----------------------------------------------------------------------------- |
|
44 | 45 | # Module errors |
|
45 | 46 | #----------------------------------------------------------------------------- |
|
46 | 47 | |
|
47 | 48 | class PIDFileError(Exception): |
|
48 | 49 | pass |
|
49 | 50 | |
|
50 | 51 | |
|
51 | 52 | #----------------------------------------------------------------------------- |
|
52 | 53 | # Crash handler for this application |
|
53 | 54 | #----------------------------------------------------------------------------- |
|
54 | 55 | |
|
55 | 56 | class ParallelCrashHandler(CrashHandler): |
|
56 | 57 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" |
|
57 | 58 | |
|
58 | 59 | def __init__(self, app): |
|
59 | 60 | contact_name = release.authors['Min'][0] |
|
60 | 61 | contact_email = release.author_email |
|
61 | 62 | bug_tracker = 'https://github.com/ipython/ipython/issues' |
|
62 | 63 | super(ParallelCrashHandler,self).__init__( |
|
63 | 64 | app, contact_name, contact_email, bug_tracker |
|
64 | 65 | ) |
|
65 | 66 | |
|
66 | 67 | |
|
67 | 68 | #----------------------------------------------------------------------------- |
|
68 | 69 | # Main application |
|
69 | 70 | #----------------------------------------------------------------------------- |
|
70 | 71 | base_aliases = {} |
|
71 | 72 | base_aliases.update(base_ip_aliases) |
|
72 | 73 | base_aliases.update({ |
|
73 | 74 | 'work-dir' : 'BaseParallelApplication.work_dir', |
|
74 | 75 | 'log-to-file' : 'BaseParallelApplication.log_to_file', |
|
75 | 76 | 'clean-logs' : 'BaseParallelApplication.clean_logs', |
|
76 | 77 | 'log-url' : 'BaseParallelApplication.log_url', |
|
77 | 78 | 'cluster-id' : 'BaseParallelApplication.cluster_id', |
|
78 | 79 | }) |
|
79 | 80 | |
|
80 | 81 | base_flags = { |
|
81 | 82 | 'log-to-file' : ( |
|
82 | 83 | {'BaseParallelApplication' : {'log_to_file' : True}}, |
|
83 | 84 | "send log output to a file" |
|
84 | 85 | ) |
|
85 | 86 | } |
|
86 | 87 | base_flags.update(base_ip_flags) |
|
87 | 88 | |
|
88 | 89 | class BaseParallelApplication(BaseIPythonApplication): |
|
89 | 90 | """The base Application for IPython.parallel apps |
|
90 | 91 | |
|
91 | 92 | Principle extensions to BaseIPyythonApplication: |
|
92 | 93 | |
|
93 | 94 | * work_dir |
|
94 | 95 | * remote logging via pyzmq |
|
95 | 96 | * IOLoop instance |
|
96 | 97 | """ |
|
97 | 98 | |
|
98 | 99 | crash_handler_class = ParallelCrashHandler |
|
99 | 100 | |
|
100 | 101 | def _log_level_default(self): |
|
101 | 102 | # temporarily override default_log_level to INFO |
|
102 | 103 | return logging.INFO |
|
103 | 104 | |
|
104 | 105 | def _log_format_default(self): |
|
105 | 106 | """override default log format to include time""" |
|
106 | 107 | return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s" |
|
107 | 108 | |
|
108 |
work_dir = Unicode( |
|
|
109 | work_dir = Unicode(py3compat.getcwd(), config=True, | |
|
109 | 110 | help='Set the working dir for the process.' |
|
110 | 111 | ) |
|
111 | 112 | def _work_dir_changed(self, name, old, new): |
|
112 | 113 | self.work_dir = unicode_type(expand_path(new)) |
|
113 | 114 | |
|
114 | 115 | log_to_file = Bool(config=True, |
|
115 | 116 | help="whether to log to a file") |
|
116 | 117 | |
|
117 | 118 | clean_logs = Bool(False, config=True, |
|
118 | 119 | help="whether to cleanup old logfiles before starting") |
|
119 | 120 | |
|
120 | 121 | log_url = Unicode('', config=True, |
|
121 | 122 | help="The ZMQ URL of the iplogger to aggregate logging.") |
|
122 | 123 | |
|
123 | 124 | cluster_id = Unicode('', config=True, |
|
124 | 125 | help="""String id to add to runtime files, to prevent name collisions when |
|
125 | 126 | using multiple clusters with a single profile simultaneously. |
|
126 | 127 | |
|
127 | 128 | When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json' |
|
128 | 129 | |
|
129 | 130 | Since this is text inserted into filenames, typical recommendations apply: |
|
130 | 131 | Simple character strings are ideal, and spaces are not recommended (but should |
|
131 | 132 | generally work). |
|
132 | 133 | """ |
|
133 | 134 | ) |
|
134 | 135 | def _cluster_id_changed(self, name, old, new): |
|
135 | 136 | self.name = self.__class__.name |
|
136 | 137 | if new: |
|
137 | 138 | self.name += '-%s'%new |
|
138 | 139 | |
|
139 | 140 | def _config_files_default(self): |
|
140 | 141 | return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py'] |
|
141 | 142 | |
|
142 | 143 | loop = Instance('zmq.eventloop.ioloop.IOLoop') |
|
143 | 144 | def _loop_default(self): |
|
144 | 145 | from zmq.eventloop.ioloop import IOLoop |
|
145 | 146 | return IOLoop.instance() |
|
146 | 147 | |
|
147 | 148 | aliases = Dict(base_aliases) |
|
148 | 149 | flags = Dict(base_flags) |
|
149 | 150 | |
|
150 | 151 | @catch_config_error |
|
151 | 152 | def initialize(self, argv=None): |
|
152 | 153 | """initialize the app""" |
|
153 | 154 | super(BaseParallelApplication, self).initialize(argv) |
|
154 | 155 | self.to_work_dir() |
|
155 | 156 | self.reinit_logging() |
|
156 | 157 | |
|
157 | 158 | def to_work_dir(self): |
|
158 | 159 | wd = self.work_dir |
|
159 |
if unicode_type(wd) != |
|
|
160 | if unicode_type(wd) != py3compat.getcwd(): | |
|
160 | 161 | os.chdir(wd) |
|
161 | 162 | self.log.info("Changing to working dir: %s" % wd) |
|
162 | 163 | # This is the working dir by now. |
|
163 | 164 | sys.path.insert(0, '') |
|
164 | 165 | |
|
165 | 166 | def reinit_logging(self): |
|
166 | 167 | # Remove old log files |
|
167 | 168 | log_dir = self.profile_dir.log_dir |
|
168 | 169 | if self.clean_logs: |
|
169 | 170 | for f in os.listdir(log_dir): |
|
170 | 171 | if re.match(r'%s-\d+\.(log|err|out)' % self.name, f): |
|
171 | 172 | try: |
|
172 | 173 | os.remove(os.path.join(log_dir, f)) |
|
173 | 174 | except (OSError, IOError): |
|
174 | 175 | # probably just conflict from sibling process |
|
175 | 176 | # already removing it |
|
176 | 177 | pass |
|
177 | 178 | if self.log_to_file: |
|
178 | 179 | # Start logging to the new log file |
|
179 | 180 | log_filename = self.name + u'-' + str(os.getpid()) + u'.log' |
|
180 | 181 | logfile = os.path.join(log_dir, log_filename) |
|
181 | 182 | open_log_file = open(logfile, 'w') |
|
182 | 183 | else: |
|
183 | 184 | open_log_file = None |
|
184 | 185 | if open_log_file is not None: |
|
185 | 186 | while self.log.handlers: |
|
186 | 187 | self.log.removeHandler(self.log.handlers[0]) |
|
187 | 188 | self._log_handler = logging.StreamHandler(open_log_file) |
|
188 | 189 | self.log.addHandler(self._log_handler) |
|
189 | 190 | else: |
|
190 | 191 | self._log_handler = self.log.handlers[0] |
|
191 | 192 | # Add timestamps to log format: |
|
192 | 193 | self._log_formatter = LevelFormatter(self.log_format, |
|
193 | 194 | datefmt=self.log_datefmt) |
|
194 | 195 | self._log_handler.setFormatter(self._log_formatter) |
|
195 | 196 | # do not propagate log messages to root logger |
|
196 | 197 | # ipcluster app will sometimes print duplicate messages during shutdown |
|
197 | 198 | # if this is 1 (default): |
|
198 | 199 | self.log.propagate = False |
|
199 | 200 | |
|
200 | 201 | def write_pid_file(self, overwrite=False): |
|
201 | 202 | """Create a .pid file in the pid_dir with my pid. |
|
202 | 203 | |
|
203 | 204 | This must be called after pre_construct, which sets `self.pid_dir`. |
|
204 | 205 | This raises :exc:`PIDFileError` if the pid file exists already. |
|
205 | 206 | """ |
|
206 | 207 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') |
|
207 | 208 | if os.path.isfile(pid_file): |
|
208 | 209 | pid = self.get_pid_from_file() |
|
209 | 210 | if not overwrite: |
|
210 | 211 | raise PIDFileError( |
|
211 | 212 | 'The pid file [%s] already exists. \nThis could mean that this ' |
|
212 | 213 | 'server is already running with [pid=%s].' % (pid_file, pid) |
|
213 | 214 | ) |
|
214 | 215 | with open(pid_file, 'w') as f: |
|
215 | 216 | self.log.info("Creating pid file: %s" % pid_file) |
|
216 | 217 | f.write(repr(os.getpid())+'\n') |
|
217 | 218 | |
|
218 | 219 | def remove_pid_file(self): |
|
219 | 220 | """Remove the pid file. |
|
220 | 221 | |
|
221 | 222 | This should be called at shutdown by registering a callback with |
|
222 | 223 | :func:`reactor.addSystemEventTrigger`. This needs to return |
|
223 | 224 | ``None``. |
|
224 | 225 | """ |
|
225 | 226 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') |
|
226 | 227 | if os.path.isfile(pid_file): |
|
227 | 228 | try: |
|
228 | 229 | self.log.info("Removing pid file: %s" % pid_file) |
|
229 | 230 | os.remove(pid_file) |
|
230 | 231 | except: |
|
231 | 232 | self.log.warn("Error removing the pid file: %s" % pid_file) |
|
232 | 233 | |
|
233 | 234 | def get_pid_from_file(self): |
|
234 | 235 | """Get the pid from the pid file. |
|
235 | 236 | |
|
236 | 237 | If the pid file doesn't exist a :exc:`PIDFileError` is raised. |
|
237 | 238 | """ |
|
238 | 239 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') |
|
239 | 240 | if os.path.isfile(pid_file): |
|
240 | 241 | with open(pid_file, 'r') as f: |
|
241 | 242 | s = f.read().strip() |
|
242 | 243 | try: |
|
243 | 244 | pid = int(s) |
|
244 | 245 | except: |
|
245 | 246 | raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s)) |
|
246 | 247 | return pid |
|
247 | 248 | else: |
|
248 | 249 | raise PIDFileError('pid file not found: %s' % pid_file) |
|
249 | 250 | |
|
250 | 251 | def check_pid(self, pid): |
|
251 | 252 | if os.name == 'nt': |
|
252 | 253 | try: |
|
253 | 254 | import ctypes |
|
254 | 255 | # returns 0 if no such process (of ours) exists |
|
255 | 256 | # positive int otherwise |
|
256 | 257 | p = ctypes.windll.kernel32.OpenProcess(1,0,pid) |
|
257 | 258 | except Exception: |
|
258 | 259 | self.log.warn( |
|
259 | 260 | "Could not determine whether pid %i is running via `OpenProcess`. " |
|
260 | 261 | " Making the likely assumption that it is."%pid |
|
261 | 262 | ) |
|
262 | 263 | return True |
|
263 | 264 | return bool(p) |
|
264 | 265 | else: |
|
265 | 266 | try: |
|
266 | 267 | p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE) |
|
267 | 268 | output,_ = p.communicate() |
|
268 | 269 | except OSError: |
|
269 | 270 | self.log.warn( |
|
270 | 271 | "Could not determine whether pid %i is running via `ps x`. " |
|
271 | 272 | " Making the likely assumption that it is."%pid |
|
272 | 273 | ) |
|
273 | 274 | return True |
|
274 | 275 | pids = list(map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))) |
|
275 | 276 | return pid in pids |
@@ -1,764 +1,764 b'' | |||
|
1 | 1 | """Nose Plugin that supports IPython doctests. |
|
2 | 2 | |
|
3 | 3 | Limitations: |
|
4 | 4 | |
|
5 | 5 | - When generating examples for use as doctests, make sure that you have |
|
6 | 6 | pretty-printing OFF. This can be done either by setting the |
|
7 | 7 | ``PlainTextFormatter.pprint`` option in your configuration file to False, or |
|
8 | 8 | by interactively disabling it with %Pprint. This is required so that IPython |
|
9 | 9 | output matches that of normal Python, which is used by doctest for internal |
|
10 | 10 | execution. |
|
11 | 11 | |
|
12 | 12 | - Do not rely on specific prompt numbers for results (such as using |
|
13 | 13 | '_34==True', for example). For IPython tests run via an external process the |
|
14 | 14 | prompt numbers may be different, and IPython tests run as normal python code |
|
15 | 15 | won't even have these special _NN variables set at all. |
|
16 | 16 | """ |
|
17 | 17 | |
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | 19 | # Module imports |
|
20 | 20 | |
|
21 | 21 | # From the standard library |
|
22 | 22 | import doctest |
|
23 | 23 | import inspect |
|
24 | 24 | import logging |
|
25 | 25 | import os |
|
26 | 26 | import re |
|
27 | 27 | import sys |
|
28 | 28 | import traceback |
|
29 | 29 | import unittest |
|
30 | 30 | |
|
31 | 31 | from inspect import getmodule |
|
32 | 32 | |
|
33 | 33 | # We are overriding the default doctest runner, so we need to import a few |
|
34 | 34 | # things from doctest directly |
|
35 | 35 | from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, |
|
36 | 36 | _unittest_reportflags, DocTestRunner, |
|
37 | 37 | _extract_future_flags, pdb, _OutputRedirectingPdb, |
|
38 | 38 | _exception_traceback, |
|
39 | 39 | linecache) |
|
40 | 40 | |
|
41 | 41 | # Third-party modules |
|
42 | 42 | import nose.core |
|
43 | 43 | |
|
44 | 44 | from nose.plugins import doctests, Plugin |
|
45 | 45 | from nose.util import anyp, getpackage, test_address, resolve_name, tolist |
|
46 | 46 | |
|
47 | 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 | 50 | if PY3: |
|
51 | 51 | from io import StringIO |
|
52 | 52 | else: |
|
53 | 53 | from StringIO import StringIO |
|
54 | 54 | |
|
55 | 55 | #----------------------------------------------------------------------------- |
|
56 | 56 | # Module globals and other constants |
|
57 | 57 | #----------------------------------------------------------------------------- |
|
58 | 58 | |
|
59 | 59 | log = logging.getLogger(__name__) |
|
60 | 60 | |
|
61 | 61 | |
|
62 | 62 | #----------------------------------------------------------------------------- |
|
63 | 63 | # Classes and functions |
|
64 | 64 | #----------------------------------------------------------------------------- |
|
65 | 65 | |
|
66 | 66 | def is_extension_module(filename): |
|
67 | 67 | """Return whether the given filename is an extension module. |
|
68 | 68 | |
|
69 | 69 | This simply checks that the extension is either .so or .pyd. |
|
70 | 70 | """ |
|
71 | 71 | return os.path.splitext(filename)[1].lower() in ('.so','.pyd') |
|
72 | 72 | |
|
73 | 73 | |
|
74 | 74 | class DocTestSkip(object): |
|
75 | 75 | """Object wrapper for doctests to be skipped.""" |
|
76 | 76 | |
|
77 | 77 | ds_skip = """Doctest to skip. |
|
78 | 78 | >>> 1 #doctest: +SKIP |
|
79 | 79 | """ |
|
80 | 80 | |
|
81 | 81 | def __init__(self,obj): |
|
82 | 82 | self.obj = obj |
|
83 | 83 | |
|
84 | 84 | def __getattribute__(self,key): |
|
85 | 85 | if key == '__doc__': |
|
86 | 86 | return DocTestSkip.ds_skip |
|
87 | 87 | else: |
|
88 | 88 | return getattr(object.__getattribute__(self,'obj'),key) |
|
89 | 89 | |
|
90 | 90 | # Modified version of the one in the stdlib, that fixes a python bug (doctests |
|
91 | 91 | # not found in extension modules, http://bugs.python.org/issue3158) |
|
92 | 92 | class DocTestFinder(doctest.DocTestFinder): |
|
93 | 93 | |
|
94 | 94 | def _from_module(self, module, object): |
|
95 | 95 | """ |
|
96 | 96 | Return true if the given object is defined in the given |
|
97 | 97 | module. |
|
98 | 98 | """ |
|
99 | 99 | if module is None: |
|
100 | 100 | return True |
|
101 | 101 | elif inspect.isfunction(object): |
|
102 | 102 | return module.__dict__ is object.__globals__ |
|
103 | 103 | elif inspect.isbuiltin(object): |
|
104 | 104 | return module.__name__ == object.__module__ |
|
105 | 105 | elif inspect.isclass(object): |
|
106 | 106 | return module.__name__ == object.__module__ |
|
107 | 107 | elif inspect.ismethod(object): |
|
108 | 108 | # This one may be a bug in cython that fails to correctly set the |
|
109 | 109 | # __module__ attribute of methods, but since the same error is easy |
|
110 | 110 | # to make by extension code writers, having this safety in place |
|
111 | 111 | # isn't such a bad idea |
|
112 | 112 | return module.__name__ == object.__self__.__class__.__module__ |
|
113 | 113 | elif inspect.getmodule(object) is not None: |
|
114 | 114 | return module is inspect.getmodule(object) |
|
115 | 115 | elif hasattr(object, '__module__'): |
|
116 | 116 | return module.__name__ == object.__module__ |
|
117 | 117 | elif isinstance(object, property): |
|
118 | 118 | return True # [XX] no way not be sure. |
|
119 | 119 | else: |
|
120 | 120 | raise ValueError("object must be a class or function, got %r" % object) |
|
121 | 121 | |
|
122 | 122 | def _find(self, tests, obj, name, module, source_lines, globs, seen): |
|
123 | 123 | """ |
|
124 | 124 | Find tests for the given object and any contained objects, and |
|
125 | 125 | add them to `tests`. |
|
126 | 126 | """ |
|
127 | 127 | #print '_find for:', obj, name, module # dbg |
|
128 | 128 | if hasattr(obj,"skip_doctest"): |
|
129 | 129 | #print 'SKIPPING DOCTEST FOR:',obj # dbg |
|
130 | 130 | obj = DocTestSkip(obj) |
|
131 | 131 | |
|
132 | 132 | doctest.DocTestFinder._find(self,tests, obj, name, module, |
|
133 | 133 | source_lines, globs, seen) |
|
134 | 134 | |
|
135 | 135 | # Below we re-run pieces of the above method with manual modifications, |
|
136 | 136 | # because the original code is buggy and fails to correctly identify |
|
137 | 137 | # doctests in extension modules. |
|
138 | 138 | |
|
139 | 139 | # Local shorthands |
|
140 | 140 | from inspect import isroutine, isclass, ismodule |
|
141 | 141 | |
|
142 | 142 | # Look for tests in a module's contained objects. |
|
143 | 143 | if inspect.ismodule(obj) and self._recurse: |
|
144 | 144 | for valname, val in obj.__dict__.items(): |
|
145 | 145 | valname1 = '%s.%s' % (name, valname) |
|
146 | 146 | if ( (isroutine(val) or isclass(val)) |
|
147 | 147 | and self._from_module(module, val) ): |
|
148 | 148 | |
|
149 | 149 | self._find(tests, val, valname1, module, source_lines, |
|
150 | 150 | globs, seen) |
|
151 | 151 | |
|
152 | 152 | # Look for tests in a class's contained objects. |
|
153 | 153 | if inspect.isclass(obj) and self._recurse: |
|
154 | 154 | #print 'RECURSE into class:',obj # dbg |
|
155 | 155 | for valname, val in obj.__dict__.items(): |
|
156 | 156 | # Special handling for staticmethod/classmethod. |
|
157 | 157 | if isinstance(val, staticmethod): |
|
158 | 158 | val = getattr(obj, valname) |
|
159 | 159 | if isinstance(val, classmethod): |
|
160 | 160 | val = getattr(obj, valname).__func__ |
|
161 | 161 | |
|
162 | 162 | # Recurse to methods, properties, and nested classes. |
|
163 | 163 | if ((inspect.isfunction(val) or inspect.isclass(val) or |
|
164 | 164 | inspect.ismethod(val) or |
|
165 | 165 | isinstance(val, property)) and |
|
166 | 166 | self._from_module(module, val)): |
|
167 | 167 | valname = '%s.%s' % (name, valname) |
|
168 | 168 | self._find(tests, val, valname, module, source_lines, |
|
169 | 169 | globs, seen) |
|
170 | 170 | |
|
171 | 171 | |
|
172 | 172 | class IPDoctestOutputChecker(doctest.OutputChecker): |
|
173 | 173 | """Second-chance checker with support for random tests. |
|
174 | 174 | |
|
175 | 175 | If the default comparison doesn't pass, this checker looks in the expected |
|
176 | 176 | output string for flags that tell us to ignore the output. |
|
177 | 177 | """ |
|
178 | 178 | |
|
179 | 179 | random_re = re.compile(r'#\s*random\s+') |
|
180 | 180 | |
|
181 | 181 | def check_output(self, want, got, optionflags): |
|
182 | 182 | """Check output, accepting special markers embedded in the output. |
|
183 | 183 | |
|
184 | 184 | If the output didn't pass the default validation but the special string |
|
185 | 185 | '#random' is included, we accept it.""" |
|
186 | 186 | |
|
187 | 187 | # Let the original tester verify first, in case people have valid tests |
|
188 | 188 | # that happen to have a comment saying '#random' embedded in. |
|
189 | 189 | ret = doctest.OutputChecker.check_output(self, want, got, |
|
190 | 190 | optionflags) |
|
191 | 191 | if not ret and self.random_re.search(want): |
|
192 | 192 | #print >> sys.stderr, 'RANDOM OK:',want # dbg |
|
193 | 193 | return True |
|
194 | 194 | |
|
195 | 195 | return ret |
|
196 | 196 | |
|
197 | 197 | |
|
198 | 198 | class DocTestCase(doctests.DocTestCase): |
|
199 | 199 | """Proxy for DocTestCase: provides an address() method that |
|
200 | 200 | returns the correct address for the doctest case. Otherwise |
|
201 | 201 | acts as a proxy to the test case. To provide hints for address(), |
|
202 | 202 | an obj may also be passed -- this will be used as the test object |
|
203 | 203 | for purposes of determining the test address, if it is provided. |
|
204 | 204 | """ |
|
205 | 205 | |
|
206 | 206 | # Note: this method was taken from numpy's nosetester module. |
|
207 | 207 | |
|
208 | 208 | # Subclass nose.plugins.doctests.DocTestCase to work around a bug in |
|
209 | 209 | # its constructor that blocks non-default arguments from being passed |
|
210 | 210 | # down into doctest.DocTestCase |
|
211 | 211 | |
|
212 | 212 | def __init__(self, test, optionflags=0, setUp=None, tearDown=None, |
|
213 | 213 | checker=None, obj=None, result_var='_'): |
|
214 | 214 | self._result_var = result_var |
|
215 | 215 | doctests.DocTestCase.__init__(self, test, |
|
216 | 216 | optionflags=optionflags, |
|
217 | 217 | setUp=setUp, tearDown=tearDown, |
|
218 | 218 | checker=checker) |
|
219 | 219 | # Now we must actually copy the original constructor from the stdlib |
|
220 | 220 | # doctest class, because we can't call it directly and a bug in nose |
|
221 | 221 | # means it never gets passed the right arguments. |
|
222 | 222 | |
|
223 | 223 | self._dt_optionflags = optionflags |
|
224 | 224 | self._dt_checker = checker |
|
225 | 225 | self._dt_test = test |
|
226 | 226 | self._dt_test_globs_ori = test.globs |
|
227 | 227 | self._dt_setUp = setUp |
|
228 | 228 | self._dt_tearDown = tearDown |
|
229 | 229 | |
|
230 | 230 | # XXX - store this runner once in the object! |
|
231 | 231 | runner = IPDocTestRunner(optionflags=optionflags, |
|
232 | 232 | checker=checker, verbose=False) |
|
233 | 233 | self._dt_runner = runner |
|
234 | 234 | |
|
235 | 235 | |
|
236 | 236 | # Each doctest should remember the directory it was loaded from, so |
|
237 | 237 | # things like %run work without too many contortions |
|
238 | 238 | self._ori_dir = os.path.dirname(test.filename) |
|
239 | 239 | |
|
240 | 240 | # Modified runTest from the default stdlib |
|
241 | 241 | def runTest(self): |
|
242 | 242 | test = self._dt_test |
|
243 | 243 | runner = self._dt_runner |
|
244 | 244 | |
|
245 | 245 | old = sys.stdout |
|
246 | 246 | new = StringIO() |
|
247 | 247 | optionflags = self._dt_optionflags |
|
248 | 248 | |
|
249 | 249 | if not (optionflags & REPORTING_FLAGS): |
|
250 | 250 | # The option flags don't include any reporting flags, |
|
251 | 251 | # so add the default reporting flags |
|
252 | 252 | optionflags |= _unittest_reportflags |
|
253 | 253 | |
|
254 | 254 | try: |
|
255 | 255 | # Save our current directory and switch out to the one where the |
|
256 | 256 | # test was originally created, in case another doctest did a |
|
257 | 257 | # directory change. We'll restore this in the finally clause. |
|
258 |
curdir = |
|
|
258 | curdir = getcwd() | |
|
259 | 259 | #print 'runTest in dir:', self._ori_dir # dbg |
|
260 | 260 | os.chdir(self._ori_dir) |
|
261 | 261 | |
|
262 | 262 | runner.DIVIDER = "-"*70 |
|
263 | 263 | failures, tries = runner.run(test,out=new.write, |
|
264 | 264 | clear_globs=False) |
|
265 | 265 | finally: |
|
266 | 266 | sys.stdout = old |
|
267 | 267 | os.chdir(curdir) |
|
268 | 268 | |
|
269 | 269 | if failures: |
|
270 | 270 | raise self.failureException(self.format_failure(new.getvalue())) |
|
271 | 271 | |
|
272 | 272 | def setUp(self): |
|
273 | 273 | """Modified test setup that syncs with ipython namespace""" |
|
274 | 274 | #print "setUp test", self._dt_test.examples # dbg |
|
275 | 275 | if isinstance(self._dt_test.examples[0], IPExample): |
|
276 | 276 | # for IPython examples *only*, we swap the globals with the ipython |
|
277 | 277 | # namespace, after updating it with the globals (which doctest |
|
278 | 278 | # fills with the necessary info from the module being tested). |
|
279 | 279 | self.user_ns_orig = {} |
|
280 | 280 | self.user_ns_orig.update(_ip.user_ns) |
|
281 | 281 | _ip.user_ns.update(self._dt_test.globs) |
|
282 | 282 | # We must remove the _ key in the namespace, so that Python's |
|
283 | 283 | # doctest code sets it naturally |
|
284 | 284 | _ip.user_ns.pop('_', None) |
|
285 | 285 | _ip.user_ns['__builtins__'] = builtin_mod |
|
286 | 286 | self._dt_test.globs = _ip.user_ns |
|
287 | 287 | |
|
288 | 288 | super(DocTestCase, self).setUp() |
|
289 | 289 | |
|
290 | 290 | def tearDown(self): |
|
291 | 291 | |
|
292 | 292 | # Undo the test.globs reassignment we made, so that the parent class |
|
293 | 293 | # teardown doesn't destroy the ipython namespace |
|
294 | 294 | if isinstance(self._dt_test.examples[0], IPExample): |
|
295 | 295 | self._dt_test.globs = self._dt_test_globs_ori |
|
296 | 296 | _ip.user_ns.clear() |
|
297 | 297 | _ip.user_ns.update(self.user_ns_orig) |
|
298 | 298 | |
|
299 | 299 | # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but |
|
300 | 300 | # it does look like one to me: its tearDown method tries to run |
|
301 | 301 | # |
|
302 | 302 | # delattr(builtin_mod, self._result_var) |
|
303 | 303 | # |
|
304 | 304 | # without checking that the attribute really is there; it implicitly |
|
305 | 305 | # assumes it should have been set via displayhook. But if the |
|
306 | 306 | # displayhook was never called, this doesn't necessarily happen. I |
|
307 | 307 | # haven't been able to find a little self-contained example outside of |
|
308 | 308 | # ipython that would show the problem so I can report it to the nose |
|
309 | 309 | # team, but it does happen a lot in our code. |
|
310 | 310 | # |
|
311 | 311 | # So here, we just protect as narrowly as possible by trapping an |
|
312 | 312 | # attribute error whose message would be the name of self._result_var, |
|
313 | 313 | # and letting any other error propagate. |
|
314 | 314 | try: |
|
315 | 315 | super(DocTestCase, self).tearDown() |
|
316 | 316 | except AttributeError as exc: |
|
317 | 317 | if exc.args[0] != self._result_var: |
|
318 | 318 | raise |
|
319 | 319 | |
|
320 | 320 | |
|
321 | 321 | # A simple subclassing of the original with a different class name, so we can |
|
322 | 322 | # distinguish and treat differently IPython examples from pure python ones. |
|
323 | 323 | class IPExample(doctest.Example): pass |
|
324 | 324 | |
|
325 | 325 | |
|
326 | 326 | class IPExternalExample(doctest.Example): |
|
327 | 327 | """Doctest examples to be run in an external process.""" |
|
328 | 328 | |
|
329 | 329 | def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, |
|
330 | 330 | options=None): |
|
331 | 331 | # Parent constructor |
|
332 | 332 | doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) |
|
333 | 333 | |
|
334 | 334 | # An EXTRA newline is needed to prevent pexpect hangs |
|
335 | 335 | self.source += '\n' |
|
336 | 336 | |
|
337 | 337 | |
|
338 | 338 | class IPDocTestParser(doctest.DocTestParser): |
|
339 | 339 | """ |
|
340 | 340 | A class used to parse strings containing doctest examples. |
|
341 | 341 | |
|
342 | 342 | Note: This is a version modified to properly recognize IPython input and |
|
343 | 343 | convert any IPython examples into valid Python ones. |
|
344 | 344 | """ |
|
345 | 345 | # This regular expression is used to find doctest examples in a |
|
346 | 346 | # string. It defines three groups: `source` is the source code |
|
347 | 347 | # (including leading indentation and prompts); `indent` is the |
|
348 | 348 | # indentation of the first (PS1) line of the source code; and |
|
349 | 349 | # `want` is the expected output (including leading indentation). |
|
350 | 350 | |
|
351 | 351 | # Classic Python prompts or default IPython ones |
|
352 | 352 | _PS1_PY = r'>>>' |
|
353 | 353 | _PS2_PY = r'\.\.\.' |
|
354 | 354 | |
|
355 | 355 | _PS1_IP = r'In\ \[\d+\]:' |
|
356 | 356 | _PS2_IP = r'\ \ \ \.\.\.+:' |
|
357 | 357 | |
|
358 | 358 | _RE_TPL = r''' |
|
359 | 359 | # Source consists of a PS1 line followed by zero or more PS2 lines. |
|
360 | 360 | (?P<source> |
|
361 | 361 | (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line |
|
362 | 362 | (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines |
|
363 | 363 | \n? # a newline |
|
364 | 364 | # Want consists of any non-blank lines that do not start with PS1. |
|
365 | 365 | (?P<want> (?:(?![ ]*$) # Not a blank line |
|
366 | 366 | (?![ ]*%s) # Not a line starting with PS1 |
|
367 | 367 | (?![ ]*%s) # Not a line starting with PS2 |
|
368 | 368 | .*$\n? # But any other line |
|
369 | 369 | )*) |
|
370 | 370 | ''' |
|
371 | 371 | |
|
372 | 372 | _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), |
|
373 | 373 | re.MULTILINE | re.VERBOSE) |
|
374 | 374 | |
|
375 | 375 | _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), |
|
376 | 376 | re.MULTILINE | re.VERBOSE) |
|
377 | 377 | |
|
378 | 378 | # Mark a test as being fully random. In this case, we simply append the |
|
379 | 379 | # random marker ('#random') to each individual example's output. This way |
|
380 | 380 | # we don't need to modify any other code. |
|
381 | 381 | _RANDOM_TEST = re.compile(r'#\s*all-random\s+') |
|
382 | 382 | |
|
383 | 383 | # Mark tests to be executed in an external process - currently unsupported. |
|
384 | 384 | _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') |
|
385 | 385 | |
|
386 | 386 | def ip2py(self,source): |
|
387 | 387 | """Convert input IPython source into valid Python.""" |
|
388 | 388 | block = _ip.input_transformer_manager.transform_cell(source) |
|
389 | 389 | if len(block.splitlines()) == 1: |
|
390 | 390 | return _ip.prefilter(block) |
|
391 | 391 | else: |
|
392 | 392 | return block |
|
393 | 393 | |
|
394 | 394 | def parse(self, string, name='<string>'): |
|
395 | 395 | """ |
|
396 | 396 | Divide the given string into examples and intervening text, |
|
397 | 397 | and return them as a list of alternating Examples and strings. |
|
398 | 398 | Line numbers for the Examples are 0-based. The optional |
|
399 | 399 | argument `name` is a name identifying this string, and is only |
|
400 | 400 | used for error messages. |
|
401 | 401 | """ |
|
402 | 402 | |
|
403 | 403 | #print 'Parse string:\n',string # dbg |
|
404 | 404 | |
|
405 | 405 | string = string.expandtabs() |
|
406 | 406 | # If all lines begin with the same indentation, then strip it. |
|
407 | 407 | min_indent = self._min_indent(string) |
|
408 | 408 | if min_indent > 0: |
|
409 | 409 | string = '\n'.join([l[min_indent:] for l in string.split('\n')]) |
|
410 | 410 | |
|
411 | 411 | output = [] |
|
412 | 412 | charno, lineno = 0, 0 |
|
413 | 413 | |
|
414 | 414 | # We make 'all random' tests by adding the '# random' mark to every |
|
415 | 415 | # block of output in the test. |
|
416 | 416 | if self._RANDOM_TEST.search(string): |
|
417 | 417 | random_marker = '\n# random' |
|
418 | 418 | else: |
|
419 | 419 | random_marker = '' |
|
420 | 420 | |
|
421 | 421 | # Whether to convert the input from ipython to python syntax |
|
422 | 422 | ip2py = False |
|
423 | 423 | # Find all doctest examples in the string. First, try them as Python |
|
424 | 424 | # examples, then as IPython ones |
|
425 | 425 | terms = list(self._EXAMPLE_RE_PY.finditer(string)) |
|
426 | 426 | if terms: |
|
427 | 427 | # Normal Python example |
|
428 | 428 | #print '-'*70 # dbg |
|
429 | 429 | #print 'PyExample, Source:\n',string # dbg |
|
430 | 430 | #print '-'*70 # dbg |
|
431 | 431 | Example = doctest.Example |
|
432 | 432 | else: |
|
433 | 433 | # It's an ipython example. Note that IPExamples are run |
|
434 | 434 | # in-process, so their syntax must be turned into valid python. |
|
435 | 435 | # IPExternalExamples are run out-of-process (via pexpect) so they |
|
436 | 436 | # don't need any filtering (a real ipython will be executing them). |
|
437 | 437 | terms = list(self._EXAMPLE_RE_IP.finditer(string)) |
|
438 | 438 | if self._EXTERNAL_IP.search(string): |
|
439 | 439 | #print '-'*70 # dbg |
|
440 | 440 | #print 'IPExternalExample, Source:\n',string # dbg |
|
441 | 441 | #print '-'*70 # dbg |
|
442 | 442 | Example = IPExternalExample |
|
443 | 443 | else: |
|
444 | 444 | #print '-'*70 # dbg |
|
445 | 445 | #print 'IPExample, Source:\n',string # dbg |
|
446 | 446 | #print '-'*70 # dbg |
|
447 | 447 | Example = IPExample |
|
448 | 448 | ip2py = True |
|
449 | 449 | |
|
450 | 450 | for m in terms: |
|
451 | 451 | # Add the pre-example text to `output`. |
|
452 | 452 | output.append(string[charno:m.start()]) |
|
453 | 453 | # Update lineno (lines before this example) |
|
454 | 454 | lineno += string.count('\n', charno, m.start()) |
|
455 | 455 | # Extract info from the regexp match. |
|
456 | 456 | (source, options, want, exc_msg) = \ |
|
457 | 457 | self._parse_example(m, name, lineno,ip2py) |
|
458 | 458 | |
|
459 | 459 | # Append the random-output marker (it defaults to empty in most |
|
460 | 460 | # cases, it's only non-empty for 'all-random' tests): |
|
461 | 461 | want += random_marker |
|
462 | 462 | |
|
463 | 463 | if Example is IPExternalExample: |
|
464 | 464 | options[doctest.NORMALIZE_WHITESPACE] = True |
|
465 | 465 | want += '\n' |
|
466 | 466 | |
|
467 | 467 | # Create an Example, and add it to the list. |
|
468 | 468 | if not self._IS_BLANK_OR_COMMENT(source): |
|
469 | 469 | output.append(Example(source, want, exc_msg, |
|
470 | 470 | lineno=lineno, |
|
471 | 471 | indent=min_indent+len(m.group('indent')), |
|
472 | 472 | options=options)) |
|
473 | 473 | # Update lineno (lines inside this example) |
|
474 | 474 | lineno += string.count('\n', m.start(), m.end()) |
|
475 | 475 | # Update charno. |
|
476 | 476 | charno = m.end() |
|
477 | 477 | # Add any remaining post-example text to `output`. |
|
478 | 478 | output.append(string[charno:]) |
|
479 | 479 | return output |
|
480 | 480 | |
|
481 | 481 | def _parse_example(self, m, name, lineno,ip2py=False): |
|
482 | 482 | """ |
|
483 | 483 | Given a regular expression match from `_EXAMPLE_RE` (`m`), |
|
484 | 484 | return a pair `(source, want)`, where `source` is the matched |
|
485 | 485 | example's source code (with prompts and indentation stripped); |
|
486 | 486 | and `want` is the example's expected output (with indentation |
|
487 | 487 | stripped). |
|
488 | 488 | |
|
489 | 489 | `name` is the string's name, and `lineno` is the line number |
|
490 | 490 | where the example starts; both are used for error messages. |
|
491 | 491 | |
|
492 | 492 | Optional: |
|
493 | 493 | `ip2py`: if true, filter the input via IPython to convert the syntax |
|
494 | 494 | into valid python. |
|
495 | 495 | """ |
|
496 | 496 | |
|
497 | 497 | # Get the example's indentation level. |
|
498 | 498 | indent = len(m.group('indent')) |
|
499 | 499 | |
|
500 | 500 | # Divide source into lines; check that they're properly |
|
501 | 501 | # indented; and then strip their indentation & prompts. |
|
502 | 502 | source_lines = m.group('source').split('\n') |
|
503 | 503 | |
|
504 | 504 | # We're using variable-length input prompts |
|
505 | 505 | ps1 = m.group('ps1') |
|
506 | 506 | ps2 = m.group('ps2') |
|
507 | 507 | ps1_len = len(ps1) |
|
508 | 508 | |
|
509 | 509 | self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) |
|
510 | 510 | if ps2: |
|
511 | 511 | self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) |
|
512 | 512 | |
|
513 | 513 | source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) |
|
514 | 514 | |
|
515 | 515 | if ip2py: |
|
516 | 516 | # Convert source input from IPython into valid Python syntax |
|
517 | 517 | source = self.ip2py(source) |
|
518 | 518 | |
|
519 | 519 | # Divide want into lines; check that it's properly indented; and |
|
520 | 520 | # then strip the indentation. Spaces before the last newline should |
|
521 | 521 | # be preserved, so plain rstrip() isn't good enough. |
|
522 | 522 | want = m.group('want') |
|
523 | 523 | want_lines = want.split('\n') |
|
524 | 524 | if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): |
|
525 | 525 | del want_lines[-1] # forget final newline & spaces after it |
|
526 | 526 | self._check_prefix(want_lines, ' '*indent, name, |
|
527 | 527 | lineno + len(source_lines)) |
|
528 | 528 | |
|
529 | 529 | # Remove ipython output prompt that might be present in the first line |
|
530 | 530 | want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) |
|
531 | 531 | |
|
532 | 532 | want = '\n'.join([wl[indent:] for wl in want_lines]) |
|
533 | 533 | |
|
534 | 534 | # If `want` contains a traceback message, then extract it. |
|
535 | 535 | m = self._EXCEPTION_RE.match(want) |
|
536 | 536 | if m: |
|
537 | 537 | exc_msg = m.group('msg') |
|
538 | 538 | else: |
|
539 | 539 | exc_msg = None |
|
540 | 540 | |
|
541 | 541 | # Extract options from the source. |
|
542 | 542 | options = self._find_options(source, name, lineno) |
|
543 | 543 | |
|
544 | 544 | return source, options, want, exc_msg |
|
545 | 545 | |
|
546 | 546 | def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): |
|
547 | 547 | """ |
|
548 | 548 | Given the lines of a source string (including prompts and |
|
549 | 549 | leading indentation), check to make sure that every prompt is |
|
550 | 550 | followed by a space character. If any line is not followed by |
|
551 | 551 | a space character, then raise ValueError. |
|
552 | 552 | |
|
553 | 553 | Note: IPython-modified version which takes the input prompt length as a |
|
554 | 554 | parameter, so that prompts of variable length can be dealt with. |
|
555 | 555 | """ |
|
556 | 556 | space_idx = indent+ps1_len |
|
557 | 557 | min_len = space_idx+1 |
|
558 | 558 | for i, line in enumerate(lines): |
|
559 | 559 | if len(line) >= min_len and line[space_idx] != ' ': |
|
560 | 560 | raise ValueError('line %r of the docstring for %s ' |
|
561 | 561 | 'lacks blank after %s: %r' % |
|
562 | 562 | (lineno+i+1, name, |
|
563 | 563 | line[indent:space_idx], line)) |
|
564 | 564 | |
|
565 | 565 | |
|
566 | 566 | SKIP = doctest.register_optionflag('SKIP') |
|
567 | 567 | |
|
568 | 568 | |
|
569 | 569 | class IPDocTestRunner(doctest.DocTestRunner,object): |
|
570 | 570 | """Test runner that synchronizes the IPython namespace with test globals. |
|
571 | 571 | """ |
|
572 | 572 | |
|
573 | 573 | def run(self, test, compileflags=None, out=None, clear_globs=True): |
|
574 | 574 | |
|
575 | 575 | # Hack: ipython needs access to the execution context of the example, |
|
576 | 576 | # so that it can propagate user variables loaded by %run into |
|
577 | 577 | # test.globs. We put them here into our modified %run as a function |
|
578 | 578 | # attribute. Our new %run will then only make the namespace update |
|
579 | 579 | # when called (rather than unconconditionally updating test.globs here |
|
580 | 580 | # for all examples, most of which won't be calling %run anyway). |
|
581 | 581 | #_ip._ipdoctest_test_globs = test.globs |
|
582 | 582 | #_ip._ipdoctest_test_filename = test.filename |
|
583 | 583 | |
|
584 | 584 | test.globs.update(_ip.user_ns) |
|
585 | 585 | |
|
586 | 586 | return super(IPDocTestRunner,self).run(test, |
|
587 | 587 | compileflags,out,clear_globs) |
|
588 | 588 | |
|
589 | 589 | |
|
590 | 590 | class DocFileCase(doctest.DocFileCase): |
|
591 | 591 | """Overrides to provide filename |
|
592 | 592 | """ |
|
593 | 593 | def address(self): |
|
594 | 594 | return (self._dt_test.filename, None, None) |
|
595 | 595 | |
|
596 | 596 | |
|
597 | 597 | class ExtensionDoctest(doctests.Doctest): |
|
598 | 598 | """Nose Plugin that supports doctests in extension modules. |
|
599 | 599 | """ |
|
600 | 600 | name = 'extdoctest' # call nosetests with --with-extdoctest |
|
601 | 601 | enabled = True |
|
602 | 602 | |
|
603 | 603 | def options(self, parser, env=os.environ): |
|
604 | 604 | Plugin.options(self, parser, env) |
|
605 | 605 | parser.add_option('--doctest-tests', action='store_true', |
|
606 | 606 | dest='doctest_tests', |
|
607 | 607 | default=env.get('NOSE_DOCTEST_TESTS',True), |
|
608 | 608 | help="Also look for doctests in test modules. " |
|
609 | 609 | "Note that classes, methods and functions should " |
|
610 | 610 | "have either doctests or non-doctest tests, " |
|
611 | 611 | "not both. [NOSE_DOCTEST_TESTS]") |
|
612 | 612 | parser.add_option('--doctest-extension', action="append", |
|
613 | 613 | dest="doctestExtension", |
|
614 | 614 | help="Also look for doctests in files with " |
|
615 | 615 | "this extension [NOSE_DOCTEST_EXTENSION]") |
|
616 | 616 | # Set the default as a list, if given in env; otherwise |
|
617 | 617 | # an additional value set on the command line will cause |
|
618 | 618 | # an error. |
|
619 | 619 | env_setting = env.get('NOSE_DOCTEST_EXTENSION') |
|
620 | 620 | if env_setting is not None: |
|
621 | 621 | parser.set_defaults(doctestExtension=tolist(env_setting)) |
|
622 | 622 | |
|
623 | 623 | |
|
624 | 624 | def configure(self, options, config): |
|
625 | 625 | Plugin.configure(self, options, config) |
|
626 | 626 | # Pull standard doctest plugin out of config; we will do doctesting |
|
627 | 627 | config.plugins.plugins = [p for p in config.plugins.plugins |
|
628 | 628 | if p.name != 'doctest'] |
|
629 | 629 | self.doctest_tests = options.doctest_tests |
|
630 | 630 | self.extension = tolist(options.doctestExtension) |
|
631 | 631 | |
|
632 | 632 | self.parser = doctest.DocTestParser() |
|
633 | 633 | self.finder = DocTestFinder() |
|
634 | 634 | self.checker = IPDoctestOutputChecker() |
|
635 | 635 | self.globs = None |
|
636 | 636 | self.extraglobs = None |
|
637 | 637 | |
|
638 | 638 | |
|
639 | 639 | def loadTestsFromExtensionModule(self,filename): |
|
640 | 640 | bpath,mod = os.path.split(filename) |
|
641 | 641 | modname = os.path.splitext(mod)[0] |
|
642 | 642 | try: |
|
643 | 643 | sys.path.append(bpath) |
|
644 | 644 | module = __import__(modname) |
|
645 | 645 | tests = list(self.loadTestsFromModule(module)) |
|
646 | 646 | finally: |
|
647 | 647 | sys.path.pop() |
|
648 | 648 | return tests |
|
649 | 649 | |
|
650 | 650 | # NOTE: the method below is almost a copy of the original one in nose, with |
|
651 | 651 | # a few modifications to control output checking. |
|
652 | 652 | |
|
653 | 653 | def loadTestsFromModule(self, module): |
|
654 | 654 | #print '*** ipdoctest - lTM',module # dbg |
|
655 | 655 | |
|
656 | 656 | if not self.matches(module.__name__): |
|
657 | 657 | log.debug("Doctest doesn't want module %s", module) |
|
658 | 658 | return |
|
659 | 659 | |
|
660 | 660 | tests = self.finder.find(module,globs=self.globs, |
|
661 | 661 | extraglobs=self.extraglobs) |
|
662 | 662 | if not tests: |
|
663 | 663 | return |
|
664 | 664 | |
|
665 | 665 | # always use whitespace and ellipsis options |
|
666 | 666 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |
|
667 | 667 | |
|
668 | 668 | tests.sort() |
|
669 | 669 | module_file = module.__file__ |
|
670 | 670 | if module_file[-4:] in ('.pyc', '.pyo'): |
|
671 | 671 | module_file = module_file[:-1] |
|
672 | 672 | for test in tests: |
|
673 | 673 | if not test.examples: |
|
674 | 674 | continue |
|
675 | 675 | if not test.filename: |
|
676 | 676 | test.filename = module_file |
|
677 | 677 | |
|
678 | 678 | yield DocTestCase(test, |
|
679 | 679 | optionflags=optionflags, |
|
680 | 680 | checker=self.checker) |
|
681 | 681 | |
|
682 | 682 | |
|
683 | 683 | def loadTestsFromFile(self, filename): |
|
684 | 684 | #print "ipdoctest - from file", filename # dbg |
|
685 | 685 | if is_extension_module(filename): |
|
686 | 686 | for t in self.loadTestsFromExtensionModule(filename): |
|
687 | 687 | yield t |
|
688 | 688 | else: |
|
689 | 689 | if self.extension and anyp(filename.endswith, self.extension): |
|
690 | 690 | name = os.path.basename(filename) |
|
691 | 691 | dh = open(filename) |
|
692 | 692 | try: |
|
693 | 693 | doc = dh.read() |
|
694 | 694 | finally: |
|
695 | 695 | dh.close() |
|
696 | 696 | test = self.parser.get_doctest( |
|
697 | 697 | doc, globs={'__file__': filename}, name=name, |
|
698 | 698 | filename=filename, lineno=0) |
|
699 | 699 | if test.examples: |
|
700 | 700 | #print 'FileCase:',test.examples # dbg |
|
701 | 701 | yield DocFileCase(test) |
|
702 | 702 | else: |
|
703 | 703 | yield False # no tests to load |
|
704 | 704 | |
|
705 | 705 | |
|
706 | 706 | class IPythonDoctest(ExtensionDoctest): |
|
707 | 707 | """Nose Plugin that supports doctests in extension modules. |
|
708 | 708 | """ |
|
709 | 709 | name = 'ipdoctest' # call nosetests with --with-ipdoctest |
|
710 | 710 | enabled = True |
|
711 | 711 | |
|
712 | 712 | def makeTest(self, obj, parent): |
|
713 | 713 | """Look for doctests in the given object, which will be a |
|
714 | 714 | function, method or class. |
|
715 | 715 | """ |
|
716 | 716 | #print 'Plugin analyzing:', obj, parent # dbg |
|
717 | 717 | # always use whitespace and ellipsis options |
|
718 | 718 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |
|
719 | 719 | |
|
720 | 720 | doctests = self.finder.find(obj, module=getmodule(parent)) |
|
721 | 721 | if doctests: |
|
722 | 722 | for test in doctests: |
|
723 | 723 | if len(test.examples) == 0: |
|
724 | 724 | continue |
|
725 | 725 | |
|
726 | 726 | yield DocTestCase(test, obj=obj, |
|
727 | 727 | optionflags=optionflags, |
|
728 | 728 | checker=self.checker) |
|
729 | 729 | |
|
730 | 730 | def options(self, parser, env=os.environ): |
|
731 | 731 | #print "Options for nose plugin:", self.name # dbg |
|
732 | 732 | Plugin.options(self, parser, env) |
|
733 | 733 | parser.add_option('--ipdoctest-tests', action='store_true', |
|
734 | 734 | dest='ipdoctest_tests', |
|
735 | 735 | default=env.get('NOSE_IPDOCTEST_TESTS',True), |
|
736 | 736 | help="Also look for doctests in test modules. " |
|
737 | 737 | "Note that classes, methods and functions should " |
|
738 | 738 | "have either doctests or non-doctest tests, " |
|
739 | 739 | "not both. [NOSE_IPDOCTEST_TESTS]") |
|
740 | 740 | parser.add_option('--ipdoctest-extension', action="append", |
|
741 | 741 | dest="ipdoctest_extension", |
|
742 | 742 | help="Also look for doctests in files with " |
|
743 | 743 | "this extension [NOSE_IPDOCTEST_EXTENSION]") |
|
744 | 744 | # Set the default as a list, if given in env; otherwise |
|
745 | 745 | # an additional value set on the command line will cause |
|
746 | 746 | # an error. |
|
747 | 747 | env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') |
|
748 | 748 | if env_setting is not None: |
|
749 | 749 | parser.set_defaults(ipdoctest_extension=tolist(env_setting)) |
|
750 | 750 | |
|
751 | 751 | def configure(self, options, config): |
|
752 | 752 | #print "Configuring nose plugin:", self.name # dbg |
|
753 | 753 | Plugin.configure(self, options, config) |
|
754 | 754 | # Pull standard doctest plugin out of config; we will do doctesting |
|
755 | 755 | config.plugins.plugins = [p for p in config.plugins.plugins |
|
756 | 756 | if p.name != 'doctest'] |
|
757 | 757 | self.doctest_tests = options.ipdoctest_tests |
|
758 | 758 | self.extension = tolist(options.ipdoctest_extension) |
|
759 | 759 | |
|
760 | 760 | self.parser = IPDocTestParser() |
|
761 | 761 | self.finder = DocTestFinder(parser=self.parser) |
|
762 | 762 | self.checker = IPDoctestOutputChecker() |
|
763 | 763 | self.globs = None |
|
764 | 764 | self.extraglobs = None |
@@ -1,187 +1,187 b'' | |||
|
1 | 1 | """Windows-specific implementation of process utilities. |
|
2 | 2 | |
|
3 | 3 | This file is only meant to be imported by process.py, not by end-users. |
|
4 | 4 | """ |
|
5 | 5 | |
|
6 | 6 | #----------------------------------------------------------------------------- |
|
7 | 7 | # Copyright (C) 2010-2011 The IPython Development Team |
|
8 | 8 | # |
|
9 | 9 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | 10 | # the file COPYING, distributed as part of this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | from __future__ import print_function |
|
17 | 17 | |
|
18 | 18 | # stdlib |
|
19 | 19 | import os |
|
20 | 20 | import sys |
|
21 | 21 | import ctypes |
|
22 | 22 | |
|
23 | 23 | from ctypes import c_int, POINTER |
|
24 | 24 | from ctypes.wintypes import LPCWSTR, HLOCAL |
|
25 | 25 | from subprocess import STDOUT |
|
26 | 26 | |
|
27 | 27 | # our own imports |
|
28 | 28 | from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split |
|
29 | 29 | from . import py3compat |
|
30 | 30 | from .encoding import DEFAULT_ENCODING |
|
31 | 31 | |
|
32 | 32 | #----------------------------------------------------------------------------- |
|
33 | 33 | # Function definitions |
|
34 | 34 | #----------------------------------------------------------------------------- |
|
35 | 35 | |
|
36 | 36 | class AvoidUNCPath(object): |
|
37 | 37 | """A context manager to protect command execution from UNC paths. |
|
38 | 38 | |
|
39 | 39 | In the Win32 API, commands can't be invoked with the cwd being a UNC path. |
|
40 | 40 | This context manager temporarily changes directory to the 'C:' drive on |
|
41 | 41 | entering, and restores the original working directory on exit. |
|
42 | 42 | |
|
43 | 43 | The context manager returns the starting working directory *if* it made a |
|
44 | 44 | change and None otherwise, so that users can apply the necessary adjustment |
|
45 | 45 | to their system calls in the event of a change. |
|
46 | 46 | |
|
47 | 47 | Examples |
|
48 | 48 | -------- |
|
49 | 49 | :: |
|
50 | 50 | cmd = 'dir' |
|
51 | 51 | with AvoidUNCPath() as path: |
|
52 | 52 | if path is not None: |
|
53 | 53 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
54 | 54 | os.system(cmd) |
|
55 | 55 | """ |
|
56 | 56 | def __enter__(self): |
|
57 |
self.path = |
|
|
57 | self.path = py3compat.getcwd() | |
|
58 | 58 | self.is_unc_path = self.path.startswith(r"\\") |
|
59 | 59 | if self.is_unc_path: |
|
60 | 60 | # change to c drive (as cmd.exe cannot handle UNC addresses) |
|
61 | 61 | os.chdir("C:") |
|
62 | 62 | return self.path |
|
63 | 63 | else: |
|
64 | 64 | # We return None to signal that there was no change in the working |
|
65 | 65 | # directory |
|
66 | 66 | return None |
|
67 | 67 | |
|
68 | 68 | def __exit__(self, exc_type, exc_value, traceback): |
|
69 | 69 | if self.is_unc_path: |
|
70 | 70 | os.chdir(self.path) |
|
71 | 71 | |
|
72 | 72 | |
|
73 | 73 | def _find_cmd(cmd): |
|
74 | 74 | """Find the full path to a .bat or .exe using the win32api module.""" |
|
75 | 75 | try: |
|
76 | 76 | from win32api import SearchPath |
|
77 | 77 | except ImportError: |
|
78 | 78 | raise ImportError('you need to have pywin32 installed for this to work') |
|
79 | 79 | else: |
|
80 | 80 | PATH = os.environ['PATH'] |
|
81 | 81 | extensions = ['.exe', '.com', '.bat', '.py'] |
|
82 | 82 | path = None |
|
83 | 83 | for ext in extensions: |
|
84 | 84 | try: |
|
85 | 85 | path = SearchPath(PATH, cmd, ext)[0] |
|
86 | 86 | except: |
|
87 | 87 | pass |
|
88 | 88 | if path is None: |
|
89 | 89 | raise OSError("command %r not found" % cmd) |
|
90 | 90 | else: |
|
91 | 91 | return path |
|
92 | 92 | |
|
93 | 93 | |
|
94 | 94 | def _system_body(p): |
|
95 | 95 | """Callback for _system.""" |
|
96 | 96 | enc = DEFAULT_ENCODING |
|
97 | 97 | for line in read_no_interrupt(p.stdout).splitlines(): |
|
98 | 98 | line = line.decode(enc, 'replace') |
|
99 | 99 | print(line, file=sys.stdout) |
|
100 | 100 | for line in read_no_interrupt(p.stderr).splitlines(): |
|
101 | 101 | line = line.decode(enc, 'replace') |
|
102 | 102 | print(line, file=sys.stderr) |
|
103 | 103 | |
|
104 | 104 | # Wait to finish for returncode |
|
105 | 105 | return p.wait() |
|
106 | 106 | |
|
107 | 107 | |
|
108 | 108 | def system(cmd): |
|
109 | 109 | """Win32 version of os.system() that works with network shares. |
|
110 | 110 | |
|
111 | 111 | Note that this implementation returns None, as meant for use in IPython. |
|
112 | 112 | |
|
113 | 113 | Parameters |
|
114 | 114 | ---------- |
|
115 | 115 | cmd : str |
|
116 | 116 | A command to be executed in the system shell. |
|
117 | 117 | |
|
118 | 118 | Returns |
|
119 | 119 | ------- |
|
120 | 120 | None : we explicitly do NOT return the subprocess status code, as this |
|
121 | 121 | utility is meant to be used extensively in IPython, where any return value |
|
122 | 122 | would trigger :func:`sys.displayhook` calls. |
|
123 | 123 | """ |
|
124 | 124 | # The controller provides interactivity with both |
|
125 | 125 | # stdin and stdout |
|
126 | 126 | #import _process_win32_controller |
|
127 | 127 | #_process_win32_controller.system(cmd) |
|
128 | 128 | |
|
129 | 129 | with AvoidUNCPath() as path: |
|
130 | 130 | if path is not None: |
|
131 | 131 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
132 | 132 | return process_handler(cmd, _system_body) |
|
133 | 133 | |
|
134 | 134 | def getoutput(cmd): |
|
135 | 135 | """Return standard output of executing cmd in a shell. |
|
136 | 136 | |
|
137 | 137 | Accepts the same arguments as os.system(). |
|
138 | 138 | |
|
139 | 139 | Parameters |
|
140 | 140 | ---------- |
|
141 | 141 | cmd : str |
|
142 | 142 | A command to be executed in the system shell. |
|
143 | 143 | |
|
144 | 144 | Returns |
|
145 | 145 | ------- |
|
146 | 146 | stdout : str |
|
147 | 147 | """ |
|
148 | 148 | |
|
149 | 149 | with AvoidUNCPath() as path: |
|
150 | 150 | if path is not None: |
|
151 | 151 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
152 | 152 | out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT) |
|
153 | 153 | |
|
154 | 154 | if out is None: |
|
155 | 155 | out = b'' |
|
156 | 156 | return py3compat.bytes_to_str(out) |
|
157 | 157 | |
|
158 | 158 | try: |
|
159 | 159 | CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW |
|
160 | 160 | CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)] |
|
161 | 161 | CommandLineToArgvW.restype = POINTER(LPCWSTR) |
|
162 | 162 | LocalFree = ctypes.windll.kernel32.LocalFree |
|
163 | 163 | LocalFree.res_type = HLOCAL |
|
164 | 164 | LocalFree.arg_types = [HLOCAL] |
|
165 | 165 | |
|
166 | 166 | def arg_split(commandline, posix=False, strict=True): |
|
167 | 167 | """Split a command line's arguments in a shell-like manner. |
|
168 | 168 | |
|
169 | 169 | This is a special version for windows that use a ctypes call to CommandLineToArgvW |
|
170 | 170 | to do the argv splitting. The posix paramter is ignored. |
|
171 | 171 | |
|
172 | 172 | If strict=False, process_common.arg_split(...strict=False) is used instead. |
|
173 | 173 | """ |
|
174 | 174 | #CommandLineToArgvW returns path to executable if called with empty string. |
|
175 | 175 | if commandline.strip() == "": |
|
176 | 176 | return [] |
|
177 | 177 | if not strict: |
|
178 | 178 | # not really a cl-arg, fallback on _process_common |
|
179 | 179 | return py_arg_split(commandline, posix=posix, strict=strict) |
|
180 | 180 | argvn = c_int() |
|
181 | 181 | result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn)) |
|
182 | 182 | result_array_type = LPCWSTR * argvn.value |
|
183 | 183 | result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))] |
|
184 | 184 | retval = LocalFree(result_pointer) |
|
185 | 185 | return result |
|
186 | 186 | except AttributeError: |
|
187 | 187 | arg_split = py_arg_split |
@@ -1,577 +1,577 b'' | |||
|
1 | 1 | """Windows-specific implementation of process utilities with direct WinAPI. |
|
2 | 2 | |
|
3 | 3 | This file is meant to be used by process.py |
|
4 | 4 | """ |
|
5 | 5 | |
|
6 | 6 | #----------------------------------------------------------------------------- |
|
7 | 7 | # Copyright (C) 2010-2011 The IPython Development Team |
|
8 | 8 | # |
|
9 | 9 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | 10 | # the file COPYING, distributed as part of this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | from __future__ import print_function |
|
14 | 14 | |
|
15 | 15 | # stdlib |
|
16 | 16 | import os, sys, threading |
|
17 | 17 | import ctypes, msvcrt |
|
18 | 18 | |
|
19 | 19 | # local imports |
|
20 | from .py3compat import unicode_type | |
|
20 | from . import py3compat | |
|
21 | 21 | |
|
22 | 22 | # Win32 API types needed for the API calls |
|
23 | 23 | from ctypes import POINTER |
|
24 | 24 | from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \ |
|
25 | 25 | ULONG, LPCWSTR |
|
26 | 26 | LPDWORD = POINTER(DWORD) |
|
27 | 27 | LPHANDLE = POINTER(HANDLE) |
|
28 | 28 | ULONG_PTR = POINTER(ULONG) |
|
29 | 29 | class SECURITY_ATTRIBUTES(ctypes.Structure): |
|
30 | 30 | _fields_ = [("nLength", DWORD), |
|
31 | 31 | ("lpSecurityDescriptor", LPVOID), |
|
32 | 32 | ("bInheritHandle", BOOL)] |
|
33 | 33 | LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES) |
|
34 | 34 | class STARTUPINFO(ctypes.Structure): |
|
35 | 35 | _fields_ = [("cb", DWORD), |
|
36 | 36 | ("lpReserved", LPCWSTR), |
|
37 | 37 | ("lpDesktop", LPCWSTR), |
|
38 | 38 | ("lpTitle", LPCWSTR), |
|
39 | 39 | ("dwX", DWORD), |
|
40 | 40 | ("dwY", DWORD), |
|
41 | 41 | ("dwXSize", DWORD), |
|
42 | 42 | ("dwYSize", DWORD), |
|
43 | 43 | ("dwXCountChars", DWORD), |
|
44 | 44 | ("dwYCountChars", DWORD), |
|
45 | 45 | ("dwFillAttribute", DWORD), |
|
46 | 46 | ("dwFlags", DWORD), |
|
47 | 47 | ("wShowWindow", WORD), |
|
48 | 48 | ("cbReserved2", WORD), |
|
49 | 49 | ("lpReserved2", LPVOID), |
|
50 | 50 | ("hStdInput", HANDLE), |
|
51 | 51 | ("hStdOutput", HANDLE), |
|
52 | 52 | ("hStdError", HANDLE)] |
|
53 | 53 | LPSTARTUPINFO = POINTER(STARTUPINFO) |
|
54 | 54 | class PROCESS_INFORMATION(ctypes.Structure): |
|
55 | 55 | _fields_ = [("hProcess", HANDLE), |
|
56 | 56 | ("hThread", HANDLE), |
|
57 | 57 | ("dwProcessId", DWORD), |
|
58 | 58 | ("dwThreadId", DWORD)] |
|
59 | 59 | LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) |
|
60 | 60 | |
|
61 | 61 | # Win32 API constants needed |
|
62 | 62 | ERROR_HANDLE_EOF = 38 |
|
63 | 63 | ERROR_BROKEN_PIPE = 109 |
|
64 | 64 | ERROR_NO_DATA = 232 |
|
65 | 65 | HANDLE_FLAG_INHERIT = 0x0001 |
|
66 | 66 | STARTF_USESTDHANDLES = 0x0100 |
|
67 | 67 | CREATE_SUSPENDED = 0x0004 |
|
68 | 68 | CREATE_NEW_CONSOLE = 0x0010 |
|
69 | 69 | CREATE_NO_WINDOW = 0x08000000 |
|
70 | 70 | STILL_ACTIVE = 259 |
|
71 | 71 | WAIT_TIMEOUT = 0x0102 |
|
72 | 72 | WAIT_FAILED = 0xFFFFFFFF |
|
73 | 73 | INFINITE = 0xFFFFFFFF |
|
74 | 74 | DUPLICATE_SAME_ACCESS = 0x00000002 |
|
75 | 75 | ENABLE_ECHO_INPUT = 0x0004 |
|
76 | 76 | ENABLE_LINE_INPUT = 0x0002 |
|
77 | 77 | ENABLE_PROCESSED_INPUT = 0x0001 |
|
78 | 78 | |
|
79 | 79 | # Win32 API functions needed |
|
80 | 80 | GetLastError = ctypes.windll.kernel32.GetLastError |
|
81 | 81 | GetLastError.argtypes = [] |
|
82 | 82 | GetLastError.restype = DWORD |
|
83 | 83 | |
|
84 | 84 | CreateFile = ctypes.windll.kernel32.CreateFileW |
|
85 | 85 | CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE] |
|
86 | 86 | CreateFile.restype = HANDLE |
|
87 | 87 | |
|
88 | 88 | CreatePipe = ctypes.windll.kernel32.CreatePipe |
|
89 | 89 | CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE), |
|
90 | 90 | LPSECURITY_ATTRIBUTES, DWORD] |
|
91 | 91 | CreatePipe.restype = BOOL |
|
92 | 92 | |
|
93 | 93 | CreateProcess = ctypes.windll.kernel32.CreateProcessW |
|
94 | 94 | CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES, |
|
95 | 95 | LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO, |
|
96 | 96 | LPPROCESS_INFORMATION] |
|
97 | 97 | CreateProcess.restype = BOOL |
|
98 | 98 | |
|
99 | 99 | GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess |
|
100 | 100 | GetExitCodeProcess.argtypes = [HANDLE, LPDWORD] |
|
101 | 101 | GetExitCodeProcess.restype = BOOL |
|
102 | 102 | |
|
103 | 103 | GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess |
|
104 | 104 | GetCurrentProcess.argtypes = [] |
|
105 | 105 | GetCurrentProcess.restype = HANDLE |
|
106 | 106 | |
|
107 | 107 | ResumeThread = ctypes.windll.kernel32.ResumeThread |
|
108 | 108 | ResumeThread.argtypes = [HANDLE] |
|
109 | 109 | ResumeThread.restype = DWORD |
|
110 | 110 | |
|
111 | 111 | ReadFile = ctypes.windll.kernel32.ReadFile |
|
112 | 112 | ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] |
|
113 | 113 | ReadFile.restype = BOOL |
|
114 | 114 | |
|
115 | 115 | WriteFile = ctypes.windll.kernel32.WriteFile |
|
116 | 116 | WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] |
|
117 | 117 | WriteFile.restype = BOOL |
|
118 | 118 | |
|
119 | 119 | GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode |
|
120 | 120 | GetConsoleMode.argtypes = [HANDLE, LPDWORD] |
|
121 | 121 | GetConsoleMode.restype = BOOL |
|
122 | 122 | |
|
123 | 123 | SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode |
|
124 | 124 | SetConsoleMode.argtypes = [HANDLE, DWORD] |
|
125 | 125 | SetConsoleMode.restype = BOOL |
|
126 | 126 | |
|
127 | 127 | FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer |
|
128 | 128 | FlushConsoleInputBuffer.argtypes = [HANDLE] |
|
129 | 129 | FlushConsoleInputBuffer.restype = BOOL |
|
130 | 130 | |
|
131 | 131 | WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject |
|
132 | 132 | WaitForSingleObject.argtypes = [HANDLE, DWORD] |
|
133 | 133 | WaitForSingleObject.restype = DWORD |
|
134 | 134 | |
|
135 | 135 | DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle |
|
136 | 136 | DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE, |
|
137 | 137 | DWORD, BOOL, DWORD] |
|
138 | 138 | DuplicateHandle.restype = BOOL |
|
139 | 139 | |
|
140 | 140 | SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation |
|
141 | 141 | SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD] |
|
142 | 142 | SetHandleInformation.restype = BOOL |
|
143 | 143 | |
|
144 | 144 | CloseHandle = ctypes.windll.kernel32.CloseHandle |
|
145 | 145 | CloseHandle.argtypes = [HANDLE] |
|
146 | 146 | CloseHandle.restype = BOOL |
|
147 | 147 | |
|
148 | 148 | CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW |
|
149 | 149 | CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)] |
|
150 | 150 | CommandLineToArgvW.restype = POINTER(LPCWSTR) |
|
151 | 151 | |
|
152 | 152 | LocalFree = ctypes.windll.kernel32.LocalFree |
|
153 | 153 | LocalFree.argtypes = [HLOCAL] |
|
154 | 154 | LocalFree.restype = HLOCAL |
|
155 | 155 | |
|
156 | 156 | class AvoidUNCPath(object): |
|
157 | 157 | """A context manager to protect command execution from UNC paths. |
|
158 | 158 | |
|
159 | 159 | In the Win32 API, commands can't be invoked with the cwd being a UNC path. |
|
160 | 160 | This context manager temporarily changes directory to the 'C:' drive on |
|
161 | 161 | entering, and restores the original working directory on exit. |
|
162 | 162 | |
|
163 | 163 | The context manager returns the starting working directory *if* it made a |
|
164 | 164 | change and None otherwise, so that users can apply the necessary adjustment |
|
165 | 165 | to their system calls in the event of a change. |
|
166 | 166 | |
|
167 | 167 | Examples |
|
168 | 168 | -------- |
|
169 | 169 | :: |
|
170 | 170 | cmd = 'dir' |
|
171 | 171 | with AvoidUNCPath() as path: |
|
172 | 172 | if path is not None: |
|
173 | 173 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
174 | 174 | os.system(cmd) |
|
175 | 175 | """ |
|
176 | 176 | def __enter__(self): |
|
177 |
self.path = |
|
|
177 | self.path = py3compat.getcwd() | |
|
178 | 178 | self.is_unc_path = self.path.startswith(r"\\") |
|
179 | 179 | if self.is_unc_path: |
|
180 | 180 | # change to c drive (as cmd.exe cannot handle UNC addresses) |
|
181 | 181 | os.chdir("C:") |
|
182 | 182 | return self.path |
|
183 | 183 | else: |
|
184 | 184 | # We return None to signal that there was no change in the working |
|
185 | 185 | # directory |
|
186 | 186 | return None |
|
187 | 187 | |
|
188 | 188 | def __exit__(self, exc_type, exc_value, traceback): |
|
189 | 189 | if self.is_unc_path: |
|
190 | 190 | os.chdir(self.path) |
|
191 | 191 | |
|
192 | 192 | |
|
193 | 193 | class Win32ShellCommandController(object): |
|
194 | 194 | """Runs a shell command in a 'with' context. |
|
195 | 195 | |
|
196 | 196 | This implementation is Win32-specific. |
|
197 | 197 | |
|
198 | 198 | Example: |
|
199 | 199 | # Runs the command interactively with default console stdin/stdout |
|
200 | 200 | with ShellCommandController('python -i') as scc: |
|
201 | 201 | scc.run() |
|
202 | 202 | |
|
203 | 203 | # Runs the command using the provided functions for stdin/stdout |
|
204 | 204 | def my_stdout_func(s): |
|
205 | 205 | # print or save the string 's' |
|
206 | 206 | write_to_stdout(s) |
|
207 | 207 | def my_stdin_func(): |
|
208 | 208 | # If input is available, return it as a string. |
|
209 | 209 | if input_available(): |
|
210 | 210 | return get_input() |
|
211 | 211 | # If no input available, return None after a short delay to |
|
212 | 212 | # keep from blocking. |
|
213 | 213 | else: |
|
214 | 214 | time.sleep(0.01) |
|
215 | 215 | return None |
|
216 | 216 | |
|
217 | 217 | with ShellCommandController('python -i') as scc: |
|
218 | 218 | scc.run(my_stdout_func, my_stdin_func) |
|
219 | 219 | """ |
|
220 | 220 | |
|
221 | 221 | def __init__(self, cmd, mergeout = True): |
|
222 | 222 | """Initializes the shell command controller. |
|
223 | 223 | |
|
224 | 224 | The cmd is the program to execute, and mergeout is |
|
225 | 225 | whether to blend stdout and stderr into one output |
|
226 | 226 | in stdout. Merging them together in this fashion more |
|
227 | 227 | reliably keeps stdout and stderr in the correct order |
|
228 | 228 | especially for interactive shell usage. |
|
229 | 229 | """ |
|
230 | 230 | self.cmd = cmd |
|
231 | 231 | self.mergeout = mergeout |
|
232 | 232 | |
|
233 | 233 | def __enter__(self): |
|
234 | 234 | cmd = self.cmd |
|
235 | 235 | mergeout = self.mergeout |
|
236 | 236 | |
|
237 | 237 | self.hstdout, self.hstdin, self.hstderr = None, None, None |
|
238 | 238 | self.piProcInfo = None |
|
239 | 239 | try: |
|
240 | 240 | p_hstdout, c_hstdout, p_hstderr, \ |
|
241 | 241 | c_hstderr, p_hstdin, c_hstdin = [None]*6 |
|
242 | 242 | |
|
243 | 243 | # SECURITY_ATTRIBUTES with inherit handle set to True |
|
244 | 244 | saAttr = SECURITY_ATTRIBUTES() |
|
245 | 245 | saAttr.nLength = ctypes.sizeof(saAttr) |
|
246 | 246 | saAttr.bInheritHandle = True |
|
247 | 247 | saAttr.lpSecurityDescriptor = None |
|
248 | 248 | |
|
249 | 249 | def create_pipe(uninherit): |
|
250 | 250 | """Creates a Windows pipe, which consists of two handles. |
|
251 | 251 | |
|
252 | 252 | The 'uninherit' parameter controls which handle is not |
|
253 | 253 | inherited by the child process. |
|
254 | 254 | """ |
|
255 | 255 | handles = HANDLE(), HANDLE() |
|
256 | 256 | if not CreatePipe(ctypes.byref(handles[0]), |
|
257 | 257 | ctypes.byref(handles[1]), ctypes.byref(saAttr), 0): |
|
258 | 258 | raise ctypes.WinError() |
|
259 | 259 | if not SetHandleInformation(handles[uninherit], |
|
260 | 260 | HANDLE_FLAG_INHERIT, 0): |
|
261 | 261 | raise ctypes.WinError() |
|
262 | 262 | return handles[0].value, handles[1].value |
|
263 | 263 | |
|
264 | 264 | p_hstdout, c_hstdout = create_pipe(uninherit=0) |
|
265 | 265 | # 'mergeout' signals that stdout and stderr should be merged. |
|
266 | 266 | # We do that by using one pipe for both of them. |
|
267 | 267 | if mergeout: |
|
268 | 268 | c_hstderr = HANDLE() |
|
269 | 269 | if not DuplicateHandle(GetCurrentProcess(), c_hstdout, |
|
270 | 270 | GetCurrentProcess(), ctypes.byref(c_hstderr), |
|
271 | 271 | 0, True, DUPLICATE_SAME_ACCESS): |
|
272 | 272 | raise ctypes.WinError() |
|
273 | 273 | else: |
|
274 | 274 | p_hstderr, c_hstderr = create_pipe(uninherit=0) |
|
275 | 275 | c_hstdin, p_hstdin = create_pipe(uninherit=1) |
|
276 | 276 | |
|
277 | 277 | # Create the process object |
|
278 | 278 | piProcInfo = PROCESS_INFORMATION() |
|
279 | 279 | siStartInfo = STARTUPINFO() |
|
280 | 280 | siStartInfo.cb = ctypes.sizeof(siStartInfo) |
|
281 | 281 | siStartInfo.hStdInput = c_hstdin |
|
282 | 282 | siStartInfo.hStdOutput = c_hstdout |
|
283 | 283 | siStartInfo.hStdError = c_hstderr |
|
284 | 284 | siStartInfo.dwFlags = STARTF_USESTDHANDLES |
|
285 | 285 | dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE |
|
286 | 286 | |
|
287 | 287 | if not CreateProcess(None, |
|
288 | 288 | u"cmd.exe /c " + cmd, |
|
289 | 289 | None, None, True, dwCreationFlags, |
|
290 | 290 | None, None, ctypes.byref(siStartInfo), |
|
291 | 291 | ctypes.byref(piProcInfo)): |
|
292 | 292 | raise ctypes.WinError() |
|
293 | 293 | |
|
294 | 294 | # Close this process's versions of the child handles |
|
295 | 295 | CloseHandle(c_hstdin) |
|
296 | 296 | c_hstdin = None |
|
297 | 297 | CloseHandle(c_hstdout) |
|
298 | 298 | c_hstdout = None |
|
299 | 299 | if c_hstderr != None: |
|
300 | 300 | CloseHandle(c_hstderr) |
|
301 | 301 | c_hstderr = None |
|
302 | 302 | |
|
303 | 303 | # Transfer ownership of the parent handles to the object |
|
304 | 304 | self.hstdin = p_hstdin |
|
305 | 305 | p_hstdin = None |
|
306 | 306 | self.hstdout = p_hstdout |
|
307 | 307 | p_hstdout = None |
|
308 | 308 | if not mergeout: |
|
309 | 309 | self.hstderr = p_hstderr |
|
310 | 310 | p_hstderr = None |
|
311 | 311 | self.piProcInfo = piProcInfo |
|
312 | 312 | |
|
313 | 313 | finally: |
|
314 | 314 | if p_hstdin: |
|
315 | 315 | CloseHandle(p_hstdin) |
|
316 | 316 | if c_hstdin: |
|
317 | 317 | CloseHandle(c_hstdin) |
|
318 | 318 | if p_hstdout: |
|
319 | 319 | CloseHandle(p_hstdout) |
|
320 | 320 | if c_hstdout: |
|
321 | 321 | CloseHandle(c_hstdout) |
|
322 | 322 | if p_hstderr: |
|
323 | 323 | CloseHandle(p_hstderr) |
|
324 | 324 | if c_hstderr: |
|
325 | 325 | CloseHandle(c_hstderr) |
|
326 | 326 | |
|
327 | 327 | return self |
|
328 | 328 | |
|
329 | 329 | def _stdin_thread(self, handle, hprocess, func, stdout_func): |
|
330 | 330 | exitCode = DWORD() |
|
331 | 331 | bytesWritten = DWORD(0) |
|
332 | 332 | while True: |
|
333 | 333 | #print("stdin thread loop start") |
|
334 | 334 | # Get the input string (may be bytes or unicode) |
|
335 | 335 | data = func() |
|
336 | 336 | |
|
337 | 337 | # None signals to poll whether the process has exited |
|
338 | 338 | if data is None: |
|
339 | 339 | #print("checking for process completion") |
|
340 | 340 | if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)): |
|
341 | 341 | raise ctypes.WinError() |
|
342 | 342 | if exitCode.value != STILL_ACTIVE: |
|
343 | 343 | return |
|
344 | 344 | # TESTING: Does zero-sized writefile help? |
|
345 | 345 | if not WriteFile(handle, "", 0, |
|
346 | 346 | ctypes.byref(bytesWritten), None): |
|
347 | 347 | raise ctypes.WinError() |
|
348 | 348 | continue |
|
349 | 349 | #print("\nGot str %s\n" % repr(data), file=sys.stderr) |
|
350 | 350 | |
|
351 | 351 | # Encode the string to the console encoding |
|
352 | 352 | if isinstance(data, unicode): #FIXME: Python3 |
|
353 | 353 | data = data.encode('utf_8') |
|
354 | 354 | |
|
355 | 355 | # What we have now must be a string of bytes |
|
356 | 356 | if not isinstance(data, str): #FIXME: Python3 |
|
357 | 357 | raise RuntimeError("internal stdin function string error") |
|
358 | 358 | |
|
359 | 359 | # An empty string signals EOF |
|
360 | 360 | if len(data) == 0: |
|
361 | 361 | return |
|
362 | 362 | |
|
363 | 363 | # In a windows console, sometimes the input is echoed, |
|
364 | 364 | # but sometimes not. How do we determine when to do this? |
|
365 | 365 | stdout_func(data) |
|
366 | 366 | # WriteFile may not accept all the data at once. |
|
367 | 367 | # Loop until everything is processed |
|
368 | 368 | while len(data) != 0: |
|
369 | 369 | #print("Calling writefile") |
|
370 | 370 | if not WriteFile(handle, data, len(data), |
|
371 | 371 | ctypes.byref(bytesWritten), None): |
|
372 | 372 | # This occurs at exit |
|
373 | 373 | if GetLastError() == ERROR_NO_DATA: |
|
374 | 374 | return |
|
375 | 375 | raise ctypes.WinError() |
|
376 | 376 | #print("Called writefile") |
|
377 | 377 | data = data[bytesWritten.value:] |
|
378 | 378 | |
|
379 | 379 | def _stdout_thread(self, handle, func): |
|
380 | 380 | # Allocate the output buffer |
|
381 | 381 | data = ctypes.create_string_buffer(4096) |
|
382 | 382 | while True: |
|
383 | 383 | bytesRead = DWORD(0) |
|
384 | 384 | if not ReadFile(handle, data, 4096, |
|
385 | 385 | ctypes.byref(bytesRead), None): |
|
386 | 386 | le = GetLastError() |
|
387 | 387 | if le == ERROR_BROKEN_PIPE: |
|
388 | 388 | return |
|
389 | 389 | else: |
|
390 | 390 | raise ctypes.WinError() |
|
391 | 391 | # FIXME: Python3 |
|
392 | 392 | s = data.value[0:bytesRead.value] |
|
393 | 393 | #print("\nv: %s" % repr(s), file=sys.stderr) |
|
394 | 394 | func(s.decode('utf_8', 'replace')) |
|
395 | 395 | |
|
396 | 396 | def run(self, stdout_func = None, stdin_func = None, stderr_func = None): |
|
397 | 397 | """Runs the process, using the provided functions for I/O. |
|
398 | 398 | |
|
399 | 399 | The function stdin_func should return strings whenever a |
|
400 | 400 | character or characters become available. |
|
401 | 401 | The functions stdout_func and stderr_func are called whenever |
|
402 | 402 | something is printed to stdout or stderr, respectively. |
|
403 | 403 | These functions are called from different threads (but not |
|
404 | 404 | concurrently, because of the GIL). |
|
405 | 405 | """ |
|
406 | 406 | if stdout_func == None and stdin_func == None and stderr_func == None: |
|
407 | 407 | return self._run_stdio() |
|
408 | 408 | |
|
409 | 409 | if stderr_func != None and self.mergeout: |
|
410 | 410 | raise RuntimeError("Shell command was initiated with " |
|
411 | 411 | "merged stdin/stdout, but a separate stderr_func " |
|
412 | 412 | "was provided to the run() method") |
|
413 | 413 | |
|
414 | 414 | # Create a thread for each input/output handle |
|
415 | 415 | stdin_thread = None |
|
416 | 416 | threads = [] |
|
417 | 417 | if stdin_func: |
|
418 | 418 | stdin_thread = threading.Thread(target=self._stdin_thread, |
|
419 | 419 | args=(self.hstdin, self.piProcInfo.hProcess, |
|
420 | 420 | stdin_func, stdout_func)) |
|
421 | 421 | threads.append(threading.Thread(target=self._stdout_thread, |
|
422 | 422 | args=(self.hstdout, stdout_func))) |
|
423 | 423 | if not self.mergeout: |
|
424 | 424 | if stderr_func == None: |
|
425 | 425 | stderr_func = stdout_func |
|
426 | 426 | threads.append(threading.Thread(target=self._stdout_thread, |
|
427 | 427 | args=(self.hstderr, stderr_func))) |
|
428 | 428 | # Start the I/O threads and the process |
|
429 | 429 | if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF: |
|
430 | 430 | raise ctypes.WinError() |
|
431 | 431 | if stdin_thread is not None: |
|
432 | 432 | stdin_thread.start() |
|
433 | 433 | for thread in threads: |
|
434 | 434 | thread.start() |
|
435 | 435 | # Wait for the process to complete |
|
436 | 436 | if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \ |
|
437 | 437 | WAIT_FAILED: |
|
438 | 438 | raise ctypes.WinError() |
|
439 | 439 | # Wait for the I/O threads to complete |
|
440 | 440 | for thread in threads: |
|
441 | 441 | thread.join() |
|
442 | 442 | |
|
443 | 443 | # Wait for the stdin thread to complete |
|
444 | 444 | if stdin_thread is not None: |
|
445 | 445 | stdin_thread.join() |
|
446 | 446 | |
|
447 | 447 | def _stdin_raw_nonblock(self): |
|
448 | 448 | """Use the raw Win32 handle of sys.stdin to do non-blocking reads""" |
|
449 | 449 | # WARNING: This is experimental, and produces inconsistent results. |
|
450 | 450 | # It's possible for the handle not to be appropriate for use |
|
451 | 451 | # with WaitForSingleObject, among other things. |
|
452 | 452 | handle = msvcrt.get_osfhandle(sys.stdin.fileno()) |
|
453 | 453 | result = WaitForSingleObject(handle, 100) |
|
454 | 454 | if result == WAIT_FAILED: |
|
455 | 455 | raise ctypes.WinError() |
|
456 | 456 | elif result == WAIT_TIMEOUT: |
|
457 | 457 | print(".", end='') |
|
458 | 458 | return None |
|
459 | 459 | else: |
|
460 | 460 | data = ctypes.create_string_buffer(256) |
|
461 | 461 | bytesRead = DWORD(0) |
|
462 | 462 | print('?', end='') |
|
463 | 463 | |
|
464 | 464 | if not ReadFile(handle, data, 256, |
|
465 | 465 | ctypes.byref(bytesRead), None): |
|
466 | 466 | raise ctypes.WinError() |
|
467 | 467 | # This ensures the non-blocking works with an actual console |
|
468 | 468 | # Not checking the error, so the processing will still work with |
|
469 | 469 | # other handle types |
|
470 | 470 | FlushConsoleInputBuffer(handle) |
|
471 | 471 | |
|
472 | 472 | data = data.value |
|
473 | 473 | data = data.replace('\r\n', '\n') |
|
474 | 474 | data = data.replace('\r', '\n') |
|
475 | 475 | print(repr(data) + " ", end='') |
|
476 | 476 | return data |
|
477 | 477 | |
|
478 | 478 | def _stdin_raw_block(self): |
|
479 | 479 | """Use a blocking stdin read""" |
|
480 | 480 | # The big problem with the blocking read is that it doesn't |
|
481 | 481 | # exit when it's supposed to in all contexts. An extra |
|
482 | 482 | # key-press may be required to trigger the exit. |
|
483 | 483 | try: |
|
484 | 484 | data = sys.stdin.read(1) |
|
485 | 485 | data = data.replace('\r', '\n') |
|
486 | 486 | return data |
|
487 | 487 | except WindowsError as we: |
|
488 | 488 | if we.winerror == ERROR_NO_DATA: |
|
489 | 489 | # This error occurs when the pipe is closed |
|
490 | 490 | return None |
|
491 | 491 | else: |
|
492 | 492 | # Otherwise let the error propagate |
|
493 | 493 | raise we |
|
494 | 494 | |
|
495 | 495 | def _stdout_raw(self, s): |
|
496 | 496 | """Writes the string to stdout""" |
|
497 | 497 | print(s, end='', file=sys.stdout) |
|
498 | 498 | sys.stdout.flush() |
|
499 | 499 | |
|
500 | 500 | def _stderr_raw(self, s): |
|
501 | 501 | """Writes the string to stdout""" |
|
502 | 502 | print(s, end='', file=sys.stderr) |
|
503 | 503 | sys.stderr.flush() |
|
504 | 504 | |
|
505 | 505 | def _run_stdio(self): |
|
506 | 506 | """Runs the process using the system standard I/O. |
|
507 | 507 | |
|
508 | 508 | IMPORTANT: stdin needs to be asynchronous, so the Python |
|
509 | 509 | sys.stdin object is not used. Instead, |
|
510 | 510 | msvcrt.kbhit/getwch are used asynchronously. |
|
511 | 511 | """ |
|
512 | 512 | # Disable Line and Echo mode |
|
513 | 513 | #lpMode = DWORD() |
|
514 | 514 | #handle = msvcrt.get_osfhandle(sys.stdin.fileno()) |
|
515 | 515 | #if GetConsoleMode(handle, ctypes.byref(lpMode)): |
|
516 | 516 | # set_console_mode = True |
|
517 | 517 | # if not SetConsoleMode(handle, lpMode.value & |
|
518 | 518 | # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)): |
|
519 | 519 | # raise ctypes.WinError() |
|
520 | 520 | |
|
521 | 521 | if self.mergeout: |
|
522 | 522 | return self.run(stdout_func = self._stdout_raw, |
|
523 | 523 | stdin_func = self._stdin_raw_block) |
|
524 | 524 | else: |
|
525 | 525 | return self.run(stdout_func = self._stdout_raw, |
|
526 | 526 | stdin_func = self._stdin_raw_block, |
|
527 | 527 | stderr_func = self._stderr_raw) |
|
528 | 528 | |
|
529 | 529 | # Restore the previous console mode |
|
530 | 530 | #if set_console_mode: |
|
531 | 531 | # if not SetConsoleMode(handle, lpMode.value): |
|
532 | 532 | # raise ctypes.WinError() |
|
533 | 533 | |
|
534 | 534 | def __exit__(self, exc_type, exc_value, traceback): |
|
535 | 535 | if self.hstdin: |
|
536 | 536 | CloseHandle(self.hstdin) |
|
537 | 537 | self.hstdin = None |
|
538 | 538 | if self.hstdout: |
|
539 | 539 | CloseHandle(self.hstdout) |
|
540 | 540 | self.hstdout = None |
|
541 | 541 | if self.hstderr: |
|
542 | 542 | CloseHandle(self.hstderr) |
|
543 | 543 | self.hstderr = None |
|
544 | 544 | if self.piProcInfo != None: |
|
545 | 545 | CloseHandle(self.piProcInfo.hProcess) |
|
546 | 546 | CloseHandle(self.piProcInfo.hThread) |
|
547 | 547 | self.piProcInfo = None |
|
548 | 548 | |
|
549 | 549 | |
|
550 | 550 | def system(cmd): |
|
551 | 551 | """Win32 version of os.system() that works with network shares. |
|
552 | 552 | |
|
553 | 553 | Note that this implementation returns None, as meant for use in IPython. |
|
554 | 554 | |
|
555 | 555 | Parameters |
|
556 | 556 | ---------- |
|
557 | 557 | cmd : str |
|
558 | 558 | A command to be executed in the system shell. |
|
559 | 559 | |
|
560 | 560 | Returns |
|
561 | 561 | ------- |
|
562 | 562 | None : we explicitly do NOT return the subprocess status code, as this |
|
563 | 563 | utility is meant to be used extensively in IPython, where any return value |
|
564 | 564 | would trigger :func:`sys.displayhook` calls. |
|
565 | 565 | """ |
|
566 | 566 | with AvoidUNCPath() as path: |
|
567 | 567 | if path is not None: |
|
568 | 568 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
569 | 569 | with Win32ShellCommandController(cmd) as scc: |
|
570 | 570 | scc.run() |
|
571 | 571 | |
|
572 | 572 | |
|
573 | 573 | if __name__ == "__main__": |
|
574 | 574 | print("Test starting!") |
|
575 | 575 | #system("cmd") |
|
576 | 576 | system("python -i") |
|
577 | 577 | print("Test finished!") |
@@ -1,574 +1,574 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | Utilities for path handling. |
|
4 | 4 | """ |
|
5 | 5 | |
|
6 | 6 | #----------------------------------------------------------------------------- |
|
7 | 7 | # Copyright (C) 2008-2011 The IPython Development Team |
|
8 | 8 | # |
|
9 | 9 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | 10 | # the file COPYING, distributed as part of this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | import os |
|
18 | 18 | import sys |
|
19 | 19 | import errno |
|
20 | 20 | import shutil |
|
21 | 21 | import random |
|
22 | 22 | import tempfile |
|
23 | 23 | import warnings |
|
24 | 24 | from hashlib import md5 |
|
25 | 25 | import glob |
|
26 | 26 | |
|
27 | 27 | import IPython |
|
28 | 28 | from IPython.testing.skipdoctest import skip_doctest |
|
29 | 29 | from IPython.utils.process import system |
|
30 | 30 | from IPython.utils.importstring import import_item |
|
31 | 31 | from IPython.utils import py3compat |
|
32 | 32 | #----------------------------------------------------------------------------- |
|
33 | 33 | # Code |
|
34 | 34 | #----------------------------------------------------------------------------- |
|
35 | 35 | |
|
36 | 36 | fs_encoding = sys.getfilesystemencoding() |
|
37 | 37 | |
|
38 | 38 | def _get_long_path_name(path): |
|
39 | 39 | """Dummy no-op.""" |
|
40 | 40 | return path |
|
41 | 41 | |
|
42 | 42 | def _writable_dir(path): |
|
43 | 43 | """Whether `path` is a directory, to which the user has write access.""" |
|
44 | 44 | return os.path.isdir(path) and os.access(path, os.W_OK) |
|
45 | 45 | |
|
46 | 46 | if sys.platform == 'win32': |
|
47 | 47 | @skip_doctest |
|
48 | 48 | def _get_long_path_name(path): |
|
49 | 49 | """Get a long path name (expand ~) on Windows using ctypes. |
|
50 | 50 | |
|
51 | 51 | Examples |
|
52 | 52 | -------- |
|
53 | 53 | |
|
54 | 54 | >>> get_long_path_name('c:\\docume~1') |
|
55 | 55 | u'c:\\\\Documents and Settings' |
|
56 | 56 | |
|
57 | 57 | """ |
|
58 | 58 | try: |
|
59 | 59 | import ctypes |
|
60 | 60 | except ImportError: |
|
61 | 61 | raise ImportError('you need to have ctypes installed for this to work') |
|
62 | 62 | _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW |
|
63 | 63 | _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, |
|
64 | 64 | ctypes.c_uint ] |
|
65 | 65 | |
|
66 | 66 | buf = ctypes.create_unicode_buffer(260) |
|
67 | 67 | rv = _GetLongPathName(path, buf, 260) |
|
68 | 68 | if rv == 0 or rv > 260: |
|
69 | 69 | return path |
|
70 | 70 | else: |
|
71 | 71 | return buf.value |
|
72 | 72 | |
|
73 | 73 | |
|
74 | 74 | def get_long_path_name(path): |
|
75 | 75 | """Expand a path into its long form. |
|
76 | 76 | |
|
77 | 77 | On Windows this expands any ~ in the paths. On other platforms, it is |
|
78 | 78 | a null operation. |
|
79 | 79 | """ |
|
80 | 80 | return _get_long_path_name(path) |
|
81 | 81 | |
|
82 | 82 | |
|
83 | 83 | def unquote_filename(name, win32=(sys.platform=='win32')): |
|
84 | 84 | """ On Windows, remove leading and trailing quotes from filenames. |
|
85 | 85 | """ |
|
86 | 86 | if win32: |
|
87 | 87 | if name.startswith(("'", '"')) and name.endswith(("'", '"')): |
|
88 | 88 | name = name[1:-1] |
|
89 | 89 | return name |
|
90 | 90 | |
|
91 | 91 | def compress_user(path): |
|
92 | 92 | """Reverse of :func:`os.path.expanduser` |
|
93 | 93 | """ |
|
94 | 94 | home = os.path.expanduser('~') |
|
95 | 95 | if path.startswith(home): |
|
96 | 96 | path = "~" + path[len(home):] |
|
97 | 97 | return path |
|
98 | 98 | |
|
99 | 99 | def get_py_filename(name, force_win32=None): |
|
100 | 100 | """Return a valid python filename in the current directory. |
|
101 | 101 | |
|
102 | 102 | If the given name is not a file, it adds '.py' and searches again. |
|
103 | 103 | Raises IOError with an informative message if the file isn't found. |
|
104 | 104 | |
|
105 | 105 | On Windows, apply Windows semantics to the filename. In particular, remove |
|
106 | 106 | any quoting that has been applied to it. This option can be forced for |
|
107 | 107 | testing purposes. |
|
108 | 108 | """ |
|
109 | 109 | |
|
110 | 110 | name = os.path.expanduser(name) |
|
111 | 111 | if force_win32 is None: |
|
112 | 112 | win32 = (sys.platform == 'win32') |
|
113 | 113 | else: |
|
114 | 114 | win32 = force_win32 |
|
115 | 115 | name = unquote_filename(name, win32=win32) |
|
116 | 116 | if not os.path.isfile(name) and not name.endswith('.py'): |
|
117 | 117 | name += '.py' |
|
118 | 118 | if os.path.isfile(name): |
|
119 | 119 | return name |
|
120 | 120 | else: |
|
121 | 121 | raise IOError('File `%r` not found.' % name) |
|
122 | 122 | |
|
123 | 123 | |
|
124 | 124 | def filefind(filename, path_dirs=None): |
|
125 | 125 | """Find a file by looking through a sequence of paths. |
|
126 | 126 | |
|
127 | 127 | This iterates through a sequence of paths looking for a file and returns |
|
128 | 128 | the full, absolute path of the first occurence of the file. If no set of |
|
129 | 129 | path dirs is given, the filename is tested as is, after running through |
|
130 | 130 | :func:`expandvars` and :func:`expanduser`. Thus a simple call:: |
|
131 | 131 | |
|
132 | 132 | filefind('myfile.txt') |
|
133 | 133 | |
|
134 | 134 | will find the file in the current working dir, but:: |
|
135 | 135 | |
|
136 | 136 | filefind('~/myfile.txt') |
|
137 | 137 | |
|
138 | 138 | Will find the file in the users home directory. This function does not |
|
139 | 139 | automatically try any paths, such as the cwd or the user's home directory. |
|
140 | 140 | |
|
141 | 141 | Parameters |
|
142 | 142 | ---------- |
|
143 | 143 | filename : str |
|
144 | 144 | The filename to look for. |
|
145 | 145 | path_dirs : str, None or sequence of str |
|
146 | 146 | The sequence of paths to look for the file in. If None, the filename |
|
147 | 147 | need to be absolute or be in the cwd. If a string, the string is |
|
148 | 148 | put into a sequence and the searched. If a sequence, walk through |
|
149 | 149 | each element and join with ``filename``, calling :func:`expandvars` |
|
150 | 150 | and :func:`expanduser` before testing for existence. |
|
151 | 151 | |
|
152 | 152 | Returns |
|
153 | 153 | ------- |
|
154 | 154 | Raises :exc:`IOError` or returns absolute path to file. |
|
155 | 155 | """ |
|
156 | 156 | |
|
157 | 157 | # If paths are quoted, abspath gets confused, strip them... |
|
158 | 158 | filename = filename.strip('"').strip("'") |
|
159 | 159 | # If the input is an absolute path, just check it exists |
|
160 | 160 | if os.path.isabs(filename) and os.path.isfile(filename): |
|
161 | 161 | return filename |
|
162 | 162 | |
|
163 | 163 | if path_dirs is None: |
|
164 | 164 | path_dirs = ("",) |
|
165 | 165 | elif isinstance(path_dirs, py3compat.string_types): |
|
166 | 166 | path_dirs = (path_dirs,) |
|
167 | 167 | |
|
168 | 168 | for path in path_dirs: |
|
169 |
if path == '.': path = |
|
|
169 | if path == '.': path = py3compat.getcwd() | |
|
170 | 170 | testname = expand_path(os.path.join(path, filename)) |
|
171 | 171 | if os.path.isfile(testname): |
|
172 | 172 | return os.path.abspath(testname) |
|
173 | 173 | |
|
174 | 174 | raise IOError("File %r does not exist in any of the search paths: %r" % |
|
175 | 175 | (filename, path_dirs) ) |
|
176 | 176 | |
|
177 | 177 | |
|
178 | 178 | class HomeDirError(Exception): |
|
179 | 179 | pass |
|
180 | 180 | |
|
181 | 181 | |
|
182 | 182 | def get_home_dir(require_writable=False): |
|
183 | 183 | """Return the 'home' directory, as a unicode string. |
|
184 | 184 | |
|
185 | 185 | Uses os.path.expanduser('~'), and checks for writability. |
|
186 | 186 | |
|
187 | 187 | See stdlib docs for how this is determined. |
|
188 | 188 | $HOME is first priority on *ALL* platforms. |
|
189 | 189 | |
|
190 | 190 | Parameters |
|
191 | 191 | ---------- |
|
192 | 192 | |
|
193 | 193 | require_writable : bool [default: False] |
|
194 | 194 | if True: |
|
195 | 195 | guarantees the return value is a writable directory, otherwise |
|
196 | 196 | raises HomeDirError |
|
197 | 197 | if False: |
|
198 | 198 | The path is resolved, but it is not guaranteed to exist or be writable. |
|
199 | 199 | """ |
|
200 | 200 | |
|
201 | 201 | homedir = os.path.expanduser('~') |
|
202 | 202 | # Next line will make things work even when /home/ is a symlink to |
|
203 | 203 | # /usr/home as it is on FreeBSD, for example |
|
204 | 204 | homedir = os.path.realpath(homedir) |
|
205 | 205 | |
|
206 | 206 | if not _writable_dir(homedir) and os.name == 'nt': |
|
207 | 207 | # expanduser failed, use the registry to get the 'My Documents' folder. |
|
208 | 208 | try: |
|
209 | 209 | try: |
|
210 | 210 | import winreg as wreg # Py 3 |
|
211 | 211 | except ImportError: |
|
212 | 212 | import _winreg as wreg # Py 2 |
|
213 | 213 | key = wreg.OpenKey( |
|
214 | 214 | wreg.HKEY_CURRENT_USER, |
|
215 | 215 | "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" |
|
216 | 216 | ) |
|
217 | 217 | homedir = wreg.QueryValueEx(key,'Personal')[0] |
|
218 | 218 | key.Close() |
|
219 | 219 | except: |
|
220 | 220 | pass |
|
221 | 221 | |
|
222 | 222 | if (not require_writable) or _writable_dir(homedir): |
|
223 | 223 | return py3compat.cast_unicode(homedir, fs_encoding) |
|
224 | 224 | else: |
|
225 | 225 | raise HomeDirError('%s is not a writable dir, ' |
|
226 | 226 | 'set $HOME environment variable to override' % homedir) |
|
227 | 227 | |
|
228 | 228 | def get_xdg_dir(): |
|
229 | 229 | """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. |
|
230 | 230 | |
|
231 | 231 | This is only for non-OS X posix (Linux,Unix,etc.) systems. |
|
232 | 232 | """ |
|
233 | 233 | |
|
234 | 234 | env = os.environ |
|
235 | 235 | |
|
236 | 236 | if os.name == 'posix' and sys.platform != 'darwin': |
|
237 | 237 | # Linux, Unix, AIX, etc. |
|
238 | 238 | # use ~/.config if empty OR not set |
|
239 | 239 | xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') |
|
240 | 240 | if xdg and _writable_dir(xdg): |
|
241 | 241 | return py3compat.cast_unicode(xdg, fs_encoding) |
|
242 | 242 | |
|
243 | 243 | return None |
|
244 | 244 | |
|
245 | 245 | |
|
246 | 246 | def get_xdg_cache_dir(): |
|
247 | 247 | """Return the XDG_CACHE_HOME, if it is defined and exists, else None. |
|
248 | 248 | |
|
249 | 249 | This is only for non-OS X posix (Linux,Unix,etc.) systems. |
|
250 | 250 | """ |
|
251 | 251 | |
|
252 | 252 | env = os.environ |
|
253 | 253 | |
|
254 | 254 | if os.name == 'posix' and sys.platform != 'darwin': |
|
255 | 255 | # Linux, Unix, AIX, etc. |
|
256 | 256 | # use ~/.cache if empty OR not set |
|
257 | 257 | xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache') |
|
258 | 258 | if xdg and _writable_dir(xdg): |
|
259 | 259 | return py3compat.cast_unicode(xdg, fs_encoding) |
|
260 | 260 | |
|
261 | 261 | return None |
|
262 | 262 | |
|
263 | 263 | |
|
264 | 264 | def get_ipython_dir(): |
|
265 | 265 | """Get the IPython directory for this platform and user. |
|
266 | 266 | |
|
267 | 267 | This uses the logic in `get_home_dir` to find the home directory |
|
268 | 268 | and then adds .ipython to the end of the path. |
|
269 | 269 | """ |
|
270 | 270 | |
|
271 | 271 | env = os.environ |
|
272 | 272 | pjoin = os.path.join |
|
273 | 273 | |
|
274 | 274 | |
|
275 | 275 | ipdir_def = '.ipython' |
|
276 | 276 | xdg_def = 'ipython' |
|
277 | 277 | |
|
278 | 278 | home_dir = get_home_dir() |
|
279 | 279 | xdg_dir = get_xdg_dir() |
|
280 | 280 | |
|
281 | 281 | # import pdb; pdb.set_trace() # dbg |
|
282 | 282 | if 'IPYTHON_DIR' in env: |
|
283 | 283 | warnings.warn('The environment variable IPYTHON_DIR is deprecated. ' |
|
284 | 284 | 'Please use IPYTHONDIR instead.') |
|
285 | 285 | ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None)) |
|
286 | 286 | if ipdir is None: |
|
287 | 287 | # not set explicitly, use XDG_CONFIG_HOME or HOME |
|
288 | 288 | home_ipdir = pjoin(home_dir, ipdir_def) |
|
289 | 289 | if xdg_dir: |
|
290 | 290 | # use XDG, as long as the user isn't already |
|
291 | 291 | # using $HOME/.ipython and *not* XDG/ipython |
|
292 | 292 | |
|
293 | 293 | xdg_ipdir = pjoin(xdg_dir, xdg_def) |
|
294 | 294 | |
|
295 | 295 | if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir): |
|
296 | 296 | ipdir = xdg_ipdir |
|
297 | 297 | |
|
298 | 298 | if ipdir is None: |
|
299 | 299 | # not using XDG |
|
300 | 300 | ipdir = home_ipdir |
|
301 | 301 | |
|
302 | 302 | ipdir = os.path.normpath(os.path.expanduser(ipdir)) |
|
303 | 303 | |
|
304 | 304 | if os.path.exists(ipdir) and not _writable_dir(ipdir): |
|
305 | 305 | # ipdir exists, but is not writable |
|
306 | 306 | warnings.warn("IPython dir '%s' is not a writable location," |
|
307 | 307 | " using a temp directory."%ipdir) |
|
308 | 308 | ipdir = tempfile.mkdtemp() |
|
309 | 309 | elif not os.path.exists(ipdir): |
|
310 | 310 | parent = os.path.dirname(ipdir) |
|
311 | 311 | if not _writable_dir(parent): |
|
312 | 312 | # ipdir does not exist and parent isn't writable |
|
313 | 313 | warnings.warn("IPython parent '%s' is not a writable location," |
|
314 | 314 | " using a temp directory."%parent) |
|
315 | 315 | ipdir = tempfile.mkdtemp() |
|
316 | 316 | |
|
317 | 317 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
318 | 318 | |
|
319 | 319 | |
|
320 | 320 | def get_ipython_cache_dir(): |
|
321 | 321 | """Get the cache directory it is created if it does not exist.""" |
|
322 | 322 | xdgdir = get_xdg_cache_dir() |
|
323 | 323 | if xdgdir is None: |
|
324 | 324 | return get_ipython_dir() |
|
325 | 325 | ipdir = os.path.join(xdgdir, "ipython") |
|
326 | 326 | if not os.path.exists(ipdir) and _writable_dir(xdgdir): |
|
327 | 327 | os.makedirs(ipdir) |
|
328 | 328 | elif not _writable_dir(xdgdir): |
|
329 | 329 | return get_ipython_dir() |
|
330 | 330 | |
|
331 | 331 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
332 | 332 | |
|
333 | 333 | |
|
334 | 334 | def get_ipython_package_dir(): |
|
335 | 335 | """Get the base directory where IPython itself is installed.""" |
|
336 | 336 | ipdir = os.path.dirname(IPython.__file__) |
|
337 | 337 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
338 | 338 | |
|
339 | 339 | |
|
340 | 340 | def get_ipython_module_path(module_str): |
|
341 | 341 | """Find the path to an IPython module in this version of IPython. |
|
342 | 342 | |
|
343 | 343 | This will always find the version of the module that is in this importable |
|
344 | 344 | IPython package. This will always return the path to the ``.py`` |
|
345 | 345 | version of the module. |
|
346 | 346 | """ |
|
347 | 347 | if module_str == 'IPython': |
|
348 | 348 | return os.path.join(get_ipython_package_dir(), '__init__.py') |
|
349 | 349 | mod = import_item(module_str) |
|
350 | 350 | the_path = mod.__file__.replace('.pyc', '.py') |
|
351 | 351 | the_path = the_path.replace('.pyo', '.py') |
|
352 | 352 | return py3compat.cast_unicode(the_path, fs_encoding) |
|
353 | 353 | |
|
354 | 354 | def locate_profile(profile='default'): |
|
355 | 355 | """Find the path to the folder associated with a given profile. |
|
356 | 356 | |
|
357 | 357 | I.e. find $IPYTHONDIR/profile_whatever. |
|
358 | 358 | """ |
|
359 | 359 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
360 | 360 | try: |
|
361 | 361 | pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) |
|
362 | 362 | except ProfileDirError: |
|
363 | 363 | # IOError makes more sense when people are expecting a path |
|
364 | 364 | raise IOError("Couldn't find profile %r" % profile) |
|
365 | 365 | return pd.location |
|
366 | 366 | |
|
367 | 367 | def expand_path(s): |
|
368 | 368 | """Expand $VARS and ~names in a string, like a shell |
|
369 | 369 | |
|
370 | 370 | :Examples: |
|
371 | 371 | |
|
372 | 372 | In [2]: os.environ['FOO']='test' |
|
373 | 373 | |
|
374 | 374 | In [3]: expand_path('variable FOO is $FOO') |
|
375 | 375 | Out[3]: 'variable FOO is test' |
|
376 | 376 | """ |
|
377 | 377 | # This is a pretty subtle hack. When expand user is given a UNC path |
|
378 | 378 | # on Windows (\\server\share$\%username%), os.path.expandvars, removes |
|
379 | 379 | # the $ to get (\\server\share\%username%). I think it considered $ |
|
380 | 380 | # alone an empty var. But, we need the $ to remains there (it indicates |
|
381 | 381 | # a hidden share). |
|
382 | 382 | if os.name=='nt': |
|
383 | 383 | s = s.replace('$\\', 'IPYTHON_TEMP') |
|
384 | 384 | s = os.path.expandvars(os.path.expanduser(s)) |
|
385 | 385 | if os.name=='nt': |
|
386 | 386 | s = s.replace('IPYTHON_TEMP', '$\\') |
|
387 | 387 | return s |
|
388 | 388 | |
|
389 | 389 | |
|
390 | 390 | def unescape_glob(string): |
|
391 | 391 | """Unescape glob pattern in `string`.""" |
|
392 | 392 | def unescape(s): |
|
393 | 393 | for pattern in '*[]!?': |
|
394 | 394 | s = s.replace(r'\{0}'.format(pattern), pattern) |
|
395 | 395 | return s |
|
396 | 396 | return '\\'.join(map(unescape, string.split('\\\\'))) |
|
397 | 397 | |
|
398 | 398 | |
|
399 | 399 | def shellglob(args): |
|
400 | 400 | """ |
|
401 | 401 | Do glob expansion for each element in `args` and return a flattened list. |
|
402 | 402 | |
|
403 | 403 | Unmatched glob pattern will remain as-is in the returned list. |
|
404 | 404 | |
|
405 | 405 | """ |
|
406 | 406 | expanded = [] |
|
407 | 407 | # Do not unescape backslash in Windows as it is interpreted as |
|
408 | 408 | # path separator: |
|
409 | 409 | unescape = unescape_glob if sys.platform != 'win32' else lambda x: x |
|
410 | 410 | for a in args: |
|
411 | 411 | expanded.extend(glob.glob(a) or [unescape(a)]) |
|
412 | 412 | return expanded |
|
413 | 413 | |
|
414 | 414 | |
|
415 | 415 | def target_outdated(target,deps): |
|
416 | 416 | """Determine whether a target is out of date. |
|
417 | 417 | |
|
418 | 418 | target_outdated(target,deps) -> 1/0 |
|
419 | 419 | |
|
420 | 420 | deps: list of filenames which MUST exist. |
|
421 | 421 | target: single filename which may or may not exist. |
|
422 | 422 | |
|
423 | 423 | If target doesn't exist or is older than any file listed in deps, return |
|
424 | 424 | true, otherwise return false. |
|
425 | 425 | """ |
|
426 | 426 | try: |
|
427 | 427 | target_time = os.path.getmtime(target) |
|
428 | 428 | except os.error: |
|
429 | 429 | return 1 |
|
430 | 430 | for dep in deps: |
|
431 | 431 | dep_time = os.path.getmtime(dep) |
|
432 | 432 | if dep_time > target_time: |
|
433 | 433 | #print "For target",target,"Dep failed:",dep # dbg |
|
434 | 434 | #print "times (dep,tar):",dep_time,target_time # dbg |
|
435 | 435 | return 1 |
|
436 | 436 | return 0 |
|
437 | 437 | |
|
438 | 438 | |
|
439 | 439 | def target_update(target,deps,cmd): |
|
440 | 440 | """Update a target with a given command given a list of dependencies. |
|
441 | 441 | |
|
442 | 442 | target_update(target,deps,cmd) -> runs cmd if target is outdated. |
|
443 | 443 | |
|
444 | 444 | This is just a wrapper around target_outdated() which calls the given |
|
445 | 445 | command if target is outdated.""" |
|
446 | 446 | |
|
447 | 447 | if target_outdated(target,deps): |
|
448 | 448 | system(cmd) |
|
449 | 449 | |
|
450 | 450 | def filehash(path): |
|
451 | 451 | """Make an MD5 hash of a file, ignoring any differences in line |
|
452 | 452 | ending characters.""" |
|
453 | 453 | with open(path, "rU") as f: |
|
454 | 454 | return md5(py3compat.str_to_bytes(f.read())).hexdigest() |
|
455 | 455 | |
|
456 | 456 | # If the config is unmodified from the default, we'll just delete it. |
|
457 | 457 | # These are consistent for 0.10.x, thankfully. We're not going to worry about |
|
458 | 458 | # older versions. |
|
459 | 459 | old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3', |
|
460 | 460 | 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'} |
|
461 | 461 | |
|
462 | 462 | def check_for_old_config(ipython_dir=None): |
|
463 | 463 | """Check for old config files, and present a warning if they exist. |
|
464 | 464 | |
|
465 | 465 | A link to the docs of the new config is included in the message. |
|
466 | 466 | |
|
467 | 467 | This should mitigate confusion with the transition to the new |
|
468 | 468 | config system in 0.11. |
|
469 | 469 | """ |
|
470 | 470 | if ipython_dir is None: |
|
471 | 471 | ipython_dir = get_ipython_dir() |
|
472 | 472 | |
|
473 | 473 | old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py'] |
|
474 | 474 | warned = False |
|
475 | 475 | for cfg in old_configs: |
|
476 | 476 | f = os.path.join(ipython_dir, cfg) |
|
477 | 477 | if os.path.exists(f): |
|
478 | 478 | if filehash(f) == old_config_md5.get(cfg, ''): |
|
479 | 479 | os.unlink(f) |
|
480 | 480 | else: |
|
481 | 481 | warnings.warn("Found old IPython config file %r (modified by user)"%f) |
|
482 | 482 | warned = True |
|
483 | 483 | |
|
484 | 484 | if warned: |
|
485 | 485 | warnings.warn(""" |
|
486 | 486 | The IPython configuration system has changed as of 0.11, and these files will |
|
487 | 487 | be ignored. See http://ipython.github.com/ipython-doc/dev/config for details |
|
488 | 488 | of the new config system. |
|
489 | 489 | To start configuring IPython, do `ipython profile create`, and edit |
|
490 | 490 | `ipython_config.py` in <ipython_dir>/profile_default. |
|
491 | 491 | If you need to leave the old config files in place for an older version of |
|
492 | 492 | IPython and want to suppress this warning message, set |
|
493 | 493 | `c.InteractiveShellApp.ignore_old_config=True` in the new config.""") |
|
494 | 494 | |
|
495 | 495 | def get_security_file(filename, profile='default'): |
|
496 | 496 | """Return the absolute path of a security file given by filename and profile |
|
497 | 497 | |
|
498 | 498 | This allows users and developers to find security files without |
|
499 | 499 | knowledge of the IPython directory structure. The search path |
|
500 | 500 | will be ['.', profile.security_dir] |
|
501 | 501 | |
|
502 | 502 | Parameters |
|
503 | 503 | ---------- |
|
504 | 504 | |
|
505 | 505 | filename : str |
|
506 | 506 | The file to be found. If it is passed as an absolute path, it will |
|
507 | 507 | simply be returned. |
|
508 | 508 | profile : str [default: 'default'] |
|
509 | 509 | The name of the profile to search. Leaving this unspecified |
|
510 | 510 | The file to be found. If it is passed as an absolute path, fname will |
|
511 | 511 | simply be returned. |
|
512 | 512 | |
|
513 | 513 | Returns |
|
514 | 514 | ------- |
|
515 | 515 | Raises :exc:`IOError` if file not found or returns absolute path to file. |
|
516 | 516 | """ |
|
517 | 517 | # import here, because profiledir also imports from utils.path |
|
518 | 518 | from IPython.core.profiledir import ProfileDir |
|
519 | 519 | try: |
|
520 | 520 | pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) |
|
521 | 521 | except Exception: |
|
522 | 522 | # will raise ProfileDirError if no such profile |
|
523 | 523 | raise IOError("Profile %r not found") |
|
524 | 524 | return filefind(filename, ['.', pd.security_dir]) |
|
525 | 525 | |
|
526 | 526 | |
|
527 | 527 | ENOLINK = 1998 |
|
528 | 528 | |
|
529 | 529 | def link(src, dst): |
|
530 | 530 | """Hard links ``src`` to ``dst``, returning 0 or errno. |
|
531 | 531 | |
|
532 | 532 | Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't |
|
533 | 533 | supported by the operating system. |
|
534 | 534 | """ |
|
535 | 535 | |
|
536 | 536 | if not hasattr(os, "link"): |
|
537 | 537 | return ENOLINK |
|
538 | 538 | link_errno = 0 |
|
539 | 539 | try: |
|
540 | 540 | os.link(src, dst) |
|
541 | 541 | except OSError as e: |
|
542 | 542 | link_errno = e.errno |
|
543 | 543 | return link_errno |
|
544 | 544 | |
|
545 | 545 | |
|
546 | 546 | def link_or_copy(src, dst): |
|
547 | 547 | """Attempts to hardlink ``src`` to ``dst``, copying if the link fails. |
|
548 | 548 | |
|
549 | 549 | Attempts to maintain the semantics of ``shutil.copy``. |
|
550 | 550 | |
|
551 | 551 | Because ``os.link`` does not overwrite files, a unique temporary file |
|
552 | 552 | will be used if the target already exists, then that file will be moved |
|
553 | 553 | into place. |
|
554 | 554 | """ |
|
555 | 555 | |
|
556 | 556 | if os.path.isdir(dst): |
|
557 | 557 | dst = os.path.join(dst, os.path.basename(src)) |
|
558 | 558 | |
|
559 | 559 | link_errno = link(src, dst) |
|
560 | 560 | if link_errno == errno.EEXIST: |
|
561 | 561 | new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), ) |
|
562 | 562 | try: |
|
563 | 563 | link_or_copy(src, new_dst) |
|
564 | 564 | except: |
|
565 | 565 | try: |
|
566 | 566 | os.remove(new_dst) |
|
567 | 567 | except OSError: |
|
568 | 568 | pass |
|
569 | 569 | raise |
|
570 | 570 | os.rename(new_dst, dst) |
|
571 | 571 | elif link_errno != 0: |
|
572 | 572 | # Either link isn't supported, or the filesystem doesn't support |
|
573 | 573 | # linking, or 'src' and 'dst' are on different filesystems. |
|
574 | 574 | shutil.copy(src, dst) |
@@ -1,122 +1,123 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | Utilities for working with external processes. |
|
4 | 4 | """ |
|
5 | 5 | |
|
6 | 6 | #----------------------------------------------------------------------------- |
|
7 | 7 | # Copyright (C) 2008-2011 The IPython Development Team |
|
8 | 8 | # |
|
9 | 9 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | 10 | # the file COPYING, distributed as part of this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | from __future__ import print_function |
|
17 | 17 | |
|
18 | 18 | # Stdlib |
|
19 | 19 | import os |
|
20 | 20 | import sys |
|
21 | 21 | import shlex |
|
22 | 22 | |
|
23 | 23 | # Our own |
|
24 | 24 | if sys.platform == 'win32': |
|
25 | 25 | from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split |
|
26 | 26 | else: |
|
27 | 27 | from ._process_posix import _find_cmd, system, getoutput, arg_split |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | from ._process_common import getoutputerror, get_output_error_code |
|
31 | from . import py3compat | |
|
31 | 32 | |
|
32 | 33 | #----------------------------------------------------------------------------- |
|
33 | 34 | # Code |
|
34 | 35 | #----------------------------------------------------------------------------- |
|
35 | 36 | |
|
36 | 37 | |
|
37 | 38 | class FindCmdError(Exception): |
|
38 | 39 | pass |
|
39 | 40 | |
|
40 | 41 | |
|
41 | 42 | def find_cmd(cmd): |
|
42 | 43 | """Find absolute path to executable cmd in a cross platform manner. |
|
43 | 44 | |
|
44 | 45 | This function tries to determine the full path to a command line program |
|
45 | 46 | using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the |
|
46 | 47 | time it will use the version that is first on the users `PATH`. |
|
47 | 48 | |
|
48 | 49 | Warning, don't use this to find IPython command line programs as there |
|
49 | 50 | is a risk you will find the wrong one. Instead find those using the |
|
50 | 51 | following code and looking for the application itself:: |
|
51 | 52 | |
|
52 | 53 | from IPython.utils.path import get_ipython_module_path |
|
53 | 54 | from IPython.utils.process import pycmd2argv |
|
54 | 55 | argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp')) |
|
55 | 56 | |
|
56 | 57 | Parameters |
|
57 | 58 | ---------- |
|
58 | 59 | cmd : str |
|
59 | 60 | The command line program to look for. |
|
60 | 61 | """ |
|
61 | 62 | try: |
|
62 | 63 | path = _find_cmd(cmd).rstrip() |
|
63 | 64 | except OSError: |
|
64 | 65 | raise FindCmdError('command could not be found: %s' % cmd) |
|
65 | 66 | # which returns empty if not found |
|
66 | 67 | if path == '': |
|
67 | 68 | raise FindCmdError('command could not be found: %s' % cmd) |
|
68 | 69 | return os.path.abspath(path) |
|
69 | 70 | |
|
70 | 71 | |
|
71 | 72 | def is_cmd_found(cmd): |
|
72 | 73 | """Check whether executable `cmd` exists or not and return a bool.""" |
|
73 | 74 | try: |
|
74 | 75 | find_cmd(cmd) |
|
75 | 76 | return True |
|
76 | 77 | except FindCmdError: |
|
77 | 78 | return False |
|
78 | 79 | |
|
79 | 80 | |
|
80 | 81 | def pycmd2argv(cmd): |
|
81 | 82 | r"""Take the path of a python command and return a list (argv-style). |
|
82 | 83 | |
|
83 | 84 | This only works on Python based command line programs and will find the |
|
84 | 85 | location of the ``python`` executable using ``sys.executable`` to make |
|
85 | 86 | sure the right version is used. |
|
86 | 87 | |
|
87 | 88 | For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe, |
|
88 | 89 | .com or .bat, and [, cmd] otherwise. |
|
89 | 90 | |
|
90 | 91 | Parameters |
|
91 | 92 | ---------- |
|
92 | 93 | cmd : string |
|
93 | 94 | The path of the command. |
|
94 | 95 | |
|
95 | 96 | Returns |
|
96 | 97 | ------- |
|
97 | 98 | argv-style list. |
|
98 | 99 | """ |
|
99 | 100 | ext = os.path.splitext(cmd)[1] |
|
100 | 101 | if ext in ['.exe', '.com', '.bat']: |
|
101 | 102 | return [cmd] |
|
102 | 103 | else: |
|
103 | 104 | return [sys.executable, cmd] |
|
104 | 105 | |
|
105 | 106 | |
|
106 | 107 | def abbrev_cwd(): |
|
107 | 108 | """ Return abbreviated version of cwd, e.g. d:mydir """ |
|
108 |
cwd = |
|
|
109 | cwd = py3compat.getcwd().replace('\\','/') | |
|
109 | 110 | drivepart = '' |
|
110 | 111 | tail = cwd |
|
111 | 112 | if sys.platform == 'win32': |
|
112 | 113 | if len(cwd) < 4: |
|
113 | 114 | return cwd |
|
114 | 115 | drivepart,tail = os.path.splitdrive(cwd) |
|
115 | 116 | |
|
116 | 117 | |
|
117 | 118 | parts = tail.split('/') |
|
118 | 119 | if len(parts) > 2: |
|
119 | 120 | tail = '/'.join(parts[-2:]) |
|
120 | 121 | |
|
121 | 122 | return (drivepart + ( |
|
122 | 123 | cwd == '/' and '/' or tail)) |
@@ -1,239 +1,242 b'' | |||
|
1 | 1 | # coding: utf-8 |
|
2 | 2 | """Compatibility tricks for Python 3. Mainly to do with unicode.""" |
|
3 | 3 | import functools |
|
4 | import os | |
|
4 | 5 | import sys |
|
5 | 6 | import re |
|
6 | 7 | import types |
|
7 | 8 | |
|
8 | 9 | from .encoding import DEFAULT_ENCODING |
|
9 | 10 | |
|
10 | 11 | orig_open = open |
|
11 | 12 | |
|
12 | 13 | def no_code(x, encoding=None): |
|
13 | 14 | return x |
|
14 | 15 | |
|
15 | 16 | def decode(s, encoding=None): |
|
16 | 17 | encoding = encoding or DEFAULT_ENCODING |
|
17 | 18 | return s.decode(encoding, "replace") |
|
18 | 19 | |
|
19 | 20 | def encode(u, encoding=None): |
|
20 | 21 | encoding = encoding or DEFAULT_ENCODING |
|
21 | 22 | return u.encode(encoding, "replace") |
|
22 | 23 | |
|
23 | 24 | |
|
24 | 25 | def cast_unicode(s, encoding=None): |
|
25 | 26 | if isinstance(s, bytes): |
|
26 | 27 | return decode(s, encoding) |
|
27 | 28 | return s |
|
28 | 29 | |
|
29 | 30 | def cast_bytes(s, encoding=None): |
|
30 | 31 | if not isinstance(s, bytes): |
|
31 | 32 | return encode(s, encoding) |
|
32 | 33 | return s |
|
33 | 34 | |
|
34 | 35 | def _modify_str_or_docstring(str_change_func): |
|
35 | 36 | @functools.wraps(str_change_func) |
|
36 | 37 | def wrapper(func_or_str): |
|
37 | 38 | if isinstance(func_or_str, string_types): |
|
38 | 39 | func = None |
|
39 | 40 | doc = func_or_str |
|
40 | 41 | else: |
|
41 | 42 | func = func_or_str |
|
42 | 43 | doc = func.__doc__ |
|
43 | 44 | |
|
44 | 45 | doc = str_change_func(doc) |
|
45 | 46 | |
|
46 | 47 | if func: |
|
47 | 48 | func.__doc__ = doc |
|
48 | 49 | return func |
|
49 | 50 | return doc |
|
50 | 51 | return wrapper |
|
51 | 52 | |
|
52 | 53 | def safe_unicode(e): |
|
53 | 54 | """unicode(e) with various fallbacks. Used for exceptions, which may not be |
|
54 | 55 | safe to call unicode() on. |
|
55 | 56 | """ |
|
56 | 57 | try: |
|
57 | 58 | return unicode_type(e) |
|
58 | 59 | except UnicodeError: |
|
59 | 60 | pass |
|
60 | 61 | |
|
61 | 62 | try: |
|
62 | 63 | return str_to_unicode(str(e)) |
|
63 | 64 | except UnicodeError: |
|
64 | 65 | pass |
|
65 | 66 | |
|
66 | 67 | try: |
|
67 | 68 | return str_to_unicode(repr(e)) |
|
68 | 69 | except UnicodeError: |
|
69 | 70 | pass |
|
70 | 71 | |
|
71 | 72 | return u'Unrecoverably corrupt evalue' |
|
72 | 73 | |
|
73 | 74 | if sys.version_info[0] >= 3: |
|
74 | 75 | PY3 = True |
|
75 | 76 | |
|
76 | 77 | input = input |
|
77 | 78 | builtin_mod_name = "builtins" |
|
78 | 79 | import builtins as builtin_mod |
|
79 | 80 | |
|
80 | 81 | str_to_unicode = no_code |
|
81 | 82 | unicode_to_str = no_code |
|
82 | 83 | str_to_bytes = encode |
|
83 | 84 | bytes_to_str = decode |
|
84 | 85 | cast_bytes_py2 = no_code |
|
85 | 86 | |
|
86 | 87 | string_types = (str,) |
|
87 | 88 | unicode_type = str |
|
88 | 89 | |
|
89 | 90 | def isidentifier(s, dotted=False): |
|
90 | 91 | if dotted: |
|
91 | 92 | return all(isidentifier(a) for a in s.split(".")) |
|
92 | 93 | return s.isidentifier() |
|
93 | 94 | |
|
94 | 95 | open = orig_open |
|
95 | 96 | xrange = range |
|
96 | 97 | def iteritems(d): return iter(d.items()) |
|
97 | 98 | def itervalues(d): return iter(d.values()) |
|
99 | getcwd = os.getcwd | |
|
98 | 100 | |
|
99 | 101 | MethodType = types.MethodType |
|
100 | 102 | |
|
101 | 103 | def execfile(fname, glob, loc=None): |
|
102 | 104 | loc = loc if (loc is not None) else glob |
|
103 | 105 | with open(fname, 'rb') as f: |
|
104 | 106 | exec(compile(f.read(), fname, 'exec'), glob, loc) |
|
105 | 107 | |
|
106 | 108 | # Refactor print statements in doctests. |
|
107 | 109 | _print_statement_re = re.compile(r"\bprint (?P<expr>.*)$", re.MULTILINE) |
|
108 | 110 | def _print_statement_sub(match): |
|
109 | 111 | expr = match.groups('expr') |
|
110 | 112 | return "print(%s)" % expr |
|
111 | 113 | |
|
112 | 114 | @_modify_str_or_docstring |
|
113 | 115 | def doctest_refactor_print(doc): |
|
114 | 116 | """Refactor 'print x' statements in a doctest to print(x) style. 2to3 |
|
115 | 117 | unfortunately doesn't pick up on our doctests. |
|
116 | 118 | |
|
117 | 119 | Can accept a string or a function, so it can be used as a decorator.""" |
|
118 | 120 | return _print_statement_re.sub(_print_statement_sub, doc) |
|
119 | 121 | |
|
120 | 122 | # Abstract u'abc' syntax: |
|
121 | 123 | @_modify_str_or_docstring |
|
122 | 124 | def u_format(s): |
|
123 | 125 | """"{u}'abc'" --> "'abc'" (Python 3) |
|
124 | 126 | |
|
125 | 127 | Accepts a string or a function, so it can be used as a decorator.""" |
|
126 | 128 | return s.format(u='') |
|
127 | 129 | |
|
128 | 130 | else: |
|
129 | 131 | PY3 = False |
|
130 | 132 | |
|
131 | 133 | input = raw_input |
|
132 | 134 | builtin_mod_name = "__builtin__" |
|
133 | 135 | import __builtin__ as builtin_mod |
|
134 | 136 | |
|
135 | 137 | str_to_unicode = decode |
|
136 | 138 | unicode_to_str = encode |
|
137 | 139 | str_to_bytes = no_code |
|
138 | 140 | bytes_to_str = no_code |
|
139 | 141 | cast_bytes_py2 = cast_bytes |
|
140 | 142 | |
|
141 | 143 | string_types = (str, unicode) |
|
142 | 144 | unicode_type = unicode |
|
143 | 145 | |
|
144 | 146 | import re |
|
145 | 147 | _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") |
|
146 | 148 | def isidentifier(s, dotted=False): |
|
147 | 149 | if dotted: |
|
148 | 150 | return all(isidentifier(a) for a in s.split(".")) |
|
149 | 151 | return bool(_name_re.match(s)) |
|
150 | 152 | |
|
151 | 153 | class open(object): |
|
152 | 154 | """Wrapper providing key part of Python 3 open() interface.""" |
|
153 | 155 | def __init__(self, fname, mode="r", encoding="utf-8"): |
|
154 | 156 | self.f = orig_open(fname, mode) |
|
155 | 157 | self.enc = encoding |
|
156 | 158 | |
|
157 | 159 | def write(self, s): |
|
158 | 160 | return self.f.write(s.encode(self.enc)) |
|
159 | 161 | |
|
160 | 162 | def read(self, size=-1): |
|
161 | 163 | return self.f.read(size).decode(self.enc) |
|
162 | 164 | |
|
163 | 165 | def close(self): |
|
164 | 166 | return self.f.close() |
|
165 | 167 | |
|
166 | 168 | def __enter__(self): |
|
167 | 169 | return self |
|
168 | 170 | |
|
169 | 171 | def __exit__(self, etype, value, traceback): |
|
170 | 172 | self.f.close() |
|
171 | 173 | |
|
172 | 174 | xrange = xrange |
|
173 | 175 | def iteritems(d): return d.iteritems() |
|
174 | 176 | def itervalues(d): return d.itervalues() |
|
177 | getcwd = os.getcwdu | |
|
175 | 178 | |
|
176 | 179 | def MethodType(func, instance): |
|
177 | 180 | return types.MethodType(func, instance, type(instance)) |
|
178 | 181 | |
|
179 | 182 | # don't override system execfile on 2.x: |
|
180 | 183 | execfile = execfile |
|
181 | 184 | |
|
182 | 185 | def doctest_refactor_print(func_or_str): |
|
183 | 186 | return func_or_str |
|
184 | 187 | |
|
185 | 188 | |
|
186 | 189 | # Abstract u'abc' syntax: |
|
187 | 190 | @_modify_str_or_docstring |
|
188 | 191 | def u_format(s): |
|
189 | 192 | """"{u}'abc'" --> "u'abc'" (Python 2) |
|
190 | 193 | |
|
191 | 194 | Accepts a string or a function, so it can be used as a decorator.""" |
|
192 | 195 | return s.format(u='u') |
|
193 | 196 | |
|
194 | 197 | if sys.platform == 'win32': |
|
195 | 198 | def execfile(fname, glob=None, loc=None): |
|
196 | 199 | loc = loc if (loc is not None) else glob |
|
197 | 200 | # The rstrip() is necessary b/c trailing whitespace in files will |
|
198 | 201 | # cause an IndentationError in Python 2.6 (this was fixed in 2.7, |
|
199 | 202 | # but we still support 2.6). See issue 1027. |
|
200 | 203 | scripttext = builtin_mod.open(fname).read().rstrip() + '\n' |
|
201 | 204 | # compile converts unicode filename to str assuming |
|
202 | 205 | # ascii. Let's do the conversion before calling compile |
|
203 | 206 | if isinstance(fname, unicode): |
|
204 | 207 | filename = unicode_to_str(fname) |
|
205 | 208 | else: |
|
206 | 209 | filename = fname |
|
207 | 210 | exec(compile(scripttext, filename, 'exec'), glob, loc) |
|
208 | 211 | else: |
|
209 | 212 | def execfile(fname, *where): |
|
210 | 213 | if isinstance(fname, unicode): |
|
211 | 214 | filename = fname.encode(sys.getfilesystemencoding()) |
|
212 | 215 | else: |
|
213 | 216 | filename = fname |
|
214 | 217 | builtin_mod.execfile(filename, *where) |
|
215 | 218 | |
|
216 | 219 | # Parts below taken from six: |
|
217 | 220 | # Copyright (c) 2010-2013 Benjamin Peterson |
|
218 | 221 | # |
|
219 | 222 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
|
220 | 223 | # of this software and associated documentation files (the "Software"), to deal |
|
221 | 224 | # in the Software without restriction, including without limitation the rights |
|
222 | 225 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
223 | 226 | # copies of the Software, and to permit persons to whom the Software is |
|
224 | 227 | # furnished to do so, subject to the following conditions: |
|
225 | 228 | # |
|
226 | 229 | # The above copyright notice and this permission notice shall be included in all |
|
227 | 230 | # copies or substantial portions of the Software. |
|
228 | 231 | # |
|
229 | 232 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
230 | 233 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
231 | 234 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
232 | 235 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
233 | 236 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
234 | 237 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
235 | 238 | # SOFTWARE. |
|
236 | 239 | |
|
237 | 240 | def with_metaclass(meta, *bases): |
|
238 | 241 | """Create a base class with a metaclass.""" |
|
239 | 242 | return meta("NewBase", bases, {}) |
@@ -1,162 +1,164 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | Utilities for working with terminals. |
|
4 | 4 | |
|
5 | 5 | Authors: |
|
6 | 6 | |
|
7 | 7 | * Brian E. Granger |
|
8 | 8 | * Fernando Perez |
|
9 | 9 | * Alexander Belchenko (e-mail: bialix AT ukr.net) |
|
10 | 10 | """ |
|
11 | 11 | |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | # Copyright (C) 2008-2011 The IPython Development Team |
|
14 | 14 | # |
|
15 | 15 | # Distributed under the terms of the BSD License. The full license is in |
|
16 | 16 | # the file COPYING, distributed as part of this software. |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | 19 | #----------------------------------------------------------------------------- |
|
20 | 20 | # Imports |
|
21 | 21 | #----------------------------------------------------------------------------- |
|
22 | 22 | |
|
23 | 23 | import os |
|
24 | 24 | import struct |
|
25 | 25 | import sys |
|
26 | 26 | import warnings |
|
27 | 27 | |
|
28 | from . import py3compat | |
|
29 | ||
|
28 | 30 | #----------------------------------------------------------------------------- |
|
29 | 31 | # Code |
|
30 | 32 | #----------------------------------------------------------------------------- |
|
31 | 33 | |
|
32 | 34 | # This variable is part of the expected API of the module: |
|
33 | 35 | ignore_termtitle = True |
|
34 | 36 | |
|
35 | 37 | |
|
36 | 38 | def _term_clear(): |
|
37 | 39 | pass |
|
38 | 40 | |
|
39 | 41 | |
|
40 | 42 | if os.name == 'posix': |
|
41 | 43 | def _term_clear(): |
|
42 | 44 | os.system('clear') |
|
43 | 45 | |
|
44 | 46 | |
|
45 | 47 | if sys.platform == 'win32': |
|
46 | 48 | def _term_clear(): |
|
47 | 49 | os.system('cls') |
|
48 | 50 | |
|
49 | 51 | |
|
50 | 52 | def term_clear(): |
|
51 | 53 | _term_clear() |
|
52 | 54 | |
|
53 | 55 | |
|
54 | 56 | def toggle_set_term_title(val): |
|
55 | 57 | """Control whether set_term_title is active or not. |
|
56 | 58 | |
|
57 | 59 | set_term_title() allows writing to the console titlebar. In embedded |
|
58 | 60 | widgets this can cause problems, so this call can be used to toggle it on |
|
59 | 61 | or off as needed. |
|
60 | 62 | |
|
61 | 63 | The default state of the module is for the function to be disabled. |
|
62 | 64 | |
|
63 | 65 | Parameters |
|
64 | 66 | ---------- |
|
65 | 67 | val : bool |
|
66 | 68 | If True, set_term_title() actually writes to the terminal (using the |
|
67 | 69 | appropriate platform-specific module). If False, it is a no-op. |
|
68 | 70 | """ |
|
69 | 71 | global ignore_termtitle |
|
70 | 72 | ignore_termtitle = not(val) |
|
71 | 73 | |
|
72 | 74 | |
|
73 | 75 | def _set_term_title(*args,**kw): |
|
74 | 76 | """Dummy no-op.""" |
|
75 | 77 | pass |
|
76 | 78 | |
|
77 | 79 | |
|
78 | 80 | def _set_term_title_xterm(title): |
|
79 | 81 | """ Change virtual terminal title in xterm-workalikes """ |
|
80 | 82 | sys.stdout.write('\033]0;%s\007' % title) |
|
81 | 83 | |
|
82 | 84 | if os.name == 'posix': |
|
83 | 85 | TERM = os.environ.get('TERM','') |
|
84 | 86 | if TERM.startswith('xterm'): |
|
85 | 87 | _set_term_title = _set_term_title_xterm |
|
86 | 88 | |
|
87 | 89 | |
|
88 | 90 | if sys.platform == 'win32': |
|
89 | 91 | try: |
|
90 | 92 | import ctypes |
|
91 | 93 | |
|
92 | 94 | SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW |
|
93 | 95 | SetConsoleTitleW.argtypes = [ctypes.c_wchar_p] |
|
94 | 96 | |
|
95 | 97 | def _set_term_title(title): |
|
96 | 98 | """Set terminal title using ctypes to access the Win32 APIs.""" |
|
97 | 99 | SetConsoleTitleW(title) |
|
98 | 100 | except ImportError: |
|
99 | 101 | def _set_term_title(title): |
|
100 | 102 | """Set terminal title using the 'title' command.""" |
|
101 | 103 | global ignore_termtitle |
|
102 | 104 | |
|
103 | 105 | try: |
|
104 | 106 | # Cannot be on network share when issuing system commands |
|
105 |
curr = |
|
|
107 | curr = py3compat.getcwd() | |
|
106 | 108 | os.chdir("C:") |
|
107 | 109 | ret = os.system("title " + title) |
|
108 | 110 | finally: |
|
109 | 111 | os.chdir(curr) |
|
110 | 112 | if ret: |
|
111 | 113 | # non-zero return code signals error, don't try again |
|
112 | 114 | ignore_termtitle = True |
|
113 | 115 | |
|
114 | 116 | |
|
115 | 117 | def set_term_title(title): |
|
116 | 118 | """Set terminal title using the necessary platform-dependent calls.""" |
|
117 | 119 | if ignore_termtitle: |
|
118 | 120 | return |
|
119 | 121 | _set_term_title(title) |
|
120 | 122 | |
|
121 | 123 | |
|
122 | 124 | def freeze_term_title(): |
|
123 | 125 | warnings.warn("This function is deprecated, use toggle_set_term_title()") |
|
124 | 126 | global ignore_termtitle |
|
125 | 127 | ignore_termtitle = True |
|
126 | 128 | |
|
127 | 129 | |
|
128 | 130 | def get_terminal_size(defaultx=80, defaulty=25): |
|
129 | 131 | return defaultx, defaulty |
|
130 | 132 | |
|
131 | 133 | |
|
132 | 134 | if sys.platform == 'win32': |
|
133 | 135 | def get_terminal_size(defaultx=80, defaulty=25): |
|
134 | 136 | """Return size of current terminal console. |
|
135 | 137 | |
|
136 | 138 | This function try to determine actual size of current working |
|
137 | 139 | console window and return tuple (sizex, sizey) if success, |
|
138 | 140 | or default size (defaultx, defaulty) otherwise. |
|
139 | 141 | |
|
140 | 142 | Dependencies: ctypes should be installed. |
|
141 | 143 | |
|
142 | 144 | Author: Alexander Belchenko (e-mail: bialix AT ukr.net) |
|
143 | 145 | """ |
|
144 | 146 | try: |
|
145 | 147 | import ctypes |
|
146 | 148 | except ImportError: |
|
147 | 149 | return defaultx, defaulty |
|
148 | 150 | |
|
149 | 151 | h = ctypes.windll.kernel32.GetStdHandle(-11) |
|
150 | 152 | csbi = ctypes.create_string_buffer(22) |
|
151 | 153 | res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) |
|
152 | 154 | |
|
153 | 155 | if res: |
|
154 | 156 | (bufx, bufy, curx, cury, wattr, |
|
155 | 157 | left, top, right, bottom, maxx, maxy) = struct.unpack( |
|
156 | 158 | "hhhhHhhhhhh", csbi.raw) |
|
157 | 159 | sizex = right - left + 1 |
|
158 | 160 | sizey = bottom - top + 1 |
|
159 | 161 | return (sizex, sizey) |
|
160 | 162 | else: |
|
161 | 163 | return (defaultx, defaulty) |
|
162 | 164 |
@@ -1,643 +1,643 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """Tests for IPython.utils.path.py""" |
|
3 | 3 | |
|
4 | 4 | #----------------------------------------------------------------------------- |
|
5 | 5 | # Copyright (C) 2008-2011 The IPython Development Team |
|
6 | 6 | # |
|
7 | 7 | # Distributed under the terms of the BSD License. The full license is in |
|
8 | 8 | # the file COPYING, distributed as part of this software. |
|
9 | 9 | #----------------------------------------------------------------------------- |
|
10 | 10 | |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | # Imports |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | |
|
15 | 15 | from __future__ import with_statement |
|
16 | 16 | |
|
17 | 17 | import os |
|
18 | 18 | import shutil |
|
19 | 19 | import sys |
|
20 | 20 | import tempfile |
|
21 | 21 | from contextlib import contextmanager |
|
22 | 22 | |
|
23 | 23 | from os.path import join, abspath, split |
|
24 | 24 | |
|
25 | 25 | import nose.tools as nt |
|
26 | 26 | |
|
27 | 27 | from nose import with_setup |
|
28 | 28 | |
|
29 | 29 | import IPython |
|
30 | 30 | from IPython.testing import decorators as dec |
|
31 | 31 | from IPython.testing.decorators import (skip_if_not_win32, skip_win32, |
|
32 | 32 | onlyif_unicode_paths,) |
|
33 | 33 | from IPython.testing.tools import make_tempfile, AssertPrints |
|
34 | 34 | from IPython.utils import path |
|
35 | 35 | from IPython.utils import py3compat |
|
36 | 36 | from IPython.utils.tempdir import TemporaryDirectory |
|
37 | 37 | |
|
38 | 38 | # Platform-dependent imports |
|
39 | 39 | try: |
|
40 | 40 | import winreg as wreg # Py 3 |
|
41 | 41 | except ImportError: |
|
42 | 42 | try: |
|
43 | 43 | import _winreg as wreg # Py 2 |
|
44 | 44 | except ImportError: |
|
45 | 45 | #Fake _winreg module on none windows platforms |
|
46 | 46 | import types |
|
47 | 47 | wr_name = "winreg" if py3compat.PY3 else "_winreg" |
|
48 | 48 | sys.modules[wr_name] = types.ModuleType(wr_name) |
|
49 | 49 | try: |
|
50 | 50 | import winreg as wreg |
|
51 | 51 | except ImportError: |
|
52 | 52 | import _winreg as wreg |
|
53 | 53 | #Add entries that needs to be stubbed by the testing code |
|
54 | 54 | (wreg.OpenKey, wreg.QueryValueEx,) = (None, None) |
|
55 | 55 | |
|
56 | 56 | try: |
|
57 | 57 | reload |
|
58 | 58 | except NameError: # Python 3 |
|
59 | 59 | from imp import reload |
|
60 | 60 | |
|
61 | 61 | #----------------------------------------------------------------------------- |
|
62 | 62 | # Globals |
|
63 | 63 | #----------------------------------------------------------------------------- |
|
64 | 64 | env = os.environ |
|
65 | 65 | TEST_FILE_PATH = split(abspath(__file__))[0] |
|
66 | 66 | TMP_TEST_DIR = tempfile.mkdtemp() |
|
67 | 67 | HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir") |
|
68 | 68 | XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir") |
|
69 | 69 | XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir") |
|
70 | 70 | IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython') |
|
71 | 71 | # |
|
72 | 72 | # Setup/teardown functions/decorators |
|
73 | 73 | # |
|
74 | 74 | |
|
75 | 75 | def setup(): |
|
76 | 76 | """Setup testenvironment for the module: |
|
77 | 77 | |
|
78 | 78 | - Adds dummy home dir tree |
|
79 | 79 | """ |
|
80 | 80 | # Do not mask exceptions here. In particular, catching WindowsError is a |
|
81 | 81 | # problem because that exception is only defined on Windows... |
|
82 | 82 | os.makedirs(IP_TEST_DIR) |
|
83 | 83 | os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython')) |
|
84 | 84 | os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython')) |
|
85 | 85 | |
|
86 | 86 | |
|
87 | 87 | def teardown(): |
|
88 | 88 | """Teardown testenvironment for the module: |
|
89 | 89 | |
|
90 | 90 | - Remove dummy home dir tree |
|
91 | 91 | """ |
|
92 | 92 | # Note: we remove the parent test dir, which is the root of all test |
|
93 | 93 | # subdirs we may have created. Use shutil instead of os.removedirs, so |
|
94 | 94 | # that non-empty directories are all recursively removed. |
|
95 | 95 | shutil.rmtree(TMP_TEST_DIR) |
|
96 | 96 | |
|
97 | 97 | |
|
98 | 98 | def setup_environment(): |
|
99 | 99 | """Setup testenvironment for some functions that are tested |
|
100 | 100 | in this module. In particular this functions stores attributes |
|
101 | 101 | and other things that we need to stub in some test functions. |
|
102 | 102 | This needs to be done on a function level and not module level because |
|
103 | 103 | each testfunction needs a pristine environment. |
|
104 | 104 | """ |
|
105 | 105 | global oldstuff, platformstuff |
|
106 | 106 | oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd()) |
|
107 | 107 | |
|
108 | 108 | if os.name == 'nt': |
|
109 | 109 | platformstuff = (wreg.OpenKey, wreg.QueryValueEx,) |
|
110 | 110 | |
|
111 | 111 | |
|
112 | 112 | def teardown_environment(): |
|
113 | 113 | """Restore things that were remembered by the setup_environment function |
|
114 | 114 | """ |
|
115 | 115 | (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff |
|
116 | 116 | os.chdir(old_wd) |
|
117 | 117 | reload(path) |
|
118 | 118 | |
|
119 | 119 | for key in list(env): |
|
120 | 120 | if key not in oldenv: |
|
121 | 121 | del env[key] |
|
122 | 122 | env.update(oldenv) |
|
123 | 123 | if hasattr(sys, 'frozen'): |
|
124 | 124 | del sys.frozen |
|
125 | 125 | if os.name == 'nt': |
|
126 | 126 | (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff |
|
127 | 127 | |
|
128 | 128 | # Build decorator that uses the setup_environment/setup_environment |
|
129 | 129 | with_environment = with_setup(setup_environment, teardown_environment) |
|
130 | 130 | |
|
131 | 131 | @skip_if_not_win32 |
|
132 | 132 | @with_environment |
|
133 | 133 | def test_get_home_dir_1(): |
|
134 | 134 | """Testcase for py2exe logic, un-compressed lib |
|
135 | 135 | """ |
|
136 | 136 | unfrozen = path.get_home_dir() |
|
137 | 137 | sys.frozen = True |
|
138 | 138 | |
|
139 | 139 | #fake filename for IPython.__init__ |
|
140 | 140 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) |
|
141 | 141 | |
|
142 | 142 | home_dir = path.get_home_dir() |
|
143 | 143 | nt.assert_equal(home_dir, unfrozen) |
|
144 | 144 | |
|
145 | 145 | |
|
146 | 146 | @skip_if_not_win32 |
|
147 | 147 | @with_environment |
|
148 | 148 | def test_get_home_dir_2(): |
|
149 | 149 | """Testcase for py2exe logic, compressed lib |
|
150 | 150 | """ |
|
151 | 151 | unfrozen = path.get_home_dir() |
|
152 | 152 | sys.frozen = True |
|
153 | 153 | #fake filename for IPython.__init__ |
|
154 | 154 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower() |
|
155 | 155 | |
|
156 | 156 | home_dir = path.get_home_dir(True) |
|
157 | 157 | nt.assert_equal(home_dir, unfrozen) |
|
158 | 158 | |
|
159 | 159 | |
|
160 | 160 | @with_environment |
|
161 | 161 | def test_get_home_dir_3(): |
|
162 | 162 | """get_home_dir() uses $HOME if set""" |
|
163 | 163 | env["HOME"] = HOME_TEST_DIR |
|
164 | 164 | home_dir = path.get_home_dir(True) |
|
165 | 165 | # get_home_dir expands symlinks |
|
166 | 166 | nt.assert_equal(home_dir, os.path.realpath(env["HOME"])) |
|
167 | 167 | |
|
168 | 168 | |
|
169 | 169 | @with_environment |
|
170 | 170 | def test_get_home_dir_4(): |
|
171 | 171 | """get_home_dir() still works if $HOME is not set""" |
|
172 | 172 | |
|
173 | 173 | if 'HOME' in env: del env['HOME'] |
|
174 | 174 | # this should still succeed, but we don't care what the answer is |
|
175 | 175 | home = path.get_home_dir(False) |
|
176 | 176 | |
|
177 | 177 | @with_environment |
|
178 | 178 | def test_get_home_dir_5(): |
|
179 | 179 | """raise HomeDirError if $HOME is specified, but not a writable dir""" |
|
180 | 180 | env['HOME'] = abspath(HOME_TEST_DIR+'garbage') |
|
181 | 181 | # set os.name = posix, to prevent My Documents fallback on Windows |
|
182 | 182 | os.name = 'posix' |
|
183 | 183 | nt.assert_raises(path.HomeDirError, path.get_home_dir, True) |
|
184 | 184 | |
|
185 | 185 | |
|
186 | 186 | # Should we stub wreg fully so we can run the test on all platforms? |
|
187 | 187 | @skip_if_not_win32 |
|
188 | 188 | @with_environment |
|
189 | 189 | def test_get_home_dir_8(): |
|
190 | 190 | """Using registry hack for 'My Documents', os=='nt' |
|
191 | 191 | |
|
192 | 192 | HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing. |
|
193 | 193 | """ |
|
194 | 194 | os.name = 'nt' |
|
195 | 195 | # Remove from stub environment all keys that may be set |
|
196 | 196 | for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']: |
|
197 | 197 | env.pop(key, None) |
|
198 | 198 | |
|
199 | 199 | #Stub windows registry functions |
|
200 | 200 | def OpenKey(x, y): |
|
201 | 201 | class key: |
|
202 | 202 | def Close(self): |
|
203 | 203 | pass |
|
204 | 204 | return key() |
|
205 | 205 | def QueryValueEx(x, y): |
|
206 | 206 | return [abspath(HOME_TEST_DIR)] |
|
207 | 207 | |
|
208 | 208 | wreg.OpenKey = OpenKey |
|
209 | 209 | wreg.QueryValueEx = QueryValueEx |
|
210 | 210 | |
|
211 | 211 | home_dir = path.get_home_dir() |
|
212 | 212 | nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) |
|
213 | 213 | |
|
214 | 214 | |
|
215 | 215 | @with_environment |
|
216 | 216 | def test_get_ipython_dir_1(): |
|
217 | 217 | """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions.""" |
|
218 | 218 | env_ipdir = os.path.join("someplace", ".ipython") |
|
219 | 219 | path._writable_dir = lambda path: True |
|
220 | 220 | env['IPYTHONDIR'] = env_ipdir |
|
221 | 221 | ipdir = path.get_ipython_dir() |
|
222 | 222 | nt.assert_equal(ipdir, env_ipdir) |
|
223 | 223 | |
|
224 | 224 | |
|
225 | 225 | @with_environment |
|
226 | 226 | def test_get_ipython_dir_2(): |
|
227 | 227 | """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions.""" |
|
228 | 228 | path.get_home_dir = lambda : "someplace" |
|
229 | 229 | path.get_xdg_dir = lambda : None |
|
230 | 230 | path._writable_dir = lambda path: True |
|
231 | 231 | os.name = "posix" |
|
232 | 232 | env.pop('IPYTHON_DIR', None) |
|
233 | 233 | env.pop('IPYTHONDIR', None) |
|
234 | 234 | env.pop('XDG_CONFIG_HOME', None) |
|
235 | 235 | ipdir = path.get_ipython_dir() |
|
236 | 236 | nt.assert_equal(ipdir, os.path.join("someplace", ".ipython")) |
|
237 | 237 | |
|
238 | 238 | @with_environment |
|
239 | 239 | def test_get_ipython_dir_3(): |
|
240 | 240 | """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist.""" |
|
241 | 241 | path.get_home_dir = lambda : "someplace" |
|
242 | 242 | path._writable_dir = lambda path: True |
|
243 | 243 | os.name = "posix" |
|
244 | 244 | env.pop('IPYTHON_DIR', None) |
|
245 | 245 | env.pop('IPYTHONDIR', None) |
|
246 | 246 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR |
|
247 | 247 | ipdir = path.get_ipython_dir() |
|
248 | 248 | if sys.platform == "darwin": |
|
249 | 249 | expected = os.path.join("someplace", ".ipython") |
|
250 | 250 | else: |
|
251 | 251 | expected = os.path.join(XDG_TEST_DIR, "ipython") |
|
252 | 252 | nt.assert_equal(ipdir, expected) |
|
253 | 253 | |
|
254 | 254 | @with_environment |
|
255 | 255 | def test_get_ipython_dir_4(): |
|
256 | 256 | """test_get_ipython_dir_4, use XDG if both exist.""" |
|
257 | 257 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
258 | 258 | os.name = "posix" |
|
259 | 259 | env.pop('IPYTHON_DIR', None) |
|
260 | 260 | env.pop('IPYTHONDIR', None) |
|
261 | 261 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR |
|
262 | 262 | ipdir = path.get_ipython_dir() |
|
263 | 263 | if sys.platform == "darwin": |
|
264 | 264 | expected = os.path.join(HOME_TEST_DIR, ".ipython") |
|
265 | 265 | else: |
|
266 | 266 | expected = os.path.join(XDG_TEST_DIR, "ipython") |
|
267 | 267 | nt.assert_equal(ipdir, expected) |
|
268 | 268 | |
|
269 | 269 | @with_environment |
|
270 | 270 | def test_get_ipython_dir_5(): |
|
271 | 271 | """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist.""" |
|
272 | 272 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
273 | 273 | os.name = "posix" |
|
274 | 274 | env.pop('IPYTHON_DIR', None) |
|
275 | 275 | env.pop('IPYTHONDIR', None) |
|
276 | 276 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR |
|
277 | 277 | os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython')) |
|
278 | 278 | ipdir = path.get_ipython_dir() |
|
279 | 279 | nt.assert_equal(ipdir, IP_TEST_DIR) |
|
280 | 280 | |
|
281 | 281 | @with_environment |
|
282 | 282 | def test_get_ipython_dir_6(): |
|
283 | 283 | """test_get_ipython_dir_6, use XDG if defined and neither exist.""" |
|
284 | 284 | xdg = os.path.join(HOME_TEST_DIR, 'somexdg') |
|
285 | 285 | os.mkdir(xdg) |
|
286 | 286 | shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython')) |
|
287 | 287 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
288 | 288 | path.get_xdg_dir = lambda : xdg |
|
289 | 289 | os.name = "posix" |
|
290 | 290 | env.pop('IPYTHON_DIR', None) |
|
291 | 291 | env.pop('IPYTHONDIR', None) |
|
292 | 292 | env.pop('XDG_CONFIG_HOME', None) |
|
293 | 293 | xdg_ipdir = os.path.join(xdg, "ipython") |
|
294 | 294 | ipdir = path.get_ipython_dir() |
|
295 | 295 | nt.assert_equal(ipdir, xdg_ipdir) |
|
296 | 296 | |
|
297 | 297 | @with_environment |
|
298 | 298 | def test_get_ipython_dir_7(): |
|
299 | 299 | """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR""" |
|
300 | 300 | path._writable_dir = lambda path: True |
|
301 | 301 | home_dir = os.path.normpath(os.path.expanduser('~')) |
|
302 | 302 | env['IPYTHONDIR'] = os.path.join('~', 'somewhere') |
|
303 | 303 | ipdir = path.get_ipython_dir() |
|
304 | 304 | nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere')) |
|
305 | 305 | |
|
306 | 306 | @skip_win32 |
|
307 | 307 | @with_environment |
|
308 | 308 | def test_get_ipython_dir_8(): |
|
309 | 309 | """test_get_ipython_dir_8, test / home directory""" |
|
310 | 310 | old = path._writable_dir, path.get_xdg_dir |
|
311 | 311 | try: |
|
312 | 312 | path._writable_dir = lambda path: bool(path) |
|
313 | 313 | path.get_xdg_dir = lambda: None |
|
314 | 314 | env.pop('IPYTHON_DIR', None) |
|
315 | 315 | env.pop('IPYTHONDIR', None) |
|
316 | 316 | env['HOME'] = '/' |
|
317 | 317 | nt.assert_equal(path.get_ipython_dir(), '/.ipython') |
|
318 | 318 | finally: |
|
319 | 319 | path._writable_dir, path.get_xdg_dir = old |
|
320 | 320 | |
|
321 | 321 | @with_environment |
|
322 | 322 | def test_get_xdg_dir_0(): |
|
323 | 323 | """test_get_xdg_dir_0, check xdg_dir""" |
|
324 | 324 | reload(path) |
|
325 | 325 | path._writable_dir = lambda path: True |
|
326 | 326 | path.get_home_dir = lambda : 'somewhere' |
|
327 | 327 | os.name = "posix" |
|
328 | 328 | sys.platform = "linux2" |
|
329 | 329 | env.pop('IPYTHON_DIR', None) |
|
330 | 330 | env.pop('IPYTHONDIR', None) |
|
331 | 331 | env.pop('XDG_CONFIG_HOME', None) |
|
332 | 332 | |
|
333 | 333 | nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config')) |
|
334 | 334 | |
|
335 | 335 | |
|
336 | 336 | @with_environment |
|
337 | 337 | def test_get_xdg_dir_1(): |
|
338 | 338 | """test_get_xdg_dir_1, check nonexistant xdg_dir""" |
|
339 | 339 | reload(path) |
|
340 | 340 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
341 | 341 | os.name = "posix" |
|
342 | 342 | sys.platform = "linux2" |
|
343 | 343 | env.pop('IPYTHON_DIR', None) |
|
344 | 344 | env.pop('IPYTHONDIR', None) |
|
345 | 345 | env.pop('XDG_CONFIG_HOME', None) |
|
346 | 346 | nt.assert_equal(path.get_xdg_dir(), None) |
|
347 | 347 | |
|
348 | 348 | @with_environment |
|
349 | 349 | def test_get_xdg_dir_2(): |
|
350 | 350 | """test_get_xdg_dir_2, check xdg_dir default to ~/.config""" |
|
351 | 351 | reload(path) |
|
352 | 352 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
353 | 353 | os.name = "posix" |
|
354 | 354 | sys.platform = "linux2" |
|
355 | 355 | env.pop('IPYTHON_DIR', None) |
|
356 | 356 | env.pop('IPYTHONDIR', None) |
|
357 | 357 | env.pop('XDG_CONFIG_HOME', None) |
|
358 | 358 | cfgdir=os.path.join(path.get_home_dir(), '.config') |
|
359 | 359 | if not os.path.exists(cfgdir): |
|
360 | 360 | os.makedirs(cfgdir) |
|
361 | 361 | |
|
362 | 362 | nt.assert_equal(path.get_xdg_dir(), cfgdir) |
|
363 | 363 | |
|
364 | 364 | @with_environment |
|
365 | 365 | def test_get_xdg_dir_3(): |
|
366 | 366 | """test_get_xdg_dir_3, check xdg_dir not used on OS X""" |
|
367 | 367 | reload(path) |
|
368 | 368 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
369 | 369 | os.name = "posix" |
|
370 | 370 | sys.platform = "darwin" |
|
371 | 371 | env.pop('IPYTHON_DIR', None) |
|
372 | 372 | env.pop('IPYTHONDIR', None) |
|
373 | 373 | env.pop('XDG_CONFIG_HOME', None) |
|
374 | 374 | cfgdir=os.path.join(path.get_home_dir(), '.config') |
|
375 | 375 | if not os.path.exists(cfgdir): |
|
376 | 376 | os.makedirs(cfgdir) |
|
377 | 377 | |
|
378 | 378 | nt.assert_equal(path.get_xdg_dir(), None) |
|
379 | 379 | |
|
380 | 380 | def test_filefind(): |
|
381 | 381 | """Various tests for filefind""" |
|
382 | 382 | f = tempfile.NamedTemporaryFile() |
|
383 | 383 | # print 'fname:',f.name |
|
384 | 384 | alt_dirs = path.get_ipython_dir() |
|
385 | 385 | t = path.filefind(f.name, alt_dirs) |
|
386 | 386 | # print 'found:',t |
|
387 | 387 | |
|
388 | 388 | @with_environment |
|
389 | 389 | def test_get_ipython_cache_dir(): |
|
390 | 390 | os.environ["HOME"] = HOME_TEST_DIR |
|
391 | 391 | if os.name == 'posix' and sys.platform != 'darwin': |
|
392 | 392 | # test default |
|
393 | 393 | os.makedirs(os.path.join(HOME_TEST_DIR, ".cache")) |
|
394 | 394 | os.environ.pop("XDG_CACHE_HOME", None) |
|
395 | 395 | ipdir = path.get_ipython_cache_dir() |
|
396 | 396 | nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"), |
|
397 | 397 | ipdir) |
|
398 | 398 | nt.assert_true(os.path.isdir(ipdir)) |
|
399 | 399 | |
|
400 | 400 | # test env override |
|
401 | 401 | os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR |
|
402 | 402 | ipdir = path.get_ipython_cache_dir() |
|
403 | 403 | nt.assert_true(os.path.isdir(ipdir)) |
|
404 | 404 | nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython")) |
|
405 | 405 | else: |
|
406 | 406 | nt.assert_equal(path.get_ipython_cache_dir(), |
|
407 | 407 | path.get_ipython_dir()) |
|
408 | 408 | |
|
409 | 409 | def test_get_ipython_package_dir(): |
|
410 | 410 | ipdir = path.get_ipython_package_dir() |
|
411 | 411 | nt.assert_true(os.path.isdir(ipdir)) |
|
412 | 412 | |
|
413 | 413 | |
|
414 | 414 | def test_get_ipython_module_path(): |
|
415 | 415 | ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp') |
|
416 | 416 | nt.assert_true(os.path.isfile(ipapp_path)) |
|
417 | 417 | |
|
418 | 418 | |
|
419 | 419 | @dec.skip_if_not_win32 |
|
420 | 420 | def test_get_long_path_name_win32(): |
|
421 | 421 | with TemporaryDirectory() as tmpdir: |
|
422 | 422 | |
|
423 | 423 | # Make a long path. |
|
424 | 424 | long_path = os.path.join(tmpdir, u'this is my long path name') |
|
425 | 425 | os.makedirs(long_path) |
|
426 | 426 | |
|
427 | 427 | # Test to see if the short path evaluates correctly. |
|
428 | 428 | short_path = os.path.join(tmpdir, u'THISIS~1') |
|
429 | 429 | evaluated_path = path.get_long_path_name(short_path) |
|
430 | 430 | nt.assert_equal(evaluated_path.lower(), long_path.lower()) |
|
431 | 431 | |
|
432 | 432 | |
|
433 | 433 | @dec.skip_win32 |
|
434 | 434 | def test_get_long_path_name(): |
|
435 | 435 | p = path.get_long_path_name('/usr/local') |
|
436 | 436 | nt.assert_equal(p,'/usr/local') |
|
437 | 437 | |
|
438 | 438 | @dec.skip_win32 # can't create not-user-writable dir on win |
|
439 | 439 | @with_environment |
|
440 | 440 | def test_not_writable_ipdir(): |
|
441 | 441 | tmpdir = tempfile.mkdtemp() |
|
442 | 442 | os.name = "posix" |
|
443 | 443 | env.pop('IPYTHON_DIR', None) |
|
444 | 444 | env.pop('IPYTHONDIR', None) |
|
445 | 445 | env.pop('XDG_CONFIG_HOME', None) |
|
446 | 446 | env['HOME'] = tmpdir |
|
447 | 447 | ipdir = os.path.join(tmpdir, '.ipython') |
|
448 | 448 | os.mkdir(ipdir) |
|
449 | 449 | os.chmod(ipdir, 600) |
|
450 | 450 | with AssertPrints('is not a writable location', channel='stderr'): |
|
451 | 451 | ipdir = path.get_ipython_dir() |
|
452 | 452 | env.pop('IPYTHON_DIR', None) |
|
453 | 453 | |
|
454 | 454 | def test_unquote_filename(): |
|
455 | 455 | for win32 in (True, False): |
|
456 | 456 | nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py') |
|
457 | 457 | nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py') |
|
458 | 458 | nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py') |
|
459 | 459 | nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py') |
|
460 | 460 | nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py') |
|
461 | 461 | nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py') |
|
462 | 462 | nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"') |
|
463 | 463 | nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"') |
|
464 | 464 | nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'") |
|
465 | 465 | nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'") |
|
466 | 466 | |
|
467 | 467 | @with_environment |
|
468 | 468 | def test_get_py_filename(): |
|
469 | 469 | os.chdir(TMP_TEST_DIR) |
|
470 | 470 | for win32 in (True, False): |
|
471 | 471 | with make_tempfile('foo.py'): |
|
472 | 472 | nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py') |
|
473 | 473 | nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py') |
|
474 | 474 | with make_tempfile('foo'): |
|
475 | 475 | nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo') |
|
476 | 476 | nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) |
|
477 | 477 | nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32) |
|
478 | 478 | nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) |
|
479 | 479 | true_fn = 'foo with spaces.py' |
|
480 | 480 | with make_tempfile(true_fn): |
|
481 | 481 | nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn) |
|
482 | 482 | nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn) |
|
483 | 483 | if win32: |
|
484 | 484 | nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn) |
|
485 | 485 | nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn) |
|
486 | 486 | else: |
|
487 | 487 | nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False) |
|
488 | 488 | nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False) |
|
489 | 489 | |
|
490 | 490 | @onlyif_unicode_paths |
|
491 | 491 | def test_unicode_in_filename(): |
|
492 | 492 | """When a file doesn't exist, the exception raised should be safe to call |
|
493 | 493 | str() on - i.e. in Python 2 it must only have ASCII characters. |
|
494 | 494 | |
|
495 | 495 | https://github.com/ipython/ipython/issues/875 |
|
496 | 496 | """ |
|
497 | 497 | try: |
|
498 | 498 | # these calls should not throw unicode encode exceptions |
|
499 | 499 | path.get_py_filename(u'fooéè.py', force_win32=False) |
|
500 | 500 | except IOError as ex: |
|
501 | 501 | str(ex) |
|
502 | 502 | |
|
503 | 503 | |
|
504 | 504 | class TestShellGlob(object): |
|
505 | 505 | |
|
506 | 506 | @classmethod |
|
507 | 507 | def setUpClass(cls): |
|
508 | 508 | cls.filenames_start_with_a = ['a0', 'a1', 'a2'] |
|
509 | 509 | cls.filenames_end_with_b = ['0b', '1b', '2b'] |
|
510 | 510 | cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b |
|
511 | 511 | cls.tempdir = TemporaryDirectory() |
|
512 | 512 | td = cls.tempdir.name |
|
513 | 513 | |
|
514 | 514 | with cls.in_tempdir(): |
|
515 | 515 | # Create empty files |
|
516 | 516 | for fname in cls.filenames: |
|
517 | 517 | open(os.path.join(td, fname), 'w').close() |
|
518 | 518 | |
|
519 | 519 | @classmethod |
|
520 | 520 | def tearDownClass(cls): |
|
521 | 521 | cls.tempdir.cleanup() |
|
522 | 522 | |
|
523 | 523 | @classmethod |
|
524 | 524 | @contextmanager |
|
525 | 525 | def in_tempdir(cls): |
|
526 |
save = |
|
|
526 | save = py3compat.getcwd() | |
|
527 | 527 | try: |
|
528 | 528 | os.chdir(cls.tempdir.name) |
|
529 | 529 | yield |
|
530 | 530 | finally: |
|
531 | 531 | os.chdir(save) |
|
532 | 532 | |
|
533 | 533 | def check_match(self, patterns, matches): |
|
534 | 534 | with self.in_tempdir(): |
|
535 | 535 | # glob returns unordered list. that's why sorted is required. |
|
536 | 536 | nt.assert_equals(sorted(path.shellglob(patterns)), |
|
537 | 537 | sorted(matches)) |
|
538 | 538 | |
|
539 | 539 | def common_cases(self): |
|
540 | 540 | return [ |
|
541 | 541 | (['*'], self.filenames), |
|
542 | 542 | (['a*'], self.filenames_start_with_a), |
|
543 | 543 | (['*c'], ['*c']), |
|
544 | 544 | (['*', 'a*', '*b', '*c'], self.filenames |
|
545 | 545 | + self.filenames_start_with_a |
|
546 | 546 | + self.filenames_end_with_b |
|
547 | 547 | + ['*c']), |
|
548 | 548 | (['a[012]'], self.filenames_start_with_a), |
|
549 | 549 | ] |
|
550 | 550 | |
|
551 | 551 | @skip_win32 |
|
552 | 552 | def test_match_posix(self): |
|
553 | 553 | for (patterns, matches) in self.common_cases() + [ |
|
554 | 554 | ([r'\*'], ['*']), |
|
555 | 555 | ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a), |
|
556 | 556 | ([r'a\[012]'], ['a[012]']), |
|
557 | 557 | ]: |
|
558 | 558 | yield (self.check_match, patterns, matches) |
|
559 | 559 | |
|
560 | 560 | @skip_if_not_win32 |
|
561 | 561 | def test_match_windows(self): |
|
562 | 562 | for (patterns, matches) in self.common_cases() + [ |
|
563 | 563 | # In windows, backslash is interpreted as path |
|
564 | 564 | # separator. Therefore, you can't escape glob |
|
565 | 565 | # using it. |
|
566 | 566 | ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a), |
|
567 | 567 | ([r'a\[012]'], [r'a\[012]']), |
|
568 | 568 | ]: |
|
569 | 569 | yield (self.check_match, patterns, matches) |
|
570 | 570 | |
|
571 | 571 | |
|
572 | 572 | def test_unescape_glob(): |
|
573 | 573 | nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?') |
|
574 | 574 | nt.assert_equals(path.unescape_glob(r'\\*'), r'\*') |
|
575 | 575 | nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*') |
|
576 | 576 | nt.assert_equals(path.unescape_glob(r'\\a'), r'\a') |
|
577 | 577 | nt.assert_equals(path.unescape_glob(r'\a'), r'\a') |
|
578 | 578 | |
|
579 | 579 | |
|
580 | 580 | class TestLinkOrCopy(object): |
|
581 | 581 | def setUp(self): |
|
582 | 582 | self.tempdir = TemporaryDirectory() |
|
583 | 583 | self.src = self.dst("src") |
|
584 | 584 | with open(self.src, "w") as f: |
|
585 | 585 | f.write("Hello, world!") |
|
586 | 586 | |
|
587 | 587 | def tearDown(self): |
|
588 | 588 | self.tempdir.cleanup() |
|
589 | 589 | |
|
590 | 590 | def dst(self, *args): |
|
591 | 591 | return os.path.join(self.tempdir.name, *args) |
|
592 | 592 | |
|
593 | 593 | def assert_inode_not_equal(self, a, b): |
|
594 | 594 | nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino, |
|
595 | 595 | "%r and %r do reference the same indoes" %(a, b)) |
|
596 | 596 | |
|
597 | 597 | def assert_inode_equal(self, a, b): |
|
598 | 598 | nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino, |
|
599 | 599 | "%r and %r do not reference the same indoes" %(a, b)) |
|
600 | 600 | |
|
601 | 601 | def assert_content_equal(self, a, b): |
|
602 | 602 | with open(a) as a_f: |
|
603 | 603 | with open(b) as b_f: |
|
604 | 604 | nt.assert_equals(a_f.read(), b_f.read()) |
|
605 | 605 | |
|
606 | 606 | @skip_win32 |
|
607 | 607 | def test_link_successful(self): |
|
608 | 608 | dst = self.dst("target") |
|
609 | 609 | path.link_or_copy(self.src, dst) |
|
610 | 610 | self.assert_inode_equal(self.src, dst) |
|
611 | 611 | |
|
612 | 612 | @skip_win32 |
|
613 | 613 | def test_link_into_dir(self): |
|
614 | 614 | dst = self.dst("some_dir") |
|
615 | 615 | os.mkdir(dst) |
|
616 | 616 | path.link_or_copy(self.src, dst) |
|
617 | 617 | expected_dst = self.dst("some_dir", os.path.basename(self.src)) |
|
618 | 618 | self.assert_inode_equal(self.src, expected_dst) |
|
619 | 619 | |
|
620 | 620 | @skip_win32 |
|
621 | 621 | def test_target_exists(self): |
|
622 | 622 | dst = self.dst("target") |
|
623 | 623 | open(dst, "w").close() |
|
624 | 624 | path.link_or_copy(self.src, dst) |
|
625 | 625 | self.assert_inode_equal(self.src, dst) |
|
626 | 626 | |
|
627 | 627 | @skip_win32 |
|
628 | 628 | def test_no_link(self): |
|
629 | 629 | real_link = os.link |
|
630 | 630 | try: |
|
631 | 631 | del os.link |
|
632 | 632 | dst = self.dst("target") |
|
633 | 633 | path.link_or_copy(self.src, dst) |
|
634 | 634 | self.assert_content_equal(self.src, dst) |
|
635 | 635 | self.assert_inode_not_equal(self.src, dst) |
|
636 | 636 | finally: |
|
637 | 637 | os.link = real_link |
|
638 | 638 | |
|
639 | 639 | @skip_if_not_win32 |
|
640 | 640 | def test_windows(self): |
|
641 | 641 | dst = self.dst("target") |
|
642 | 642 | path.link_or_copy(self.src, dst) |
|
643 | 643 | self.assert_content_equal(self.src, dst) |
General Comments 0
You need to be logged in to leave comments.
Login now