Show More
The requested changes are too big and content was truncated. Show full diff
@@ -1,381 +1,382 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | An application for IPython. |
|
3 | An application for IPython. | |
4 |
|
4 | |||
5 | All top-level applications should use the classes in this module for |
|
5 | All top-level applications should use the classes in this module for | |
6 | handling configuration and creating configurables. |
|
6 | handling configuration and creating configurables. | |
7 |
|
7 | |||
8 | The job of an :class:`Application` is to create the master configuration |
|
8 | The job of an :class:`Application` is to create the master configuration | |
9 | object and then create the configurable objects, passing the config to them. |
|
9 | object and then create the configurable objects, passing the config to them. | |
10 |
|
10 | |||
11 | Authors: |
|
11 | Authors: | |
12 |
|
12 | |||
13 | * Brian Granger |
|
13 | * Brian Granger | |
14 | * Fernando Perez |
|
14 | * Fernando Perez | |
15 | * Min RK |
|
15 | * Min RK | |
16 |
|
16 | |||
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
19 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
20 | # Copyright (C) 2008 The IPython Development Team |
|
20 | # Copyright (C) 2008 The IPython Development Team | |
21 | # |
|
21 | # | |
22 | # Distributed under the terms of the BSD License. The full license is in |
|
22 | # Distributed under the terms of the BSD License. The full license is in | |
23 | # the file COPYING, distributed as part of this software. |
|
23 | # the file COPYING, distributed as part of this software. | |
24 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
25 |
|
25 | |||
26 | #----------------------------------------------------------------------------- |
|
26 | #----------------------------------------------------------------------------- | |
27 | # Imports |
|
27 | # Imports | |
28 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
29 |
|
29 | |||
30 | import atexit |
|
30 | import atexit | |
31 | import errno |
|
31 | import errno | |
32 | import glob |
|
32 | import glob | |
33 | import logging |
|
33 | import logging | |
34 | import os |
|
34 | import os | |
35 | import shutil |
|
35 | import shutil | |
36 | import sys |
|
36 | import sys | |
37 |
|
37 | |||
38 | from IPython.config.application import Application, catch_config_error |
|
38 | from IPython.config.application import Application, catch_config_error | |
39 | from IPython.config.loader import ConfigFileNotFound |
|
39 | from IPython.config.loader import ConfigFileNotFound | |
40 | from IPython.core import release, crashhandler |
|
40 | from IPython.core import release, crashhandler | |
41 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
41 | from IPython.core.profiledir import ProfileDir, ProfileDirError | |
42 | from IPython.utils.path import get_ipython_dir, get_ipython_package_dir |
|
42 | from IPython.utils.path import get_ipython_dir, get_ipython_package_dir | |
|
43 | from IPython.utils import py3compat | |||
43 | from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance |
|
44 | from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance | |
44 |
|
45 | |||
45 | #----------------------------------------------------------------------------- |
|
46 | #----------------------------------------------------------------------------- | |
46 | # Classes and functions |
|
47 | # Classes and functions | |
47 | #----------------------------------------------------------------------------- |
|
48 | #----------------------------------------------------------------------------- | |
48 |
|
49 | |||
49 |
|
50 | |||
50 | #----------------------------------------------------------------------------- |
|
51 | #----------------------------------------------------------------------------- | |
51 | # Base Application Class |
|
52 | # Base Application Class | |
52 | #----------------------------------------------------------------------------- |
|
53 | #----------------------------------------------------------------------------- | |
53 |
|
54 | |||
54 | # aliases and flags |
|
55 | # aliases and flags | |
55 |
|
56 | |||
56 | base_aliases = { |
|
57 | base_aliases = { | |
57 | 'profile-dir' : 'ProfileDir.location', |
|
58 | 'profile-dir' : 'ProfileDir.location', | |
58 | 'profile' : 'BaseIPythonApplication.profile', |
|
59 | 'profile' : 'BaseIPythonApplication.profile', | |
59 | 'ipython-dir' : 'BaseIPythonApplication.ipython_dir', |
|
60 | 'ipython-dir' : 'BaseIPythonApplication.ipython_dir', | |
60 | 'log-level' : 'Application.log_level', |
|
61 | 'log-level' : 'Application.log_level', | |
61 | 'config' : 'BaseIPythonApplication.extra_config_file', |
|
62 | 'config' : 'BaseIPythonApplication.extra_config_file', | |
62 | } |
|
63 | } | |
63 |
|
64 | |||
64 | base_flags = dict( |
|
65 | base_flags = dict( | |
65 | debug = ({'Application' : {'log_level' : logging.DEBUG}}, |
|
66 | debug = ({'Application' : {'log_level' : logging.DEBUG}}, | |
66 | "set log level to logging.DEBUG (maximize logging output)"), |
|
67 | "set log level to logging.DEBUG (maximize logging output)"), | |
67 | quiet = ({'Application' : {'log_level' : logging.CRITICAL}}, |
|
68 | quiet = ({'Application' : {'log_level' : logging.CRITICAL}}, | |
68 | "set log level to logging.CRITICAL (minimize logging output)"), |
|
69 | "set log level to logging.CRITICAL (minimize logging output)"), | |
69 | init = ({'BaseIPythonApplication' : { |
|
70 | init = ({'BaseIPythonApplication' : { | |
70 | 'copy_config_files' : True, |
|
71 | 'copy_config_files' : True, | |
71 | 'auto_create' : True} |
|
72 | 'auto_create' : True} | |
72 | }, """Initialize profile with default config files. This is equivalent |
|
73 | }, """Initialize profile with default config files. This is equivalent | |
73 | to running `ipython profile create <profile>` prior to startup. |
|
74 | to running `ipython profile create <profile>` prior to startup. | |
74 | """) |
|
75 | """) | |
75 | ) |
|
76 | ) | |
76 |
|
77 | |||
77 |
|
78 | |||
78 | class BaseIPythonApplication(Application): |
|
79 | class BaseIPythonApplication(Application): | |
79 |
|
80 | |||
80 | name = Unicode(u'ipython') |
|
81 | name = Unicode(u'ipython') | |
81 | description = Unicode(u'IPython: an enhanced interactive Python shell.') |
|
82 | description = Unicode(u'IPython: an enhanced interactive Python shell.') | |
82 | version = Unicode(release.version) |
|
83 | version = Unicode(release.version) | |
83 |
|
84 | |||
84 | aliases = Dict(base_aliases) |
|
85 | aliases = Dict(base_aliases) | |
85 | flags = Dict(base_flags) |
|
86 | flags = Dict(base_flags) | |
86 | classes = List([ProfileDir]) |
|
87 | classes = List([ProfileDir]) | |
87 |
|
88 | |||
88 | # Track whether the config_file has changed, |
|
89 | # Track whether the config_file has changed, | |
89 | # because some logic happens only if we aren't using the default. |
|
90 | # because some logic happens only if we aren't using the default. | |
90 | config_file_specified = Set() |
|
91 | config_file_specified = Set() | |
91 |
|
92 | |||
92 | config_file_name = Unicode() |
|
93 | config_file_name = Unicode() | |
93 | def _config_file_name_default(self): |
|
94 | def _config_file_name_default(self): | |
94 | return self.name.replace('-','_') + u'_config.py' |
|
95 | return self.name.replace('-','_') + u'_config.py' | |
95 | def _config_file_name_changed(self, name, old, new): |
|
96 | def _config_file_name_changed(self, name, old, new): | |
96 | if new != old: |
|
97 | if new != old: | |
97 | self.config_file_specified.add(new) |
|
98 | self.config_file_specified.add(new) | |
98 |
|
99 | |||
99 | # The directory that contains IPython's builtin profiles. |
|
100 | # The directory that contains IPython's builtin profiles. | |
100 | builtin_profile_dir = Unicode( |
|
101 | builtin_profile_dir = Unicode( | |
101 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') |
|
102 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') | |
102 | ) |
|
103 | ) | |
103 |
|
104 | |||
104 | config_file_paths = List(Unicode) |
|
105 | config_file_paths = List(Unicode) | |
105 | def _config_file_paths_default(self): |
|
106 | def _config_file_paths_default(self): | |
106 |
return [ |
|
107 | return [py3compat.getcwd()] | |
107 |
|
108 | |||
108 | extra_config_file = Unicode(config=True, |
|
109 | extra_config_file = Unicode(config=True, | |
109 | help="""Path to an extra config file to load. |
|
110 | help="""Path to an extra config file to load. | |
110 |
|
111 | |||
111 | If specified, load this config file in addition to any other IPython config. |
|
112 | If specified, load this config file in addition to any other IPython config. | |
112 | """) |
|
113 | """) | |
113 | def _extra_config_file_changed(self, name, old, new): |
|
114 | def _extra_config_file_changed(self, name, old, new): | |
114 | try: |
|
115 | try: | |
115 | self.config_files.remove(old) |
|
116 | self.config_files.remove(old) | |
116 | except ValueError: |
|
117 | except ValueError: | |
117 | pass |
|
118 | pass | |
118 | self.config_file_specified.add(new) |
|
119 | self.config_file_specified.add(new) | |
119 | self.config_files.append(new) |
|
120 | self.config_files.append(new) | |
120 |
|
121 | |||
121 | profile = Unicode(u'default', config=True, |
|
122 | profile = Unicode(u'default', config=True, | |
122 | help="""The IPython profile to use.""" |
|
123 | help="""The IPython profile to use.""" | |
123 | ) |
|
124 | ) | |
124 |
|
125 | |||
125 | def _profile_changed(self, name, old, new): |
|
126 | def _profile_changed(self, name, old, new): | |
126 | self.builtin_profile_dir = os.path.join( |
|
127 | self.builtin_profile_dir = os.path.join( | |
127 | get_ipython_package_dir(), u'config', u'profile', new |
|
128 | get_ipython_package_dir(), u'config', u'profile', new | |
128 | ) |
|
129 | ) | |
129 |
|
130 | |||
130 | ipython_dir = Unicode(config=True, |
|
131 | ipython_dir = Unicode(config=True, | |
131 | help=""" |
|
132 | help=""" | |
132 | The name of the IPython directory. This directory is used for logging |
|
133 | The name of the IPython directory. This directory is used for logging | |
133 | configuration (through profiles), history storage, etc. The default |
|
134 | configuration (through profiles), history storage, etc. The default | |
134 | is usually $HOME/.ipython. This options can also be specified through |
|
135 | is usually $HOME/.ipython. This options can also be specified through | |
135 | the environment variable IPYTHONDIR. |
|
136 | the environment variable IPYTHONDIR. | |
136 | """ |
|
137 | """ | |
137 | ) |
|
138 | ) | |
138 | def _ipython_dir_default(self): |
|
139 | def _ipython_dir_default(self): | |
139 | d = get_ipython_dir() |
|
140 | d = get_ipython_dir() | |
140 | self._ipython_dir_changed('ipython_dir', d, d) |
|
141 | self._ipython_dir_changed('ipython_dir', d, d) | |
141 | return d |
|
142 | return d | |
142 |
|
143 | |||
143 | _in_init_profile_dir = False |
|
144 | _in_init_profile_dir = False | |
144 | profile_dir = Instance(ProfileDir) |
|
145 | profile_dir = Instance(ProfileDir) | |
145 | def _profile_dir_default(self): |
|
146 | def _profile_dir_default(self): | |
146 | # avoid recursion |
|
147 | # avoid recursion | |
147 | if self._in_init_profile_dir: |
|
148 | if self._in_init_profile_dir: | |
148 | return |
|
149 | return | |
149 | # profile_dir requested early, force initialization |
|
150 | # profile_dir requested early, force initialization | |
150 | self.init_profile_dir() |
|
151 | self.init_profile_dir() | |
151 | return self.profile_dir |
|
152 | return self.profile_dir | |
152 |
|
153 | |||
153 | overwrite = Bool(False, config=True, |
|
154 | overwrite = Bool(False, config=True, | |
154 | help="""Whether to overwrite existing config files when copying""") |
|
155 | help="""Whether to overwrite existing config files when copying""") | |
155 | auto_create = Bool(False, config=True, |
|
156 | auto_create = Bool(False, config=True, | |
156 | help="""Whether to create profile dir if it doesn't exist""") |
|
157 | help="""Whether to create profile dir if it doesn't exist""") | |
157 |
|
158 | |||
158 | config_files = List(Unicode) |
|
159 | config_files = List(Unicode) | |
159 | def _config_files_default(self): |
|
160 | def _config_files_default(self): | |
160 | return [self.config_file_name] |
|
161 | return [self.config_file_name] | |
161 |
|
162 | |||
162 | copy_config_files = Bool(False, config=True, |
|
163 | copy_config_files = Bool(False, config=True, | |
163 | help="""Whether to install the default config files into the profile dir. |
|
164 | help="""Whether to install the default config files into the profile dir. | |
164 | If a new profile is being created, and IPython contains config files for that |
|
165 | If a new profile is being created, and IPython contains config files for that | |
165 | profile, then they will be staged into the new directory. Otherwise, |
|
166 | profile, then they will be staged into the new directory. Otherwise, | |
166 | default config files will be automatically generated. |
|
167 | default config files will be automatically generated. | |
167 | """) |
|
168 | """) | |
168 |
|
169 | |||
169 | verbose_crash = Bool(False, config=True, |
|
170 | verbose_crash = Bool(False, config=True, | |
170 | help="""Create a massive crash report when IPython encounters what may be an |
|
171 | help="""Create a massive crash report when IPython encounters what may be an | |
171 | internal error. The default is to append a short message to the |
|
172 | internal error. The default is to append a short message to the | |
172 | usual traceback""") |
|
173 | usual traceback""") | |
173 |
|
174 | |||
174 | # The class to use as the crash handler. |
|
175 | # The class to use as the crash handler. | |
175 | crash_handler_class = Type(crashhandler.CrashHandler) |
|
176 | crash_handler_class = Type(crashhandler.CrashHandler) | |
176 |
|
177 | |||
177 | @catch_config_error |
|
178 | @catch_config_error | |
178 | def __init__(self, **kwargs): |
|
179 | def __init__(self, **kwargs): | |
179 | super(BaseIPythonApplication, self).__init__(**kwargs) |
|
180 | super(BaseIPythonApplication, self).__init__(**kwargs) | |
180 | # ensure current working directory exists |
|
181 | # ensure current working directory exists | |
181 | try: |
|
182 | try: | |
182 |
directory = |
|
183 | directory = py3compat.getcwd() | |
183 | except: |
|
184 | except: | |
184 | # raise exception |
|
185 | # raise exception | |
185 | self.log.error("Current working directory doesn't exist.") |
|
186 | self.log.error("Current working directory doesn't exist.") | |
186 | raise |
|
187 | raise | |
187 |
|
188 | |||
188 | #------------------------------------------------------------------------- |
|
189 | #------------------------------------------------------------------------- | |
189 | # Various stages of Application creation |
|
190 | # Various stages of Application creation | |
190 | #------------------------------------------------------------------------- |
|
191 | #------------------------------------------------------------------------- | |
191 |
|
192 | |||
192 | def init_crash_handler(self): |
|
193 | def init_crash_handler(self): | |
193 | """Create a crash handler, typically setting sys.excepthook to it.""" |
|
194 | """Create a crash handler, typically setting sys.excepthook to it.""" | |
194 | self.crash_handler = self.crash_handler_class(self) |
|
195 | self.crash_handler = self.crash_handler_class(self) | |
195 | sys.excepthook = self.excepthook |
|
196 | sys.excepthook = self.excepthook | |
196 | def unset_crashhandler(): |
|
197 | def unset_crashhandler(): | |
197 | sys.excepthook = sys.__excepthook__ |
|
198 | sys.excepthook = sys.__excepthook__ | |
198 | atexit.register(unset_crashhandler) |
|
199 | atexit.register(unset_crashhandler) | |
199 |
|
200 | |||
200 | def excepthook(self, etype, evalue, tb): |
|
201 | def excepthook(self, etype, evalue, tb): | |
201 | """this is sys.excepthook after init_crashhandler |
|
202 | """this is sys.excepthook after init_crashhandler | |
202 |
|
203 | |||
203 | set self.verbose_crash=True to use our full crashhandler, instead of |
|
204 | set self.verbose_crash=True to use our full crashhandler, instead of | |
204 | a regular traceback with a short message (crash_handler_lite) |
|
205 | a regular traceback with a short message (crash_handler_lite) | |
205 | """ |
|
206 | """ | |
206 |
|
207 | |||
207 | if self.verbose_crash: |
|
208 | if self.verbose_crash: | |
208 | return self.crash_handler(etype, evalue, tb) |
|
209 | return self.crash_handler(etype, evalue, tb) | |
209 | else: |
|
210 | else: | |
210 | return crashhandler.crash_handler_lite(etype, evalue, tb) |
|
211 | return crashhandler.crash_handler_lite(etype, evalue, tb) | |
211 |
|
212 | |||
212 | def _ipython_dir_changed(self, name, old, new): |
|
213 | def _ipython_dir_changed(self, name, old, new): | |
213 | if old in sys.path: |
|
214 | if old in sys.path: | |
214 | sys.path.remove(old) |
|
215 | sys.path.remove(old) | |
215 | sys.path.append(os.path.abspath(new)) |
|
216 | sys.path.append(os.path.abspath(new)) | |
216 | if not os.path.isdir(new): |
|
217 | if not os.path.isdir(new): | |
217 | os.makedirs(new, mode=0o777) |
|
218 | os.makedirs(new, mode=0o777) | |
218 | readme = os.path.join(new, 'README') |
|
219 | readme = os.path.join(new, 'README') | |
219 | readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') |
|
220 | readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') | |
220 | if not os.path.exists(readme) and os.path.exists(readme_src): |
|
221 | if not os.path.exists(readme) and os.path.exists(readme_src): | |
221 | shutil.copy(readme_src, readme) |
|
222 | shutil.copy(readme_src, readme) | |
222 | for d in ('extensions', 'nbextensions'): |
|
223 | for d in ('extensions', 'nbextensions'): | |
223 | path = os.path.join(new, d) |
|
224 | path = os.path.join(new, d) | |
224 | if not os.path.exists(path): |
|
225 | if not os.path.exists(path): | |
225 | try: |
|
226 | try: | |
226 | os.mkdir(path) |
|
227 | os.mkdir(path) | |
227 | except OSError as e: |
|
228 | except OSError as e: | |
228 | if e.errno != errno.EEXIST: |
|
229 | if e.errno != errno.EEXIST: | |
229 | self.log.error("couldn't create path %s: %s", path, e) |
|
230 | self.log.error("couldn't create path %s: %s", path, e) | |
230 | self.log.debug("IPYTHONDIR set to: %s" % new) |
|
231 | self.log.debug("IPYTHONDIR set to: %s" % new) | |
231 |
|
232 | |||
232 | def load_config_file(self, suppress_errors=True): |
|
233 | def load_config_file(self, suppress_errors=True): | |
233 | """Load the config file. |
|
234 | """Load the config file. | |
234 |
|
235 | |||
235 | By default, errors in loading config are handled, and a warning |
|
236 | By default, errors in loading config are handled, and a warning | |
236 | printed on screen. For testing, the suppress_errors option is set |
|
237 | printed on screen. For testing, the suppress_errors option is set | |
237 | to False, so errors will make tests fail. |
|
238 | to False, so errors will make tests fail. | |
238 | """ |
|
239 | """ | |
239 | self.log.debug("Searching path %s for config files", self.config_file_paths) |
|
240 | self.log.debug("Searching path %s for config files", self.config_file_paths) | |
240 | base_config = 'ipython_config.py' |
|
241 | base_config = 'ipython_config.py' | |
241 | self.log.debug("Attempting to load config file: %s" % |
|
242 | self.log.debug("Attempting to load config file: %s" % | |
242 | base_config) |
|
243 | base_config) | |
243 | try: |
|
244 | try: | |
244 | Application.load_config_file( |
|
245 | Application.load_config_file( | |
245 | self, |
|
246 | self, | |
246 | base_config, |
|
247 | base_config, | |
247 | path=self.config_file_paths |
|
248 | path=self.config_file_paths | |
248 | ) |
|
249 | ) | |
249 | except ConfigFileNotFound: |
|
250 | except ConfigFileNotFound: | |
250 | # ignore errors loading parent |
|
251 | # ignore errors loading parent | |
251 | self.log.debug("Config file %s not found", base_config) |
|
252 | self.log.debug("Config file %s not found", base_config) | |
252 | pass |
|
253 | pass | |
253 |
|
254 | |||
254 | for config_file_name in self.config_files: |
|
255 | for config_file_name in self.config_files: | |
255 | if not config_file_name or config_file_name == base_config: |
|
256 | if not config_file_name or config_file_name == base_config: | |
256 | continue |
|
257 | continue | |
257 | self.log.debug("Attempting to load config file: %s" % |
|
258 | self.log.debug("Attempting to load config file: %s" % | |
258 | self.config_file_name) |
|
259 | self.config_file_name) | |
259 | try: |
|
260 | try: | |
260 | Application.load_config_file( |
|
261 | Application.load_config_file( | |
261 | self, |
|
262 | self, | |
262 | config_file_name, |
|
263 | config_file_name, | |
263 | path=self.config_file_paths |
|
264 | path=self.config_file_paths | |
264 | ) |
|
265 | ) | |
265 | except ConfigFileNotFound: |
|
266 | except ConfigFileNotFound: | |
266 | # Only warn if the default config file was NOT being used. |
|
267 | # Only warn if the default config file was NOT being used. | |
267 | if config_file_name in self.config_file_specified: |
|
268 | if config_file_name in self.config_file_specified: | |
268 | msg = self.log.warn |
|
269 | msg = self.log.warn | |
269 | else: |
|
270 | else: | |
270 | msg = self.log.debug |
|
271 | msg = self.log.debug | |
271 | msg("Config file not found, skipping: %s", config_file_name) |
|
272 | msg("Config file not found, skipping: %s", config_file_name) | |
272 | except: |
|
273 | except: | |
273 | # For testing purposes. |
|
274 | # For testing purposes. | |
274 | if not suppress_errors: |
|
275 | if not suppress_errors: | |
275 | raise |
|
276 | raise | |
276 | self.log.warn("Error loading config file: %s" % |
|
277 | self.log.warn("Error loading config file: %s" % | |
277 | self.config_file_name, exc_info=True) |
|
278 | self.config_file_name, exc_info=True) | |
278 |
|
279 | |||
279 | def init_profile_dir(self): |
|
280 | def init_profile_dir(self): | |
280 | """initialize the profile dir""" |
|
281 | """initialize the profile dir""" | |
281 | self._in_init_profile_dir = True |
|
282 | self._in_init_profile_dir = True | |
282 | if self.profile_dir is not None: |
|
283 | if self.profile_dir is not None: | |
283 | # already ran |
|
284 | # already ran | |
284 | return |
|
285 | return | |
285 | if 'ProfileDir.location' not in self.config: |
|
286 | if 'ProfileDir.location' not in self.config: | |
286 | # location not specified, find by profile name |
|
287 | # location not specified, find by profile name | |
287 | try: |
|
288 | try: | |
288 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
289 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) | |
289 | except ProfileDirError: |
|
290 | except ProfileDirError: | |
290 | # not found, maybe create it (always create default profile) |
|
291 | # not found, maybe create it (always create default profile) | |
291 | if self.auto_create or self.profile == 'default': |
|
292 | if self.auto_create or self.profile == 'default': | |
292 | try: |
|
293 | try: | |
293 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
294 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) | |
294 | except ProfileDirError: |
|
295 | except ProfileDirError: | |
295 | self.log.fatal("Could not create profile: %r"%self.profile) |
|
296 | self.log.fatal("Could not create profile: %r"%self.profile) | |
296 | self.exit(1) |
|
297 | self.exit(1) | |
297 | else: |
|
298 | else: | |
298 | self.log.info("Created profile dir: %r"%p.location) |
|
299 | self.log.info("Created profile dir: %r"%p.location) | |
299 | else: |
|
300 | else: | |
300 | self.log.fatal("Profile %r not found."%self.profile) |
|
301 | self.log.fatal("Profile %r not found."%self.profile) | |
301 | self.exit(1) |
|
302 | self.exit(1) | |
302 | else: |
|
303 | else: | |
303 | self.log.info("Using existing profile dir: %r"%p.location) |
|
304 | self.log.info("Using existing profile dir: %r"%p.location) | |
304 | else: |
|
305 | else: | |
305 | location = self.config.ProfileDir.location |
|
306 | location = self.config.ProfileDir.location | |
306 | # location is fully specified |
|
307 | # location is fully specified | |
307 | try: |
|
308 | try: | |
308 | p = ProfileDir.find_profile_dir(location, self.config) |
|
309 | p = ProfileDir.find_profile_dir(location, self.config) | |
309 | except ProfileDirError: |
|
310 | except ProfileDirError: | |
310 | # not found, maybe create it |
|
311 | # not found, maybe create it | |
311 | if self.auto_create: |
|
312 | if self.auto_create: | |
312 | try: |
|
313 | try: | |
313 | p = ProfileDir.create_profile_dir(location, self.config) |
|
314 | p = ProfileDir.create_profile_dir(location, self.config) | |
314 | except ProfileDirError: |
|
315 | except ProfileDirError: | |
315 | self.log.fatal("Could not create profile directory: %r"%location) |
|
316 | self.log.fatal("Could not create profile directory: %r"%location) | |
316 | self.exit(1) |
|
317 | self.exit(1) | |
317 | else: |
|
318 | else: | |
318 | self.log.info("Creating new profile dir: %r"%location) |
|
319 | self.log.info("Creating new profile dir: %r"%location) | |
319 | else: |
|
320 | else: | |
320 | self.log.fatal("Profile directory %r not found."%location) |
|
321 | self.log.fatal("Profile directory %r not found."%location) | |
321 | self.exit(1) |
|
322 | self.exit(1) | |
322 | else: |
|
323 | else: | |
323 | self.log.info("Using existing profile dir: %r"%location) |
|
324 | self.log.info("Using existing profile dir: %r"%location) | |
324 |
|
325 | |||
325 | self.profile_dir = p |
|
326 | self.profile_dir = p | |
326 | self.config_file_paths.append(p.location) |
|
327 | self.config_file_paths.append(p.location) | |
327 | self._in_init_profile_dir = False |
|
328 | self._in_init_profile_dir = False | |
328 |
|
329 | |||
329 | def init_config_files(self): |
|
330 | def init_config_files(self): | |
330 | """[optionally] copy default config files into profile dir.""" |
|
331 | """[optionally] copy default config files into profile dir.""" | |
331 | # copy config files |
|
332 | # copy config files | |
332 | path = self.builtin_profile_dir |
|
333 | path = self.builtin_profile_dir | |
333 | if self.copy_config_files: |
|
334 | if self.copy_config_files: | |
334 | src = self.profile |
|
335 | src = self.profile | |
335 |
|
336 | |||
336 | cfg = self.config_file_name |
|
337 | cfg = self.config_file_name | |
337 | if path and os.path.exists(os.path.join(path, cfg)): |
|
338 | if path and os.path.exists(os.path.join(path, cfg)): | |
338 | self.log.warn("Staging %r from %s into %r [overwrite=%s]"%( |
|
339 | self.log.warn("Staging %r from %s into %r [overwrite=%s]"%( | |
339 | cfg, src, self.profile_dir.location, self.overwrite) |
|
340 | cfg, src, self.profile_dir.location, self.overwrite) | |
340 | ) |
|
341 | ) | |
341 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) |
|
342 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) | |
342 | else: |
|
343 | else: | |
343 | self.stage_default_config_file() |
|
344 | self.stage_default_config_file() | |
344 | else: |
|
345 | else: | |
345 | # Still stage *bundled* config files, but not generated ones |
|
346 | # Still stage *bundled* config files, but not generated ones | |
346 | # This is necessary for `ipython profile=sympy` to load the profile |
|
347 | # This is necessary for `ipython profile=sympy` to load the profile | |
347 | # on the first go |
|
348 | # on the first go | |
348 | files = glob.glob(os.path.join(path, '*.py')) |
|
349 | files = glob.glob(os.path.join(path, '*.py')) | |
349 | for fullpath in files: |
|
350 | for fullpath in files: | |
350 | cfg = os.path.basename(fullpath) |
|
351 | cfg = os.path.basename(fullpath) | |
351 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): |
|
352 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): | |
352 | # file was copied |
|
353 | # file was copied | |
353 | self.log.warn("Staging bundled %s from %s into %r"%( |
|
354 | self.log.warn("Staging bundled %s from %s into %r"%( | |
354 | cfg, self.profile, self.profile_dir.location) |
|
355 | cfg, self.profile, self.profile_dir.location) | |
355 | ) |
|
356 | ) | |
356 |
|
357 | |||
357 |
|
358 | |||
358 | def stage_default_config_file(self): |
|
359 | def stage_default_config_file(self): | |
359 | """auto generate default config file, and stage it into the profile.""" |
|
360 | """auto generate default config file, and stage it into the profile.""" | |
360 | s = self.generate_config_file() |
|
361 | s = self.generate_config_file() | |
361 | fname = os.path.join(self.profile_dir.location, self.config_file_name) |
|
362 | fname = os.path.join(self.profile_dir.location, self.config_file_name) | |
362 | if self.overwrite or not os.path.exists(fname): |
|
363 | if self.overwrite or not os.path.exists(fname): | |
363 | self.log.warn("Generating default config file: %r"%(fname)) |
|
364 | self.log.warn("Generating default config file: %r"%(fname)) | |
364 | with open(fname, 'w') as f: |
|
365 | with open(fname, 'w') as f: | |
365 | f.write(s) |
|
366 | f.write(s) | |
366 |
|
367 | |||
367 | @catch_config_error |
|
368 | @catch_config_error | |
368 | def initialize(self, argv=None): |
|
369 | def initialize(self, argv=None): | |
369 | # don't hook up crash handler before parsing command-line |
|
370 | # don't hook up crash handler before parsing command-line | |
370 | self.parse_command_line(argv) |
|
371 | self.parse_command_line(argv) | |
371 | self.init_crash_handler() |
|
372 | self.init_crash_handler() | |
372 | if self.subapp is not None: |
|
373 | if self.subapp is not None: | |
373 | # stop here if subapp is taking over |
|
374 | # stop here if subapp is taking over | |
374 | return |
|
375 | return | |
375 | cl_config = self.config |
|
376 | cl_config = self.config | |
376 | self.init_profile_dir() |
|
377 | self.init_profile_dir() | |
377 | self.init_config_files() |
|
378 | self.init_config_files() | |
378 | self.load_config_file() |
|
379 | self.load_config_file() | |
379 | # enforce cl-opts override configfile opts: |
|
380 | # enforce cl-opts override configfile opts: | |
380 | self.update_config(cl_config) |
|
381 | self.update_config(cl_config) | |
381 |
|
382 |
@@ -1,216 +1,216 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """sys.excepthook for IPython itself, leaves a detailed report on disk. |
|
2 | """sys.excepthook for IPython itself, leaves a detailed report on disk. | |
3 |
|
3 | |||
4 | Authors: |
|
4 | Authors: | |
5 |
|
5 | |||
6 | * Fernando Perez |
|
6 | * Fernando Perez | |
7 | * Brian E. Granger |
|
7 | * Brian E. Granger | |
8 | """ |
|
8 | """ | |
9 |
|
9 | |||
10 | #----------------------------------------------------------------------------- |
|
10 | #----------------------------------------------------------------------------- | |
11 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> |
|
11 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> | |
12 | # Copyright (C) 2008-2011 The IPython Development Team |
|
12 | # Copyright (C) 2008-2011 The IPython Development Team | |
13 | # |
|
13 | # | |
14 | # Distributed under the terms of the BSD License. The full license is in |
|
14 | # Distributed under the terms of the BSD License. The full license is in | |
15 | # the file COPYING, distributed as part of this software. |
|
15 | # the file COPYING, distributed as part of this software. | |
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 |
|
17 | |||
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 | # Imports |
|
19 | # Imports | |
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
21 | from __future__ import print_function |
|
21 | from __future__ import print_function | |
22 |
|
22 | |||
23 | import os |
|
23 | import os | |
24 | import sys |
|
24 | import sys | |
25 | import traceback |
|
25 | import traceback | |
26 | from pprint import pformat |
|
26 | from pprint import pformat | |
27 |
|
27 | |||
28 | from IPython.core import ultratb |
|
28 | from IPython.core import ultratb | |
29 | from IPython.core.release import author_email |
|
29 | from IPython.core.release import author_email | |
30 | from IPython.utils.sysinfo import sys_info |
|
30 | from IPython.utils.sysinfo import sys_info | |
31 | from IPython.utils.py3compat import input |
|
31 | from IPython.utils.py3compat import input, getcwd | |
32 |
|
32 | |||
33 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
34 | # Code |
|
34 | # Code | |
35 | #----------------------------------------------------------------------------- |
|
35 | #----------------------------------------------------------------------------- | |
36 |
|
36 | |||
37 | # Template for the user message. |
|
37 | # Template for the user message. | |
38 | _default_message_template = """\ |
|
38 | _default_message_template = """\ | |
39 | Oops, {app_name} crashed. We do our best to make it stable, but... |
|
39 | Oops, {app_name} crashed. We do our best to make it stable, but... | |
40 |
|
40 | |||
41 | A crash report was automatically generated with the following information: |
|
41 | A crash report was automatically generated with the following information: | |
42 | - A verbatim copy of the crash traceback. |
|
42 | - A verbatim copy of the crash traceback. | |
43 | - A copy of your input history during this session. |
|
43 | - A copy of your input history during this session. | |
44 | - Data on your current {app_name} configuration. |
|
44 | - Data on your current {app_name} configuration. | |
45 |
|
45 | |||
46 | It was left in the file named: |
|
46 | It was left in the file named: | |
47 | \t'{crash_report_fname}' |
|
47 | \t'{crash_report_fname}' | |
48 | If you can email this file to the developers, the information in it will help |
|
48 | If you can email this file to the developers, the information in it will help | |
49 | them in understanding and correcting the problem. |
|
49 | them in understanding and correcting the problem. | |
50 |
|
50 | |||
51 | You can mail it to: {contact_name} at {contact_email} |
|
51 | You can mail it to: {contact_name} at {contact_email} | |
52 | with the subject '{app_name} Crash Report'. |
|
52 | with the subject '{app_name} Crash Report'. | |
53 |
|
53 | |||
54 | If you want to do it now, the following command will work (under Unix): |
|
54 | If you want to do it now, the following command will work (under Unix): | |
55 | mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} |
|
55 | mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} | |
56 |
|
56 | |||
57 | To ensure accurate tracking of this issue, please file a report about it at: |
|
57 | To ensure accurate tracking of this issue, please file a report about it at: | |
58 | {bug_tracker} |
|
58 | {bug_tracker} | |
59 | """ |
|
59 | """ | |
60 |
|
60 | |||
61 | _lite_message_template = """ |
|
61 | _lite_message_template = """ | |
62 | If you suspect this is an IPython bug, please report it at: |
|
62 | If you suspect this is an IPython bug, please report it at: | |
63 | https://github.com/ipython/ipython/issues |
|
63 | https://github.com/ipython/ipython/issues | |
64 | or send an email to the mailing list at {email} |
|
64 | or send an email to the mailing list at {email} | |
65 |
|
65 | |||
66 | You can print a more detailed traceback right now with "%tb", or use "%debug" |
|
66 | You can print a more detailed traceback right now with "%tb", or use "%debug" | |
67 | to interactively debug it. |
|
67 | to interactively debug it. | |
68 |
|
68 | |||
69 | Extra-detailed tracebacks for bug-reporting purposes can be enabled via: |
|
69 | Extra-detailed tracebacks for bug-reporting purposes can be enabled via: | |
70 | {config}Application.verbose_crash=True |
|
70 | {config}Application.verbose_crash=True | |
71 | """ |
|
71 | """ | |
72 |
|
72 | |||
73 |
|
73 | |||
74 | class CrashHandler(object): |
|
74 | class CrashHandler(object): | |
75 | """Customizable crash handlers for IPython applications. |
|
75 | """Customizable crash handlers for IPython applications. | |
76 |
|
76 | |||
77 | Instances of this class provide a :meth:`__call__` method which can be |
|
77 | Instances of this class provide a :meth:`__call__` method which can be | |
78 | used as a ``sys.excepthook``. The :meth:`__call__` signature is:: |
|
78 | used as a ``sys.excepthook``. The :meth:`__call__` signature is:: | |
79 |
|
79 | |||
80 | def __call__(self, etype, evalue, etb) |
|
80 | def __call__(self, etype, evalue, etb) | |
81 | """ |
|
81 | """ | |
82 |
|
82 | |||
83 | message_template = _default_message_template |
|
83 | message_template = _default_message_template | |
84 | section_sep = '\n\n'+'*'*75+'\n\n' |
|
84 | section_sep = '\n\n'+'*'*75+'\n\n' | |
85 |
|
85 | |||
86 | def __init__(self, app, contact_name=None, contact_email=None, |
|
86 | def __init__(self, app, contact_name=None, contact_email=None, | |
87 | bug_tracker=None, show_crash_traceback=True, call_pdb=False): |
|
87 | bug_tracker=None, show_crash_traceback=True, call_pdb=False): | |
88 | """Create a new crash handler |
|
88 | """Create a new crash handler | |
89 |
|
89 | |||
90 | Parameters |
|
90 | Parameters | |
91 | ---------- |
|
91 | ---------- | |
92 | app : Application |
|
92 | app : Application | |
93 | A running :class:`Application` instance, which will be queried at |
|
93 | A running :class:`Application` instance, which will be queried at | |
94 | crash time for internal information. |
|
94 | crash time for internal information. | |
95 |
|
95 | |||
96 | contact_name : str |
|
96 | contact_name : str | |
97 | A string with the name of the person to contact. |
|
97 | A string with the name of the person to contact. | |
98 |
|
98 | |||
99 | contact_email : str |
|
99 | contact_email : str | |
100 | A string with the email address of the contact. |
|
100 | A string with the email address of the contact. | |
101 |
|
101 | |||
102 | bug_tracker : str |
|
102 | bug_tracker : str | |
103 | A string with the URL for your project's bug tracker. |
|
103 | A string with the URL for your project's bug tracker. | |
104 |
|
104 | |||
105 | show_crash_traceback : bool |
|
105 | show_crash_traceback : bool | |
106 | If false, don't print the crash traceback on stderr, only generate |
|
106 | If false, don't print the crash traceback on stderr, only generate | |
107 | the on-disk report |
|
107 | the on-disk report | |
108 |
|
108 | |||
109 | Non-argument instance attributes: |
|
109 | Non-argument instance attributes: | |
110 |
|
110 | |||
111 | These instances contain some non-argument attributes which allow for |
|
111 | These instances contain some non-argument attributes which allow for | |
112 | further customization of the crash handler's behavior. Please see the |
|
112 | further customization of the crash handler's behavior. Please see the | |
113 | source for further details. |
|
113 | source for further details. | |
114 | """ |
|
114 | """ | |
115 | self.crash_report_fname = "Crash_report_%s.txt" % app.name |
|
115 | self.crash_report_fname = "Crash_report_%s.txt" % app.name | |
116 | self.app = app |
|
116 | self.app = app | |
117 | self.call_pdb = call_pdb |
|
117 | self.call_pdb = call_pdb | |
118 | #self.call_pdb = True # dbg |
|
118 | #self.call_pdb = True # dbg | |
119 | self.show_crash_traceback = show_crash_traceback |
|
119 | self.show_crash_traceback = show_crash_traceback | |
120 | self.info = dict(app_name = app.name, |
|
120 | self.info = dict(app_name = app.name, | |
121 | contact_name = contact_name, |
|
121 | contact_name = contact_name, | |
122 | contact_email = contact_email, |
|
122 | contact_email = contact_email, | |
123 | bug_tracker = bug_tracker, |
|
123 | bug_tracker = bug_tracker, | |
124 | crash_report_fname = self.crash_report_fname) |
|
124 | crash_report_fname = self.crash_report_fname) | |
125 |
|
125 | |||
126 |
|
126 | |||
127 | def __call__(self, etype, evalue, etb): |
|
127 | def __call__(self, etype, evalue, etb): | |
128 | """Handle an exception, call for compatible with sys.excepthook""" |
|
128 | """Handle an exception, call for compatible with sys.excepthook""" | |
129 |
|
129 | |||
130 | # do not allow the crash handler to be called twice without reinstalling it |
|
130 | # do not allow the crash handler to be called twice without reinstalling it | |
131 | # this prevents unlikely errors in the crash handling from entering an |
|
131 | # this prevents unlikely errors in the crash handling from entering an | |
132 | # infinite loop. |
|
132 | # infinite loop. | |
133 | sys.excepthook = sys.__excepthook__ |
|
133 | sys.excepthook = sys.__excepthook__ | |
134 |
|
134 | |||
135 | # Report tracebacks shouldn't use color in general (safer for users) |
|
135 | # Report tracebacks shouldn't use color in general (safer for users) | |
136 | color_scheme = 'NoColor' |
|
136 | color_scheme = 'NoColor' | |
137 |
|
137 | |||
138 | # Use this ONLY for developer debugging (keep commented out for release) |
|
138 | # Use this ONLY for developer debugging (keep commented out for release) | |
139 | #color_scheme = 'Linux' # dbg |
|
139 | #color_scheme = 'Linux' # dbg | |
140 | try: |
|
140 | try: | |
141 | rptdir = self.app.ipython_dir |
|
141 | rptdir = self.app.ipython_dir | |
142 | except: |
|
142 | except: | |
143 |
rptdir = |
|
143 | rptdir = getcwd() | |
144 | if rptdir is None or not os.path.isdir(rptdir): |
|
144 | if rptdir is None or not os.path.isdir(rptdir): | |
145 |
rptdir = |
|
145 | rptdir = getcwd() | |
146 | report_name = os.path.join(rptdir,self.crash_report_fname) |
|
146 | report_name = os.path.join(rptdir,self.crash_report_fname) | |
147 | # write the report filename into the instance dict so it can get |
|
147 | # write the report filename into the instance dict so it can get | |
148 | # properly expanded out in the user message template |
|
148 | # properly expanded out in the user message template | |
149 | self.crash_report_fname = report_name |
|
149 | self.crash_report_fname = report_name | |
150 | self.info['crash_report_fname'] = report_name |
|
150 | self.info['crash_report_fname'] = report_name | |
151 | TBhandler = ultratb.VerboseTB( |
|
151 | TBhandler = ultratb.VerboseTB( | |
152 | color_scheme=color_scheme, |
|
152 | color_scheme=color_scheme, | |
153 | long_header=1, |
|
153 | long_header=1, | |
154 | call_pdb=self.call_pdb, |
|
154 | call_pdb=self.call_pdb, | |
155 | ) |
|
155 | ) | |
156 | if self.call_pdb: |
|
156 | if self.call_pdb: | |
157 | TBhandler(etype,evalue,etb) |
|
157 | TBhandler(etype,evalue,etb) | |
158 | return |
|
158 | return | |
159 | else: |
|
159 | else: | |
160 | traceback = TBhandler.text(etype,evalue,etb,context=31) |
|
160 | traceback = TBhandler.text(etype,evalue,etb,context=31) | |
161 |
|
161 | |||
162 | # print traceback to screen |
|
162 | # print traceback to screen | |
163 | if self.show_crash_traceback: |
|
163 | if self.show_crash_traceback: | |
164 | print(traceback, file=sys.stderr) |
|
164 | print(traceback, file=sys.stderr) | |
165 |
|
165 | |||
166 | # and generate a complete report on disk |
|
166 | # and generate a complete report on disk | |
167 | try: |
|
167 | try: | |
168 | report = open(report_name,'w') |
|
168 | report = open(report_name,'w') | |
169 | except: |
|
169 | except: | |
170 | print('Could not create crash report on disk.', file=sys.stderr) |
|
170 | print('Could not create crash report on disk.', file=sys.stderr) | |
171 | return |
|
171 | return | |
172 |
|
172 | |||
173 | # Inform user on stderr of what happened |
|
173 | # Inform user on stderr of what happened | |
174 | print('\n'+'*'*70+'\n', file=sys.stderr) |
|
174 | print('\n'+'*'*70+'\n', file=sys.stderr) | |
175 | print(self.message_template.format(**self.info), file=sys.stderr) |
|
175 | print(self.message_template.format(**self.info), file=sys.stderr) | |
176 |
|
176 | |||
177 | # Construct report on disk |
|
177 | # Construct report on disk | |
178 | report.write(self.make_report(traceback)) |
|
178 | report.write(self.make_report(traceback)) | |
179 | report.close() |
|
179 | report.close() | |
180 | input("Hit <Enter> to quit (your terminal may close):") |
|
180 | input("Hit <Enter> to quit (your terminal may close):") | |
181 |
|
181 | |||
182 | def make_report(self,traceback): |
|
182 | def make_report(self,traceback): | |
183 | """Return a string containing a crash report.""" |
|
183 | """Return a string containing a crash report.""" | |
184 |
|
184 | |||
185 | sec_sep = self.section_sep |
|
185 | sec_sep = self.section_sep | |
186 |
|
186 | |||
187 | report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] |
|
187 | report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] | |
188 | rpt_add = report.append |
|
188 | rpt_add = report.append | |
189 | rpt_add(sys_info()) |
|
189 | rpt_add(sys_info()) | |
190 |
|
190 | |||
191 | try: |
|
191 | try: | |
192 | config = pformat(self.app.config) |
|
192 | config = pformat(self.app.config) | |
193 | rpt_add(sec_sep) |
|
193 | rpt_add(sec_sep) | |
194 | rpt_add('Application name: %s\n\n' % self.app_name) |
|
194 | rpt_add('Application name: %s\n\n' % self.app_name) | |
195 | rpt_add('Current user configuration structure:\n\n') |
|
195 | rpt_add('Current user configuration structure:\n\n') | |
196 | rpt_add(config) |
|
196 | rpt_add(config) | |
197 | except: |
|
197 | except: | |
198 | pass |
|
198 | pass | |
199 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) |
|
199 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) | |
200 |
|
200 | |||
201 | return ''.join(report) |
|
201 | return ''.join(report) | |
202 |
|
202 | |||
203 |
|
203 | |||
204 | def crash_handler_lite(etype, evalue, tb): |
|
204 | def crash_handler_lite(etype, evalue, tb): | |
205 | """a light excepthook, adding a small message to the usual traceback""" |
|
205 | """a light excepthook, adding a small message to the usual traceback""" | |
206 | traceback.print_exception(etype, evalue, tb) |
|
206 | traceback.print_exception(etype, evalue, tb) | |
207 |
|
207 | |||
208 | from IPython.core.interactiveshell import InteractiveShell |
|
208 | from IPython.core.interactiveshell import InteractiveShell | |
209 | if InteractiveShell.initialized(): |
|
209 | if InteractiveShell.initialized(): | |
210 | # we are in a Shell environment, give %magic example |
|
210 | # we are in a Shell environment, give %magic example | |
211 | config = "%config " |
|
211 | config = "%config " | |
212 | else: |
|
212 | else: | |
213 | # we are not in a shell, show generic config |
|
213 | # we are not in a shell, show generic config | |
214 | config = "c." |
|
214 | config = "c." | |
215 | print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr) |
|
215 | print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr) | |
216 |
|
216 |
@@ -1,807 +1,808 b'' | |||||
1 | """ History related magics and functionality """ |
|
1 | """ History related magics and functionality """ | |
2 | #----------------------------------------------------------------------------- |
|
2 | #----------------------------------------------------------------------------- | |
3 | # Copyright (C) 2010-2011 The IPython Development Team. |
|
3 | # Copyright (C) 2010-2011 The IPython Development Team. | |
4 | # |
|
4 | # | |
5 | # Distributed under the terms of the BSD License. |
|
5 | # Distributed under the terms of the BSD License. | |
6 | # |
|
6 | # | |
7 | # The full license is in the file COPYING.txt, distributed with this software. |
|
7 | # The full license is in the file COPYING.txt, distributed with this software. | |
8 | #----------------------------------------------------------------------------- |
|
8 | #----------------------------------------------------------------------------- | |
9 |
|
9 | |||
10 | #----------------------------------------------------------------------------- |
|
10 | #----------------------------------------------------------------------------- | |
11 | # Imports |
|
11 | # Imports | |
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 | from __future__ import print_function |
|
13 | from __future__ import print_function | |
14 |
|
14 | |||
15 | # Stdlib imports |
|
15 | # Stdlib imports | |
16 | import atexit |
|
16 | import atexit | |
17 | import datetime |
|
17 | import datetime | |
18 | import os |
|
18 | import os | |
19 | import re |
|
19 | import re | |
20 | try: |
|
20 | try: | |
21 | import sqlite3 |
|
21 | import sqlite3 | |
22 | except ImportError: |
|
22 | except ImportError: | |
23 | try: |
|
23 | try: | |
24 | from pysqlite2 import dbapi2 as sqlite3 |
|
24 | from pysqlite2 import dbapi2 as sqlite3 | |
25 | except ImportError: |
|
25 | except ImportError: | |
26 | sqlite3 = None |
|
26 | sqlite3 = None | |
27 | import threading |
|
27 | import threading | |
28 |
|
28 | |||
29 | # Our own packages |
|
29 | # Our own packages | |
30 | from IPython.config.configurable import Configurable |
|
30 | from IPython.config.configurable import Configurable | |
31 | from IPython.external.decorator import decorator |
|
31 | from IPython.external.decorator import decorator | |
32 | from IPython.utils.path import locate_profile |
|
32 | from IPython.utils.path import locate_profile | |
|
33 | from IPython.utils import py3compat | |||
33 | from IPython.utils.traitlets import ( |
|
34 | from IPython.utils.traitlets import ( | |
34 | Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError, |
|
35 | Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError, | |
35 | ) |
|
36 | ) | |
36 | from IPython.utils.warn import warn |
|
37 | from IPython.utils.warn import warn | |
37 |
|
38 | |||
38 | #----------------------------------------------------------------------------- |
|
39 | #----------------------------------------------------------------------------- | |
39 | # Classes and functions |
|
40 | # Classes and functions | |
40 | #----------------------------------------------------------------------------- |
|
41 | #----------------------------------------------------------------------------- | |
41 |
|
42 | |||
42 | class DummyDB(object): |
|
43 | class DummyDB(object): | |
43 | """Dummy DB that will act as a black hole for history. |
|
44 | """Dummy DB that will act as a black hole for history. | |
44 |
|
45 | |||
45 | Only used in the absence of sqlite""" |
|
46 | Only used in the absence of sqlite""" | |
46 | def execute(*args, **kwargs): |
|
47 | def execute(*args, **kwargs): | |
47 | return [] |
|
48 | return [] | |
48 |
|
49 | |||
49 | def commit(self, *args, **kwargs): |
|
50 | def commit(self, *args, **kwargs): | |
50 | pass |
|
51 | pass | |
51 |
|
52 | |||
52 | def __enter__(self, *args, **kwargs): |
|
53 | def __enter__(self, *args, **kwargs): | |
53 | pass |
|
54 | pass | |
54 |
|
55 | |||
55 | def __exit__(self, *args, **kwargs): |
|
56 | def __exit__(self, *args, **kwargs): | |
56 | pass |
|
57 | pass | |
57 |
|
58 | |||
58 |
|
59 | |||
59 | @decorator |
|
60 | @decorator | |
60 | def needs_sqlite(f, self, *a, **kw): |
|
61 | def needs_sqlite(f, self, *a, **kw): | |
61 | """return an empty list in the absence of sqlite""" |
|
62 | """return an empty list in the absence of sqlite""" | |
62 | if sqlite3 is None or not self.enabled: |
|
63 | if sqlite3 is None or not self.enabled: | |
63 | return [] |
|
64 | return [] | |
64 | else: |
|
65 | else: | |
65 | return f(self, *a, **kw) |
|
66 | return f(self, *a, **kw) | |
66 |
|
67 | |||
67 |
|
68 | |||
68 | if sqlite3 is not None: |
|
69 | if sqlite3 is not None: | |
69 | DatabaseError = sqlite3.DatabaseError |
|
70 | DatabaseError = sqlite3.DatabaseError | |
70 | else: |
|
71 | else: | |
71 | class DatabaseError(Exception): |
|
72 | class DatabaseError(Exception): | |
72 | "Dummy exception when sqlite could not be imported. Should never occur." |
|
73 | "Dummy exception when sqlite could not be imported. Should never occur." | |
73 |
|
74 | |||
74 | @decorator |
|
75 | @decorator | |
75 | def catch_corrupt_db(f, self, *a, **kw): |
|
76 | def catch_corrupt_db(f, self, *a, **kw): | |
76 | """A decorator which wraps HistoryAccessor method calls to catch errors from |
|
77 | """A decorator which wraps HistoryAccessor method calls to catch errors from | |
77 | a corrupt SQLite database, move the old database out of the way, and create |
|
78 | a corrupt SQLite database, move the old database out of the way, and create | |
78 | a new one. |
|
79 | a new one. | |
79 | """ |
|
80 | """ | |
80 | try: |
|
81 | try: | |
81 | return f(self, *a, **kw) |
|
82 | return f(self, *a, **kw) | |
82 | except DatabaseError: |
|
83 | except DatabaseError: | |
83 | if os.path.isfile(self.hist_file): |
|
84 | if os.path.isfile(self.hist_file): | |
84 | # Try to move the file out of the way |
|
85 | # Try to move the file out of the way | |
85 | base,ext = os.path.splitext(self.hist_file) |
|
86 | base,ext = os.path.splitext(self.hist_file) | |
86 | newpath = base + '-corrupt' + ext |
|
87 | newpath = base + '-corrupt' + ext | |
87 | os.rename(self.hist_file, newpath) |
|
88 | os.rename(self.hist_file, newpath) | |
88 | self.init_db() |
|
89 | self.init_db() | |
89 | print("ERROR! History file wasn't a valid SQLite database.", |
|
90 | print("ERROR! History file wasn't a valid SQLite database.", | |
90 | "It was moved to %s" % newpath, "and a new file created.") |
|
91 | "It was moved to %s" % newpath, "and a new file created.") | |
91 | return [] |
|
92 | return [] | |
92 |
|
93 | |||
93 | else: |
|
94 | else: | |
94 | # The hist_file is probably :memory: or something else. |
|
95 | # The hist_file is probably :memory: or something else. | |
95 | raise |
|
96 | raise | |
96 |
|
97 | |||
97 |
|
98 | |||
98 |
|
99 | |||
99 | class HistoryAccessor(Configurable): |
|
100 | class HistoryAccessor(Configurable): | |
100 | """Access the history database without adding to it. |
|
101 | """Access the history database without adding to it. | |
101 |
|
102 | |||
102 | This is intended for use by standalone history tools. IPython shells use |
|
103 | This is intended for use by standalone history tools. IPython shells use | |
103 | HistoryManager, below, which is a subclass of this.""" |
|
104 | HistoryManager, below, which is a subclass of this.""" | |
104 |
|
105 | |||
105 | # String holding the path to the history file |
|
106 | # String holding the path to the history file | |
106 | hist_file = Unicode(config=True, |
|
107 | hist_file = Unicode(config=True, | |
107 | help="""Path to file to use for SQLite history database. |
|
108 | help="""Path to file to use for SQLite history database. | |
108 |
|
109 | |||
109 | By default, IPython will put the history database in the IPython |
|
110 | By default, IPython will put the history database in the IPython | |
110 | profile directory. If you would rather share one history among |
|
111 | profile directory. If you would rather share one history among | |
111 | profiles, you can set this value in each, so that they are consistent. |
|
112 | profiles, you can set this value in each, so that they are consistent. | |
112 |
|
113 | |||
113 | Due to an issue with fcntl, SQLite is known to misbehave on some NFS |
|
114 | Due to an issue with fcntl, SQLite is known to misbehave on some NFS | |
114 | mounts. If you see IPython hanging, try setting this to something on a |
|
115 | mounts. If you see IPython hanging, try setting this to something on a | |
115 | local disk, e.g:: |
|
116 | local disk, e.g:: | |
116 |
|
117 | |||
117 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite |
|
118 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite | |
118 |
|
119 | |||
119 | """) |
|
120 | """) | |
120 |
|
121 | |||
121 | enabled = Bool(True, config=True, |
|
122 | enabled = Bool(True, config=True, | |
122 | help="""enable the SQLite history |
|
123 | help="""enable the SQLite history | |
123 |
|
124 | |||
124 | set enabled=False to disable the SQLite history, |
|
125 | set enabled=False to disable the SQLite history, | |
125 | in which case there will be no stored history, no SQLite connection, |
|
126 | in which case there will be no stored history, no SQLite connection, | |
126 | and no background saving thread. This may be necessary in some |
|
127 | and no background saving thread. This may be necessary in some | |
127 | threaded environments where IPython is embedded. |
|
128 | threaded environments where IPython is embedded. | |
128 | """ |
|
129 | """ | |
129 | ) |
|
130 | ) | |
130 |
|
131 | |||
131 | connection_options = Dict(config=True, |
|
132 | connection_options = Dict(config=True, | |
132 | help="""Options for configuring the SQLite connection |
|
133 | help="""Options for configuring the SQLite connection | |
133 |
|
134 | |||
134 | These options are passed as keyword args to sqlite3.connect |
|
135 | These options are passed as keyword args to sqlite3.connect | |
135 | when establishing database conenctions. |
|
136 | when establishing database conenctions. | |
136 | """ |
|
137 | """ | |
137 | ) |
|
138 | ) | |
138 |
|
139 | |||
139 | # The SQLite database |
|
140 | # The SQLite database | |
140 | db = Any() |
|
141 | db = Any() | |
141 | def _db_changed(self, name, old, new): |
|
142 | def _db_changed(self, name, old, new): | |
142 | """validate the db, since it can be an Instance of two different types""" |
|
143 | """validate the db, since it can be an Instance of two different types""" | |
143 | connection_types = (DummyDB,) |
|
144 | connection_types = (DummyDB,) | |
144 | if sqlite3 is not None: |
|
145 | if sqlite3 is not None: | |
145 | connection_types = (DummyDB, sqlite3.Connection) |
|
146 | connection_types = (DummyDB, sqlite3.Connection) | |
146 | if not isinstance(new, connection_types): |
|
147 | if not isinstance(new, connection_types): | |
147 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ |
|
148 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ | |
148 | (self.__class__.__name__, new) |
|
149 | (self.__class__.__name__, new) | |
149 | raise TraitError(msg) |
|
150 | raise TraitError(msg) | |
150 |
|
151 | |||
151 | def __init__(self, profile='default', hist_file=u'', **traits): |
|
152 | def __init__(self, profile='default', hist_file=u'', **traits): | |
152 | """Create a new history accessor. |
|
153 | """Create a new history accessor. | |
153 |
|
154 | |||
154 | Parameters |
|
155 | Parameters | |
155 | ---------- |
|
156 | ---------- | |
156 | profile : str |
|
157 | profile : str | |
157 | The name of the profile from which to open history. |
|
158 | The name of the profile from which to open history. | |
158 | hist_file : str |
|
159 | hist_file : str | |
159 | Path to an SQLite history database stored by IPython. If specified, |
|
160 | Path to an SQLite history database stored by IPython. If specified, | |
160 | hist_file overrides profile. |
|
161 | hist_file overrides profile. | |
161 | config : |
|
162 | config : | |
162 | Config object. hist_file can also be set through this. |
|
163 | Config object. hist_file can also be set through this. | |
163 | """ |
|
164 | """ | |
164 | # We need a pointer back to the shell for various tasks. |
|
165 | # We need a pointer back to the shell for various tasks. | |
165 | super(HistoryAccessor, self).__init__(**traits) |
|
166 | super(HistoryAccessor, self).__init__(**traits) | |
166 | # defer setting hist_file from kwarg until after init, |
|
167 | # defer setting hist_file from kwarg until after init, | |
167 | # otherwise the default kwarg value would clobber any value |
|
168 | # otherwise the default kwarg value would clobber any value | |
168 | # set by config |
|
169 | # set by config | |
169 | if hist_file: |
|
170 | if hist_file: | |
170 | self.hist_file = hist_file |
|
171 | self.hist_file = hist_file | |
171 |
|
172 | |||
172 | if self.hist_file == u'': |
|
173 | if self.hist_file == u'': | |
173 | # No one has set the hist_file, yet. |
|
174 | # No one has set the hist_file, yet. | |
174 | self.hist_file = self._get_hist_file_name(profile) |
|
175 | self.hist_file = self._get_hist_file_name(profile) | |
175 |
|
176 | |||
176 | if sqlite3 is None and self.enabled: |
|
177 | if sqlite3 is None and self.enabled: | |
177 | warn("IPython History requires SQLite, your history will not be saved") |
|
178 | warn("IPython History requires SQLite, your history will not be saved") | |
178 | self.enabled = False |
|
179 | self.enabled = False | |
179 |
|
180 | |||
180 | self.init_db() |
|
181 | self.init_db() | |
181 |
|
182 | |||
182 | def _get_hist_file_name(self, profile='default'): |
|
183 | def _get_hist_file_name(self, profile='default'): | |
183 | """Find the history file for the given profile name. |
|
184 | """Find the history file for the given profile name. | |
184 |
|
185 | |||
185 | This is overridden by the HistoryManager subclass, to use the shell's |
|
186 | This is overridden by the HistoryManager subclass, to use the shell's | |
186 | active profile. |
|
187 | active profile. | |
187 |
|
188 | |||
188 | Parameters |
|
189 | Parameters | |
189 | ---------- |
|
190 | ---------- | |
190 | profile : str |
|
191 | profile : str | |
191 | The name of a profile which has a history file. |
|
192 | The name of a profile which has a history file. | |
192 | """ |
|
193 | """ | |
193 | return os.path.join(locate_profile(profile), 'history.sqlite') |
|
194 | return os.path.join(locate_profile(profile), 'history.sqlite') | |
194 |
|
195 | |||
195 | @catch_corrupt_db |
|
196 | @catch_corrupt_db | |
196 | def init_db(self): |
|
197 | def init_db(self): | |
197 | """Connect to the database, and create tables if necessary.""" |
|
198 | """Connect to the database, and create tables if necessary.""" | |
198 | if not self.enabled: |
|
199 | if not self.enabled: | |
199 | self.db = DummyDB() |
|
200 | self.db = DummyDB() | |
200 | return |
|
201 | return | |
201 |
|
202 | |||
202 | # use detect_types so that timestamps return datetime objects |
|
203 | # use detect_types so that timestamps return datetime objects | |
203 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) |
|
204 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) | |
204 | kwargs.update(self.connection_options) |
|
205 | kwargs.update(self.connection_options) | |
205 | self.db = sqlite3.connect(self.hist_file, **kwargs) |
|
206 | self.db = sqlite3.connect(self.hist_file, **kwargs) | |
206 | self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer |
|
207 | self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer | |
207 | primary key autoincrement, start timestamp, |
|
208 | primary key autoincrement, start timestamp, | |
208 | end timestamp, num_cmds integer, remark text)""") |
|
209 | end timestamp, num_cmds integer, remark text)""") | |
209 | self.db.execute("""CREATE TABLE IF NOT EXISTS history |
|
210 | self.db.execute("""CREATE TABLE IF NOT EXISTS history | |
210 | (session integer, line integer, source text, source_raw text, |
|
211 | (session integer, line integer, source text, source_raw text, | |
211 | PRIMARY KEY (session, line))""") |
|
212 | PRIMARY KEY (session, line))""") | |
212 | # Output history is optional, but ensure the table's there so it can be |
|
213 | # Output history is optional, but ensure the table's there so it can be | |
213 | # enabled later. |
|
214 | # enabled later. | |
214 | self.db.execute("""CREATE TABLE IF NOT EXISTS output_history |
|
215 | self.db.execute("""CREATE TABLE IF NOT EXISTS output_history | |
215 | (session integer, line integer, output text, |
|
216 | (session integer, line integer, output text, | |
216 | PRIMARY KEY (session, line))""") |
|
217 | PRIMARY KEY (session, line))""") | |
217 | self.db.commit() |
|
218 | self.db.commit() | |
218 |
|
219 | |||
219 | def writeout_cache(self): |
|
220 | def writeout_cache(self): | |
220 | """Overridden by HistoryManager to dump the cache before certain |
|
221 | """Overridden by HistoryManager to dump the cache before certain | |
221 | database lookups.""" |
|
222 | database lookups.""" | |
222 | pass |
|
223 | pass | |
223 |
|
224 | |||
224 | ## ------------------------------- |
|
225 | ## ------------------------------- | |
225 | ## Methods for retrieving history: |
|
226 | ## Methods for retrieving history: | |
226 | ## ------------------------------- |
|
227 | ## ------------------------------- | |
227 | def _run_sql(self, sql, params, raw=True, output=False): |
|
228 | def _run_sql(self, sql, params, raw=True, output=False): | |
228 | """Prepares and runs an SQL query for the history database. |
|
229 | """Prepares and runs an SQL query for the history database. | |
229 |
|
230 | |||
230 | Parameters |
|
231 | Parameters | |
231 | ---------- |
|
232 | ---------- | |
232 | sql : str |
|
233 | sql : str | |
233 | Any filtering expressions to go after SELECT ... FROM ... |
|
234 | Any filtering expressions to go after SELECT ... FROM ... | |
234 | params : tuple |
|
235 | params : tuple | |
235 | Parameters passed to the SQL query (to replace "?") |
|
236 | Parameters passed to the SQL query (to replace "?") | |
236 | raw, output : bool |
|
237 | raw, output : bool | |
237 | See :meth:`get_range` |
|
238 | See :meth:`get_range` | |
238 |
|
239 | |||
239 | Returns |
|
240 | Returns | |
240 | ------- |
|
241 | ------- | |
241 | Tuples as :meth:`get_range` |
|
242 | Tuples as :meth:`get_range` | |
242 | """ |
|
243 | """ | |
243 | toget = 'source_raw' if raw else 'source' |
|
244 | toget = 'source_raw' if raw else 'source' | |
244 | sqlfrom = "history" |
|
245 | sqlfrom = "history" | |
245 | if output: |
|
246 | if output: | |
246 | sqlfrom = "history LEFT JOIN output_history USING (session, line)" |
|
247 | sqlfrom = "history LEFT JOIN output_history USING (session, line)" | |
247 | toget = "history.%s, output_history.output" % toget |
|
248 | toget = "history.%s, output_history.output" % toget | |
248 | cur = self.db.execute("SELECT session, line, %s FROM %s " %\ |
|
249 | cur = self.db.execute("SELECT session, line, %s FROM %s " %\ | |
249 | (toget, sqlfrom) + sql, params) |
|
250 | (toget, sqlfrom) + sql, params) | |
250 | if output: # Regroup into 3-tuples, and parse JSON |
|
251 | if output: # Regroup into 3-tuples, and parse JSON | |
251 | return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) |
|
252 | return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) | |
252 | return cur |
|
253 | return cur | |
253 |
|
254 | |||
254 | @needs_sqlite |
|
255 | @needs_sqlite | |
255 | @catch_corrupt_db |
|
256 | @catch_corrupt_db | |
256 | def get_session_info(self, session=0): |
|
257 | def get_session_info(self, session=0): | |
257 | """get info about a session |
|
258 | """get info about a session | |
258 |
|
259 | |||
259 | Parameters |
|
260 | Parameters | |
260 | ---------- |
|
261 | ---------- | |
261 |
|
262 | |||
262 | session : int |
|
263 | session : int | |
263 | Session number to retrieve. The current session is 0, and negative |
|
264 | Session number to retrieve. The current session is 0, and negative | |
264 | numbers count back from current session, so -1 is previous session. |
|
265 | numbers count back from current session, so -1 is previous session. | |
265 |
|
266 | |||
266 | Returns |
|
267 | Returns | |
267 | ------- |
|
268 | ------- | |
268 |
|
269 | |||
269 | (session_id [int], start [datetime], end [datetime], num_cmds [int], |
|
270 | (session_id [int], start [datetime], end [datetime], num_cmds [int], | |
270 | remark [unicode]) |
|
271 | remark [unicode]) | |
271 |
|
272 | |||
272 | Sessions that are running or did not exit cleanly will have `end=None` |
|
273 | Sessions that are running or did not exit cleanly will have `end=None` | |
273 | and `num_cmds=None`. |
|
274 | and `num_cmds=None`. | |
274 |
|
275 | |||
275 | """ |
|
276 | """ | |
276 |
|
277 | |||
277 | if session <= 0: |
|
278 | if session <= 0: | |
278 | session += self.session_number |
|
279 | session += self.session_number | |
279 |
|
280 | |||
280 | query = "SELECT * from sessions where session == ?" |
|
281 | query = "SELECT * from sessions where session == ?" | |
281 | return self.db.execute(query, (session,)).fetchone() |
|
282 | return self.db.execute(query, (session,)).fetchone() | |
282 |
|
283 | |||
283 | @catch_corrupt_db |
|
284 | @catch_corrupt_db | |
284 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): |
|
285 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): | |
285 | """Get the last n lines from the history database. |
|
286 | """Get the last n lines from the history database. | |
286 |
|
287 | |||
287 | Parameters |
|
288 | Parameters | |
288 | ---------- |
|
289 | ---------- | |
289 | n : int |
|
290 | n : int | |
290 | The number of lines to get |
|
291 | The number of lines to get | |
291 | raw, output : bool |
|
292 | raw, output : bool | |
292 | See :meth:`get_range` |
|
293 | See :meth:`get_range` | |
293 | include_latest : bool |
|
294 | include_latest : bool | |
294 | If False (default), n+1 lines are fetched, and the latest one |
|
295 | If False (default), n+1 lines are fetched, and the latest one | |
295 | is discarded. This is intended to be used where the function |
|
296 | is discarded. This is intended to be used where the function | |
296 | is called by a user command, which it should not return. |
|
297 | is called by a user command, which it should not return. | |
297 |
|
298 | |||
298 | Returns |
|
299 | Returns | |
299 | ------- |
|
300 | ------- | |
300 | Tuples as :meth:`get_range` |
|
301 | Tuples as :meth:`get_range` | |
301 | """ |
|
302 | """ | |
302 | self.writeout_cache() |
|
303 | self.writeout_cache() | |
303 | if not include_latest: |
|
304 | if not include_latest: | |
304 | n += 1 |
|
305 | n += 1 | |
305 | cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?", |
|
306 | cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?", | |
306 | (n,), raw=raw, output=output) |
|
307 | (n,), raw=raw, output=output) | |
307 | if not include_latest: |
|
308 | if not include_latest: | |
308 | return reversed(list(cur)[1:]) |
|
309 | return reversed(list(cur)[1:]) | |
309 | return reversed(list(cur)) |
|
310 | return reversed(list(cur)) | |
310 |
|
311 | |||
311 | @catch_corrupt_db |
|
312 | @catch_corrupt_db | |
312 | def search(self, pattern="*", raw=True, search_raw=True, |
|
313 | def search(self, pattern="*", raw=True, search_raw=True, | |
313 | output=False, n=None, unique=False): |
|
314 | output=False, n=None, unique=False): | |
314 | """Search the database using unix glob-style matching (wildcards |
|
315 | """Search the database using unix glob-style matching (wildcards | |
315 | * and ?). |
|
316 | * and ?). | |
316 |
|
317 | |||
317 | Parameters |
|
318 | Parameters | |
318 | ---------- |
|
319 | ---------- | |
319 | pattern : str |
|
320 | pattern : str | |
320 | The wildcarded pattern to match when searching |
|
321 | The wildcarded pattern to match when searching | |
321 | search_raw : bool |
|
322 | search_raw : bool | |
322 | If True, search the raw input, otherwise, the parsed input |
|
323 | If True, search the raw input, otherwise, the parsed input | |
323 | raw, output : bool |
|
324 | raw, output : bool | |
324 | See :meth:`get_range` |
|
325 | See :meth:`get_range` | |
325 | n : None or int |
|
326 | n : None or int | |
326 | If an integer is given, it defines the limit of |
|
327 | If an integer is given, it defines the limit of | |
327 | returned entries. |
|
328 | returned entries. | |
328 | unique : bool |
|
329 | unique : bool | |
329 | When it is true, return only unique entries. |
|
330 | When it is true, return only unique entries. | |
330 |
|
331 | |||
331 | Returns |
|
332 | Returns | |
332 | ------- |
|
333 | ------- | |
333 | Tuples as :meth:`get_range` |
|
334 | Tuples as :meth:`get_range` | |
334 | """ |
|
335 | """ | |
335 | tosearch = "source_raw" if search_raw else "source" |
|
336 | tosearch = "source_raw" if search_raw else "source" | |
336 | if output: |
|
337 | if output: | |
337 | tosearch = "history." + tosearch |
|
338 | tosearch = "history." + tosearch | |
338 | self.writeout_cache() |
|
339 | self.writeout_cache() | |
339 | sqlform = "WHERE %s GLOB ?" % tosearch |
|
340 | sqlform = "WHERE %s GLOB ?" % tosearch | |
340 | params = (pattern,) |
|
341 | params = (pattern,) | |
341 | if unique: |
|
342 | if unique: | |
342 | sqlform += ' GROUP BY {0}'.format(tosearch) |
|
343 | sqlform += ' GROUP BY {0}'.format(tosearch) | |
343 | if n is not None: |
|
344 | if n is not None: | |
344 | sqlform += " ORDER BY session DESC, line DESC LIMIT ?" |
|
345 | sqlform += " ORDER BY session DESC, line DESC LIMIT ?" | |
345 | params += (n,) |
|
346 | params += (n,) | |
346 | elif unique: |
|
347 | elif unique: | |
347 | sqlform += " ORDER BY session, line" |
|
348 | sqlform += " ORDER BY session, line" | |
348 | cur = self._run_sql(sqlform, params, raw=raw, output=output) |
|
349 | cur = self._run_sql(sqlform, params, raw=raw, output=output) | |
349 | if n is not None: |
|
350 | if n is not None: | |
350 | return reversed(list(cur)) |
|
351 | return reversed(list(cur)) | |
351 | return cur |
|
352 | return cur | |
352 |
|
353 | |||
353 | @catch_corrupt_db |
|
354 | @catch_corrupt_db | |
354 | def get_range(self, session, start=1, stop=None, raw=True,output=False): |
|
355 | def get_range(self, session, start=1, stop=None, raw=True,output=False): | |
355 | """Retrieve input by session. |
|
356 | """Retrieve input by session. | |
356 |
|
357 | |||
357 | Parameters |
|
358 | Parameters | |
358 | ---------- |
|
359 | ---------- | |
359 | session : int |
|
360 | session : int | |
360 | Session number to retrieve. |
|
361 | Session number to retrieve. | |
361 | start : int |
|
362 | start : int | |
362 | First line to retrieve. |
|
363 | First line to retrieve. | |
363 | stop : int |
|
364 | stop : int | |
364 | End of line range (excluded from output itself). If None, retrieve |
|
365 | End of line range (excluded from output itself). If None, retrieve | |
365 | to the end of the session. |
|
366 | to the end of the session. | |
366 | raw : bool |
|
367 | raw : bool | |
367 | If True, return untranslated input |
|
368 | If True, return untranslated input | |
368 | output : bool |
|
369 | output : bool | |
369 | If True, attempt to include output. This will be 'real' Python |
|
370 | If True, attempt to include output. This will be 'real' Python | |
370 | objects for the current session, or text reprs from previous |
|
371 | objects for the current session, or text reprs from previous | |
371 | sessions if db_log_output was enabled at the time. Where no output |
|
372 | sessions if db_log_output was enabled at the time. Where no output | |
372 | is found, None is used. |
|
373 | is found, None is used. | |
373 |
|
374 | |||
374 | Returns |
|
375 | Returns | |
375 | ------- |
|
376 | ------- | |
376 | An iterator over the desired lines. Each line is a 3-tuple, either |
|
377 | An iterator over the desired lines. Each line is a 3-tuple, either | |
377 | (session, line, input) if output is False, or |
|
378 | (session, line, input) if output is False, or | |
378 | (session, line, (input, output)) if output is True. |
|
379 | (session, line, (input, output)) if output is True. | |
379 | """ |
|
380 | """ | |
380 | if stop: |
|
381 | if stop: | |
381 | lineclause = "line >= ? AND line < ?" |
|
382 | lineclause = "line >= ? AND line < ?" | |
382 | params = (session, start, stop) |
|
383 | params = (session, start, stop) | |
383 | else: |
|
384 | else: | |
384 | lineclause = "line>=?" |
|
385 | lineclause = "line>=?" | |
385 | params = (session, start) |
|
386 | params = (session, start) | |
386 |
|
387 | |||
387 | return self._run_sql("WHERE session==? AND %s" % lineclause, |
|
388 | return self._run_sql("WHERE session==? AND %s" % lineclause, | |
388 | params, raw=raw, output=output) |
|
389 | params, raw=raw, output=output) | |
389 |
|
390 | |||
390 | def get_range_by_str(self, rangestr, raw=True, output=False): |
|
391 | def get_range_by_str(self, rangestr, raw=True, output=False): | |
391 | """Get lines of history from a string of ranges, as used by magic |
|
392 | """Get lines of history from a string of ranges, as used by magic | |
392 | commands %hist, %save, %macro, etc. |
|
393 | commands %hist, %save, %macro, etc. | |
393 |
|
394 | |||
394 | Parameters |
|
395 | Parameters | |
395 | ---------- |
|
396 | ---------- | |
396 | rangestr : str |
|
397 | rangestr : str | |
397 | A string specifying ranges, e.g. "5 ~2/1-4". See |
|
398 | A string specifying ranges, e.g. "5 ~2/1-4". See | |
398 | :func:`magic_history` for full details. |
|
399 | :func:`magic_history` for full details. | |
399 | raw, output : bool |
|
400 | raw, output : bool | |
400 | As :meth:`get_range` |
|
401 | As :meth:`get_range` | |
401 |
|
402 | |||
402 | Returns |
|
403 | Returns | |
403 | ------- |
|
404 | ------- | |
404 | Tuples as :meth:`get_range` |
|
405 | Tuples as :meth:`get_range` | |
405 | """ |
|
406 | """ | |
406 | for sess, s, e in extract_hist_ranges(rangestr): |
|
407 | for sess, s, e in extract_hist_ranges(rangestr): | |
407 | for line in self.get_range(sess, s, e, raw=raw, output=output): |
|
408 | for line in self.get_range(sess, s, e, raw=raw, output=output): | |
408 | yield line |
|
409 | yield line | |
409 |
|
410 | |||
410 |
|
411 | |||
411 | class HistoryManager(HistoryAccessor): |
|
412 | class HistoryManager(HistoryAccessor): | |
412 | """A class to organize all history-related functionality in one place. |
|
413 | """A class to organize all history-related functionality in one place. | |
413 | """ |
|
414 | """ | |
414 | # Public interface |
|
415 | # Public interface | |
415 |
|
416 | |||
416 | # An instance of the IPython shell we are attached to |
|
417 | # An instance of the IPython shell we are attached to | |
417 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') |
|
418 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | |
418 | # Lists to hold processed and raw history. These start with a blank entry |
|
419 | # Lists to hold processed and raw history. These start with a blank entry | |
419 | # so that we can index them starting from 1 |
|
420 | # so that we can index them starting from 1 | |
420 | input_hist_parsed = List([""]) |
|
421 | input_hist_parsed = List([""]) | |
421 | input_hist_raw = List([""]) |
|
422 | input_hist_raw = List([""]) | |
422 | # A list of directories visited during session |
|
423 | # A list of directories visited during session | |
423 | dir_hist = List() |
|
424 | dir_hist = List() | |
424 | def _dir_hist_default(self): |
|
425 | def _dir_hist_default(self): | |
425 | try: |
|
426 | try: | |
426 |
return [ |
|
427 | return [py3compat.getcwd()] | |
427 | except OSError: |
|
428 | except OSError: | |
428 | return [] |
|
429 | return [] | |
429 |
|
430 | |||
430 | # A dict of output history, keyed with ints from the shell's |
|
431 | # A dict of output history, keyed with ints from the shell's | |
431 | # execution count. |
|
432 | # execution count. | |
432 | output_hist = Dict() |
|
433 | output_hist = Dict() | |
433 | # The text/plain repr of outputs. |
|
434 | # The text/plain repr of outputs. | |
434 | output_hist_reprs = Dict() |
|
435 | output_hist_reprs = Dict() | |
435 |
|
436 | |||
436 | # The number of the current session in the history database |
|
437 | # The number of the current session in the history database | |
437 | session_number = Integer() |
|
438 | session_number = Integer() | |
438 | # Should we log output to the database? (default no) |
|
439 | # Should we log output to the database? (default no) | |
439 | db_log_output = Bool(False, config=True) |
|
440 | db_log_output = Bool(False, config=True) | |
440 | # Write to database every x commands (higher values save disk access & power) |
|
441 | # Write to database every x commands (higher values save disk access & power) | |
441 | # Values of 1 or less effectively disable caching. |
|
442 | # Values of 1 or less effectively disable caching. | |
442 | db_cache_size = Integer(0, config=True) |
|
443 | db_cache_size = Integer(0, config=True) | |
443 | # The input and output caches |
|
444 | # The input and output caches | |
444 | db_input_cache = List() |
|
445 | db_input_cache = List() | |
445 | db_output_cache = List() |
|
446 | db_output_cache = List() | |
446 |
|
447 | |||
447 | # History saving in separate thread |
|
448 | # History saving in separate thread | |
448 | save_thread = Instance('IPython.core.history.HistorySavingThread') |
|
449 | save_thread = Instance('IPython.core.history.HistorySavingThread') | |
449 | try: # Event is a function returning an instance of _Event... |
|
450 | try: # Event is a function returning an instance of _Event... | |
450 | save_flag = Instance(threading._Event) |
|
451 | save_flag = Instance(threading._Event) | |
451 | except AttributeError: # ...until Python 3.3, when it's a class. |
|
452 | except AttributeError: # ...until Python 3.3, when it's a class. | |
452 | save_flag = Instance(threading.Event) |
|
453 | save_flag = Instance(threading.Event) | |
453 |
|
454 | |||
454 | # Private interface |
|
455 | # Private interface | |
455 | # Variables used to store the three last inputs from the user. On each new |
|
456 | # Variables used to store the three last inputs from the user. On each new | |
456 | # history update, we populate the user's namespace with these, shifted as |
|
457 | # history update, we populate the user's namespace with these, shifted as | |
457 | # necessary. |
|
458 | # necessary. | |
458 | _i00 = Unicode(u'') |
|
459 | _i00 = Unicode(u'') | |
459 | _i = Unicode(u'') |
|
460 | _i = Unicode(u'') | |
460 | _ii = Unicode(u'') |
|
461 | _ii = Unicode(u'') | |
461 | _iii = Unicode(u'') |
|
462 | _iii = Unicode(u'') | |
462 |
|
463 | |||
463 | # A regex matching all forms of the exit command, so that we don't store |
|
464 | # A regex matching all forms of the exit command, so that we don't store | |
464 | # them in the history (it's annoying to rewind the first entry and land on |
|
465 | # them in the history (it's annoying to rewind the first entry and land on | |
465 | # an exit call). |
|
466 | # an exit call). | |
466 | _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$") |
|
467 | _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$") | |
467 |
|
468 | |||
468 | def __init__(self, shell=None, config=None, **traits): |
|
469 | def __init__(self, shell=None, config=None, **traits): | |
469 | """Create a new history manager associated with a shell instance. |
|
470 | """Create a new history manager associated with a shell instance. | |
470 | """ |
|
471 | """ | |
471 | # We need a pointer back to the shell for various tasks. |
|
472 | # We need a pointer back to the shell for various tasks. | |
472 | super(HistoryManager, self).__init__(shell=shell, config=config, |
|
473 | super(HistoryManager, self).__init__(shell=shell, config=config, | |
473 | **traits) |
|
474 | **traits) | |
474 | self.save_flag = threading.Event() |
|
475 | self.save_flag = threading.Event() | |
475 | self.db_input_cache_lock = threading.Lock() |
|
476 | self.db_input_cache_lock = threading.Lock() | |
476 | self.db_output_cache_lock = threading.Lock() |
|
477 | self.db_output_cache_lock = threading.Lock() | |
477 | if self.enabled and self.hist_file != ':memory:': |
|
478 | if self.enabled and self.hist_file != ':memory:': | |
478 | self.save_thread = HistorySavingThread(self) |
|
479 | self.save_thread = HistorySavingThread(self) | |
479 | self.save_thread.start() |
|
480 | self.save_thread.start() | |
480 |
|
481 | |||
481 | self.new_session() |
|
482 | self.new_session() | |
482 |
|
483 | |||
483 | def _get_hist_file_name(self, profile=None): |
|
484 | def _get_hist_file_name(self, profile=None): | |
484 | """Get default history file name based on the Shell's profile. |
|
485 | """Get default history file name based on the Shell's profile. | |
485 |
|
486 | |||
486 | The profile parameter is ignored, but must exist for compatibility with |
|
487 | The profile parameter is ignored, but must exist for compatibility with | |
487 | the parent class.""" |
|
488 | the parent class.""" | |
488 | profile_dir = self.shell.profile_dir.location |
|
489 | profile_dir = self.shell.profile_dir.location | |
489 | return os.path.join(profile_dir, 'history.sqlite') |
|
490 | return os.path.join(profile_dir, 'history.sqlite') | |
490 |
|
491 | |||
491 | @needs_sqlite |
|
492 | @needs_sqlite | |
492 | def new_session(self, conn=None): |
|
493 | def new_session(self, conn=None): | |
493 | """Get a new session number.""" |
|
494 | """Get a new session number.""" | |
494 | if conn is None: |
|
495 | if conn is None: | |
495 | conn = self.db |
|
496 | conn = self.db | |
496 |
|
497 | |||
497 | with conn: |
|
498 | with conn: | |
498 | cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, |
|
499 | cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, | |
499 | NULL, "") """, (datetime.datetime.now(),)) |
|
500 | NULL, "") """, (datetime.datetime.now(),)) | |
500 | self.session_number = cur.lastrowid |
|
501 | self.session_number = cur.lastrowid | |
501 |
|
502 | |||
502 | def end_session(self): |
|
503 | def end_session(self): | |
503 | """Close the database session, filling in the end time and line count.""" |
|
504 | """Close the database session, filling in the end time and line count.""" | |
504 | self.writeout_cache() |
|
505 | self.writeout_cache() | |
505 | with self.db: |
|
506 | with self.db: | |
506 | self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE |
|
507 | self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE | |
507 | session==?""", (datetime.datetime.now(), |
|
508 | session==?""", (datetime.datetime.now(), | |
508 | len(self.input_hist_parsed)-1, self.session_number)) |
|
509 | len(self.input_hist_parsed)-1, self.session_number)) | |
509 | self.session_number = 0 |
|
510 | self.session_number = 0 | |
510 |
|
511 | |||
511 | def name_session(self, name): |
|
512 | def name_session(self, name): | |
512 | """Give the current session a name in the history database.""" |
|
513 | """Give the current session a name in the history database.""" | |
513 | with self.db: |
|
514 | with self.db: | |
514 | self.db.execute("UPDATE sessions SET remark=? WHERE session==?", |
|
515 | self.db.execute("UPDATE sessions SET remark=? WHERE session==?", | |
515 | (name, self.session_number)) |
|
516 | (name, self.session_number)) | |
516 |
|
517 | |||
517 | def reset(self, new_session=True): |
|
518 | def reset(self, new_session=True): | |
518 | """Clear the session history, releasing all object references, and |
|
519 | """Clear the session history, releasing all object references, and | |
519 | optionally open a new session.""" |
|
520 | optionally open a new session.""" | |
520 | self.output_hist.clear() |
|
521 | self.output_hist.clear() | |
521 | # The directory history can't be completely empty |
|
522 | # The directory history can't be completely empty | |
522 |
self.dir_hist[:] = [ |
|
523 | self.dir_hist[:] = [py3compat.getcwd()] | |
523 |
|
524 | |||
524 | if new_session: |
|
525 | if new_session: | |
525 | if self.session_number: |
|
526 | if self.session_number: | |
526 | self.end_session() |
|
527 | self.end_session() | |
527 | self.input_hist_parsed[:] = [""] |
|
528 | self.input_hist_parsed[:] = [""] | |
528 | self.input_hist_raw[:] = [""] |
|
529 | self.input_hist_raw[:] = [""] | |
529 | self.new_session() |
|
530 | self.new_session() | |
530 |
|
531 | |||
531 | # ------------------------------ |
|
532 | # ------------------------------ | |
532 | # Methods for retrieving history |
|
533 | # Methods for retrieving history | |
533 | # ------------------------------ |
|
534 | # ------------------------------ | |
534 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): |
|
535 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): | |
535 | """Get input and output history from the current session. Called by |
|
536 | """Get input and output history from the current session. Called by | |
536 | get_range, and takes similar parameters.""" |
|
537 | get_range, and takes similar parameters.""" | |
537 | input_hist = self.input_hist_raw if raw else self.input_hist_parsed |
|
538 | input_hist = self.input_hist_raw if raw else self.input_hist_parsed | |
538 |
|
539 | |||
539 | n = len(input_hist) |
|
540 | n = len(input_hist) | |
540 | if start < 0: |
|
541 | if start < 0: | |
541 | start += n |
|
542 | start += n | |
542 | if not stop or (stop > n): |
|
543 | if not stop or (stop > n): | |
543 | stop = n |
|
544 | stop = n | |
544 | elif stop < 0: |
|
545 | elif stop < 0: | |
545 | stop += n |
|
546 | stop += n | |
546 |
|
547 | |||
547 | for i in range(start, stop): |
|
548 | for i in range(start, stop): | |
548 | if output: |
|
549 | if output: | |
549 | line = (input_hist[i], self.output_hist_reprs.get(i)) |
|
550 | line = (input_hist[i], self.output_hist_reprs.get(i)) | |
550 | else: |
|
551 | else: | |
551 | line = input_hist[i] |
|
552 | line = input_hist[i] | |
552 | yield (0, i, line) |
|
553 | yield (0, i, line) | |
553 |
|
554 | |||
554 | def get_range(self, session=0, start=1, stop=None, raw=True,output=False): |
|
555 | def get_range(self, session=0, start=1, stop=None, raw=True,output=False): | |
555 | """Retrieve input by session. |
|
556 | """Retrieve input by session. | |
556 |
|
557 | |||
557 | Parameters |
|
558 | Parameters | |
558 | ---------- |
|
559 | ---------- | |
559 | session : int |
|
560 | session : int | |
560 | Session number to retrieve. The current session is 0, and negative |
|
561 | Session number to retrieve. The current session is 0, and negative | |
561 | numbers count back from current session, so -1 is previous session. |
|
562 | numbers count back from current session, so -1 is previous session. | |
562 | start : int |
|
563 | start : int | |
563 | First line to retrieve. |
|
564 | First line to retrieve. | |
564 | stop : int |
|
565 | stop : int | |
565 | End of line range (excluded from output itself). If None, retrieve |
|
566 | End of line range (excluded from output itself). If None, retrieve | |
566 | to the end of the session. |
|
567 | to the end of the session. | |
567 | raw : bool |
|
568 | raw : bool | |
568 | If True, return untranslated input |
|
569 | If True, return untranslated input | |
569 | output : bool |
|
570 | output : bool | |
570 | If True, attempt to include output. This will be 'real' Python |
|
571 | If True, attempt to include output. This will be 'real' Python | |
571 | objects for the current session, or text reprs from previous |
|
572 | objects for the current session, or text reprs from previous | |
572 | sessions if db_log_output was enabled at the time. Where no output |
|
573 | sessions if db_log_output was enabled at the time. Where no output | |
573 | is found, None is used. |
|
574 | is found, None is used. | |
574 |
|
575 | |||
575 | Returns |
|
576 | Returns | |
576 | ------- |
|
577 | ------- | |
577 | An iterator over the desired lines. Each line is a 3-tuple, either |
|
578 | An iterator over the desired lines. Each line is a 3-tuple, either | |
578 | (session, line, input) if output is False, or |
|
579 | (session, line, input) if output is False, or | |
579 | (session, line, (input, output)) if output is True. |
|
580 | (session, line, (input, output)) if output is True. | |
580 | """ |
|
581 | """ | |
581 | if session <= 0: |
|
582 | if session <= 0: | |
582 | session += self.session_number |
|
583 | session += self.session_number | |
583 | if session==self.session_number: # Current session |
|
584 | if session==self.session_number: # Current session | |
584 | return self._get_range_session(start, stop, raw, output) |
|
585 | return self._get_range_session(start, stop, raw, output) | |
585 | return super(HistoryManager, self).get_range(session, start, stop, raw, |
|
586 | return super(HistoryManager, self).get_range(session, start, stop, raw, | |
586 | output) |
|
587 | output) | |
587 |
|
588 | |||
588 | ## ---------------------------- |
|
589 | ## ---------------------------- | |
589 | ## Methods for storing history: |
|
590 | ## Methods for storing history: | |
590 | ## ---------------------------- |
|
591 | ## ---------------------------- | |
591 | def store_inputs(self, line_num, source, source_raw=None): |
|
592 | def store_inputs(self, line_num, source, source_raw=None): | |
592 | """Store source and raw input in history and create input cache |
|
593 | """Store source and raw input in history and create input cache | |
593 | variables _i*. |
|
594 | variables _i*. | |
594 |
|
595 | |||
595 | Parameters |
|
596 | Parameters | |
596 | ---------- |
|
597 | ---------- | |
597 | line_num : int |
|
598 | line_num : int | |
598 | The prompt number of this input. |
|
599 | The prompt number of this input. | |
599 |
|
600 | |||
600 | source : str |
|
601 | source : str | |
601 | Python input. |
|
602 | Python input. | |
602 |
|
603 | |||
603 | source_raw : str, optional |
|
604 | source_raw : str, optional | |
604 | If given, this is the raw input without any IPython transformations |
|
605 | If given, this is the raw input without any IPython transformations | |
605 | applied to it. If not given, ``source`` is used. |
|
606 | applied to it. If not given, ``source`` is used. | |
606 | """ |
|
607 | """ | |
607 | if source_raw is None: |
|
608 | if source_raw is None: | |
608 | source_raw = source |
|
609 | source_raw = source | |
609 | source = source.rstrip('\n') |
|
610 | source = source.rstrip('\n') | |
610 | source_raw = source_raw.rstrip('\n') |
|
611 | source_raw = source_raw.rstrip('\n') | |
611 |
|
612 | |||
612 | # do not store exit/quit commands |
|
613 | # do not store exit/quit commands | |
613 | if self._exit_re.match(source_raw.strip()): |
|
614 | if self._exit_re.match(source_raw.strip()): | |
614 | return |
|
615 | return | |
615 |
|
616 | |||
616 | self.input_hist_parsed.append(source) |
|
617 | self.input_hist_parsed.append(source) | |
617 | self.input_hist_raw.append(source_raw) |
|
618 | self.input_hist_raw.append(source_raw) | |
618 |
|
619 | |||
619 | with self.db_input_cache_lock: |
|
620 | with self.db_input_cache_lock: | |
620 | self.db_input_cache.append((line_num, source, source_raw)) |
|
621 | self.db_input_cache.append((line_num, source, source_raw)) | |
621 | # Trigger to flush cache and write to DB. |
|
622 | # Trigger to flush cache and write to DB. | |
622 | if len(self.db_input_cache) >= self.db_cache_size: |
|
623 | if len(self.db_input_cache) >= self.db_cache_size: | |
623 | self.save_flag.set() |
|
624 | self.save_flag.set() | |
624 |
|
625 | |||
625 | # update the auto _i variables |
|
626 | # update the auto _i variables | |
626 | self._iii = self._ii |
|
627 | self._iii = self._ii | |
627 | self._ii = self._i |
|
628 | self._ii = self._i | |
628 | self._i = self._i00 |
|
629 | self._i = self._i00 | |
629 | self._i00 = source_raw |
|
630 | self._i00 = source_raw | |
630 |
|
631 | |||
631 | # hackish access to user namespace to create _i1,_i2... dynamically |
|
632 | # hackish access to user namespace to create _i1,_i2... dynamically | |
632 | new_i = '_i%s' % line_num |
|
633 | new_i = '_i%s' % line_num | |
633 | to_main = {'_i': self._i, |
|
634 | to_main = {'_i': self._i, | |
634 | '_ii': self._ii, |
|
635 | '_ii': self._ii, | |
635 | '_iii': self._iii, |
|
636 | '_iii': self._iii, | |
636 | new_i : self._i00 } |
|
637 | new_i : self._i00 } | |
637 |
|
638 | |||
638 | if self.shell is not None: |
|
639 | if self.shell is not None: | |
639 | self.shell.push(to_main, interactive=False) |
|
640 | self.shell.push(to_main, interactive=False) | |
640 |
|
641 | |||
641 | def store_output(self, line_num): |
|
642 | def store_output(self, line_num): | |
642 | """If database output logging is enabled, this saves all the |
|
643 | """If database output logging is enabled, this saves all the | |
643 | outputs from the indicated prompt number to the database. It's |
|
644 | outputs from the indicated prompt number to the database. It's | |
644 | called by run_cell after code has been executed. |
|
645 | called by run_cell after code has been executed. | |
645 |
|
646 | |||
646 | Parameters |
|
647 | Parameters | |
647 | ---------- |
|
648 | ---------- | |
648 | line_num : int |
|
649 | line_num : int | |
649 | The line number from which to save outputs |
|
650 | The line number from which to save outputs | |
650 | """ |
|
651 | """ | |
651 | if (not self.db_log_output) or (line_num not in self.output_hist_reprs): |
|
652 | if (not self.db_log_output) or (line_num not in self.output_hist_reprs): | |
652 | return |
|
653 | return | |
653 | output = self.output_hist_reprs[line_num] |
|
654 | output = self.output_hist_reprs[line_num] | |
654 |
|
655 | |||
655 | with self.db_output_cache_lock: |
|
656 | with self.db_output_cache_lock: | |
656 | self.db_output_cache.append((line_num, output)) |
|
657 | self.db_output_cache.append((line_num, output)) | |
657 | if self.db_cache_size <= 1: |
|
658 | if self.db_cache_size <= 1: | |
658 | self.save_flag.set() |
|
659 | self.save_flag.set() | |
659 |
|
660 | |||
660 | def _writeout_input_cache(self, conn): |
|
661 | def _writeout_input_cache(self, conn): | |
661 | with conn: |
|
662 | with conn: | |
662 | for line in self.db_input_cache: |
|
663 | for line in self.db_input_cache: | |
663 | conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)", |
|
664 | conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)", | |
664 | (self.session_number,)+line) |
|
665 | (self.session_number,)+line) | |
665 |
|
666 | |||
666 | def _writeout_output_cache(self, conn): |
|
667 | def _writeout_output_cache(self, conn): | |
667 | with conn: |
|
668 | with conn: | |
668 | for line in self.db_output_cache: |
|
669 | for line in self.db_output_cache: | |
669 | conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", |
|
670 | conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", | |
670 | (self.session_number,)+line) |
|
671 | (self.session_number,)+line) | |
671 |
|
672 | |||
672 | @needs_sqlite |
|
673 | @needs_sqlite | |
673 | def writeout_cache(self, conn=None): |
|
674 | def writeout_cache(self, conn=None): | |
674 | """Write any entries in the cache to the database.""" |
|
675 | """Write any entries in the cache to the database.""" | |
675 | if conn is None: |
|
676 | if conn is None: | |
676 | conn = self.db |
|
677 | conn = self.db | |
677 |
|
678 | |||
678 | with self.db_input_cache_lock: |
|
679 | with self.db_input_cache_lock: | |
679 | try: |
|
680 | try: | |
680 | self._writeout_input_cache(conn) |
|
681 | self._writeout_input_cache(conn) | |
681 | except sqlite3.IntegrityError: |
|
682 | except sqlite3.IntegrityError: | |
682 | self.new_session(conn) |
|
683 | self.new_session(conn) | |
683 | print("ERROR! Session/line number was not unique in", |
|
684 | print("ERROR! Session/line number was not unique in", | |
684 | "database. History logging moved to new session", |
|
685 | "database. History logging moved to new session", | |
685 | self.session_number) |
|
686 | self.session_number) | |
686 | try: |
|
687 | try: | |
687 | # Try writing to the new session. If this fails, don't |
|
688 | # Try writing to the new session. If this fails, don't | |
688 | # recurse |
|
689 | # recurse | |
689 | self._writeout_input_cache(conn) |
|
690 | self._writeout_input_cache(conn) | |
690 | except sqlite3.IntegrityError: |
|
691 | except sqlite3.IntegrityError: | |
691 | pass |
|
692 | pass | |
692 | finally: |
|
693 | finally: | |
693 | self.db_input_cache = [] |
|
694 | self.db_input_cache = [] | |
694 |
|
695 | |||
695 | with self.db_output_cache_lock: |
|
696 | with self.db_output_cache_lock: | |
696 | try: |
|
697 | try: | |
697 | self._writeout_output_cache(conn) |
|
698 | self._writeout_output_cache(conn) | |
698 | except sqlite3.IntegrityError: |
|
699 | except sqlite3.IntegrityError: | |
699 | print("!! Session/line number for output was not unique", |
|
700 | print("!! Session/line number for output was not unique", | |
700 | "in database. Output will not be stored.") |
|
701 | "in database. Output will not be stored.") | |
701 | finally: |
|
702 | finally: | |
702 | self.db_output_cache = [] |
|
703 | self.db_output_cache = [] | |
703 |
|
704 | |||
704 |
|
705 | |||
705 | class HistorySavingThread(threading.Thread): |
|
706 | class HistorySavingThread(threading.Thread): | |
706 | """This thread takes care of writing history to the database, so that |
|
707 | """This thread takes care of writing history to the database, so that | |
707 | the UI isn't held up while that happens. |
|
708 | the UI isn't held up while that happens. | |
708 |
|
709 | |||
709 | It waits for the HistoryManager's save_flag to be set, then writes out |
|
710 | It waits for the HistoryManager's save_flag to be set, then writes out | |
710 | the history cache. The main thread is responsible for setting the flag when |
|
711 | the history cache. The main thread is responsible for setting the flag when | |
711 | the cache size reaches a defined threshold.""" |
|
712 | the cache size reaches a defined threshold.""" | |
712 | daemon = True |
|
713 | daemon = True | |
713 | stop_now = False |
|
714 | stop_now = False | |
714 | enabled = True |
|
715 | enabled = True | |
715 | def __init__(self, history_manager): |
|
716 | def __init__(self, history_manager): | |
716 | super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread") |
|
717 | super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread") | |
717 | self.history_manager = history_manager |
|
718 | self.history_manager = history_manager | |
718 | self.enabled = history_manager.enabled |
|
719 | self.enabled = history_manager.enabled | |
719 | atexit.register(self.stop) |
|
720 | atexit.register(self.stop) | |
720 |
|
721 | |||
721 | @needs_sqlite |
|
722 | @needs_sqlite | |
722 | def run(self): |
|
723 | def run(self): | |
723 | # We need a separate db connection per thread: |
|
724 | # We need a separate db connection per thread: | |
724 | try: |
|
725 | try: | |
725 | self.db = sqlite3.connect(self.history_manager.hist_file, |
|
726 | self.db = sqlite3.connect(self.history_manager.hist_file, | |
726 | **self.history_manager.connection_options |
|
727 | **self.history_manager.connection_options | |
727 | ) |
|
728 | ) | |
728 | while True: |
|
729 | while True: | |
729 | self.history_manager.save_flag.wait() |
|
730 | self.history_manager.save_flag.wait() | |
730 | if self.stop_now: |
|
731 | if self.stop_now: | |
731 | return |
|
732 | return | |
732 | self.history_manager.save_flag.clear() |
|
733 | self.history_manager.save_flag.clear() | |
733 | self.history_manager.writeout_cache(self.db) |
|
734 | self.history_manager.writeout_cache(self.db) | |
734 | except Exception as e: |
|
735 | except Exception as e: | |
735 | print(("The history saving thread hit an unexpected error (%s)." |
|
736 | print(("The history saving thread hit an unexpected error (%s)." | |
736 | "History will not be written to the database.") % repr(e)) |
|
737 | "History will not be written to the database.") % repr(e)) | |
737 |
|
738 | |||
738 | def stop(self): |
|
739 | def stop(self): | |
739 | """This can be called from the main thread to safely stop this thread. |
|
740 | """This can be called from the main thread to safely stop this thread. | |
740 |
|
741 | |||
741 | Note that it does not attempt to write out remaining history before |
|
742 | Note that it does not attempt to write out remaining history before | |
742 | exiting. That should be done by calling the HistoryManager's |
|
743 | exiting. That should be done by calling the HistoryManager's | |
743 | end_session method.""" |
|
744 | end_session method.""" | |
744 | self.stop_now = True |
|
745 | self.stop_now = True | |
745 | self.history_manager.save_flag.set() |
|
746 | self.history_manager.save_flag.set() | |
746 | self.join() |
|
747 | self.join() | |
747 |
|
748 | |||
748 |
|
749 | |||
749 | # To match, e.g. ~5/8-~2/3 |
|
750 | # To match, e.g. ~5/8-~2/3 | |
750 | range_re = re.compile(r""" |
|
751 | range_re = re.compile(r""" | |
751 | ((?P<startsess>~?\d+)/)? |
|
752 | ((?P<startsess>~?\d+)/)? | |
752 | (?P<start>\d+)? |
|
753 | (?P<start>\d+)? | |
753 | ((?P<sep>[\-:]) |
|
754 | ((?P<sep>[\-:]) | |
754 | ((?P<endsess>~?\d+)/)? |
|
755 | ((?P<endsess>~?\d+)/)? | |
755 | (?P<end>\d+))? |
|
756 | (?P<end>\d+))? | |
756 | $""", re.VERBOSE) |
|
757 | $""", re.VERBOSE) | |
757 |
|
758 | |||
758 |
|
759 | |||
759 | def extract_hist_ranges(ranges_str): |
|
760 | def extract_hist_ranges(ranges_str): | |
760 | """Turn a string of history ranges into 3-tuples of (session, start, stop). |
|
761 | """Turn a string of history ranges into 3-tuples of (session, start, stop). | |
761 |
|
762 | |||
762 | Examples |
|
763 | Examples | |
763 | -------- |
|
764 | -------- | |
764 | list(extract_input_ranges("~8/5-~7/4 2")) |
|
765 | list(extract_input_ranges("~8/5-~7/4 2")) | |
765 | [(-8, 5, None), (-7, 1, 4), (0, 2, 3)] |
|
766 | [(-8, 5, None), (-7, 1, 4), (0, 2, 3)] | |
766 | """ |
|
767 | """ | |
767 | for range_str in ranges_str.split(): |
|
768 | for range_str in ranges_str.split(): | |
768 | rmatch = range_re.match(range_str) |
|
769 | rmatch = range_re.match(range_str) | |
769 | if not rmatch: |
|
770 | if not rmatch: | |
770 | continue |
|
771 | continue | |
771 | start = rmatch.group("start") |
|
772 | start = rmatch.group("start") | |
772 | if start: |
|
773 | if start: | |
773 | start = int(start) |
|
774 | start = int(start) | |
774 | end = rmatch.group("end") |
|
775 | end = rmatch.group("end") | |
775 | # If no end specified, get (a, a + 1) |
|
776 | # If no end specified, get (a, a + 1) | |
776 | end = int(end) if end else start + 1 |
|
777 | end = int(end) if end else start + 1 | |
777 | else: # start not specified |
|
778 | else: # start not specified | |
778 | if not rmatch.group('startsess'): # no startsess |
|
779 | if not rmatch.group('startsess'): # no startsess | |
779 | continue |
|
780 | continue | |
780 | start = 1 |
|
781 | start = 1 | |
781 | end = None # provide the entire session hist |
|
782 | end = None # provide the entire session hist | |
782 |
|
783 | |||
783 | if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3] |
|
784 | if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3] | |
784 | end += 1 |
|
785 | end += 1 | |
785 | startsess = rmatch.group("startsess") or "0" |
|
786 | startsess = rmatch.group("startsess") or "0" | |
786 | endsess = rmatch.group("endsess") or startsess |
|
787 | endsess = rmatch.group("endsess") or startsess | |
787 | startsess = int(startsess.replace("~","-")) |
|
788 | startsess = int(startsess.replace("~","-")) | |
788 | endsess = int(endsess.replace("~","-")) |
|
789 | endsess = int(endsess.replace("~","-")) | |
789 | assert endsess >= startsess, "start session must be earlier than end session" |
|
790 | assert endsess >= startsess, "start session must be earlier than end session" | |
790 |
|
791 | |||
791 | if endsess == startsess: |
|
792 | if endsess == startsess: | |
792 | yield (startsess, start, end) |
|
793 | yield (startsess, start, end) | |
793 | continue |
|
794 | continue | |
794 | # Multiple sessions in one range: |
|
795 | # Multiple sessions in one range: | |
795 | yield (startsess, start, None) |
|
796 | yield (startsess, start, None) | |
796 | for sess in range(startsess+1, endsess): |
|
797 | for sess in range(startsess+1, endsess): | |
797 | yield (sess, 1, None) |
|
798 | yield (sess, 1, None) | |
798 | yield (endsess, 1, end) |
|
799 | yield (endsess, 1, end) | |
799 |
|
800 | |||
800 |
|
801 | |||
801 | def _format_lineno(session, line): |
|
802 | def _format_lineno(session, line): | |
802 | """Helper function to format line numbers properly.""" |
|
803 | """Helper function to format line numbers properly.""" | |
803 | if session == 0: |
|
804 | if session == 0: | |
804 | return str(line) |
|
805 | return str(line) | |
805 | return "%s#%s" % (session, line) |
|
806 | return "%s#%s" % (session, line) | |
806 |
|
807 | |||
807 |
|
808 |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,740 +1,741 b'' | |||||
1 | """Implementation of magic functions for interaction with the OS. |
|
1 | """Implementation of magic functions for interaction with the OS. | |
2 |
|
2 | |||
3 | Note: this module is named 'osm' instead of 'os' to avoid a collision with the |
|
3 | Note: this module is named 'osm' instead of 'os' to avoid a collision with the | |
4 | builtin. |
|
4 | builtin. | |
5 | """ |
|
5 | """ | |
6 | from __future__ import print_function |
|
6 | from __future__ import print_function | |
7 | #----------------------------------------------------------------------------- |
|
7 | #----------------------------------------------------------------------------- | |
8 | # Copyright (c) 2012 The IPython Development Team. |
|
8 | # Copyright (c) 2012 The IPython Development Team. | |
9 | # |
|
9 | # | |
10 | # Distributed under the terms of the Modified BSD License. |
|
10 | # Distributed under the terms of the Modified BSD License. | |
11 | # |
|
11 | # | |
12 | # The full license is in the file COPYING.txt, distributed with this software. |
|
12 | # The full license is in the file COPYING.txt, distributed with this software. | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | # Imports |
|
16 | # Imports | |
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 |
|
18 | |||
19 | # Stdlib |
|
19 | # Stdlib | |
20 | import io |
|
20 | import io | |
21 | import os |
|
21 | import os | |
22 | import re |
|
22 | import re | |
23 | import sys |
|
23 | import sys | |
24 | from pprint import pformat |
|
24 | from pprint import pformat | |
25 |
|
25 | |||
26 | # Our own packages |
|
26 | # Our own packages | |
27 | from IPython.core import magic_arguments |
|
27 | from IPython.core import magic_arguments | |
28 | from IPython.core import oinspect |
|
28 | from IPython.core import oinspect | |
29 | from IPython.core import page |
|
29 | from IPython.core import page | |
30 | from IPython.core.alias import AliasError, Alias |
|
30 | from IPython.core.alias import AliasError, Alias | |
31 | from IPython.core.error import UsageError |
|
31 | from IPython.core.error import UsageError | |
32 | from IPython.core.magic import ( |
|
32 | from IPython.core.magic import ( | |
33 | Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic |
|
33 | Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic | |
34 | ) |
|
34 | ) | |
35 | from IPython.testing.skipdoctest import skip_doctest |
|
35 | from IPython.testing.skipdoctest import skip_doctest | |
36 | from IPython.utils.openpy import source_to_unicode |
|
36 | from IPython.utils.openpy import source_to_unicode | |
37 | from IPython.utils.path import unquote_filename |
|
37 | from IPython.utils.path import unquote_filename | |
38 | from IPython.utils.process import abbrev_cwd |
|
38 | from IPython.utils.process import abbrev_cwd | |
|
39 | from IPython.utils import py3compat | |||
39 | from IPython.utils.py3compat import unicode_type |
|
40 | from IPython.utils.py3compat import unicode_type | |
40 | from IPython.utils.terminal import set_term_title |
|
41 | from IPython.utils.terminal import set_term_title | |
41 |
|
42 | |||
42 | #----------------------------------------------------------------------------- |
|
43 | #----------------------------------------------------------------------------- | |
43 | # Magic implementation classes |
|
44 | # Magic implementation classes | |
44 | #----------------------------------------------------------------------------- |
|
45 | #----------------------------------------------------------------------------- | |
45 | @magics_class |
|
46 | @magics_class | |
46 | class OSMagics(Magics): |
|
47 | class OSMagics(Magics): | |
47 | """Magics to interact with the underlying OS (shell-type functionality). |
|
48 | """Magics to interact with the underlying OS (shell-type functionality). | |
48 | """ |
|
49 | """ | |
49 |
|
50 | |||
50 | @skip_doctest |
|
51 | @skip_doctest | |
51 | @line_magic |
|
52 | @line_magic | |
52 | def alias(self, parameter_s=''): |
|
53 | def alias(self, parameter_s=''): | |
53 | """Define an alias for a system command. |
|
54 | """Define an alias for a system command. | |
54 |
|
55 | |||
55 | '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd' |
|
56 | '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd' | |
56 |
|
57 | |||
57 | Then, typing 'alias_name params' will execute the system command 'cmd |
|
58 | Then, typing 'alias_name params' will execute the system command 'cmd | |
58 | params' (from your underlying operating system). |
|
59 | params' (from your underlying operating system). | |
59 |
|
60 | |||
60 | Aliases have lower precedence than magic functions and Python normal |
|
61 | Aliases have lower precedence than magic functions and Python normal | |
61 | variables, so if 'foo' is both a Python variable and an alias, the |
|
62 | variables, so if 'foo' is both a Python variable and an alias, the | |
62 | alias can not be executed until 'del foo' removes the Python variable. |
|
63 | alias can not be executed until 'del foo' removes the Python variable. | |
63 |
|
64 | |||
64 | You can use the %l specifier in an alias definition to represent the |
|
65 | You can use the %l specifier in an alias definition to represent the | |
65 | whole line when the alias is called. For example:: |
|
66 | whole line when the alias is called. For example:: | |
66 |
|
67 | |||
67 | In [2]: alias bracket echo "Input in brackets: <%l>" |
|
68 | In [2]: alias bracket echo "Input in brackets: <%l>" | |
68 | In [3]: bracket hello world |
|
69 | In [3]: bracket hello world | |
69 | Input in brackets: <hello world> |
|
70 | Input in brackets: <hello world> | |
70 |
|
71 | |||
71 | You can also define aliases with parameters using %s specifiers (one |
|
72 | You can also define aliases with parameters using %s specifiers (one | |
72 | per parameter):: |
|
73 | per parameter):: | |
73 |
|
74 | |||
74 | In [1]: alias parts echo first %s second %s |
|
75 | In [1]: alias parts echo first %s second %s | |
75 | In [2]: %parts A B |
|
76 | In [2]: %parts A B | |
76 | first A second B |
|
77 | first A second B | |
77 | In [3]: %parts A |
|
78 | In [3]: %parts A | |
78 | Incorrect number of arguments: 2 expected. |
|
79 | Incorrect number of arguments: 2 expected. | |
79 | parts is an alias to: 'echo first %s second %s' |
|
80 | parts is an alias to: 'echo first %s second %s' | |
80 |
|
81 | |||
81 | Note that %l and %s are mutually exclusive. You can only use one or |
|
82 | Note that %l and %s are mutually exclusive. You can only use one or | |
82 | the other in your aliases. |
|
83 | the other in your aliases. | |
83 |
|
84 | |||
84 | Aliases expand Python variables just like system calls using ! or !! |
|
85 | Aliases expand Python variables just like system calls using ! or !! | |
85 | do: all expressions prefixed with '$' get expanded. For details of |
|
86 | do: all expressions prefixed with '$' get expanded. For details of | |
86 | the semantic rules, see PEP-215: |
|
87 | the semantic rules, see PEP-215: | |
87 | http://www.python.org/peps/pep-0215.html. This is the library used by |
|
88 | http://www.python.org/peps/pep-0215.html. This is the library used by | |
88 | IPython for variable expansion. If you want to access a true shell |
|
89 | IPython for variable expansion. If you want to access a true shell | |
89 | variable, an extra $ is necessary to prevent its expansion by |
|
90 | variable, an extra $ is necessary to prevent its expansion by | |
90 | IPython:: |
|
91 | IPython:: | |
91 |
|
92 | |||
92 | In [6]: alias show echo |
|
93 | In [6]: alias show echo | |
93 | In [7]: PATH='A Python string' |
|
94 | In [7]: PATH='A Python string' | |
94 | In [8]: show $PATH |
|
95 | In [8]: show $PATH | |
95 | A Python string |
|
96 | A Python string | |
96 | In [9]: show $$PATH |
|
97 | In [9]: show $$PATH | |
97 | /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... |
|
98 | /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... | |
98 |
|
99 | |||
99 | You can use the alias facility to acess all of $PATH. See the %rehash |
|
100 | You can use the alias facility to acess all of $PATH. See the %rehash | |
100 | and %rehashx functions, which automatically create aliases for the |
|
101 | and %rehashx functions, which automatically create aliases for the | |
101 | contents of your $PATH. |
|
102 | contents of your $PATH. | |
102 |
|
103 | |||
103 | If called with no parameters, %alias prints the current alias table.""" |
|
104 | If called with no parameters, %alias prints the current alias table.""" | |
104 |
|
105 | |||
105 | par = parameter_s.strip() |
|
106 | par = parameter_s.strip() | |
106 | if not par: |
|
107 | if not par: | |
107 | aliases = sorted(self.shell.alias_manager.aliases) |
|
108 | aliases = sorted(self.shell.alias_manager.aliases) | |
108 | # stored = self.shell.db.get('stored_aliases', {} ) |
|
109 | # stored = self.shell.db.get('stored_aliases', {} ) | |
109 | # for k, v in stored: |
|
110 | # for k, v in stored: | |
110 | # atab.append(k, v[0]) |
|
111 | # atab.append(k, v[0]) | |
111 |
|
112 | |||
112 | print("Total number of aliases:", len(aliases)) |
|
113 | print("Total number of aliases:", len(aliases)) | |
113 | sys.stdout.flush() |
|
114 | sys.stdout.flush() | |
114 | return aliases |
|
115 | return aliases | |
115 |
|
116 | |||
116 | # Now try to define a new one |
|
117 | # Now try to define a new one | |
117 | try: |
|
118 | try: | |
118 | alias,cmd = par.split(None, 1) |
|
119 | alias,cmd = par.split(None, 1) | |
119 | except TypeError: |
|
120 | except TypeError: | |
120 | print(oinspect.getdoc(self.alias)) |
|
121 | print(oinspect.getdoc(self.alias)) | |
121 | return |
|
122 | return | |
122 |
|
123 | |||
123 | try: |
|
124 | try: | |
124 | self.shell.alias_manager.define_alias(alias, cmd) |
|
125 | self.shell.alias_manager.define_alias(alias, cmd) | |
125 | except AliasError as e: |
|
126 | except AliasError as e: | |
126 | print(e) |
|
127 | print(e) | |
127 | # end magic_alias |
|
128 | # end magic_alias | |
128 |
|
129 | |||
129 | @line_magic |
|
130 | @line_magic | |
130 | def unalias(self, parameter_s=''): |
|
131 | def unalias(self, parameter_s=''): | |
131 | """Remove an alias""" |
|
132 | """Remove an alias""" | |
132 |
|
133 | |||
133 | aname = parameter_s.strip() |
|
134 | aname = parameter_s.strip() | |
134 | try: |
|
135 | try: | |
135 | self.shell.alias_manager.undefine_alias(aname) |
|
136 | self.shell.alias_manager.undefine_alias(aname) | |
136 | except ValueError as e: |
|
137 | except ValueError as e: | |
137 | print(e) |
|
138 | print(e) | |
138 | return |
|
139 | return | |
139 |
|
140 | |||
140 | stored = self.shell.db.get('stored_aliases', {} ) |
|
141 | stored = self.shell.db.get('stored_aliases', {} ) | |
141 | if aname in stored: |
|
142 | if aname in stored: | |
142 | print("Removing %stored alias",aname) |
|
143 | print("Removing %stored alias",aname) | |
143 | del stored[aname] |
|
144 | del stored[aname] | |
144 | self.shell.db['stored_aliases'] = stored |
|
145 | self.shell.db['stored_aliases'] = stored | |
145 |
|
146 | |||
146 | @line_magic |
|
147 | @line_magic | |
147 | def rehashx(self, parameter_s=''): |
|
148 | def rehashx(self, parameter_s=''): | |
148 | """Update the alias table with all executable files in $PATH. |
|
149 | """Update the alias table with all executable files in $PATH. | |
149 |
|
150 | |||
150 | This version explicitly checks that every entry in $PATH is a file |
|
151 | This version explicitly checks that every entry in $PATH is a file | |
151 | with execute access (os.X_OK), so it is much slower than %rehash. |
|
152 | with execute access (os.X_OK), so it is much slower than %rehash. | |
152 |
|
153 | |||
153 | Under Windows, it checks executability as a match against a |
|
154 | Under Windows, it checks executability as a match against a | |
154 | '|'-separated string of extensions, stored in the IPython config |
|
155 | '|'-separated string of extensions, stored in the IPython config | |
155 | variable win_exec_ext. This defaults to 'exe|com|bat'. |
|
156 | variable win_exec_ext. This defaults to 'exe|com|bat'. | |
156 |
|
157 | |||
157 | This function also resets the root module cache of module completer, |
|
158 | This function also resets the root module cache of module completer, | |
158 | used on slow filesystems. |
|
159 | used on slow filesystems. | |
159 | """ |
|
160 | """ | |
160 | from IPython.core.alias import InvalidAliasError |
|
161 | from IPython.core.alias import InvalidAliasError | |
161 |
|
162 | |||
162 | # for the benefit of module completer in ipy_completers.py |
|
163 | # for the benefit of module completer in ipy_completers.py | |
163 | del self.shell.db['rootmodules_cache'] |
|
164 | del self.shell.db['rootmodules_cache'] | |
164 |
|
165 | |||
165 | path = [os.path.abspath(os.path.expanduser(p)) for p in |
|
166 | path = [os.path.abspath(os.path.expanduser(p)) for p in | |
166 | os.environ.get('PATH','').split(os.pathsep)] |
|
167 | os.environ.get('PATH','').split(os.pathsep)] | |
167 | path = filter(os.path.isdir,path) |
|
168 | path = filter(os.path.isdir,path) | |
168 |
|
169 | |||
169 | syscmdlist = [] |
|
170 | syscmdlist = [] | |
170 | # Now define isexec in a cross platform manner. |
|
171 | # Now define isexec in a cross platform manner. | |
171 | if os.name == 'posix': |
|
172 | if os.name == 'posix': | |
172 | isexec = lambda fname:os.path.isfile(fname) and \ |
|
173 | isexec = lambda fname:os.path.isfile(fname) and \ | |
173 | os.access(fname,os.X_OK) |
|
174 | os.access(fname,os.X_OK) | |
174 | else: |
|
175 | else: | |
175 | try: |
|
176 | try: | |
176 | winext = os.environ['pathext'].replace(';','|').replace('.','') |
|
177 | winext = os.environ['pathext'].replace(';','|').replace('.','') | |
177 | except KeyError: |
|
178 | except KeyError: | |
178 | winext = 'exe|com|bat|py' |
|
179 | winext = 'exe|com|bat|py' | |
179 | if 'py' not in winext: |
|
180 | if 'py' not in winext: | |
180 | winext += '|py' |
|
181 | winext += '|py' | |
181 | execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) |
|
182 | execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) | |
182 | isexec = lambda fname:os.path.isfile(fname) and execre.match(fname) |
|
183 | isexec = lambda fname:os.path.isfile(fname) and execre.match(fname) | |
183 |
savedir = |
|
184 | savedir = py3compat.getcwd() | |
184 |
|
185 | |||
185 | # Now walk the paths looking for executables to alias. |
|
186 | # Now walk the paths looking for executables to alias. | |
186 | try: |
|
187 | try: | |
187 | # write the whole loop for posix/Windows so we don't have an if in |
|
188 | # write the whole loop for posix/Windows so we don't have an if in | |
188 | # the innermost part |
|
189 | # the innermost part | |
189 | if os.name == 'posix': |
|
190 | if os.name == 'posix': | |
190 | for pdir in path: |
|
191 | for pdir in path: | |
191 | os.chdir(pdir) |
|
192 | os.chdir(pdir) | |
192 | for ff in os.listdir(pdir): |
|
193 | for ff in os.listdir(pdir): | |
193 | if isexec(ff): |
|
194 | if isexec(ff): | |
194 | try: |
|
195 | try: | |
195 | # Removes dots from the name since ipython |
|
196 | # Removes dots from the name since ipython | |
196 | # will assume names with dots to be python. |
|
197 | # will assume names with dots to be python. | |
197 | if not self.shell.alias_manager.is_alias(ff): |
|
198 | if not self.shell.alias_manager.is_alias(ff): | |
198 | self.shell.alias_manager.define_alias( |
|
199 | self.shell.alias_manager.define_alias( | |
199 | ff.replace('.',''), ff) |
|
200 | ff.replace('.',''), ff) | |
200 | except InvalidAliasError: |
|
201 | except InvalidAliasError: | |
201 | pass |
|
202 | pass | |
202 | else: |
|
203 | else: | |
203 | syscmdlist.append(ff) |
|
204 | syscmdlist.append(ff) | |
204 | else: |
|
205 | else: | |
205 | no_alias = Alias.blacklist |
|
206 | no_alias = Alias.blacklist | |
206 | for pdir in path: |
|
207 | for pdir in path: | |
207 | os.chdir(pdir) |
|
208 | os.chdir(pdir) | |
208 | for ff in os.listdir(pdir): |
|
209 | for ff in os.listdir(pdir): | |
209 | base, ext = os.path.splitext(ff) |
|
210 | base, ext = os.path.splitext(ff) | |
210 | if isexec(ff) and base.lower() not in no_alias: |
|
211 | if isexec(ff) and base.lower() not in no_alias: | |
211 | if ext.lower() == '.exe': |
|
212 | if ext.lower() == '.exe': | |
212 | ff = base |
|
213 | ff = base | |
213 | try: |
|
214 | try: | |
214 | # Removes dots from the name since ipython |
|
215 | # Removes dots from the name since ipython | |
215 | # will assume names with dots to be python. |
|
216 | # will assume names with dots to be python. | |
216 | self.shell.alias_manager.define_alias( |
|
217 | self.shell.alias_manager.define_alias( | |
217 | base.lower().replace('.',''), ff) |
|
218 | base.lower().replace('.',''), ff) | |
218 | except InvalidAliasError: |
|
219 | except InvalidAliasError: | |
219 | pass |
|
220 | pass | |
220 | syscmdlist.append(ff) |
|
221 | syscmdlist.append(ff) | |
221 | self.shell.db['syscmdlist'] = syscmdlist |
|
222 | self.shell.db['syscmdlist'] = syscmdlist | |
222 | finally: |
|
223 | finally: | |
223 | os.chdir(savedir) |
|
224 | os.chdir(savedir) | |
224 |
|
225 | |||
225 | @skip_doctest |
|
226 | @skip_doctest | |
226 | @line_magic |
|
227 | @line_magic | |
227 | def pwd(self, parameter_s=''): |
|
228 | def pwd(self, parameter_s=''): | |
228 | """Return the current working directory path. |
|
229 | """Return the current working directory path. | |
229 |
|
230 | |||
230 | Examples |
|
231 | Examples | |
231 | -------- |
|
232 | -------- | |
232 | :: |
|
233 | :: | |
233 |
|
234 | |||
234 | In [9]: pwd |
|
235 | In [9]: pwd | |
235 | Out[9]: '/home/tsuser/sprint/ipython' |
|
236 | Out[9]: '/home/tsuser/sprint/ipython' | |
236 | """ |
|
237 | """ | |
237 |
return |
|
238 | return py3compat.getcwd() | |
238 |
|
239 | |||
239 | @skip_doctest |
|
240 | @skip_doctest | |
240 | @line_magic |
|
241 | @line_magic | |
241 | def cd(self, parameter_s=''): |
|
242 | def cd(self, parameter_s=''): | |
242 | """Change the current working directory. |
|
243 | """Change the current working directory. | |
243 |
|
244 | |||
244 | This command automatically maintains an internal list of directories |
|
245 | This command automatically maintains an internal list of directories | |
245 | you visit during your IPython session, in the variable _dh. The |
|
246 | you visit during your IPython session, in the variable _dh. The | |
246 | command %dhist shows this history nicely formatted. You can also |
|
247 | command %dhist shows this history nicely formatted. You can also | |
247 | do 'cd -<tab>' to see directory history conveniently. |
|
248 | do 'cd -<tab>' to see directory history conveniently. | |
248 |
|
249 | |||
249 | Usage: |
|
250 | Usage: | |
250 |
|
251 | |||
251 | cd 'dir': changes to directory 'dir'. |
|
252 | cd 'dir': changes to directory 'dir'. | |
252 |
|
253 | |||
253 | cd -: changes to the last visited directory. |
|
254 | cd -: changes to the last visited directory. | |
254 |
|
255 | |||
255 | cd -<n>: changes to the n-th directory in the directory history. |
|
256 | cd -<n>: changes to the n-th directory in the directory history. | |
256 |
|
257 | |||
257 | cd --foo: change to directory that matches 'foo' in history |
|
258 | cd --foo: change to directory that matches 'foo' in history | |
258 |
|
259 | |||
259 | cd -b <bookmark_name>: jump to a bookmark set by %bookmark |
|
260 | cd -b <bookmark_name>: jump to a bookmark set by %bookmark | |
260 | (note: cd <bookmark_name> is enough if there is no |
|
261 | (note: cd <bookmark_name> is enough if there is no | |
261 | directory <bookmark_name>, but a bookmark with the name exists.) |
|
262 | directory <bookmark_name>, but a bookmark with the name exists.) | |
262 | 'cd -b <tab>' allows you to tab-complete bookmark names. |
|
263 | 'cd -b <tab>' allows you to tab-complete bookmark names. | |
263 |
|
264 | |||
264 | Options: |
|
265 | Options: | |
265 |
|
266 | |||
266 | -q: quiet. Do not print the working directory after the cd command is |
|
267 | -q: quiet. Do not print the working directory after the cd command is | |
267 | executed. By default IPython's cd command does print this directory, |
|
268 | executed. By default IPython's cd command does print this directory, | |
268 | since the default prompts do not display path information. |
|
269 | since the default prompts do not display path information. | |
269 |
|
270 | |||
270 | Note that !cd doesn't work for this purpose because the shell where |
|
271 | Note that !cd doesn't work for this purpose because the shell where | |
271 | !command runs is immediately discarded after executing 'command'. |
|
272 | !command runs is immediately discarded after executing 'command'. | |
272 |
|
273 | |||
273 | Examples |
|
274 | Examples | |
274 | -------- |
|
275 | -------- | |
275 | :: |
|
276 | :: | |
276 |
|
277 | |||
277 | In [10]: cd parent/child |
|
278 | In [10]: cd parent/child | |
278 | /home/tsuser/parent/child |
|
279 | /home/tsuser/parent/child | |
279 | """ |
|
280 | """ | |
280 |
|
281 | |||
281 |
oldcwd = |
|
282 | oldcwd = py3compat.getcwd() | |
282 | numcd = re.match(r'(-)(\d+)$',parameter_s) |
|
283 | numcd = re.match(r'(-)(\d+)$',parameter_s) | |
283 | # jump in directory history by number |
|
284 | # jump in directory history by number | |
284 | if numcd: |
|
285 | if numcd: | |
285 | nn = int(numcd.group(2)) |
|
286 | nn = int(numcd.group(2)) | |
286 | try: |
|
287 | try: | |
287 | ps = self.shell.user_ns['_dh'][nn] |
|
288 | ps = self.shell.user_ns['_dh'][nn] | |
288 | except IndexError: |
|
289 | except IndexError: | |
289 | print('The requested directory does not exist in history.') |
|
290 | print('The requested directory does not exist in history.') | |
290 | return |
|
291 | return | |
291 | else: |
|
292 | else: | |
292 | opts = {} |
|
293 | opts = {} | |
293 | elif parameter_s.startswith('--'): |
|
294 | elif parameter_s.startswith('--'): | |
294 | ps = None |
|
295 | ps = None | |
295 | fallback = None |
|
296 | fallback = None | |
296 | pat = parameter_s[2:] |
|
297 | pat = parameter_s[2:] | |
297 | dh = self.shell.user_ns['_dh'] |
|
298 | dh = self.shell.user_ns['_dh'] | |
298 | # first search only by basename (last component) |
|
299 | # first search only by basename (last component) | |
299 | for ent in reversed(dh): |
|
300 | for ent in reversed(dh): | |
300 | if pat in os.path.basename(ent) and os.path.isdir(ent): |
|
301 | if pat in os.path.basename(ent) and os.path.isdir(ent): | |
301 | ps = ent |
|
302 | ps = ent | |
302 | break |
|
303 | break | |
303 |
|
304 | |||
304 | if fallback is None and pat in ent and os.path.isdir(ent): |
|
305 | if fallback is None and pat in ent and os.path.isdir(ent): | |
305 | fallback = ent |
|
306 | fallback = ent | |
306 |
|
307 | |||
307 | # if we have no last part match, pick the first full path match |
|
308 | # if we have no last part match, pick the first full path match | |
308 | if ps is None: |
|
309 | if ps is None: | |
309 | ps = fallback |
|
310 | ps = fallback | |
310 |
|
311 | |||
311 | if ps is None: |
|
312 | if ps is None: | |
312 | print("No matching entry in directory history") |
|
313 | print("No matching entry in directory history") | |
313 | return |
|
314 | return | |
314 | else: |
|
315 | else: | |
315 | opts = {} |
|
316 | opts = {} | |
316 |
|
317 | |||
317 |
|
318 | |||
318 | else: |
|
319 | else: | |
319 | #turn all non-space-escaping backslashes to slashes, |
|
320 | #turn all non-space-escaping backslashes to slashes, | |
320 | # for c:\windows\directory\names\ |
|
321 | # for c:\windows\directory\names\ | |
321 | parameter_s = re.sub(r'\\(?! )','/', parameter_s) |
|
322 | parameter_s = re.sub(r'\\(?! )','/', parameter_s) | |
322 | opts,ps = self.parse_options(parameter_s,'qb',mode='string') |
|
323 | opts,ps = self.parse_options(parameter_s,'qb',mode='string') | |
323 | # jump to previous |
|
324 | # jump to previous | |
324 | if ps == '-': |
|
325 | if ps == '-': | |
325 | try: |
|
326 | try: | |
326 | ps = self.shell.user_ns['_dh'][-2] |
|
327 | ps = self.shell.user_ns['_dh'][-2] | |
327 | except IndexError: |
|
328 | except IndexError: | |
328 | raise UsageError('%cd -: No previous directory to change to.') |
|
329 | raise UsageError('%cd -: No previous directory to change to.') | |
329 | # jump to bookmark if needed |
|
330 | # jump to bookmark if needed | |
330 | else: |
|
331 | else: | |
331 | if not os.path.isdir(ps) or 'b' in opts: |
|
332 | if not os.path.isdir(ps) or 'b' in opts: | |
332 | bkms = self.shell.db.get('bookmarks', {}) |
|
333 | bkms = self.shell.db.get('bookmarks', {}) | |
333 |
|
334 | |||
334 | if ps in bkms: |
|
335 | if ps in bkms: | |
335 | target = bkms[ps] |
|
336 | target = bkms[ps] | |
336 | print('(bookmark:%s) -> %s' % (ps, target)) |
|
337 | print('(bookmark:%s) -> %s' % (ps, target)) | |
337 | ps = target |
|
338 | ps = target | |
338 | else: |
|
339 | else: | |
339 | if 'b' in opts: |
|
340 | if 'b' in opts: | |
340 | raise UsageError("Bookmark '%s' not found. " |
|
341 | raise UsageError("Bookmark '%s' not found. " | |
341 | "Use '%%bookmark -l' to see your bookmarks." % ps) |
|
342 | "Use '%%bookmark -l' to see your bookmarks." % ps) | |
342 |
|
343 | |||
343 | # strip extra quotes on Windows, because os.chdir doesn't like them |
|
344 | # strip extra quotes on Windows, because os.chdir doesn't like them | |
344 | ps = unquote_filename(ps) |
|
345 | ps = unquote_filename(ps) | |
345 | # at this point ps should point to the target dir |
|
346 | # at this point ps should point to the target dir | |
346 | if ps: |
|
347 | if ps: | |
347 | try: |
|
348 | try: | |
348 | os.chdir(os.path.expanduser(ps)) |
|
349 | os.chdir(os.path.expanduser(ps)) | |
349 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
350 | if hasattr(self.shell, 'term_title') and self.shell.term_title: | |
350 | set_term_title('IPython: ' + abbrev_cwd()) |
|
351 | set_term_title('IPython: ' + abbrev_cwd()) | |
351 | except OSError: |
|
352 | except OSError: | |
352 | print(sys.exc_info()[1]) |
|
353 | print(sys.exc_info()[1]) | |
353 | else: |
|
354 | else: | |
354 |
cwd = |
|
355 | cwd = py3compat.getcwd() | |
355 | dhist = self.shell.user_ns['_dh'] |
|
356 | dhist = self.shell.user_ns['_dh'] | |
356 | if oldcwd != cwd: |
|
357 | if oldcwd != cwd: | |
357 | dhist.append(cwd) |
|
358 | dhist.append(cwd) | |
358 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
359 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] | |
359 |
|
360 | |||
360 | else: |
|
361 | else: | |
361 | os.chdir(self.shell.home_dir) |
|
362 | os.chdir(self.shell.home_dir) | |
362 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
363 | if hasattr(self.shell, 'term_title') and self.shell.term_title: | |
363 | set_term_title('IPython: ' + '~') |
|
364 | set_term_title('IPython: ' + '~') | |
364 |
cwd = |
|
365 | cwd = py3compat.getcwd() | |
365 | dhist = self.shell.user_ns['_dh'] |
|
366 | dhist = self.shell.user_ns['_dh'] | |
366 |
|
367 | |||
367 | if oldcwd != cwd: |
|
368 | if oldcwd != cwd: | |
368 | dhist.append(cwd) |
|
369 | dhist.append(cwd) | |
369 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
370 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] | |
370 | if not 'q' in opts and self.shell.user_ns['_dh']: |
|
371 | if not 'q' in opts and self.shell.user_ns['_dh']: | |
371 | print(self.shell.user_ns['_dh'][-1]) |
|
372 | print(self.shell.user_ns['_dh'][-1]) | |
372 |
|
373 | |||
373 |
|
374 | |||
374 | @line_magic |
|
375 | @line_magic | |
375 | def env(self, parameter_s=''): |
|
376 | def env(self, parameter_s=''): | |
376 | """List environment variables.""" |
|
377 | """List environment variables.""" | |
377 |
|
378 | |||
378 | return dict(os.environ) |
|
379 | return dict(os.environ) | |
379 |
|
380 | |||
380 | @line_magic |
|
381 | @line_magic | |
381 | def pushd(self, parameter_s=''): |
|
382 | def pushd(self, parameter_s=''): | |
382 | """Place the current dir on stack and change directory. |
|
383 | """Place the current dir on stack and change directory. | |
383 |
|
384 | |||
384 | Usage:\\ |
|
385 | Usage:\\ | |
385 | %pushd ['dirname'] |
|
386 | %pushd ['dirname'] | |
386 | """ |
|
387 | """ | |
387 |
|
388 | |||
388 | dir_s = self.shell.dir_stack |
|
389 | dir_s = self.shell.dir_stack | |
389 | tgt = os.path.expanduser(unquote_filename(parameter_s)) |
|
390 | tgt = os.path.expanduser(unquote_filename(parameter_s)) | |
390 |
cwd = |
|
391 | cwd = py3compat.getcwd().replace(self.shell.home_dir,'~') | |
391 | if tgt: |
|
392 | if tgt: | |
392 | self.cd(parameter_s) |
|
393 | self.cd(parameter_s) | |
393 | dir_s.insert(0,cwd) |
|
394 | dir_s.insert(0,cwd) | |
394 | return self.shell.magic('dirs') |
|
395 | return self.shell.magic('dirs') | |
395 |
|
396 | |||
396 | @line_magic |
|
397 | @line_magic | |
397 | def popd(self, parameter_s=''): |
|
398 | def popd(self, parameter_s=''): | |
398 | """Change to directory popped off the top of the stack. |
|
399 | """Change to directory popped off the top of the stack. | |
399 | """ |
|
400 | """ | |
400 | if not self.shell.dir_stack: |
|
401 | if not self.shell.dir_stack: | |
401 | raise UsageError("%popd on empty stack") |
|
402 | raise UsageError("%popd on empty stack") | |
402 | top = self.shell.dir_stack.pop(0) |
|
403 | top = self.shell.dir_stack.pop(0) | |
403 | self.cd(top) |
|
404 | self.cd(top) | |
404 | print("popd ->",top) |
|
405 | print("popd ->",top) | |
405 |
|
406 | |||
406 | @line_magic |
|
407 | @line_magic | |
407 | def dirs(self, parameter_s=''): |
|
408 | def dirs(self, parameter_s=''): | |
408 | """Return the current directory stack.""" |
|
409 | """Return the current directory stack.""" | |
409 |
|
410 | |||
410 | return self.shell.dir_stack |
|
411 | return self.shell.dir_stack | |
411 |
|
412 | |||
412 | @line_magic |
|
413 | @line_magic | |
413 | def dhist(self, parameter_s=''): |
|
414 | def dhist(self, parameter_s=''): | |
414 | """Print your history of visited directories. |
|
415 | """Print your history of visited directories. | |
415 |
|
416 | |||
416 | %dhist -> print full history\\ |
|
417 | %dhist -> print full history\\ | |
417 | %dhist n -> print last n entries only\\ |
|
418 | %dhist n -> print last n entries only\\ | |
418 | %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\ |
|
419 | %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\ | |
419 |
|
420 | |||
420 | This history is automatically maintained by the %cd command, and |
|
421 | This history is automatically maintained by the %cd command, and | |
421 | always available as the global list variable _dh. You can use %cd -<n> |
|
422 | always available as the global list variable _dh. You can use %cd -<n> | |
422 | to go to directory number <n>. |
|
423 | to go to directory number <n>. | |
423 |
|
424 | |||
424 | Note that most of time, you should view directory history by entering |
|
425 | Note that most of time, you should view directory history by entering | |
425 | cd -<TAB>. |
|
426 | cd -<TAB>. | |
426 |
|
427 | |||
427 | """ |
|
428 | """ | |
428 |
|
429 | |||
429 | dh = self.shell.user_ns['_dh'] |
|
430 | dh = self.shell.user_ns['_dh'] | |
430 | if parameter_s: |
|
431 | if parameter_s: | |
431 | try: |
|
432 | try: | |
432 | args = map(int,parameter_s.split()) |
|
433 | args = map(int,parameter_s.split()) | |
433 | except: |
|
434 | except: | |
434 | self.arg_err(self.dhist) |
|
435 | self.arg_err(self.dhist) | |
435 | return |
|
436 | return | |
436 | if len(args) == 1: |
|
437 | if len(args) == 1: | |
437 | ini,fin = max(len(dh)-(args[0]),0),len(dh) |
|
438 | ini,fin = max(len(dh)-(args[0]),0),len(dh) | |
438 | elif len(args) == 2: |
|
439 | elif len(args) == 2: | |
439 | ini,fin = args |
|
440 | ini,fin = args | |
440 | fin = min(fin, len(dh)) |
|
441 | fin = min(fin, len(dh)) | |
441 | else: |
|
442 | else: | |
442 | self.arg_err(self.dhist) |
|
443 | self.arg_err(self.dhist) | |
443 | return |
|
444 | return | |
444 | else: |
|
445 | else: | |
445 | ini,fin = 0,len(dh) |
|
446 | ini,fin = 0,len(dh) | |
446 | print('Directory history (kept in _dh)') |
|
447 | print('Directory history (kept in _dh)') | |
447 | for i in range(ini, fin): |
|
448 | for i in range(ini, fin): | |
448 | print("%d: %s" % (i, dh[i])) |
|
449 | print("%d: %s" % (i, dh[i])) | |
449 |
|
450 | |||
450 | @skip_doctest |
|
451 | @skip_doctest | |
451 | @line_magic |
|
452 | @line_magic | |
452 | def sc(self, parameter_s=''): |
|
453 | def sc(self, parameter_s=''): | |
453 | """Shell capture - run shell command and capture output (DEPRECATED use !). |
|
454 | """Shell capture - run shell command and capture output (DEPRECATED use !). | |
454 |
|
455 | |||
455 | DEPRECATED. Suboptimal, retained for backwards compatibility. |
|
456 | DEPRECATED. Suboptimal, retained for backwards compatibility. | |
456 |
|
457 | |||
457 | You should use the form 'var = !command' instead. Example: |
|
458 | You should use the form 'var = !command' instead. Example: | |
458 |
|
459 | |||
459 | "%sc -l myfiles = ls ~" should now be written as |
|
460 | "%sc -l myfiles = ls ~" should now be written as | |
460 |
|
461 | |||
461 | "myfiles = !ls ~" |
|
462 | "myfiles = !ls ~" | |
462 |
|
463 | |||
463 | myfiles.s, myfiles.l and myfiles.n still apply as documented |
|
464 | myfiles.s, myfiles.l and myfiles.n still apply as documented | |
464 | below. |
|
465 | below. | |
465 |
|
466 | |||
466 | -- |
|
467 | -- | |
467 | %sc [options] varname=command |
|
468 | %sc [options] varname=command | |
468 |
|
469 | |||
469 | IPython will run the given command using commands.getoutput(), and |
|
470 | IPython will run the given command using commands.getoutput(), and | |
470 | will then update the user's interactive namespace with a variable |
|
471 | will then update the user's interactive namespace with a variable | |
471 | called varname, containing the value of the call. Your command can |
|
472 | called varname, containing the value of the call. Your command can | |
472 | contain shell wildcards, pipes, etc. |
|
473 | contain shell wildcards, pipes, etc. | |
473 |
|
474 | |||
474 | The '=' sign in the syntax is mandatory, and the variable name you |
|
475 | The '=' sign in the syntax is mandatory, and the variable name you | |
475 | supply must follow Python's standard conventions for valid names. |
|
476 | supply must follow Python's standard conventions for valid names. | |
476 |
|
477 | |||
477 | (A special format without variable name exists for internal use) |
|
478 | (A special format without variable name exists for internal use) | |
478 |
|
479 | |||
479 | Options: |
|
480 | Options: | |
480 |
|
481 | |||
481 | -l: list output. Split the output on newlines into a list before |
|
482 | -l: list output. Split the output on newlines into a list before | |
482 | assigning it to the given variable. By default the output is stored |
|
483 | assigning it to the given variable. By default the output is stored | |
483 | as a single string. |
|
484 | as a single string. | |
484 |
|
485 | |||
485 | -v: verbose. Print the contents of the variable. |
|
486 | -v: verbose. Print the contents of the variable. | |
486 |
|
487 | |||
487 | In most cases you should not need to split as a list, because the |
|
488 | In most cases you should not need to split as a list, because the | |
488 | returned value is a special type of string which can automatically |
|
489 | returned value is a special type of string which can automatically | |
489 | provide its contents either as a list (split on newlines) or as a |
|
490 | provide its contents either as a list (split on newlines) or as a | |
490 | space-separated string. These are convenient, respectively, either |
|
491 | space-separated string. These are convenient, respectively, either | |
491 | for sequential processing or to be passed to a shell command. |
|
492 | for sequential processing or to be passed to a shell command. | |
492 |
|
493 | |||
493 | For example:: |
|
494 | For example:: | |
494 |
|
495 | |||
495 | # Capture into variable a |
|
496 | # Capture into variable a | |
496 | In [1]: sc a=ls *py |
|
497 | In [1]: sc a=ls *py | |
497 |
|
498 | |||
498 | # a is a string with embedded newlines |
|
499 | # a is a string with embedded newlines | |
499 | In [2]: a |
|
500 | In [2]: a | |
500 | Out[2]: 'setup.py\\nwin32_manual_post_install.py' |
|
501 | Out[2]: 'setup.py\\nwin32_manual_post_install.py' | |
501 |
|
502 | |||
502 | # which can be seen as a list: |
|
503 | # which can be seen as a list: | |
503 | In [3]: a.l |
|
504 | In [3]: a.l | |
504 | Out[3]: ['setup.py', 'win32_manual_post_install.py'] |
|
505 | Out[3]: ['setup.py', 'win32_manual_post_install.py'] | |
505 |
|
506 | |||
506 | # or as a whitespace-separated string: |
|
507 | # or as a whitespace-separated string: | |
507 | In [4]: a.s |
|
508 | In [4]: a.s | |
508 | Out[4]: 'setup.py win32_manual_post_install.py' |
|
509 | Out[4]: 'setup.py win32_manual_post_install.py' | |
509 |
|
510 | |||
510 | # a.s is useful to pass as a single command line: |
|
511 | # a.s is useful to pass as a single command line: | |
511 | In [5]: !wc -l $a.s |
|
512 | In [5]: !wc -l $a.s | |
512 | 146 setup.py |
|
513 | 146 setup.py | |
513 | 130 win32_manual_post_install.py |
|
514 | 130 win32_manual_post_install.py | |
514 | 276 total |
|
515 | 276 total | |
515 |
|
516 | |||
516 | # while the list form is useful to loop over: |
|
517 | # while the list form is useful to loop over: | |
517 | In [6]: for f in a.l: |
|
518 | In [6]: for f in a.l: | |
518 | ...: !wc -l $f |
|
519 | ...: !wc -l $f | |
519 | ...: |
|
520 | ...: | |
520 | 146 setup.py |
|
521 | 146 setup.py | |
521 | 130 win32_manual_post_install.py |
|
522 | 130 win32_manual_post_install.py | |
522 |
|
523 | |||
523 | Similarly, the lists returned by the -l option are also special, in |
|
524 | Similarly, the lists returned by the -l option are also special, in | |
524 | the sense that you can equally invoke the .s attribute on them to |
|
525 | the sense that you can equally invoke the .s attribute on them to | |
525 | automatically get a whitespace-separated string from their contents:: |
|
526 | automatically get a whitespace-separated string from their contents:: | |
526 |
|
527 | |||
527 | In [7]: sc -l b=ls *py |
|
528 | In [7]: sc -l b=ls *py | |
528 |
|
529 | |||
529 | In [8]: b |
|
530 | In [8]: b | |
530 | Out[8]: ['setup.py', 'win32_manual_post_install.py'] |
|
531 | Out[8]: ['setup.py', 'win32_manual_post_install.py'] | |
531 |
|
532 | |||
532 | In [9]: b.s |
|
533 | In [9]: b.s | |
533 | Out[9]: 'setup.py win32_manual_post_install.py' |
|
534 | Out[9]: 'setup.py win32_manual_post_install.py' | |
534 |
|
535 | |||
535 | In summary, both the lists and strings used for output capture have |
|
536 | In summary, both the lists and strings used for output capture have | |
536 | the following special attributes:: |
|
537 | the following special attributes:: | |
537 |
|
538 | |||
538 | .l (or .list) : value as list. |
|
539 | .l (or .list) : value as list. | |
539 | .n (or .nlstr): value as newline-separated string. |
|
540 | .n (or .nlstr): value as newline-separated string. | |
540 | .s (or .spstr): value as space-separated string. |
|
541 | .s (or .spstr): value as space-separated string. | |
541 | """ |
|
542 | """ | |
542 |
|
543 | |||
543 | opts,args = self.parse_options(parameter_s, 'lv') |
|
544 | opts,args = self.parse_options(parameter_s, 'lv') | |
544 | # Try to get a variable name and command to run |
|
545 | # Try to get a variable name and command to run | |
545 | try: |
|
546 | try: | |
546 | # the variable name must be obtained from the parse_options |
|
547 | # the variable name must be obtained from the parse_options | |
547 | # output, which uses shlex.split to strip options out. |
|
548 | # output, which uses shlex.split to strip options out. | |
548 | var,_ = args.split('=', 1) |
|
549 | var,_ = args.split('=', 1) | |
549 | var = var.strip() |
|
550 | var = var.strip() | |
550 | # But the command has to be extracted from the original input |
|
551 | # But the command has to be extracted from the original input | |
551 | # parameter_s, not on what parse_options returns, to avoid the |
|
552 | # parameter_s, not on what parse_options returns, to avoid the | |
552 | # quote stripping which shlex.split performs on it. |
|
553 | # quote stripping which shlex.split performs on it. | |
553 | _,cmd = parameter_s.split('=', 1) |
|
554 | _,cmd = parameter_s.split('=', 1) | |
554 | except ValueError: |
|
555 | except ValueError: | |
555 | var,cmd = '','' |
|
556 | var,cmd = '','' | |
556 | # If all looks ok, proceed |
|
557 | # If all looks ok, proceed | |
557 | split = 'l' in opts |
|
558 | split = 'l' in opts | |
558 | out = self.shell.getoutput(cmd, split=split) |
|
559 | out = self.shell.getoutput(cmd, split=split) | |
559 | if 'v' in opts: |
|
560 | if 'v' in opts: | |
560 | print('%s ==\n%s' % (var, pformat(out))) |
|
561 | print('%s ==\n%s' % (var, pformat(out))) | |
561 | if var: |
|
562 | if var: | |
562 | self.shell.user_ns.update({var:out}) |
|
563 | self.shell.user_ns.update({var:out}) | |
563 | else: |
|
564 | else: | |
564 | return out |
|
565 | return out | |
565 |
|
566 | |||
566 | @line_cell_magic |
|
567 | @line_cell_magic | |
567 | def sx(self, line='', cell=None): |
|
568 | def sx(self, line='', cell=None): | |
568 | """Shell execute - run shell command and capture output (!! is short-hand). |
|
569 | """Shell execute - run shell command and capture output (!! is short-hand). | |
569 |
|
570 | |||
570 | %sx command |
|
571 | %sx command | |
571 |
|
572 | |||
572 | IPython will run the given command using commands.getoutput(), and |
|
573 | IPython will run the given command using commands.getoutput(), and | |
573 | return the result formatted as a list (split on '\\n'). Since the |
|
574 | return the result formatted as a list (split on '\\n'). Since the | |
574 | output is _returned_, it will be stored in ipython's regular output |
|
575 | output is _returned_, it will be stored in ipython's regular output | |
575 | cache Out[N] and in the '_N' automatic variables. |
|
576 | cache Out[N] and in the '_N' automatic variables. | |
576 |
|
577 | |||
577 | Notes: |
|
578 | Notes: | |
578 |
|
579 | |||
579 | 1) If an input line begins with '!!', then %sx is automatically |
|
580 | 1) If an input line begins with '!!', then %sx is automatically | |
580 | invoked. That is, while:: |
|
581 | invoked. That is, while:: | |
581 |
|
582 | |||
582 | !ls |
|
583 | !ls | |
583 |
|
584 | |||
584 | causes ipython to simply issue system('ls'), typing:: |
|
585 | causes ipython to simply issue system('ls'), typing:: | |
585 |
|
586 | |||
586 | !!ls |
|
587 | !!ls | |
587 |
|
588 | |||
588 | is a shorthand equivalent to:: |
|
589 | is a shorthand equivalent to:: | |
589 |
|
590 | |||
590 | %sx ls |
|
591 | %sx ls | |
591 |
|
592 | |||
592 | 2) %sx differs from %sc in that %sx automatically splits into a list, |
|
593 | 2) %sx differs from %sc in that %sx automatically splits into a list, | |
593 | like '%sc -l'. The reason for this is to make it as easy as possible |
|
594 | like '%sc -l'. The reason for this is to make it as easy as possible | |
594 | to process line-oriented shell output via further python commands. |
|
595 | to process line-oriented shell output via further python commands. | |
595 | %sc is meant to provide much finer control, but requires more |
|
596 | %sc is meant to provide much finer control, but requires more | |
596 | typing. |
|
597 | typing. | |
597 |
|
598 | |||
598 | 3) Just like %sc -l, this is a list with special attributes: |
|
599 | 3) Just like %sc -l, this is a list with special attributes: | |
599 | :: |
|
600 | :: | |
600 |
|
601 | |||
601 | .l (or .list) : value as list. |
|
602 | .l (or .list) : value as list. | |
602 | .n (or .nlstr): value as newline-separated string. |
|
603 | .n (or .nlstr): value as newline-separated string. | |
603 | .s (or .spstr): value as whitespace-separated string. |
|
604 | .s (or .spstr): value as whitespace-separated string. | |
604 |
|
605 | |||
605 | This is very useful when trying to use such lists as arguments to |
|
606 | This is very useful when trying to use such lists as arguments to | |
606 | system commands.""" |
|
607 | system commands.""" | |
607 |
|
608 | |||
608 | if cell is None: |
|
609 | if cell is None: | |
609 | # line magic |
|
610 | # line magic | |
610 | return self.shell.getoutput(line) |
|
611 | return self.shell.getoutput(line) | |
611 | else: |
|
612 | else: | |
612 | opts,args = self.parse_options(line, '', 'out=') |
|
613 | opts,args = self.parse_options(line, '', 'out=') | |
613 | output = self.shell.getoutput(cell) |
|
614 | output = self.shell.getoutput(cell) | |
614 | out_name = opts.get('out', opts.get('o')) |
|
615 | out_name = opts.get('out', opts.get('o')) | |
615 | if out_name: |
|
616 | if out_name: | |
616 | self.shell.user_ns[out_name] = output |
|
617 | self.shell.user_ns[out_name] = output | |
617 | else: |
|
618 | else: | |
618 | return output |
|
619 | return output | |
619 |
|
620 | |||
620 | system = line_cell_magic('system')(sx) |
|
621 | system = line_cell_magic('system')(sx) | |
621 | bang = cell_magic('!')(sx) |
|
622 | bang = cell_magic('!')(sx) | |
622 |
|
623 | |||
623 | @line_magic |
|
624 | @line_magic | |
624 | def bookmark(self, parameter_s=''): |
|
625 | def bookmark(self, parameter_s=''): | |
625 | """Manage IPython's bookmark system. |
|
626 | """Manage IPython's bookmark system. | |
626 |
|
627 | |||
627 | %bookmark <name> - set bookmark to current dir |
|
628 | %bookmark <name> - set bookmark to current dir | |
628 | %bookmark <name> <dir> - set bookmark to <dir> |
|
629 | %bookmark <name> <dir> - set bookmark to <dir> | |
629 | %bookmark -l - list all bookmarks |
|
630 | %bookmark -l - list all bookmarks | |
630 | %bookmark -d <name> - remove bookmark |
|
631 | %bookmark -d <name> - remove bookmark | |
631 | %bookmark -r - remove all bookmarks |
|
632 | %bookmark -r - remove all bookmarks | |
632 |
|
633 | |||
633 | You can later on access a bookmarked folder with:: |
|
634 | You can later on access a bookmarked folder with:: | |
634 |
|
635 | |||
635 | %cd -b <name> |
|
636 | %cd -b <name> | |
636 |
|
637 | |||
637 | or simply '%cd <name>' if there is no directory called <name> AND |
|
638 | or simply '%cd <name>' if there is no directory called <name> AND | |
638 | there is such a bookmark defined. |
|
639 | there is such a bookmark defined. | |
639 |
|
640 | |||
640 | Your bookmarks persist through IPython sessions, but they are |
|
641 | Your bookmarks persist through IPython sessions, but they are | |
641 | associated with each profile.""" |
|
642 | associated with each profile.""" | |
642 |
|
643 | |||
643 | opts,args = self.parse_options(parameter_s,'drl',mode='list') |
|
644 | opts,args = self.parse_options(parameter_s,'drl',mode='list') | |
644 | if len(args) > 2: |
|
645 | if len(args) > 2: | |
645 | raise UsageError("%bookmark: too many arguments") |
|
646 | raise UsageError("%bookmark: too many arguments") | |
646 |
|
647 | |||
647 | bkms = self.shell.db.get('bookmarks',{}) |
|
648 | bkms = self.shell.db.get('bookmarks',{}) | |
648 |
|
649 | |||
649 | if 'd' in opts: |
|
650 | if 'd' in opts: | |
650 | try: |
|
651 | try: | |
651 | todel = args[0] |
|
652 | todel = args[0] | |
652 | except IndexError: |
|
653 | except IndexError: | |
653 | raise UsageError( |
|
654 | raise UsageError( | |
654 | "%bookmark -d: must provide a bookmark to delete") |
|
655 | "%bookmark -d: must provide a bookmark to delete") | |
655 | else: |
|
656 | else: | |
656 | try: |
|
657 | try: | |
657 | del bkms[todel] |
|
658 | del bkms[todel] | |
658 | except KeyError: |
|
659 | except KeyError: | |
659 | raise UsageError( |
|
660 | raise UsageError( | |
660 | "%%bookmark -d: Can't delete bookmark '%s'" % todel) |
|
661 | "%%bookmark -d: Can't delete bookmark '%s'" % todel) | |
661 |
|
662 | |||
662 | elif 'r' in opts: |
|
663 | elif 'r' in opts: | |
663 | bkms = {} |
|
664 | bkms = {} | |
664 | elif 'l' in opts: |
|
665 | elif 'l' in opts: | |
665 | bks = bkms.keys() |
|
666 | bks = bkms.keys() | |
666 | bks.sort() |
|
667 | bks.sort() | |
667 | if bks: |
|
668 | if bks: | |
668 | size = max(map(len, bks)) |
|
669 | size = max(map(len, bks)) | |
669 | else: |
|
670 | else: | |
670 | size = 0 |
|
671 | size = 0 | |
671 | fmt = '%-'+str(size)+'s -> %s' |
|
672 | fmt = '%-'+str(size)+'s -> %s' | |
672 | print('Current bookmarks:') |
|
673 | print('Current bookmarks:') | |
673 | for bk in bks: |
|
674 | for bk in bks: | |
674 | print(fmt % (bk, bkms[bk])) |
|
675 | print(fmt % (bk, bkms[bk])) | |
675 | else: |
|
676 | else: | |
676 | if not args: |
|
677 | if not args: | |
677 | raise UsageError("%bookmark: You must specify the bookmark name") |
|
678 | raise UsageError("%bookmark: You must specify the bookmark name") | |
678 | elif len(args)==1: |
|
679 | elif len(args)==1: | |
679 |
bkms[args[0]] = |
|
680 | bkms[args[0]] = py3compat.getcwd() | |
680 | elif len(args)==2: |
|
681 | elif len(args)==2: | |
681 | bkms[args[0]] = args[1] |
|
682 | bkms[args[0]] = args[1] | |
682 | self.shell.db['bookmarks'] = bkms |
|
683 | self.shell.db['bookmarks'] = bkms | |
683 |
|
684 | |||
684 | @line_magic |
|
685 | @line_magic | |
685 | def pycat(self, parameter_s=''): |
|
686 | def pycat(self, parameter_s=''): | |
686 | """Show a syntax-highlighted file through a pager. |
|
687 | """Show a syntax-highlighted file through a pager. | |
687 |
|
688 | |||
688 | This magic is similar to the cat utility, but it will assume the file |
|
689 | This magic is similar to the cat utility, but it will assume the file | |
689 | to be Python source and will show it with syntax highlighting. |
|
690 | to be Python source and will show it with syntax highlighting. | |
690 |
|
691 | |||
691 | This magic command can either take a local filename, an url, |
|
692 | This magic command can either take a local filename, an url, | |
692 | an history range (see %history) or a macro as argument :: |
|
693 | an history range (see %history) or a macro as argument :: | |
693 |
|
694 | |||
694 | %pycat myscript.py |
|
695 | %pycat myscript.py | |
695 | %pycat 7-27 |
|
696 | %pycat 7-27 | |
696 | %pycat myMacro |
|
697 | %pycat myMacro | |
697 | %pycat http://www.example.com/myscript.py |
|
698 | %pycat http://www.example.com/myscript.py | |
698 | """ |
|
699 | """ | |
699 | if not parameter_s: |
|
700 | if not parameter_s: | |
700 | raise UsageError('Missing filename, URL, input history range, ' |
|
701 | raise UsageError('Missing filename, URL, input history range, ' | |
701 | 'or macro.') |
|
702 | 'or macro.') | |
702 |
|
703 | |||
703 | try : |
|
704 | try : | |
704 | cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False) |
|
705 | cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False) | |
705 | except (ValueError, IOError): |
|
706 | except (ValueError, IOError): | |
706 | print("Error: no such file, variable, URL, history range or macro") |
|
707 | print("Error: no such file, variable, URL, history range or macro") | |
707 | return |
|
708 | return | |
708 |
|
709 | |||
709 | page.page(self.shell.pycolorize(source_to_unicode(cont))) |
|
710 | page.page(self.shell.pycolorize(source_to_unicode(cont))) | |
710 |
|
711 | |||
711 | @magic_arguments.magic_arguments() |
|
712 | @magic_arguments.magic_arguments() | |
712 | @magic_arguments.argument( |
|
713 | @magic_arguments.argument( | |
713 | '-a', '--append', action='store_true', default=False, |
|
714 | '-a', '--append', action='store_true', default=False, | |
714 | help='Append contents of the cell to an existing file. ' |
|
715 | help='Append contents of the cell to an existing file. ' | |
715 | 'The file will be created if it does not exist.' |
|
716 | 'The file will be created if it does not exist.' | |
716 | ) |
|
717 | ) | |
717 | @magic_arguments.argument( |
|
718 | @magic_arguments.argument( | |
718 | 'filename', type=unicode_type, |
|
719 | 'filename', type=unicode_type, | |
719 | help='file to write' |
|
720 | help='file to write' | |
720 | ) |
|
721 | ) | |
721 | @cell_magic |
|
722 | @cell_magic | |
722 | def writefile(self, line, cell): |
|
723 | def writefile(self, line, cell): | |
723 | """Write the contents of the cell to a file. |
|
724 | """Write the contents of the cell to a file. | |
724 |
|
725 | |||
725 | The file will be overwritten unless the -a (--append) flag is specified. |
|
726 | The file will be overwritten unless the -a (--append) flag is specified. | |
726 | """ |
|
727 | """ | |
727 | args = magic_arguments.parse_argstring(self.writefile, line) |
|
728 | args = magic_arguments.parse_argstring(self.writefile, line) | |
728 | filename = os.path.expanduser(unquote_filename(args.filename)) |
|
729 | filename = os.path.expanduser(unquote_filename(args.filename)) | |
729 |
|
730 | |||
730 | if os.path.exists(filename): |
|
731 | if os.path.exists(filename): | |
731 | if args.append: |
|
732 | if args.append: | |
732 | print("Appending to %s" % filename) |
|
733 | print("Appending to %s" % filename) | |
733 | else: |
|
734 | else: | |
734 | print("Overwriting %s" % filename) |
|
735 | print("Overwriting %s" % filename) | |
735 | else: |
|
736 | else: | |
736 | print("Writing %s" % filename) |
|
737 | print("Writing %s" % filename) | |
737 |
|
738 | |||
738 | mode = 'a' if args.append else 'w' |
|
739 | mode = 'a' if args.append else 'w' | |
739 | with io.open(filename, mode, encoding='utf-8') as f: |
|
740 | with io.open(filename, mode, encoding='utf-8') as f: | |
740 | f.write(cell) |
|
741 | f.write(cell) |
@@ -1,314 +1,315 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | An application for managing IPython profiles. |
|
3 | An application for managing IPython profiles. | |
4 |
|
4 | |||
5 | To be invoked as the `ipython profile` subcommand. |
|
5 | To be invoked as the `ipython profile` subcommand. | |
6 |
|
6 | |||
7 | Authors: |
|
7 | Authors: | |
8 |
|
8 | |||
9 | * Min RK |
|
9 | * Min RK | |
10 |
|
10 | |||
11 | """ |
|
11 | """ | |
12 | from __future__ import print_function |
|
12 | from __future__ import print_function | |
13 |
|
13 | |||
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 | # Copyright (C) 2008 The IPython Development Team |
|
15 | # Copyright (C) 2008 The IPython Development Team | |
16 | # |
|
16 | # | |
17 | # Distributed under the terms of the BSD License. The full license is in |
|
17 | # Distributed under the terms of the BSD License. The full license is in | |
18 | # the file COPYING, distributed as part of this software. |
|
18 | # the file COPYING, distributed as part of this software. | |
19 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
20 |
|
20 | |||
21 | #----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
22 | # Imports |
|
22 | # Imports | |
23 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
24 |
|
24 | |||
25 | import os |
|
25 | import os | |
26 |
|
26 | |||
27 | from IPython.config.application import Application |
|
27 | from IPython.config.application import Application | |
28 | from IPython.core.application import ( |
|
28 | from IPython.core.application import ( | |
29 | BaseIPythonApplication, base_flags |
|
29 | BaseIPythonApplication, base_flags | |
30 | ) |
|
30 | ) | |
31 | from IPython.core.profiledir import ProfileDir |
|
31 | from IPython.core.profiledir import ProfileDir | |
32 | from IPython.utils.importstring import import_item |
|
32 | from IPython.utils.importstring import import_item | |
33 | from IPython.utils.path import get_ipython_dir, get_ipython_package_dir |
|
33 | from IPython.utils.path import get_ipython_dir, get_ipython_package_dir | |
|
34 | from IPython.utils import py3compat | |||
34 | from IPython.utils.traitlets import Unicode, Bool, Dict |
|
35 | from IPython.utils.traitlets import Unicode, Bool, Dict | |
35 |
|
36 | |||
36 | #----------------------------------------------------------------------------- |
|
37 | #----------------------------------------------------------------------------- | |
37 | # Constants |
|
38 | # Constants | |
38 | #----------------------------------------------------------------------------- |
|
39 | #----------------------------------------------------------------------------- | |
39 |
|
40 | |||
40 | create_help = """Create an IPython profile by name |
|
41 | create_help = """Create an IPython profile by name | |
41 |
|
42 | |||
42 | Create an ipython profile directory by its name or |
|
43 | Create an ipython profile directory by its name or | |
43 | profile directory path. Profile directories contain |
|
44 | profile directory path. Profile directories contain | |
44 | configuration, log and security related files and are named |
|
45 | configuration, log and security related files and are named | |
45 | using the convention 'profile_<name>'. By default they are |
|
46 | using the convention 'profile_<name>'. By default they are | |
46 | located in your ipython directory. Once created, you will |
|
47 | located in your ipython directory. Once created, you will | |
47 | can edit the configuration files in the profile |
|
48 | can edit the configuration files in the profile | |
48 | directory to configure IPython. Most users will create a |
|
49 | directory to configure IPython. Most users will create a | |
49 | profile directory by name, |
|
50 | profile directory by name, | |
50 | `ipython profile create myprofile`, which will put the directory |
|
51 | `ipython profile create myprofile`, which will put the directory | |
51 | in `<ipython_dir>/profile_myprofile`. |
|
52 | in `<ipython_dir>/profile_myprofile`. | |
52 | """ |
|
53 | """ | |
53 | list_help = """List available IPython profiles |
|
54 | list_help = """List available IPython profiles | |
54 |
|
55 | |||
55 | List all available profiles, by profile location, that can |
|
56 | List all available profiles, by profile location, that can | |
56 | be found in the current working directly or in the ipython |
|
57 | be found in the current working directly or in the ipython | |
57 | directory. Profile directories are named using the convention |
|
58 | directory. Profile directories are named using the convention | |
58 | 'profile_<profile>'. |
|
59 | 'profile_<profile>'. | |
59 | """ |
|
60 | """ | |
60 | profile_help = """Manage IPython profiles |
|
61 | profile_help = """Manage IPython profiles | |
61 |
|
62 | |||
62 | Profile directories contain |
|
63 | Profile directories contain | |
63 | configuration, log and security related files and are named |
|
64 | configuration, log and security related files and are named | |
64 | using the convention 'profile_<name>'. By default they are |
|
65 | using the convention 'profile_<name>'. By default they are | |
65 | located in your ipython directory. You can create profiles |
|
66 | located in your ipython directory. You can create profiles | |
66 | with `ipython profile create <name>`, or see the profiles you |
|
67 | with `ipython profile create <name>`, or see the profiles you | |
67 | already have with `ipython profile list` |
|
68 | already have with `ipython profile list` | |
68 |
|
69 | |||
69 | To get started configuring IPython, simply do: |
|
70 | To get started configuring IPython, simply do: | |
70 |
|
71 | |||
71 | $> ipython profile create |
|
72 | $> ipython profile create | |
72 |
|
73 | |||
73 | and IPython will create the default profile in <ipython_dir>/profile_default, |
|
74 | and IPython will create the default profile in <ipython_dir>/profile_default, | |
74 | where you can edit ipython_config.py to start configuring IPython. |
|
75 | where you can edit ipython_config.py to start configuring IPython. | |
75 |
|
76 | |||
76 | """ |
|
77 | """ | |
77 |
|
78 | |||
78 | _list_examples = "ipython profile list # list all profiles" |
|
79 | _list_examples = "ipython profile list # list all profiles" | |
79 |
|
80 | |||
80 | _create_examples = """ |
|
81 | _create_examples = """ | |
81 | ipython profile create foo # create profile foo w/ default config files |
|
82 | ipython profile create foo # create profile foo w/ default config files | |
82 | ipython profile create foo --reset # restage default config files over current |
|
83 | ipython profile create foo --reset # restage default config files over current | |
83 | ipython profile create foo --parallel # also stage parallel config files |
|
84 | ipython profile create foo --parallel # also stage parallel config files | |
84 | """ |
|
85 | """ | |
85 |
|
86 | |||
86 | _main_examples = """ |
|
87 | _main_examples = """ | |
87 | ipython profile create -h # show the help string for the create subcommand |
|
88 | ipython profile create -h # show the help string for the create subcommand | |
88 | ipython profile list -h # show the help string for the list subcommand |
|
89 | ipython profile list -h # show the help string for the list subcommand | |
89 |
|
90 | |||
90 | ipython locate profile foo # print the path to the directory for profile 'foo' |
|
91 | ipython locate profile foo # print the path to the directory for profile 'foo' | |
91 | """ |
|
92 | """ | |
92 |
|
93 | |||
93 | #----------------------------------------------------------------------------- |
|
94 | #----------------------------------------------------------------------------- | |
94 | # Profile Application Class (for `ipython profile` subcommand) |
|
95 | # Profile Application Class (for `ipython profile` subcommand) | |
95 | #----------------------------------------------------------------------------- |
|
96 | #----------------------------------------------------------------------------- | |
96 |
|
97 | |||
97 |
|
98 | |||
98 | def list_profiles_in(path): |
|
99 | def list_profiles_in(path): | |
99 | """list profiles in a given root directory""" |
|
100 | """list profiles in a given root directory""" | |
100 | files = os.listdir(path) |
|
101 | files = os.listdir(path) | |
101 | profiles = [] |
|
102 | profiles = [] | |
102 | for f in files: |
|
103 | for f in files: | |
103 | try: |
|
104 | try: | |
104 | full_path = os.path.join(path, f) |
|
105 | full_path = os.path.join(path, f) | |
105 | except UnicodeError: |
|
106 | except UnicodeError: | |
106 | continue |
|
107 | continue | |
107 | if os.path.isdir(full_path) and f.startswith('profile_'): |
|
108 | if os.path.isdir(full_path) and f.startswith('profile_'): | |
108 | profiles.append(f.split('_',1)[-1]) |
|
109 | profiles.append(f.split('_',1)[-1]) | |
109 | return profiles |
|
110 | return profiles | |
110 |
|
111 | |||
111 |
|
112 | |||
112 | def list_bundled_profiles(): |
|
113 | def list_bundled_profiles(): | |
113 | """list profiles that are bundled with IPython.""" |
|
114 | """list profiles that are bundled with IPython.""" | |
114 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile') |
|
115 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile') | |
115 | files = os.listdir(path) |
|
116 | files = os.listdir(path) | |
116 | profiles = [] |
|
117 | profiles = [] | |
117 | for profile in files: |
|
118 | for profile in files: | |
118 | full_path = os.path.join(path, profile) |
|
119 | full_path = os.path.join(path, profile) | |
119 | if os.path.isdir(full_path) and profile != "__pycache__": |
|
120 | if os.path.isdir(full_path) and profile != "__pycache__": | |
120 | profiles.append(profile) |
|
121 | profiles.append(profile) | |
121 | return profiles |
|
122 | return profiles | |
122 |
|
123 | |||
123 |
|
124 | |||
124 | class ProfileLocate(BaseIPythonApplication): |
|
125 | class ProfileLocate(BaseIPythonApplication): | |
125 | description = """print the path to an IPython profile dir""" |
|
126 | description = """print the path to an IPython profile dir""" | |
126 |
|
127 | |||
127 | def parse_command_line(self, argv=None): |
|
128 | def parse_command_line(self, argv=None): | |
128 | super(ProfileLocate, self).parse_command_line(argv) |
|
129 | super(ProfileLocate, self).parse_command_line(argv) | |
129 | if self.extra_args: |
|
130 | if self.extra_args: | |
130 | self.profile = self.extra_args[0] |
|
131 | self.profile = self.extra_args[0] | |
131 |
|
132 | |||
132 | def start(self): |
|
133 | def start(self): | |
133 | print(self.profile_dir.location) |
|
134 | print(self.profile_dir.location) | |
134 |
|
135 | |||
135 |
|
136 | |||
136 | class ProfileList(Application): |
|
137 | class ProfileList(Application): | |
137 | name = u'ipython-profile' |
|
138 | name = u'ipython-profile' | |
138 | description = list_help |
|
139 | description = list_help | |
139 | examples = _list_examples |
|
140 | examples = _list_examples | |
140 |
|
141 | |||
141 | aliases = Dict({ |
|
142 | aliases = Dict({ | |
142 | 'ipython-dir' : 'ProfileList.ipython_dir', |
|
143 | 'ipython-dir' : 'ProfileList.ipython_dir', | |
143 | 'log-level' : 'Application.log_level', |
|
144 | 'log-level' : 'Application.log_level', | |
144 | }) |
|
145 | }) | |
145 | flags = Dict(dict( |
|
146 | flags = Dict(dict( | |
146 | debug = ({'Application' : {'log_level' : 0}}, |
|
147 | debug = ({'Application' : {'log_level' : 0}}, | |
147 | "Set Application.log_level to 0, maximizing log output." |
|
148 | "Set Application.log_level to 0, maximizing log output." | |
148 | ) |
|
149 | ) | |
149 | )) |
|
150 | )) | |
150 |
|
151 | |||
151 | ipython_dir = Unicode(get_ipython_dir(), config=True, |
|
152 | ipython_dir = Unicode(get_ipython_dir(), config=True, | |
152 | help=""" |
|
153 | help=""" | |
153 | The name of the IPython directory. This directory is used for logging |
|
154 | The name of the IPython directory. This directory is used for logging | |
154 | configuration (through profiles), history storage, etc. The default |
|
155 | configuration (through profiles), history storage, etc. The default | |
155 | is usually $HOME/.ipython. This options can also be specified through |
|
156 | is usually $HOME/.ipython. This options can also be specified through | |
156 | the environment variable IPYTHONDIR. |
|
157 | the environment variable IPYTHONDIR. | |
157 | """ |
|
158 | """ | |
158 | ) |
|
159 | ) | |
159 |
|
160 | |||
160 |
|
161 | |||
161 | def _print_profiles(self, profiles): |
|
162 | def _print_profiles(self, profiles): | |
162 | """print list of profiles, indented.""" |
|
163 | """print list of profiles, indented.""" | |
163 | for profile in profiles: |
|
164 | for profile in profiles: | |
164 | print(' %s' % profile) |
|
165 | print(' %s' % profile) | |
165 |
|
166 | |||
166 | def list_profile_dirs(self): |
|
167 | def list_profile_dirs(self): | |
167 | profiles = list_bundled_profiles() |
|
168 | profiles = list_bundled_profiles() | |
168 | if profiles: |
|
169 | if profiles: | |
169 | print() |
|
170 | print() | |
170 | print("Available profiles in IPython:") |
|
171 | print("Available profiles in IPython:") | |
171 | self._print_profiles(profiles) |
|
172 | self._print_profiles(profiles) | |
172 | print() |
|
173 | print() | |
173 | print(" The first request for a bundled profile will copy it") |
|
174 | print(" The first request for a bundled profile will copy it") | |
174 | print(" into your IPython directory (%s)," % self.ipython_dir) |
|
175 | print(" into your IPython directory (%s)," % self.ipython_dir) | |
175 | print(" where you can customize it.") |
|
176 | print(" where you can customize it.") | |
176 |
|
177 | |||
177 | profiles = list_profiles_in(self.ipython_dir) |
|
178 | profiles = list_profiles_in(self.ipython_dir) | |
178 | if profiles: |
|
179 | if profiles: | |
179 | print() |
|
180 | print() | |
180 | print("Available profiles in %s:" % self.ipython_dir) |
|
181 | print("Available profiles in %s:" % self.ipython_dir) | |
181 | self._print_profiles(profiles) |
|
182 | self._print_profiles(profiles) | |
182 |
|
183 | |||
183 |
profiles = list_profiles_in( |
|
184 | profiles = list_profiles_in(py3compat.getcwd()) | |
184 | if profiles: |
|
185 | if profiles: | |
185 | print() |
|
186 | print() | |
186 |
print("Available profiles in current directory (%s):" % |
|
187 | print("Available profiles in current directory (%s):" % py3compat.getcwd()) | |
187 | self._print_profiles(profiles) |
|
188 | self._print_profiles(profiles) | |
188 |
|
189 | |||
189 | print() |
|
190 | print() | |
190 | print("To use any of the above profiles, start IPython with:") |
|
191 | print("To use any of the above profiles, start IPython with:") | |
191 | print(" ipython --profile=<name>") |
|
192 | print(" ipython --profile=<name>") | |
192 | print() |
|
193 | print() | |
193 |
|
194 | |||
194 | def start(self): |
|
195 | def start(self): | |
195 | self.list_profile_dirs() |
|
196 | self.list_profile_dirs() | |
196 |
|
197 | |||
197 |
|
198 | |||
198 | create_flags = {} |
|
199 | create_flags = {} | |
199 | create_flags.update(base_flags) |
|
200 | create_flags.update(base_flags) | |
200 | # don't include '--init' flag, which implies running profile create in other apps |
|
201 | # don't include '--init' flag, which implies running profile create in other apps | |
201 | create_flags.pop('init') |
|
202 | create_flags.pop('init') | |
202 | create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}}, |
|
203 | create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}}, | |
203 | "reset config files in this profile to the defaults.") |
|
204 | "reset config files in this profile to the defaults.") | |
204 | create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}}, |
|
205 | create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}}, | |
205 | "Include the config files for parallel " |
|
206 | "Include the config files for parallel " | |
206 | "computing apps (ipengine, ipcontroller, etc.)") |
|
207 | "computing apps (ipengine, ipcontroller, etc.)") | |
207 |
|
208 | |||
208 |
|
209 | |||
209 | class ProfileCreate(BaseIPythonApplication): |
|
210 | class ProfileCreate(BaseIPythonApplication): | |
210 | name = u'ipython-profile' |
|
211 | name = u'ipython-profile' | |
211 | description = create_help |
|
212 | description = create_help | |
212 | examples = _create_examples |
|
213 | examples = _create_examples | |
213 | auto_create = Bool(True, config=False) |
|
214 | auto_create = Bool(True, config=False) | |
214 | def _log_format_default(self): |
|
215 | def _log_format_default(self): | |
215 | return "[%(name)s] %(message)s" |
|
216 | return "[%(name)s] %(message)s" | |
216 |
|
217 | |||
217 | def _copy_config_files_default(self): |
|
218 | def _copy_config_files_default(self): | |
218 | return True |
|
219 | return True | |
219 |
|
220 | |||
220 | parallel = Bool(False, config=True, |
|
221 | parallel = Bool(False, config=True, | |
221 | help="whether to include parallel computing config files") |
|
222 | help="whether to include parallel computing config files") | |
222 | def _parallel_changed(self, name, old, new): |
|
223 | def _parallel_changed(self, name, old, new): | |
223 | parallel_files = [ 'ipcontroller_config.py', |
|
224 | parallel_files = [ 'ipcontroller_config.py', | |
224 | 'ipengine_config.py', |
|
225 | 'ipengine_config.py', | |
225 | 'ipcluster_config.py' |
|
226 | 'ipcluster_config.py' | |
226 | ] |
|
227 | ] | |
227 | if new: |
|
228 | if new: | |
228 | for cf in parallel_files: |
|
229 | for cf in parallel_files: | |
229 | self.config_files.append(cf) |
|
230 | self.config_files.append(cf) | |
230 | else: |
|
231 | else: | |
231 | for cf in parallel_files: |
|
232 | for cf in parallel_files: | |
232 | if cf in self.config_files: |
|
233 | if cf in self.config_files: | |
233 | self.config_files.remove(cf) |
|
234 | self.config_files.remove(cf) | |
234 |
|
235 | |||
235 | def parse_command_line(self, argv): |
|
236 | def parse_command_line(self, argv): | |
236 | super(ProfileCreate, self).parse_command_line(argv) |
|
237 | super(ProfileCreate, self).parse_command_line(argv) | |
237 | # accept positional arg as profile name |
|
238 | # accept positional arg as profile name | |
238 | if self.extra_args: |
|
239 | if self.extra_args: | |
239 | self.profile = self.extra_args[0] |
|
240 | self.profile = self.extra_args[0] | |
240 |
|
241 | |||
241 | flags = Dict(create_flags) |
|
242 | flags = Dict(create_flags) | |
242 |
|
243 | |||
243 | classes = [ProfileDir] |
|
244 | classes = [ProfileDir] | |
244 |
|
245 | |||
245 | def _import_app(self, app_path): |
|
246 | def _import_app(self, app_path): | |
246 | """import an app class""" |
|
247 | """import an app class""" | |
247 | app = None |
|
248 | app = None | |
248 | name = app_path.rsplit('.', 1)[-1] |
|
249 | name = app_path.rsplit('.', 1)[-1] | |
249 | try: |
|
250 | try: | |
250 | app = import_item(app_path) |
|
251 | app = import_item(app_path) | |
251 |
except ImportError |
|
252 | except ImportError: | |
252 | self.log.info("Couldn't import %s, config file will be excluded", name) |
|
253 | self.log.info("Couldn't import %s, config file will be excluded", name) | |
253 | except Exception: |
|
254 | except Exception: | |
254 | self.log.warn('Unexpected error importing %s', name, exc_info=True) |
|
255 | self.log.warn('Unexpected error importing %s', name, exc_info=True) | |
255 | return app |
|
256 | return app | |
256 |
|
257 | |||
257 | def init_config_files(self): |
|
258 | def init_config_files(self): | |
258 | super(ProfileCreate, self).init_config_files() |
|
259 | super(ProfileCreate, self).init_config_files() | |
259 | # use local imports, since these classes may import from here |
|
260 | # use local imports, since these classes may import from here | |
260 | from IPython.terminal.ipapp import TerminalIPythonApp |
|
261 | from IPython.terminal.ipapp import TerminalIPythonApp | |
261 | apps = [TerminalIPythonApp] |
|
262 | apps = [TerminalIPythonApp] | |
262 | for app_path in ( |
|
263 | for app_path in ( | |
263 | 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp', |
|
264 | 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp', | |
264 | 'IPython.html.notebookapp.NotebookApp', |
|
265 | 'IPython.html.notebookapp.NotebookApp', | |
265 | 'IPython.nbconvert.nbconvertapp.NbConvertApp', |
|
266 | 'IPython.nbconvert.nbconvertapp.NbConvertApp', | |
266 | ): |
|
267 | ): | |
267 | app = self._import_app(app_path) |
|
268 | app = self._import_app(app_path) | |
268 | if app is not None: |
|
269 | if app is not None: | |
269 | apps.append(app) |
|
270 | apps.append(app) | |
270 | if self.parallel: |
|
271 | if self.parallel: | |
271 | from IPython.parallel.apps.ipcontrollerapp import IPControllerApp |
|
272 | from IPython.parallel.apps.ipcontrollerapp import IPControllerApp | |
272 | from IPython.parallel.apps.ipengineapp import IPEngineApp |
|
273 | from IPython.parallel.apps.ipengineapp import IPEngineApp | |
273 | from IPython.parallel.apps.ipclusterapp import IPClusterStart |
|
274 | from IPython.parallel.apps.ipclusterapp import IPClusterStart | |
274 | from IPython.parallel.apps.iploggerapp import IPLoggerApp |
|
275 | from IPython.parallel.apps.iploggerapp import IPLoggerApp | |
275 | apps.extend([ |
|
276 | apps.extend([ | |
276 | IPControllerApp, |
|
277 | IPControllerApp, | |
277 | IPEngineApp, |
|
278 | IPEngineApp, | |
278 | IPClusterStart, |
|
279 | IPClusterStart, | |
279 | IPLoggerApp, |
|
280 | IPLoggerApp, | |
280 | ]) |
|
281 | ]) | |
281 | for App in apps: |
|
282 | for App in apps: | |
282 | app = App() |
|
283 | app = App() | |
283 | app.config.update(self.config) |
|
284 | app.config.update(self.config) | |
284 | app.log = self.log |
|
285 | app.log = self.log | |
285 | app.overwrite = self.overwrite |
|
286 | app.overwrite = self.overwrite | |
286 | app.copy_config_files=True |
|
287 | app.copy_config_files=True | |
287 | app.profile = self.profile |
|
288 | app.profile = self.profile | |
288 | app.init_profile_dir() |
|
289 | app.init_profile_dir() | |
289 | app.init_config_files() |
|
290 | app.init_config_files() | |
290 |
|
291 | |||
291 | def stage_default_config_file(self): |
|
292 | def stage_default_config_file(self): | |
292 | pass |
|
293 | pass | |
293 |
|
294 | |||
294 |
|
295 | |||
295 | class ProfileApp(Application): |
|
296 | class ProfileApp(Application): | |
296 | name = u'ipython-profile' |
|
297 | name = u'ipython-profile' | |
297 | description = profile_help |
|
298 | description = profile_help | |
298 | examples = _main_examples |
|
299 | examples = _main_examples | |
299 |
|
300 | |||
300 | subcommands = Dict(dict( |
|
301 | subcommands = Dict(dict( | |
301 | create = (ProfileCreate, ProfileCreate.description.splitlines()[0]), |
|
302 | create = (ProfileCreate, ProfileCreate.description.splitlines()[0]), | |
302 | list = (ProfileList, ProfileList.description.splitlines()[0]), |
|
303 | list = (ProfileList, ProfileList.description.splitlines()[0]), | |
303 | locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]), |
|
304 | locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]), | |
304 | )) |
|
305 | )) | |
305 |
|
306 | |||
306 | def start(self): |
|
307 | def start(self): | |
307 | if self.subapp is None: |
|
308 | if self.subapp is None: | |
308 | print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())) |
|
309 | print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())) | |
309 | print() |
|
310 | print() | |
310 | self.print_description() |
|
311 | self.print_description() | |
311 | self.print_subcommands() |
|
312 | self.print_subcommands() | |
312 | self.exit(1) |
|
313 | self.exit(1) | |
313 | else: |
|
314 | else: | |
314 | return self.subapp.start() |
|
315 | return self.subapp.start() |
@@ -1,273 +1,274 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | An object for managing IPython profile directories. |
|
3 | An object for managing IPython profile directories. | |
4 |
|
4 | |||
5 | Authors: |
|
5 | Authors: | |
6 |
|
6 | |||
7 | * Brian Granger |
|
7 | * Brian Granger | |
8 | * Fernando Perez |
|
8 | * Fernando Perez | |
9 | * Min RK |
|
9 | * Min RK | |
10 |
|
10 | |||
11 | """ |
|
11 | """ | |
12 |
|
12 | |||
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 | # Copyright (C) 2011 The IPython Development Team |
|
14 | # Copyright (C) 2011 The IPython Development Team | |
15 | # |
|
15 | # | |
16 | # Distributed under the terms of the BSD License. The full license is in |
|
16 | # Distributed under the terms of the BSD License. The full license is in | |
17 | # the file COPYING, distributed as part of this software. |
|
17 | # the file COPYING, distributed as part of this software. | |
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 |
|
19 | |||
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
21 | # Imports |
|
21 | # Imports | |
22 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
23 |
|
23 | |||
24 | import os |
|
24 | import os | |
25 | import shutil |
|
25 | import shutil | |
26 | import errno |
|
26 | import errno | |
27 |
|
27 | |||
28 | from IPython.config.configurable import LoggingConfigurable |
|
28 | from IPython.config.configurable import LoggingConfigurable | |
29 | from IPython.utils.path import get_ipython_package_dir, expand_path |
|
29 | from IPython.utils.path import get_ipython_package_dir, expand_path | |
|
30 | from IPython.utils import py3compat | |||
30 | from IPython.utils.traitlets import Unicode, Bool |
|
31 | from IPython.utils.traitlets import Unicode, Bool | |
31 |
|
32 | |||
32 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
33 | # Classes and functions |
|
34 | # Classes and functions | |
34 | #----------------------------------------------------------------------------- |
|
35 | #----------------------------------------------------------------------------- | |
35 |
|
36 | |||
36 |
|
37 | |||
37 | #----------------------------------------------------------------------------- |
|
38 | #----------------------------------------------------------------------------- | |
38 | # Module errors |
|
39 | # Module errors | |
39 | #----------------------------------------------------------------------------- |
|
40 | #----------------------------------------------------------------------------- | |
40 |
|
41 | |||
41 | class ProfileDirError(Exception): |
|
42 | class ProfileDirError(Exception): | |
42 | pass |
|
43 | pass | |
43 |
|
44 | |||
44 |
|
45 | |||
45 | #----------------------------------------------------------------------------- |
|
46 | #----------------------------------------------------------------------------- | |
46 | # Class for managing profile directories |
|
47 | # Class for managing profile directories | |
47 | #----------------------------------------------------------------------------- |
|
48 | #----------------------------------------------------------------------------- | |
48 |
|
49 | |||
49 | class ProfileDir(LoggingConfigurable): |
|
50 | class ProfileDir(LoggingConfigurable): | |
50 | """An object to manage the profile directory and its resources. |
|
51 | """An object to manage the profile directory and its resources. | |
51 |
|
52 | |||
52 | The profile directory is used by all IPython applications, to manage |
|
53 | The profile directory is used by all IPython applications, to manage | |
53 | configuration, logging and security. |
|
54 | configuration, logging and security. | |
54 |
|
55 | |||
55 | This object knows how to find, create and manage these directories. This |
|
56 | This object knows how to find, create and manage these directories. This | |
56 | should be used by any code that wants to handle profiles. |
|
57 | should be used by any code that wants to handle profiles. | |
57 | """ |
|
58 | """ | |
58 |
|
59 | |||
59 | security_dir_name = Unicode('security') |
|
60 | security_dir_name = Unicode('security') | |
60 | log_dir_name = Unicode('log') |
|
61 | log_dir_name = Unicode('log') | |
61 | startup_dir_name = Unicode('startup') |
|
62 | startup_dir_name = Unicode('startup') | |
62 | pid_dir_name = Unicode('pid') |
|
63 | pid_dir_name = Unicode('pid') | |
63 | static_dir_name = Unicode('static') |
|
64 | static_dir_name = Unicode('static') | |
64 | security_dir = Unicode(u'') |
|
65 | security_dir = Unicode(u'') | |
65 | log_dir = Unicode(u'') |
|
66 | log_dir = Unicode(u'') | |
66 | startup_dir = Unicode(u'') |
|
67 | startup_dir = Unicode(u'') | |
67 | pid_dir = Unicode(u'') |
|
68 | pid_dir = Unicode(u'') | |
68 | static_dir = Unicode(u'') |
|
69 | static_dir = Unicode(u'') | |
69 |
|
70 | |||
70 | location = Unicode(u'', config=True, |
|
71 | location = Unicode(u'', config=True, | |
71 | help="""Set the profile location directly. This overrides the logic used by the |
|
72 | help="""Set the profile location directly. This overrides the logic used by the | |
72 | `profile` option.""", |
|
73 | `profile` option.""", | |
73 | ) |
|
74 | ) | |
74 |
|
75 | |||
75 | _location_isset = Bool(False) # flag for detecting multiply set location |
|
76 | _location_isset = Bool(False) # flag for detecting multiply set location | |
76 |
|
77 | |||
77 | def _location_changed(self, name, old, new): |
|
78 | def _location_changed(self, name, old, new): | |
78 | if self._location_isset: |
|
79 | if self._location_isset: | |
79 | raise RuntimeError("Cannot set profile location more than once.") |
|
80 | raise RuntimeError("Cannot set profile location more than once.") | |
80 | self._location_isset = True |
|
81 | self._location_isset = True | |
81 | if not os.path.isdir(new): |
|
82 | if not os.path.isdir(new): | |
82 | os.makedirs(new) |
|
83 | os.makedirs(new) | |
83 |
|
84 | |||
84 | # ensure config files exist: |
|
85 | # ensure config files exist: | |
85 | self.security_dir = os.path.join(new, self.security_dir_name) |
|
86 | self.security_dir = os.path.join(new, self.security_dir_name) | |
86 | self.log_dir = os.path.join(new, self.log_dir_name) |
|
87 | self.log_dir = os.path.join(new, self.log_dir_name) | |
87 | self.startup_dir = os.path.join(new, self.startup_dir_name) |
|
88 | self.startup_dir = os.path.join(new, self.startup_dir_name) | |
88 | self.pid_dir = os.path.join(new, self.pid_dir_name) |
|
89 | self.pid_dir = os.path.join(new, self.pid_dir_name) | |
89 | self.static_dir = os.path.join(new, self.static_dir_name) |
|
90 | self.static_dir = os.path.join(new, self.static_dir_name) | |
90 | self.check_dirs() |
|
91 | self.check_dirs() | |
91 |
|
92 | |||
92 | def _log_dir_changed(self, name, old, new): |
|
93 | def _log_dir_changed(self, name, old, new): | |
93 | self.check_log_dir() |
|
94 | self.check_log_dir() | |
94 |
|
95 | |||
95 | def _mkdir(self, path, mode=None): |
|
96 | def _mkdir(self, path, mode=None): | |
96 | """ensure a directory exists at a given path |
|
97 | """ensure a directory exists at a given path | |
97 |
|
98 | |||
98 | This is a version of os.mkdir, with the following differences: |
|
99 | This is a version of os.mkdir, with the following differences: | |
99 |
|
100 | |||
100 | - returns True if it created the directory, False otherwise |
|
101 | - returns True if it created the directory, False otherwise | |
101 | - ignores EEXIST, protecting against race conditions where |
|
102 | - ignores EEXIST, protecting against race conditions where | |
102 | the dir may have been created in between the check and |
|
103 | the dir may have been created in between the check and | |
103 | the creation |
|
104 | the creation | |
104 | - sets permissions if requested and the dir already exists |
|
105 | - sets permissions if requested and the dir already exists | |
105 | """ |
|
106 | """ | |
106 | if os.path.exists(path): |
|
107 | if os.path.exists(path): | |
107 | if mode and os.stat(path).st_mode != mode: |
|
108 | if mode and os.stat(path).st_mode != mode: | |
108 | try: |
|
109 | try: | |
109 | os.chmod(path, mode) |
|
110 | os.chmod(path, mode) | |
110 | except OSError: |
|
111 | except OSError: | |
111 | self.log.warn( |
|
112 | self.log.warn( | |
112 | "Could not set permissions on %s", |
|
113 | "Could not set permissions on %s", | |
113 | path |
|
114 | path | |
114 | ) |
|
115 | ) | |
115 | return False |
|
116 | return False | |
116 | try: |
|
117 | try: | |
117 | if mode: |
|
118 | if mode: | |
118 | os.mkdir(path, mode) |
|
119 | os.mkdir(path, mode) | |
119 | else: |
|
120 | else: | |
120 | os.mkdir(path) |
|
121 | os.mkdir(path) | |
121 | except OSError as e: |
|
122 | except OSError as e: | |
122 | if e.errno == errno.EEXIST: |
|
123 | if e.errno == errno.EEXIST: | |
123 | return False |
|
124 | return False | |
124 | else: |
|
125 | else: | |
125 | raise |
|
126 | raise | |
126 |
|
127 | |||
127 | return True |
|
128 | return True | |
128 |
|
129 | |||
129 | def check_log_dir(self): |
|
130 | def check_log_dir(self): | |
130 | self._mkdir(self.log_dir) |
|
131 | self._mkdir(self.log_dir) | |
131 |
|
132 | |||
132 | def _startup_dir_changed(self, name, old, new): |
|
133 | def _startup_dir_changed(self, name, old, new): | |
133 | self.check_startup_dir() |
|
134 | self.check_startup_dir() | |
134 |
|
135 | |||
135 | def check_startup_dir(self): |
|
136 | def check_startup_dir(self): | |
136 | self._mkdir(self.startup_dir) |
|
137 | self._mkdir(self.startup_dir) | |
137 |
|
138 | |||
138 | readme = os.path.join(self.startup_dir, 'README') |
|
139 | readme = os.path.join(self.startup_dir, 'README') | |
139 | src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP') |
|
140 | src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP') | |
140 |
|
141 | |||
141 | if not os.path.exists(src): |
|
142 | if not os.path.exists(src): | |
142 | self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src) |
|
143 | self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src) | |
143 |
|
144 | |||
144 | if os.path.exists(src) and not os.path.exists(readme): |
|
145 | if os.path.exists(src) and not os.path.exists(readme): | |
145 | shutil.copy(src, readme) |
|
146 | shutil.copy(src, readme) | |
146 |
|
147 | |||
147 | def _security_dir_changed(self, name, old, new): |
|
148 | def _security_dir_changed(self, name, old, new): | |
148 | self.check_security_dir() |
|
149 | self.check_security_dir() | |
149 |
|
150 | |||
150 | def check_security_dir(self): |
|
151 | def check_security_dir(self): | |
151 | self._mkdir(self.security_dir, 0o40700) |
|
152 | self._mkdir(self.security_dir, 0o40700) | |
152 |
|
153 | |||
153 | def _pid_dir_changed(self, name, old, new): |
|
154 | def _pid_dir_changed(self, name, old, new): | |
154 | self.check_pid_dir() |
|
155 | self.check_pid_dir() | |
155 |
|
156 | |||
156 | def check_pid_dir(self): |
|
157 | def check_pid_dir(self): | |
157 | self._mkdir(self.pid_dir, 0o40700) |
|
158 | self._mkdir(self.pid_dir, 0o40700) | |
158 |
|
159 | |||
159 | def _static_dir_changed(self, name, old, new): |
|
160 | def _static_dir_changed(self, name, old, new): | |
160 | self.check_startup_dir() |
|
161 | self.check_startup_dir() | |
161 |
|
162 | |||
162 | def check_static_dir(self): |
|
163 | def check_static_dir(self): | |
163 | self._mkdir(self.static_dir) |
|
164 | self._mkdir(self.static_dir) | |
164 | custom = os.path.join(self.static_dir, 'custom') |
|
165 | custom = os.path.join(self.static_dir, 'custom') | |
165 | self._mkdir(custom) |
|
166 | self._mkdir(custom) | |
166 | from IPython.html import DEFAULT_STATIC_FILES_PATH |
|
167 | from IPython.html import DEFAULT_STATIC_FILES_PATH | |
167 | for fname in ('custom.js', 'custom.css'): |
|
168 | for fname in ('custom.js', 'custom.css'): | |
168 | src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname) |
|
169 | src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname) | |
169 | dest = os.path.join(custom, fname) |
|
170 | dest = os.path.join(custom, fname) | |
170 | if not os.path.exists(src): |
|
171 | if not os.path.exists(src): | |
171 | self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src) |
|
172 | self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src) | |
172 | continue |
|
173 | continue | |
173 | if not os.path.exists(dest): |
|
174 | if not os.path.exists(dest): | |
174 | shutil.copy(src, dest) |
|
175 | shutil.copy(src, dest) | |
175 |
|
176 | |||
176 | def check_dirs(self): |
|
177 | def check_dirs(self): | |
177 | self.check_security_dir() |
|
178 | self.check_security_dir() | |
178 | self.check_log_dir() |
|
179 | self.check_log_dir() | |
179 | self.check_pid_dir() |
|
180 | self.check_pid_dir() | |
180 | self.check_startup_dir() |
|
181 | self.check_startup_dir() | |
181 | self.check_static_dir() |
|
182 | self.check_static_dir() | |
182 |
|
183 | |||
183 | def copy_config_file(self, config_file, path=None, overwrite=False): |
|
184 | def copy_config_file(self, config_file, path=None, overwrite=False): | |
184 | """Copy a default config file into the active profile directory. |
|
185 | """Copy a default config file into the active profile directory. | |
185 |
|
186 | |||
186 | Default configuration files are kept in :mod:`IPython.config.default`. |
|
187 | Default configuration files are kept in :mod:`IPython.config.default`. | |
187 | This function moves these from that location to the working profile |
|
188 | This function moves these from that location to the working profile | |
188 | directory. |
|
189 | directory. | |
189 | """ |
|
190 | """ | |
190 | dst = os.path.join(self.location, config_file) |
|
191 | dst = os.path.join(self.location, config_file) | |
191 | if os.path.isfile(dst) and not overwrite: |
|
192 | if os.path.isfile(dst) and not overwrite: | |
192 | return False |
|
193 | return False | |
193 | if path is None: |
|
194 | if path is None: | |
194 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') |
|
195 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') | |
195 | src = os.path.join(path, config_file) |
|
196 | src = os.path.join(path, config_file) | |
196 | shutil.copy(src, dst) |
|
197 | shutil.copy(src, dst) | |
197 | return True |
|
198 | return True | |
198 |
|
199 | |||
199 | @classmethod |
|
200 | @classmethod | |
200 | def create_profile_dir(cls, profile_dir, config=None): |
|
201 | def create_profile_dir(cls, profile_dir, config=None): | |
201 | """Create a new profile directory given a full path. |
|
202 | """Create a new profile directory given a full path. | |
202 |
|
203 | |||
203 | Parameters |
|
204 | Parameters | |
204 | ---------- |
|
205 | ---------- | |
205 | profile_dir : str |
|
206 | profile_dir : str | |
206 | The full path to the profile directory. If it does exist, it will |
|
207 | The full path to the profile directory. If it does exist, it will | |
207 | be used. If not, it will be created. |
|
208 | be used. If not, it will be created. | |
208 | """ |
|
209 | """ | |
209 | return cls(location=profile_dir, config=config) |
|
210 | return cls(location=profile_dir, config=config) | |
210 |
|
211 | |||
211 | @classmethod |
|
212 | @classmethod | |
212 | def create_profile_dir_by_name(cls, path, name=u'default', config=None): |
|
213 | def create_profile_dir_by_name(cls, path, name=u'default', config=None): | |
213 | """Create a profile dir by profile name and path. |
|
214 | """Create a profile dir by profile name and path. | |
214 |
|
215 | |||
215 | Parameters |
|
216 | Parameters | |
216 | ---------- |
|
217 | ---------- | |
217 | path : unicode |
|
218 | path : unicode | |
218 | The path (directory) to put the profile directory in. |
|
219 | The path (directory) to put the profile directory in. | |
219 | name : unicode |
|
220 | name : unicode | |
220 | The name of the profile. The name of the profile directory will |
|
221 | The name of the profile. The name of the profile directory will | |
221 | be "profile_<profile>". |
|
222 | be "profile_<profile>". | |
222 | """ |
|
223 | """ | |
223 | if not os.path.isdir(path): |
|
224 | if not os.path.isdir(path): | |
224 | raise ProfileDirError('Directory not found: %s' % path) |
|
225 | raise ProfileDirError('Directory not found: %s' % path) | |
225 | profile_dir = os.path.join(path, u'profile_' + name) |
|
226 | profile_dir = os.path.join(path, u'profile_' + name) | |
226 | return cls(location=profile_dir, config=config) |
|
227 | return cls(location=profile_dir, config=config) | |
227 |
|
228 | |||
228 | @classmethod |
|
229 | @classmethod | |
229 | def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): |
|
230 | def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): | |
230 | """Find an existing profile dir by profile name, return its ProfileDir. |
|
231 | """Find an existing profile dir by profile name, return its ProfileDir. | |
231 |
|
232 | |||
232 | This searches through a sequence of paths for a profile dir. If it |
|
233 | This searches through a sequence of paths for a profile dir. If it | |
233 | is not found, a :class:`ProfileDirError` exception will be raised. |
|
234 | is not found, a :class:`ProfileDirError` exception will be raised. | |
234 |
|
235 | |||
235 | The search path algorithm is: |
|
236 | The search path algorithm is: | |
236 |
1. `` |
|
237 | 1. ``py3compat.getcwd()`` | |
237 | 2. ``ipython_dir`` |
|
238 | 2. ``ipython_dir`` | |
238 |
|
239 | |||
239 | Parameters |
|
240 | Parameters | |
240 | ---------- |
|
241 | ---------- | |
241 | ipython_dir : unicode or str |
|
242 | ipython_dir : unicode or str | |
242 | The IPython directory to use. |
|
243 | The IPython directory to use. | |
243 | name : unicode or str |
|
244 | name : unicode or str | |
244 | The name of the profile. The name of the profile directory |
|
245 | The name of the profile. The name of the profile directory | |
245 | will be "profile_<profile>". |
|
246 | will be "profile_<profile>". | |
246 | """ |
|
247 | """ | |
247 | dirname = u'profile_' + name |
|
248 | dirname = u'profile_' + name | |
248 |
paths = [ |
|
249 | paths = [py3compat.getcwd(), ipython_dir] | |
249 | for p in paths: |
|
250 | for p in paths: | |
250 | profile_dir = os.path.join(p, dirname) |
|
251 | profile_dir = os.path.join(p, dirname) | |
251 | if os.path.isdir(profile_dir): |
|
252 | if os.path.isdir(profile_dir): | |
252 | return cls(location=profile_dir, config=config) |
|
253 | return cls(location=profile_dir, config=config) | |
253 | else: |
|
254 | else: | |
254 | raise ProfileDirError('Profile directory not found in paths: %s' % dirname) |
|
255 | raise ProfileDirError('Profile directory not found in paths: %s' % dirname) | |
255 |
|
256 | |||
256 | @classmethod |
|
257 | @classmethod | |
257 | def find_profile_dir(cls, profile_dir, config=None): |
|
258 | def find_profile_dir(cls, profile_dir, config=None): | |
258 | """Find/create a profile dir and return its ProfileDir. |
|
259 | """Find/create a profile dir and return its ProfileDir. | |
259 |
|
260 | |||
260 | This will create the profile directory if it doesn't exist. |
|
261 | This will create the profile directory if it doesn't exist. | |
261 |
|
262 | |||
262 | Parameters |
|
263 | Parameters | |
263 | ---------- |
|
264 | ---------- | |
264 | profile_dir : unicode or str |
|
265 | profile_dir : unicode or str | |
265 | The path of the profile directory. This is expanded using |
|
266 | The path of the profile directory. This is expanded using | |
266 | :func:`IPython.utils.genutils.expand_path`. |
|
267 | :func:`IPython.utils.genutils.expand_path`. | |
267 | """ |
|
268 | """ | |
268 | profile_dir = expand_path(profile_dir) |
|
269 | profile_dir = expand_path(profile_dir) | |
269 | if not os.path.isdir(profile_dir): |
|
270 | if not os.path.isdir(profile_dir): | |
270 | raise ProfileDirError('Profile directory not found: %s' % profile_dir) |
|
271 | raise ProfileDirError('Profile directory not found: %s' % profile_dir) | |
271 | return cls(location=profile_dir, config=config) |
|
272 | return cls(location=profile_dir, config=config) | |
272 |
|
273 | |||
273 |
|
274 |
@@ -1,439 +1,439 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Classes for handling input/output prompts. |
|
2 | """Classes for handling input/output prompts. | |
3 |
|
3 | |||
4 | Authors: |
|
4 | Authors: | |
5 |
|
5 | |||
6 | * Fernando Perez |
|
6 | * Fernando Perez | |
7 | * Brian Granger |
|
7 | * Brian Granger | |
8 | * Thomas Kluyver |
|
8 | * Thomas Kluyver | |
9 | """ |
|
9 | """ | |
10 |
|
10 | |||
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 | # Copyright (C) 2008-2011 The IPython Development Team |
|
12 | # Copyright (C) 2008-2011 The IPython Development Team | |
13 | # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> |
|
13 | # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> | |
14 | # |
|
14 | # | |
15 | # Distributed under the terms of the BSD License. The full license is in |
|
15 | # Distributed under the terms of the BSD License. The full license is in | |
16 | # the file COPYING, distributed as part of this software. |
|
16 | # the file COPYING, distributed as part of this software. | |
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 |
|
18 | |||
19 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
20 | # Imports |
|
20 | # Imports | |
21 | #----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
22 |
|
22 | |||
23 | import os |
|
23 | import os | |
24 | import re |
|
24 | import re | |
25 | import socket |
|
25 | import socket | |
26 | import sys |
|
26 | import sys | |
27 | import time |
|
27 | import time | |
28 |
|
28 | |||
29 | from string import Formatter |
|
29 | from string import Formatter | |
30 |
|
30 | |||
31 | from IPython.config.configurable import Configurable |
|
31 | from IPython.config.configurable import Configurable | |
32 | from IPython.core import release |
|
32 | from IPython.core import release | |
33 | from IPython.utils import coloransi, py3compat |
|
33 | from IPython.utils import coloransi, py3compat | |
34 | from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int) |
|
34 | from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int) | |
35 |
|
35 | |||
36 | #----------------------------------------------------------------------------- |
|
36 | #----------------------------------------------------------------------------- | |
37 | # Color schemes for prompts |
|
37 | # Color schemes for prompts | |
38 | #----------------------------------------------------------------------------- |
|
38 | #----------------------------------------------------------------------------- | |
39 |
|
39 | |||
40 | InputColors = coloransi.InputTermColors # just a shorthand |
|
40 | InputColors = coloransi.InputTermColors # just a shorthand | |
41 | Colors = coloransi.TermColors # just a shorthand |
|
41 | Colors = coloransi.TermColors # just a shorthand | |
42 |
|
42 | |||
43 | color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors()) |
|
43 | color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors()) | |
44 |
|
44 | |||
45 | PColNoColors = coloransi.ColorScheme( |
|
45 | PColNoColors = coloransi.ColorScheme( | |
46 | 'NoColor', |
|
46 | 'NoColor', | |
47 | in_prompt = InputColors.NoColor, # Input prompt |
|
47 | in_prompt = InputColors.NoColor, # Input prompt | |
48 | in_number = InputColors.NoColor, # Input prompt number |
|
48 | in_number = InputColors.NoColor, # Input prompt number | |
49 | in_prompt2 = InputColors.NoColor, # Continuation prompt |
|
49 | in_prompt2 = InputColors.NoColor, # Continuation prompt | |
50 | in_normal = InputColors.NoColor, # color off (usu. Colors.Normal) |
|
50 | in_normal = InputColors.NoColor, # color off (usu. Colors.Normal) | |
51 |
|
51 | |||
52 | out_prompt = Colors.NoColor, # Output prompt |
|
52 | out_prompt = Colors.NoColor, # Output prompt | |
53 | out_number = Colors.NoColor, # Output prompt number |
|
53 | out_number = Colors.NoColor, # Output prompt number | |
54 |
|
54 | |||
55 | normal = Colors.NoColor # color off (usu. Colors.Normal) |
|
55 | normal = Colors.NoColor # color off (usu. Colors.Normal) | |
56 | ) |
|
56 | ) | |
57 |
|
57 | |||
58 | # make some schemes as instances so we can copy them for modification easily: |
|
58 | # make some schemes as instances so we can copy them for modification easily: | |
59 | PColLinux = coloransi.ColorScheme( |
|
59 | PColLinux = coloransi.ColorScheme( | |
60 | 'Linux', |
|
60 | 'Linux', | |
61 | in_prompt = InputColors.Green, |
|
61 | in_prompt = InputColors.Green, | |
62 | in_number = InputColors.LightGreen, |
|
62 | in_number = InputColors.LightGreen, | |
63 | in_prompt2 = InputColors.Green, |
|
63 | in_prompt2 = InputColors.Green, | |
64 | in_normal = InputColors.Normal, # color off (usu. Colors.Normal) |
|
64 | in_normal = InputColors.Normal, # color off (usu. Colors.Normal) | |
65 |
|
65 | |||
66 | out_prompt = Colors.Red, |
|
66 | out_prompt = Colors.Red, | |
67 | out_number = Colors.LightRed, |
|
67 | out_number = Colors.LightRed, | |
68 |
|
68 | |||
69 | normal = Colors.Normal |
|
69 | normal = Colors.Normal | |
70 | ) |
|
70 | ) | |
71 |
|
71 | |||
72 | # Slightly modified Linux for light backgrounds |
|
72 | # Slightly modified Linux for light backgrounds | |
73 | PColLightBG = PColLinux.copy('LightBG') |
|
73 | PColLightBG = PColLinux.copy('LightBG') | |
74 |
|
74 | |||
75 | PColLightBG.colors.update( |
|
75 | PColLightBG.colors.update( | |
76 | in_prompt = InputColors.Blue, |
|
76 | in_prompt = InputColors.Blue, | |
77 | in_number = InputColors.LightBlue, |
|
77 | in_number = InputColors.LightBlue, | |
78 | in_prompt2 = InputColors.Blue |
|
78 | in_prompt2 = InputColors.Blue | |
79 | ) |
|
79 | ) | |
80 |
|
80 | |||
81 | #----------------------------------------------------------------------------- |
|
81 | #----------------------------------------------------------------------------- | |
82 | # Utilities |
|
82 | # Utilities | |
83 | #----------------------------------------------------------------------------- |
|
83 | #----------------------------------------------------------------------------- | |
84 |
|
84 | |||
85 | class LazyEvaluate(object): |
|
85 | class LazyEvaluate(object): | |
86 | """This is used for formatting strings with values that need to be updated |
|
86 | """This is used for formatting strings with values that need to be updated | |
87 | at that time, such as the current time or working directory.""" |
|
87 | at that time, such as the current time or working directory.""" | |
88 | def __init__(self, func, *args, **kwargs): |
|
88 | def __init__(self, func, *args, **kwargs): | |
89 | self.func = func |
|
89 | self.func = func | |
90 | self.args = args |
|
90 | self.args = args | |
91 | self.kwargs = kwargs |
|
91 | self.kwargs = kwargs | |
92 |
|
92 | |||
93 | def __call__(self, **kwargs): |
|
93 | def __call__(self, **kwargs): | |
94 | self.kwargs.update(kwargs) |
|
94 | self.kwargs.update(kwargs) | |
95 | return self.func(*self.args, **self.kwargs) |
|
95 | return self.func(*self.args, **self.kwargs) | |
96 |
|
96 | |||
97 | def __str__(self): |
|
97 | def __str__(self): | |
98 | return str(self()) |
|
98 | return str(self()) | |
99 |
|
99 | |||
100 | def __unicode__(self): |
|
100 | def __unicode__(self): | |
101 | return py3compat.unicode_type(self()) |
|
101 | return py3compat.unicode_type(self()) | |
102 |
|
102 | |||
103 | def __format__(self, format_spec): |
|
103 | def __format__(self, format_spec): | |
104 | return format(self(), format_spec) |
|
104 | return format(self(), format_spec) | |
105 |
|
105 | |||
106 | def multiple_replace(dict, text): |
|
106 | def multiple_replace(dict, text): | |
107 | """ Replace in 'text' all occurences of any key in the given |
|
107 | """ Replace in 'text' all occurences of any key in the given | |
108 | dictionary by its corresponding value. Returns the new string.""" |
|
108 | dictionary by its corresponding value. Returns the new string.""" | |
109 |
|
109 | |||
110 | # Function by Xavier Defrang, originally found at: |
|
110 | # Function by Xavier Defrang, originally found at: | |
111 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330 |
|
111 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330 | |
112 |
|
112 | |||
113 | # Create a regular expression from the dictionary keys |
|
113 | # Create a regular expression from the dictionary keys | |
114 | regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys()))) |
|
114 | regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys()))) | |
115 | # For each match, look-up corresponding value in dictionary |
|
115 | # For each match, look-up corresponding value in dictionary | |
116 | return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) |
|
116 | return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) | |
117 |
|
117 | |||
118 | #----------------------------------------------------------------------------- |
|
118 | #----------------------------------------------------------------------------- | |
119 | # Special characters that can be used in prompt templates, mainly bash-like |
|
119 | # Special characters that can be used in prompt templates, mainly bash-like | |
120 | #----------------------------------------------------------------------------- |
|
120 | #----------------------------------------------------------------------------- | |
121 |
|
121 | |||
122 | # If $HOME isn't defined (Windows), make it an absurd string so that it can |
|
122 | # If $HOME isn't defined (Windows), make it an absurd string so that it can | |
123 | # never be expanded out into '~'. Basically anything which can never be a |
|
123 | # never be expanded out into '~'. Basically anything which can never be a | |
124 | # reasonable directory name will do, we just want the $HOME -> '~' operation |
|
124 | # reasonable directory name will do, we just want the $HOME -> '~' operation | |
125 | # to become a no-op. We pre-compute $HOME here so it's not done on every |
|
125 | # to become a no-op. We pre-compute $HOME here so it's not done on every | |
126 | # prompt call. |
|
126 | # prompt call. | |
127 |
|
127 | |||
128 | # FIXME: |
|
128 | # FIXME: | |
129 |
|
129 | |||
130 | # - This should be turned into a class which does proper namespace management, |
|
130 | # - This should be turned into a class which does proper namespace management, | |
131 | # since the prompt specials need to be evaluated in a certain namespace. |
|
131 | # since the prompt specials need to be evaluated in a certain namespace. | |
132 | # Currently it's just globals, which need to be managed manually by code |
|
132 | # Currently it's just globals, which need to be managed manually by code | |
133 | # below. |
|
133 | # below. | |
134 |
|
134 | |||
135 | # - I also need to split up the color schemes from the prompt specials |
|
135 | # - I also need to split up the color schemes from the prompt specials | |
136 | # somehow. I don't have a clean design for that quite yet. |
|
136 | # somehow. I don't have a clean design for that quite yet. | |
137 |
|
137 | |||
138 | HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")) |
|
138 | HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")) | |
139 |
|
139 | |||
140 | # This is needed on FreeBSD, and maybe other systems which symlink /home to |
|
140 | # This is needed on FreeBSD, and maybe other systems which symlink /home to | |
141 | # /usr/home, but retain the $HOME variable as pointing to /home |
|
141 | # /usr/home, but retain the $HOME variable as pointing to /home | |
142 | HOME = os.path.realpath(HOME) |
|
142 | HOME = os.path.realpath(HOME) | |
143 |
|
143 | |||
144 | # We precompute a few more strings here for the prompt_specials, which are |
|
144 | # We precompute a few more strings here for the prompt_specials, which are | |
145 | # fixed once ipython starts. This reduces the runtime overhead of computing |
|
145 | # fixed once ipython starts. This reduces the runtime overhead of computing | |
146 | # prompt strings. |
|
146 | # prompt strings. | |
147 | USER = py3compat.str_to_unicode(os.environ.get("USER",'')) |
|
147 | USER = py3compat.str_to_unicode(os.environ.get("USER",'')) | |
148 | HOSTNAME = py3compat.str_to_unicode(socket.gethostname()) |
|
148 | HOSTNAME = py3compat.str_to_unicode(socket.gethostname()) | |
149 | HOSTNAME_SHORT = HOSTNAME.split(".")[0] |
|
149 | HOSTNAME_SHORT = HOSTNAME.split(".")[0] | |
150 | ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$" |
|
150 | ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$" | |
151 |
|
151 | |||
152 | prompt_abbreviations = { |
|
152 | prompt_abbreviations = { | |
153 | # Prompt/history count |
|
153 | # Prompt/history count | |
154 | '%n' : '{color.number}' '{count}' '{color.prompt}', |
|
154 | '%n' : '{color.number}' '{count}' '{color.prompt}', | |
155 | r'\#': '{color.number}' '{count}' '{color.prompt}', |
|
155 | r'\#': '{color.number}' '{count}' '{color.prompt}', | |
156 | # Just the prompt counter number, WITHOUT any coloring wrappers, so users |
|
156 | # Just the prompt counter number, WITHOUT any coloring wrappers, so users | |
157 | # can get numbers displayed in whatever color they want. |
|
157 | # can get numbers displayed in whatever color they want. | |
158 | r'\N': '{count}', |
|
158 | r'\N': '{count}', | |
159 |
|
159 | |||
160 | # Prompt/history count, with the actual digits replaced by dots. Used |
|
160 | # Prompt/history count, with the actual digits replaced by dots. Used | |
161 | # mainly in continuation prompts (prompt_in2) |
|
161 | # mainly in continuation prompts (prompt_in2) | |
162 | r'\D': '{dots}', |
|
162 | r'\D': '{dots}', | |
163 |
|
163 | |||
164 | # Current time |
|
164 | # Current time | |
165 | r'\T' : '{time}', |
|
165 | r'\T' : '{time}', | |
166 | # Current working directory |
|
166 | # Current working directory | |
167 | r'\w': '{cwd}', |
|
167 | r'\w': '{cwd}', | |
168 | # Basename of current working directory. |
|
168 | # Basename of current working directory. | |
169 | # (use os.sep to make this portable across OSes) |
|
169 | # (use os.sep to make this portable across OSes) | |
170 | r'\W' : '{cwd_last}', |
|
170 | r'\W' : '{cwd_last}', | |
171 | # These X<N> are an extension to the normal bash prompts. They return |
|
171 | # These X<N> are an extension to the normal bash prompts. They return | |
172 | # N terms of the path, after replacing $HOME with '~' |
|
172 | # N terms of the path, after replacing $HOME with '~' | |
173 | r'\X0': '{cwd_x[0]}', |
|
173 | r'\X0': '{cwd_x[0]}', | |
174 | r'\X1': '{cwd_x[1]}', |
|
174 | r'\X1': '{cwd_x[1]}', | |
175 | r'\X2': '{cwd_x[2]}', |
|
175 | r'\X2': '{cwd_x[2]}', | |
176 | r'\X3': '{cwd_x[3]}', |
|
176 | r'\X3': '{cwd_x[3]}', | |
177 | r'\X4': '{cwd_x[4]}', |
|
177 | r'\X4': '{cwd_x[4]}', | |
178 | r'\X5': '{cwd_x[5]}', |
|
178 | r'\X5': '{cwd_x[5]}', | |
179 | # Y<N> are similar to X<N>, but they show '~' if it's the directory |
|
179 | # Y<N> are similar to X<N>, but they show '~' if it's the directory | |
180 | # N+1 in the list. Somewhat like %cN in tcsh. |
|
180 | # N+1 in the list. Somewhat like %cN in tcsh. | |
181 | r'\Y0': '{cwd_y[0]}', |
|
181 | r'\Y0': '{cwd_y[0]}', | |
182 | r'\Y1': '{cwd_y[1]}', |
|
182 | r'\Y1': '{cwd_y[1]}', | |
183 | r'\Y2': '{cwd_y[2]}', |
|
183 | r'\Y2': '{cwd_y[2]}', | |
184 | r'\Y3': '{cwd_y[3]}', |
|
184 | r'\Y3': '{cwd_y[3]}', | |
185 | r'\Y4': '{cwd_y[4]}', |
|
185 | r'\Y4': '{cwd_y[4]}', | |
186 | r'\Y5': '{cwd_y[5]}', |
|
186 | r'\Y5': '{cwd_y[5]}', | |
187 | # Hostname up to first . |
|
187 | # Hostname up to first . | |
188 | r'\h': HOSTNAME_SHORT, |
|
188 | r'\h': HOSTNAME_SHORT, | |
189 | # Full hostname |
|
189 | # Full hostname | |
190 | r'\H': HOSTNAME, |
|
190 | r'\H': HOSTNAME, | |
191 | # Username of current user |
|
191 | # Username of current user | |
192 | r'\u': USER, |
|
192 | r'\u': USER, | |
193 | # Escaped '\' |
|
193 | # Escaped '\' | |
194 | '\\\\': '\\', |
|
194 | '\\\\': '\\', | |
195 | # Newline |
|
195 | # Newline | |
196 | r'\n': '\n', |
|
196 | r'\n': '\n', | |
197 | # Carriage return |
|
197 | # Carriage return | |
198 | r'\r': '\r', |
|
198 | r'\r': '\r', | |
199 | # Release version |
|
199 | # Release version | |
200 | r'\v': release.version, |
|
200 | r'\v': release.version, | |
201 | # Root symbol ($ or #) |
|
201 | # Root symbol ($ or #) | |
202 | r'\$': ROOT_SYMBOL, |
|
202 | r'\$': ROOT_SYMBOL, | |
203 | } |
|
203 | } | |
204 |
|
204 | |||
205 | #----------------------------------------------------------------------------- |
|
205 | #----------------------------------------------------------------------------- | |
206 | # More utilities |
|
206 | # More utilities | |
207 | #----------------------------------------------------------------------------- |
|
207 | #----------------------------------------------------------------------------- | |
208 |
|
208 | |||
209 | def cwd_filt(depth): |
|
209 | def cwd_filt(depth): | |
210 | """Return the last depth elements of the current working directory. |
|
210 | """Return the last depth elements of the current working directory. | |
211 |
|
211 | |||
212 | $HOME is always replaced with '~'. |
|
212 | $HOME is always replaced with '~'. | |
213 | If depth==0, the full path is returned.""" |
|
213 | If depth==0, the full path is returned.""" | |
214 |
|
214 | |||
215 |
cwd = |
|
215 | cwd = py3compat.getcwd().replace(HOME,"~") | |
216 | out = os.sep.join(cwd.split(os.sep)[-depth:]) |
|
216 | out = os.sep.join(cwd.split(os.sep)[-depth:]) | |
217 | return out or os.sep |
|
217 | return out or os.sep | |
218 |
|
218 | |||
219 | def cwd_filt2(depth): |
|
219 | def cwd_filt2(depth): | |
220 | """Return the last depth elements of the current working directory. |
|
220 | """Return the last depth elements of the current working directory. | |
221 |
|
221 | |||
222 | $HOME is always replaced with '~'. |
|
222 | $HOME is always replaced with '~'. | |
223 | If depth==0, the full path is returned.""" |
|
223 | If depth==0, the full path is returned.""" | |
224 |
|
224 | |||
225 |
full_cwd = |
|
225 | full_cwd = py3compat.getcwd() | |
226 | cwd = full_cwd.replace(HOME,"~").split(os.sep) |
|
226 | cwd = full_cwd.replace(HOME,"~").split(os.sep) | |
227 | if '~' in cwd and len(cwd) == depth+1: |
|
227 | if '~' in cwd and len(cwd) == depth+1: | |
228 | depth += 1 |
|
228 | depth += 1 | |
229 | drivepart = '' |
|
229 | drivepart = '' | |
230 | if sys.platform == 'win32' and len(cwd) > depth: |
|
230 | if sys.platform == 'win32' and len(cwd) > depth: | |
231 | drivepart = os.path.splitdrive(full_cwd)[0] |
|
231 | drivepart = os.path.splitdrive(full_cwd)[0] | |
232 | out = drivepart + '/'.join(cwd[-depth:]) |
|
232 | out = drivepart + '/'.join(cwd[-depth:]) | |
233 |
|
233 | |||
234 | return out or os.sep |
|
234 | return out or os.sep | |
235 |
|
235 | |||
236 | #----------------------------------------------------------------------------- |
|
236 | #----------------------------------------------------------------------------- | |
237 | # Prompt classes |
|
237 | # Prompt classes | |
238 | #----------------------------------------------------------------------------- |
|
238 | #----------------------------------------------------------------------------- | |
239 |
|
239 | |||
240 | lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"), |
|
240 | lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"), | |
241 |
'cwd': LazyEvaluate( |
|
241 | 'cwd': LazyEvaluate(py3compat.getcwd), | |
242 |
'cwd_last': LazyEvaluate(lambda: |
|
242 | 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]), | |
243 |
'cwd_x': [LazyEvaluate(lambda: |
|
243 | 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\ | |
244 | [LazyEvaluate(cwd_filt, x) for x in range(1,6)], |
|
244 | [LazyEvaluate(cwd_filt, x) for x in range(1,6)], | |
245 | 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)] |
|
245 | 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)] | |
246 | } |
|
246 | } | |
247 |
|
247 | |||
248 | def _lenlastline(s): |
|
248 | def _lenlastline(s): | |
249 | """Get the length of the last line. More intelligent than |
|
249 | """Get the length of the last line. More intelligent than | |
250 | len(s.splitlines()[-1]). |
|
250 | len(s.splitlines()[-1]). | |
251 | """ |
|
251 | """ | |
252 | if not s or s.endswith(('\n', '\r')): |
|
252 | if not s or s.endswith(('\n', '\r')): | |
253 | return 0 |
|
253 | return 0 | |
254 | return len(s.splitlines()[-1]) |
|
254 | return len(s.splitlines()[-1]) | |
255 |
|
255 | |||
256 |
|
256 | |||
257 | class UserNSFormatter(Formatter): |
|
257 | class UserNSFormatter(Formatter): | |
258 | """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution""" |
|
258 | """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution""" | |
259 | def __init__(self, shell): |
|
259 | def __init__(self, shell): | |
260 | self.shell = shell |
|
260 | self.shell = shell | |
261 |
|
261 | |||
262 | def get_value(self, key, args, kwargs): |
|
262 | def get_value(self, key, args, kwargs): | |
263 | # try regular formatting first: |
|
263 | # try regular formatting first: | |
264 | try: |
|
264 | try: | |
265 | return Formatter.get_value(self, key, args, kwargs) |
|
265 | return Formatter.get_value(self, key, args, kwargs) | |
266 | except Exception: |
|
266 | except Exception: | |
267 | pass |
|
267 | pass | |
268 | # next, look in user_ns and builtins: |
|
268 | # next, look in user_ns and builtins: | |
269 | for container in (self.shell.user_ns, __builtins__): |
|
269 | for container in (self.shell.user_ns, __builtins__): | |
270 | if key in container: |
|
270 | if key in container: | |
271 | return container[key] |
|
271 | return container[key] | |
272 | # nothing found, put error message in its place |
|
272 | # nothing found, put error message in its place | |
273 | return "<ERROR: '%s' not found>" % key |
|
273 | return "<ERROR: '%s' not found>" % key | |
274 |
|
274 | |||
275 |
|
275 | |||
276 | class PromptManager(Configurable): |
|
276 | class PromptManager(Configurable): | |
277 | """This is the primary interface for producing IPython's prompts.""" |
|
277 | """This is the primary interface for producing IPython's prompts.""" | |
278 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') |
|
278 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | |
279 |
|
279 | |||
280 | color_scheme_table = Instance(coloransi.ColorSchemeTable) |
|
280 | color_scheme_table = Instance(coloransi.ColorSchemeTable) | |
281 | color_scheme = Unicode('Linux', config=True) |
|
281 | color_scheme = Unicode('Linux', config=True) | |
282 | def _color_scheme_changed(self, name, new_value): |
|
282 | def _color_scheme_changed(self, name, new_value): | |
283 | self.color_scheme_table.set_active_scheme(new_value) |
|
283 | self.color_scheme_table.set_active_scheme(new_value) | |
284 | for pname in ['in', 'in2', 'out', 'rewrite']: |
|
284 | for pname in ['in', 'in2', 'out', 'rewrite']: | |
285 | # We need to recalculate the number of invisible characters |
|
285 | # We need to recalculate the number of invisible characters | |
286 | self.update_prompt(pname) |
|
286 | self.update_prompt(pname) | |
287 |
|
287 | |||
288 | lazy_evaluate_fields = Dict(help=""" |
|
288 | lazy_evaluate_fields = Dict(help=""" | |
289 | This maps field names used in the prompt templates to functions which |
|
289 | This maps field names used in the prompt templates to functions which | |
290 | will be called when the prompt is rendered. This allows us to include |
|
290 | will be called when the prompt is rendered. This allows us to include | |
291 | things like the current time in the prompts. Functions are only called |
|
291 | things like the current time in the prompts. Functions are only called | |
292 | if they are used in the prompt. |
|
292 | if they are used in the prompt. | |
293 | """) |
|
293 | """) | |
294 | def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy() |
|
294 | def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy() | |
295 |
|
295 | |||
296 | in_template = Unicode('In [\\#]: ', config=True, |
|
296 | in_template = Unicode('In [\\#]: ', config=True, | |
297 | help="Input prompt. '\\#' will be transformed to the prompt number") |
|
297 | help="Input prompt. '\\#' will be transformed to the prompt number") | |
298 | in2_template = Unicode(' .\\D.: ', config=True, |
|
298 | in2_template = Unicode(' .\\D.: ', config=True, | |
299 | help="Continuation prompt.") |
|
299 | help="Continuation prompt.") | |
300 | out_template = Unicode('Out[\\#]: ', config=True, |
|
300 | out_template = Unicode('Out[\\#]: ', config=True, | |
301 | help="Output prompt. '\\#' will be transformed to the prompt number") |
|
301 | help="Output prompt. '\\#' will be transformed to the prompt number") | |
302 |
|
302 | |||
303 | justify = Bool(True, config=True, help=""" |
|
303 | justify = Bool(True, config=True, help=""" | |
304 | If True (default), each prompt will be right-aligned with the |
|
304 | If True (default), each prompt will be right-aligned with the | |
305 | preceding one. |
|
305 | preceding one. | |
306 | """) |
|
306 | """) | |
307 |
|
307 | |||
308 | # We actually store the expanded templates here: |
|
308 | # We actually store the expanded templates here: | |
309 | templates = Dict() |
|
309 | templates = Dict() | |
310 |
|
310 | |||
311 | # The number of characters in the last prompt rendered, not including |
|
311 | # The number of characters in the last prompt rendered, not including | |
312 | # colour characters. |
|
312 | # colour characters. | |
313 | width = Int() |
|
313 | width = Int() | |
314 | txtwidth = Int() # Not including right-justification |
|
314 | txtwidth = Int() # Not including right-justification | |
315 |
|
315 | |||
316 | # The number of characters in each prompt which don't contribute to width |
|
316 | # The number of characters in each prompt which don't contribute to width | |
317 | invisible_chars = Dict() |
|
317 | invisible_chars = Dict() | |
318 | def _invisible_chars_default(self): |
|
318 | def _invisible_chars_default(self): | |
319 | return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0} |
|
319 | return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0} | |
320 |
|
320 | |||
321 | def __init__(self, shell, **kwargs): |
|
321 | def __init__(self, shell, **kwargs): | |
322 | super(PromptManager, self).__init__(shell=shell, **kwargs) |
|
322 | super(PromptManager, self).__init__(shell=shell, **kwargs) | |
323 |
|
323 | |||
324 | # Prepare colour scheme table |
|
324 | # Prepare colour scheme table | |
325 | self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors, |
|
325 | self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors, | |
326 | PColLinux, PColLightBG], self.color_scheme) |
|
326 | PColLinux, PColLightBG], self.color_scheme) | |
327 |
|
327 | |||
328 | self._formatter = UserNSFormatter(shell) |
|
328 | self._formatter = UserNSFormatter(shell) | |
329 | # Prepare templates & numbers of invisible characters |
|
329 | # Prepare templates & numbers of invisible characters | |
330 | self.update_prompt('in', self.in_template) |
|
330 | self.update_prompt('in', self.in_template) | |
331 | self.update_prompt('in2', self.in2_template) |
|
331 | self.update_prompt('in2', self.in2_template) | |
332 | self.update_prompt('out', self.out_template) |
|
332 | self.update_prompt('out', self.out_template) | |
333 | self.update_prompt('rewrite') |
|
333 | self.update_prompt('rewrite') | |
334 | self.on_trait_change(self._update_prompt_trait, ['in_template', |
|
334 | self.on_trait_change(self._update_prompt_trait, ['in_template', | |
335 | 'in2_template', 'out_template']) |
|
335 | 'in2_template', 'out_template']) | |
336 |
|
336 | |||
337 | def update_prompt(self, name, new_template=None): |
|
337 | def update_prompt(self, name, new_template=None): | |
338 | """This is called when a prompt template is updated. It processes |
|
338 | """This is called when a prompt template is updated. It processes | |
339 | abbreviations used in the prompt template (like \#) and calculates how |
|
339 | abbreviations used in the prompt template (like \#) and calculates how | |
340 | many invisible characters (ANSI colour escapes) the resulting prompt |
|
340 | many invisible characters (ANSI colour escapes) the resulting prompt | |
341 | contains. |
|
341 | contains. | |
342 |
|
342 | |||
343 | It is also called for each prompt on changing the colour scheme. In both |
|
343 | It is also called for each prompt on changing the colour scheme. In both | |
344 | cases, traitlets should take care of calling this automatically. |
|
344 | cases, traitlets should take care of calling this automatically. | |
345 | """ |
|
345 | """ | |
346 | if new_template is not None: |
|
346 | if new_template is not None: | |
347 | self.templates[name] = multiple_replace(prompt_abbreviations, new_template) |
|
347 | self.templates[name] = multiple_replace(prompt_abbreviations, new_template) | |
348 | # We count invisible characters (colour escapes) on the last line of the |
|
348 | # We count invisible characters (colour escapes) on the last line of the | |
349 | # prompt, to calculate the width for lining up subsequent prompts. |
|
349 | # prompt, to calculate the width for lining up subsequent prompts. | |
350 | invis_chars = _lenlastline(self._render(name, color=True)) - \ |
|
350 | invis_chars = _lenlastline(self._render(name, color=True)) - \ | |
351 | _lenlastline(self._render(name, color=False)) |
|
351 | _lenlastline(self._render(name, color=False)) | |
352 | self.invisible_chars[name] = invis_chars |
|
352 | self.invisible_chars[name] = invis_chars | |
353 |
|
353 | |||
354 | def _update_prompt_trait(self, traitname, new_template): |
|
354 | def _update_prompt_trait(self, traitname, new_template): | |
355 | name = traitname[:-9] # Cut off '_template' |
|
355 | name = traitname[:-9] # Cut off '_template' | |
356 | self.update_prompt(name, new_template) |
|
356 | self.update_prompt(name, new_template) | |
357 |
|
357 | |||
358 | def _render(self, name, color=True, **kwargs): |
|
358 | def _render(self, name, color=True, **kwargs): | |
359 | """Render but don't justify, or update the width or txtwidth attributes. |
|
359 | """Render but don't justify, or update the width or txtwidth attributes. | |
360 | """ |
|
360 | """ | |
361 | if name == 'rewrite': |
|
361 | if name == 'rewrite': | |
362 | return self._render_rewrite(color=color) |
|
362 | return self._render_rewrite(color=color) | |
363 |
|
363 | |||
364 | if color: |
|
364 | if color: | |
365 | scheme = self.color_scheme_table.active_colors |
|
365 | scheme = self.color_scheme_table.active_colors | |
366 | if name=='out': |
|
366 | if name=='out': | |
367 | colors = color_lists['normal'] |
|
367 | colors = color_lists['normal'] | |
368 | colors.number, colors.prompt, colors.normal = \ |
|
368 | colors.number, colors.prompt, colors.normal = \ | |
369 | scheme.out_number, scheme.out_prompt, scheme.normal |
|
369 | scheme.out_number, scheme.out_prompt, scheme.normal | |
370 | else: |
|
370 | else: | |
371 | colors = color_lists['inp'] |
|
371 | colors = color_lists['inp'] | |
372 | colors.number, colors.prompt, colors.normal = \ |
|
372 | colors.number, colors.prompt, colors.normal = \ | |
373 | scheme.in_number, scheme.in_prompt, scheme.in_normal |
|
373 | scheme.in_number, scheme.in_prompt, scheme.in_normal | |
374 | if name=='in2': |
|
374 | if name=='in2': | |
375 | colors.prompt = scheme.in_prompt2 |
|
375 | colors.prompt = scheme.in_prompt2 | |
376 | else: |
|
376 | else: | |
377 | # No color |
|
377 | # No color | |
378 | colors = color_lists['nocolor'] |
|
378 | colors = color_lists['nocolor'] | |
379 | colors.number, colors.prompt, colors.normal = '', '', '' |
|
379 | colors.number, colors.prompt, colors.normal = '', '', '' | |
380 |
|
380 | |||
381 | count = self.shell.execution_count # Shorthand |
|
381 | count = self.shell.execution_count # Shorthand | |
382 | # Build the dictionary to be passed to string formatting |
|
382 | # Build the dictionary to be passed to string formatting | |
383 | fmtargs = dict(color=colors, count=count, |
|
383 | fmtargs = dict(color=colors, count=count, | |
384 | dots="."*len(str(count)), |
|
384 | dots="."*len(str(count)), | |
385 | width=self.width, txtwidth=self.txtwidth ) |
|
385 | width=self.width, txtwidth=self.txtwidth ) | |
386 | fmtargs.update(self.lazy_evaluate_fields) |
|
386 | fmtargs.update(self.lazy_evaluate_fields) | |
387 | fmtargs.update(kwargs) |
|
387 | fmtargs.update(kwargs) | |
388 |
|
388 | |||
389 | # Prepare the prompt |
|
389 | # Prepare the prompt | |
390 | prompt = colors.prompt + self.templates[name] + colors.normal |
|
390 | prompt = colors.prompt + self.templates[name] + colors.normal | |
391 |
|
391 | |||
392 | # Fill in required fields |
|
392 | # Fill in required fields | |
393 | return self._formatter.format(prompt, **fmtargs) |
|
393 | return self._formatter.format(prompt, **fmtargs) | |
394 |
|
394 | |||
395 | def _render_rewrite(self, color=True): |
|
395 | def _render_rewrite(self, color=True): | |
396 | """Render the ---> rewrite prompt.""" |
|
396 | """Render the ---> rewrite prompt.""" | |
397 | if color: |
|
397 | if color: | |
398 | scheme = self.color_scheme_table.active_colors |
|
398 | scheme = self.color_scheme_table.active_colors | |
399 | # We need a non-input version of these escapes |
|
399 | # We need a non-input version of these escapes | |
400 | color_prompt = scheme.in_prompt.replace("\001","").replace("\002","") |
|
400 | color_prompt = scheme.in_prompt.replace("\001","").replace("\002","") | |
401 | color_normal = scheme.normal |
|
401 | color_normal = scheme.normal | |
402 | else: |
|
402 | else: | |
403 | color_prompt, color_normal = '', '' |
|
403 | color_prompt, color_normal = '', '' | |
404 |
|
404 | |||
405 | return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal |
|
405 | return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal | |
406 |
|
406 | |||
407 | def render(self, name, color=True, just=None, **kwargs): |
|
407 | def render(self, name, color=True, just=None, **kwargs): | |
408 | """ |
|
408 | """ | |
409 | Render the selected prompt. |
|
409 | Render the selected prompt. | |
410 |
|
410 | |||
411 | Parameters |
|
411 | Parameters | |
412 | ---------- |
|
412 | ---------- | |
413 | name : str |
|
413 | name : str | |
414 | Which prompt to render. One of 'in', 'in2', 'out', 'rewrite' |
|
414 | Which prompt to render. One of 'in', 'in2', 'out', 'rewrite' | |
415 | color : bool |
|
415 | color : bool | |
416 | If True (default), include ANSI escape sequences for a coloured prompt. |
|
416 | If True (default), include ANSI escape sequences for a coloured prompt. | |
417 | just : bool |
|
417 | just : bool | |
418 | If True, justify the prompt to the width of the last prompt. The |
|
418 | If True, justify the prompt to the width of the last prompt. The | |
419 | default is stored in self.justify. |
|
419 | default is stored in self.justify. | |
420 | **kwargs : |
|
420 | **kwargs : | |
421 | Additional arguments will be passed to the string formatting operation, |
|
421 | Additional arguments will be passed to the string formatting operation, | |
422 | so they can override the values that would otherwise fill in the |
|
422 | so they can override the values that would otherwise fill in the | |
423 | template. |
|
423 | template. | |
424 |
|
424 | |||
425 | Returns |
|
425 | Returns | |
426 | ------- |
|
426 | ------- | |
427 | A string containing the rendered prompt. |
|
427 | A string containing the rendered prompt. | |
428 | """ |
|
428 | """ | |
429 | res = self._render(name, color=color, **kwargs) |
|
429 | res = self._render(name, color=color, **kwargs) | |
430 |
|
430 | |||
431 | # Handle justification of prompt |
|
431 | # Handle justification of prompt | |
432 | invis_chars = self.invisible_chars[name] if color else 0 |
|
432 | invis_chars = self.invisible_chars[name] if color else 0 | |
433 | self.txtwidth = _lenlastline(res) - invis_chars |
|
433 | self.txtwidth = _lenlastline(res) - invis_chars | |
434 | just = self.justify if (just is None) else just |
|
434 | just = self.justify if (just is None) else just | |
435 | # If the prompt spans more than one line, don't try to justify it: |
|
435 | # If the prompt spans more than one line, don't try to justify it: | |
436 | if just and name != 'in' and ('\n' not in res) and ('\r' not in res): |
|
436 | if just and name != 'in' and ('\n' not in res) and ('\r' not in res): | |
437 | res = res.rjust(self.width + invis_chars) |
|
437 | res = res.rjust(self.width + invis_chars) | |
438 | self.width = _lenlastline(res) - invis_chars |
|
438 | self.width = _lenlastline(res) - invis_chars | |
439 | return res |
|
439 | return res |
@@ -1,50 +1,50 b'' | |||||
1 | # coding: utf-8 |
|
1 | # coding: utf-8 | |
2 | """Tests for IPython.core.application""" |
|
2 | """Tests for IPython.core.application""" | |
3 |
|
3 | |||
4 | import os |
|
4 | import os | |
5 | import tempfile |
|
5 | import tempfile | |
6 |
|
6 | |||
7 | from IPython.core.application import BaseIPythonApplication |
|
7 | from IPython.core.application import BaseIPythonApplication | |
8 | from IPython.testing import decorators as dec |
|
8 | from IPython.testing import decorators as dec | |
9 | from IPython.utils import py3compat |
|
9 | from IPython.utils import py3compat | |
10 |
|
10 | |||
11 | @dec.onlyif_unicode_paths |
|
11 | @dec.onlyif_unicode_paths | |
12 | def test_unicode_cwd(): |
|
12 | def test_unicode_cwd(): | |
13 | """Check that IPython starts with non-ascii characters in the path.""" |
|
13 | """Check that IPython starts with non-ascii characters in the path.""" | |
14 | wd = tempfile.mkdtemp(suffix=u"β¬") |
|
14 | wd = tempfile.mkdtemp(suffix=u"β¬") | |
15 |
|
15 | |||
16 |
old_wd = |
|
16 | old_wd = py3compat.getcwd() | |
17 | os.chdir(wd) |
|
17 | os.chdir(wd) | |
18 |
#raise Exception(repr( |
|
18 | #raise Exception(repr(py3compat.getcwd())) | |
19 | try: |
|
19 | try: | |
20 | app = BaseIPythonApplication() |
|
20 | app = BaseIPythonApplication() | |
21 | # The lines below are copied from Application.initialize() |
|
21 | # The lines below are copied from Application.initialize() | |
22 | app.init_profile_dir() |
|
22 | app.init_profile_dir() | |
23 | app.init_config_files() |
|
23 | app.init_config_files() | |
24 | app.load_config_file(suppress_errors=False) |
|
24 | app.load_config_file(suppress_errors=False) | |
25 | finally: |
|
25 | finally: | |
26 | os.chdir(old_wd) |
|
26 | os.chdir(old_wd) | |
27 |
|
27 | |||
28 | @dec.onlyif_unicode_paths |
|
28 | @dec.onlyif_unicode_paths | |
29 | def test_unicode_ipdir(): |
|
29 | def test_unicode_ipdir(): | |
30 | """Check that IPython starts with non-ascii characters in the IP dir.""" |
|
30 | """Check that IPython starts with non-ascii characters in the IP dir.""" | |
31 | ipdir = tempfile.mkdtemp(suffix=u"β¬") |
|
31 | ipdir = tempfile.mkdtemp(suffix=u"β¬") | |
32 |
|
32 | |||
33 | # Create the config file, so it tries to load it. |
|
33 | # Create the config file, so it tries to load it. | |
34 | with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f: |
|
34 | with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f: | |
35 | pass |
|
35 | pass | |
36 |
|
36 | |||
37 | old_ipdir1 = os.environ.pop("IPYTHONDIR", None) |
|
37 | old_ipdir1 = os.environ.pop("IPYTHONDIR", None) | |
38 | old_ipdir2 = os.environ.pop("IPYTHON_DIR", None) |
|
38 | old_ipdir2 = os.environ.pop("IPYTHON_DIR", None) | |
39 | os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8") |
|
39 | os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8") | |
40 | try: |
|
40 | try: | |
41 | app = BaseIPythonApplication() |
|
41 | app = BaseIPythonApplication() | |
42 | # The lines below are copied from Application.initialize() |
|
42 | # The lines below are copied from Application.initialize() | |
43 | app.init_profile_dir() |
|
43 | app.init_profile_dir() | |
44 | app.init_config_files() |
|
44 | app.init_config_files() | |
45 | app.load_config_file(suppress_errors=False) |
|
45 | app.load_config_file(suppress_errors=False) | |
46 | finally: |
|
46 | finally: | |
47 | if old_ipdir1: |
|
47 | if old_ipdir1: | |
48 | os.environ["IPYTHONDIR"] = old_ipdir1 |
|
48 | os.environ["IPYTHONDIR"] = old_ipdir1 | |
49 | if old_ipdir2: |
|
49 | if old_ipdir2: | |
50 | os.environ["IPYTHONDIR"] = old_ipdir2 |
|
50 | os.environ["IPYTHONDIR"] = old_ipdir2 |
@@ -1,393 +1,394 b'' | |||||
1 | """Tests for the IPython tab-completion machinery. |
|
1 | """Tests for the IPython tab-completion machinery. | |
2 | """ |
|
2 | """ | |
3 | #----------------------------------------------------------------------------- |
|
3 | #----------------------------------------------------------------------------- | |
4 | # Module imports |
|
4 | # Module imports | |
5 | #----------------------------------------------------------------------------- |
|
5 | #----------------------------------------------------------------------------- | |
6 |
|
6 | |||
7 | # stdlib |
|
7 | # stdlib | |
8 | import os |
|
8 | import os | |
9 | import sys |
|
9 | import sys | |
10 | import unittest |
|
10 | import unittest | |
11 |
|
11 | |||
12 | # third party |
|
12 | # third party | |
13 | import nose.tools as nt |
|
13 | import nose.tools as nt | |
14 |
|
14 | |||
15 | # our own packages |
|
15 | # our own packages | |
16 | from IPython.config.loader import Config |
|
16 | from IPython.config.loader import Config | |
17 | from IPython.core import completer |
|
17 | from IPython.core import completer | |
18 | from IPython.external.decorators import knownfailureif |
|
18 | from IPython.external.decorators import knownfailureif | |
19 | from IPython.utils.tempdir import TemporaryDirectory |
|
19 | from IPython.utils.tempdir import TemporaryDirectory | |
20 | from IPython.utils.generics import complete_object |
|
20 | from IPython.utils.generics import complete_object | |
|
21 | from IPython.utils import py3compat | |||
21 | from IPython.utils.py3compat import string_types, unicode_type |
|
22 | from IPython.utils.py3compat import string_types, unicode_type | |
22 |
|
23 | |||
23 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
24 | # Test functions |
|
25 | # Test functions | |
25 | #----------------------------------------------------------------------------- |
|
26 | #----------------------------------------------------------------------------- | |
26 | def test_protect_filename(): |
|
27 | def test_protect_filename(): | |
27 | pairs = [ ('abc','abc'), |
|
28 | pairs = [ ('abc','abc'), | |
28 | (' abc',r'\ abc'), |
|
29 | (' abc',r'\ abc'), | |
29 | ('a bc',r'a\ bc'), |
|
30 | ('a bc',r'a\ bc'), | |
30 | ('a bc',r'a\ \ bc'), |
|
31 | ('a bc',r'a\ \ bc'), | |
31 | (' bc',r'\ \ bc'), |
|
32 | (' bc',r'\ \ bc'), | |
32 | ] |
|
33 | ] | |
33 | # On posix, we also protect parens and other special characters |
|
34 | # On posix, we also protect parens and other special characters | |
34 | if sys.platform != 'win32': |
|
35 | if sys.platform != 'win32': | |
35 | pairs.extend( [('a(bc',r'a\(bc'), |
|
36 | pairs.extend( [('a(bc',r'a\(bc'), | |
36 | ('a)bc',r'a\)bc'), |
|
37 | ('a)bc',r'a\)bc'), | |
37 | ('a( )bc',r'a\(\ \)bc'), |
|
38 | ('a( )bc',r'a\(\ \)bc'), | |
38 | ('a[1]bc', r'a\[1\]bc'), |
|
39 | ('a[1]bc', r'a\[1\]bc'), | |
39 | ('a{1}bc', r'a\{1\}bc'), |
|
40 | ('a{1}bc', r'a\{1\}bc'), | |
40 | ('a#bc', r'a\#bc'), |
|
41 | ('a#bc', r'a\#bc'), | |
41 | ('a?bc', r'a\?bc'), |
|
42 | ('a?bc', r'a\?bc'), | |
42 | ('a=bc', r'a\=bc'), |
|
43 | ('a=bc', r'a\=bc'), | |
43 | ('a\\bc', r'a\\bc'), |
|
44 | ('a\\bc', r'a\\bc'), | |
44 | ('a|bc', r'a\|bc'), |
|
45 | ('a|bc', r'a\|bc'), | |
45 | ('a;bc', r'a\;bc'), |
|
46 | ('a;bc', r'a\;bc'), | |
46 | ('a:bc', r'a\:bc'), |
|
47 | ('a:bc', r'a\:bc'), | |
47 | ("a'bc", r"a\'bc"), |
|
48 | ("a'bc", r"a\'bc"), | |
48 | ('a*bc', r'a\*bc'), |
|
49 | ('a*bc', r'a\*bc'), | |
49 | ('a"bc', r'a\"bc'), |
|
50 | ('a"bc', r'a\"bc'), | |
50 | ('a^bc', r'a\^bc'), |
|
51 | ('a^bc', r'a\^bc'), | |
51 | ('a&bc', r'a\&bc'), |
|
52 | ('a&bc', r'a\&bc'), | |
52 | ] ) |
|
53 | ] ) | |
53 | # run the actual tests |
|
54 | # run the actual tests | |
54 | for s1, s2 in pairs: |
|
55 | for s1, s2 in pairs: | |
55 | s1p = completer.protect_filename(s1) |
|
56 | s1p = completer.protect_filename(s1) | |
56 | nt.assert_equal(s1p, s2) |
|
57 | nt.assert_equal(s1p, s2) | |
57 |
|
58 | |||
58 |
|
59 | |||
59 | def check_line_split(splitter, test_specs): |
|
60 | def check_line_split(splitter, test_specs): | |
60 | for part1, part2, split in test_specs: |
|
61 | for part1, part2, split in test_specs: | |
61 | cursor_pos = len(part1) |
|
62 | cursor_pos = len(part1) | |
62 | line = part1+part2 |
|
63 | line = part1+part2 | |
63 | out = splitter.split_line(line, cursor_pos) |
|
64 | out = splitter.split_line(line, cursor_pos) | |
64 | nt.assert_equal(out, split) |
|
65 | nt.assert_equal(out, split) | |
65 |
|
66 | |||
66 |
|
67 | |||
67 | def test_line_split(): |
|
68 | def test_line_split(): | |
68 | """Basic line splitter test with default specs.""" |
|
69 | """Basic line splitter test with default specs.""" | |
69 | sp = completer.CompletionSplitter() |
|
70 | sp = completer.CompletionSplitter() | |
70 | # The format of the test specs is: part1, part2, expected answer. Parts 1 |
|
71 | # The format of the test specs is: part1, part2, expected answer. Parts 1 | |
71 | # and 2 are joined into the 'line' sent to the splitter, as if the cursor |
|
72 | # and 2 are joined into the 'line' sent to the splitter, as if the cursor | |
72 | # was at the end of part1. So an empty part2 represents someone hitting |
|
73 | # was at the end of part1. So an empty part2 represents someone hitting | |
73 | # tab at the end of the line, the most common case. |
|
74 | # tab at the end of the line, the most common case. | |
74 | t = [('run some/scrip', '', 'some/scrip'), |
|
75 | t = [('run some/scrip', '', 'some/scrip'), | |
75 | ('run scripts/er', 'ror.py foo', 'scripts/er'), |
|
76 | ('run scripts/er', 'ror.py foo', 'scripts/er'), | |
76 | ('echo $HOM', '', 'HOM'), |
|
77 | ('echo $HOM', '', 'HOM'), | |
77 | ('print sys.pa', '', 'sys.pa'), |
|
78 | ('print sys.pa', '', 'sys.pa'), | |
78 | ('print(sys.pa', '', 'sys.pa'), |
|
79 | ('print(sys.pa', '', 'sys.pa'), | |
79 | ("execfile('scripts/er", '', 'scripts/er'), |
|
80 | ("execfile('scripts/er", '', 'scripts/er'), | |
80 | ('a[x.', '', 'x.'), |
|
81 | ('a[x.', '', 'x.'), | |
81 | ('a[x.', 'y', 'x.'), |
|
82 | ('a[x.', 'y', 'x.'), | |
82 | ('cd "some_file/', '', 'some_file/'), |
|
83 | ('cd "some_file/', '', 'some_file/'), | |
83 | ] |
|
84 | ] | |
84 | check_line_split(sp, t) |
|
85 | check_line_split(sp, t) | |
85 | # Ensure splitting works OK with unicode by re-running the tests with |
|
86 | # Ensure splitting works OK with unicode by re-running the tests with | |
86 | # all inputs turned into unicode |
|
87 | # all inputs turned into unicode | |
87 | check_line_split(sp, [ map(unicode_type, p) for p in t] ) |
|
88 | check_line_split(sp, [ map(unicode_type, p) for p in t] ) | |
88 |
|
89 | |||
89 |
|
90 | |||
90 | def test_custom_completion_error(): |
|
91 | def test_custom_completion_error(): | |
91 | """Test that errors from custom attribute completers are silenced.""" |
|
92 | """Test that errors from custom attribute completers are silenced.""" | |
92 | ip = get_ipython() |
|
93 | ip = get_ipython() | |
93 | class A(object): pass |
|
94 | class A(object): pass | |
94 | ip.user_ns['a'] = A() |
|
95 | ip.user_ns['a'] = A() | |
95 |
|
96 | |||
96 | @complete_object.when_type(A) |
|
97 | @complete_object.when_type(A) | |
97 | def complete_A(a, existing_completions): |
|
98 | def complete_A(a, existing_completions): | |
98 | raise TypeError("this should be silenced") |
|
99 | raise TypeError("this should be silenced") | |
99 |
|
100 | |||
100 | ip.complete("a.") |
|
101 | ip.complete("a.") | |
101 |
|
102 | |||
102 |
|
103 | |||
103 | def test_unicode_completions(): |
|
104 | def test_unicode_completions(): | |
104 | ip = get_ipython() |
|
105 | ip = get_ipython() | |
105 | # Some strings that trigger different types of completion. Check them both |
|
106 | # Some strings that trigger different types of completion. Check them both | |
106 | # in str and unicode forms |
|
107 | # in str and unicode forms | |
107 | s = ['ru', '%ru', 'cd /', 'floa', 'float(x)/'] |
|
108 | s = ['ru', '%ru', 'cd /', 'floa', 'float(x)/'] | |
108 | for t in s + list(map(unicode_type, s)): |
|
109 | for t in s + list(map(unicode_type, s)): | |
109 | # We don't need to check exact completion values (they may change |
|
110 | # We don't need to check exact completion values (they may change | |
110 | # depending on the state of the namespace, but at least no exceptions |
|
111 | # depending on the state of the namespace, but at least no exceptions | |
111 | # should be thrown and the return value should be a pair of text, list |
|
112 | # should be thrown and the return value should be a pair of text, list | |
112 | # values. |
|
113 | # values. | |
113 | text, matches = ip.complete(t) |
|
114 | text, matches = ip.complete(t) | |
114 | nt.assert_true(isinstance(text, string_types)) |
|
115 | nt.assert_true(isinstance(text, string_types)) | |
115 | nt.assert_true(isinstance(matches, list)) |
|
116 | nt.assert_true(isinstance(matches, list)) | |
116 |
|
117 | |||
117 |
|
118 | |||
118 | class CompletionSplitterTestCase(unittest.TestCase): |
|
119 | class CompletionSplitterTestCase(unittest.TestCase): | |
119 | def setUp(self): |
|
120 | def setUp(self): | |
120 | self.sp = completer.CompletionSplitter() |
|
121 | self.sp = completer.CompletionSplitter() | |
121 |
|
122 | |||
122 | def test_delim_setting(self): |
|
123 | def test_delim_setting(self): | |
123 | self.sp.delims = ' ' |
|
124 | self.sp.delims = ' ' | |
124 | nt.assert_equal(self.sp.delims, ' ') |
|
125 | nt.assert_equal(self.sp.delims, ' ') | |
125 | nt.assert_equal(self.sp._delim_expr, '[\ ]') |
|
126 | nt.assert_equal(self.sp._delim_expr, '[\ ]') | |
126 |
|
127 | |||
127 | def test_spaces(self): |
|
128 | def test_spaces(self): | |
128 | """Test with only spaces as split chars.""" |
|
129 | """Test with only spaces as split chars.""" | |
129 | self.sp.delims = ' ' |
|
130 | self.sp.delims = ' ' | |
130 | t = [('foo', '', 'foo'), |
|
131 | t = [('foo', '', 'foo'), | |
131 | ('run foo', '', 'foo'), |
|
132 | ('run foo', '', 'foo'), | |
132 | ('run foo', 'bar', 'foo'), |
|
133 | ('run foo', 'bar', 'foo'), | |
133 | ] |
|
134 | ] | |
134 | check_line_split(self.sp, t) |
|
135 | check_line_split(self.sp, t) | |
135 |
|
136 | |||
136 |
|
137 | |||
137 | def test_has_open_quotes1(): |
|
138 | def test_has_open_quotes1(): | |
138 | for s in ["'", "'''", "'hi' '"]: |
|
139 | for s in ["'", "'''", "'hi' '"]: | |
139 | nt.assert_equal(completer.has_open_quotes(s), "'") |
|
140 | nt.assert_equal(completer.has_open_quotes(s), "'") | |
140 |
|
141 | |||
141 |
|
142 | |||
142 | def test_has_open_quotes2(): |
|
143 | def test_has_open_quotes2(): | |
143 | for s in ['"', '"""', '"hi" "']: |
|
144 | for s in ['"', '"""', '"hi" "']: | |
144 | nt.assert_equal(completer.has_open_quotes(s), '"') |
|
145 | nt.assert_equal(completer.has_open_quotes(s), '"') | |
145 |
|
146 | |||
146 |
|
147 | |||
147 | def test_has_open_quotes3(): |
|
148 | def test_has_open_quotes3(): | |
148 | for s in ["''", "''' '''", "'hi' 'ipython'"]: |
|
149 | for s in ["''", "''' '''", "'hi' 'ipython'"]: | |
149 | nt.assert_false(completer.has_open_quotes(s)) |
|
150 | nt.assert_false(completer.has_open_quotes(s)) | |
150 |
|
151 | |||
151 |
|
152 | |||
152 | def test_has_open_quotes4(): |
|
153 | def test_has_open_quotes4(): | |
153 | for s in ['""', '""" """', '"hi" "ipython"']: |
|
154 | for s in ['""', '""" """', '"hi" "ipython"']: | |
154 | nt.assert_false(completer.has_open_quotes(s)) |
|
155 | nt.assert_false(completer.has_open_quotes(s)) | |
155 |
|
156 | |||
156 |
|
157 | |||
157 | @knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows") |
|
158 | @knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows") | |
158 | def test_abspath_file_completions(): |
|
159 | def test_abspath_file_completions(): | |
159 | ip = get_ipython() |
|
160 | ip = get_ipython() | |
160 | with TemporaryDirectory() as tmpdir: |
|
161 | with TemporaryDirectory() as tmpdir: | |
161 | prefix = os.path.join(tmpdir, 'foo') |
|
162 | prefix = os.path.join(tmpdir, 'foo') | |
162 | suffixes = ['1', '2'] |
|
163 | suffixes = ['1', '2'] | |
163 | names = [prefix+s for s in suffixes] |
|
164 | names = [prefix+s for s in suffixes] | |
164 | for n in names: |
|
165 | for n in names: | |
165 | open(n, 'w').close() |
|
166 | open(n, 'w').close() | |
166 |
|
167 | |||
167 | # Check simple completion |
|
168 | # Check simple completion | |
168 | c = ip.complete(prefix)[1] |
|
169 | c = ip.complete(prefix)[1] | |
169 | nt.assert_equal(c, names) |
|
170 | nt.assert_equal(c, names) | |
170 |
|
171 | |||
171 | # Now check with a function call |
|
172 | # Now check with a function call | |
172 | cmd = 'a = f("%s' % prefix |
|
173 | cmd = 'a = f("%s' % prefix | |
173 | c = ip.complete(prefix, cmd)[1] |
|
174 | c = ip.complete(prefix, cmd)[1] | |
174 | comp = [prefix+s for s in suffixes] |
|
175 | comp = [prefix+s for s in suffixes] | |
175 | nt.assert_equal(c, comp) |
|
176 | nt.assert_equal(c, comp) | |
176 |
|
177 | |||
177 |
|
178 | |||
178 | def test_local_file_completions(): |
|
179 | def test_local_file_completions(): | |
179 | ip = get_ipython() |
|
180 | ip = get_ipython() | |
180 |
cwd = |
|
181 | cwd = py3compat.getcwd() | |
181 | try: |
|
182 | try: | |
182 | with TemporaryDirectory() as tmpdir: |
|
183 | with TemporaryDirectory() as tmpdir: | |
183 | os.chdir(tmpdir) |
|
184 | os.chdir(tmpdir) | |
184 | prefix = './foo' |
|
185 | prefix = './foo' | |
185 | suffixes = ['1', '2'] |
|
186 | suffixes = ['1', '2'] | |
186 | names = [prefix+s for s in suffixes] |
|
187 | names = [prefix+s for s in suffixes] | |
187 | for n in names: |
|
188 | for n in names: | |
188 | open(n, 'w').close() |
|
189 | open(n, 'w').close() | |
189 |
|
190 | |||
190 | # Check simple completion |
|
191 | # Check simple completion | |
191 | c = ip.complete(prefix)[1] |
|
192 | c = ip.complete(prefix)[1] | |
192 | nt.assert_equal(c, names) |
|
193 | nt.assert_equal(c, names) | |
193 |
|
194 | |||
194 | # Now check with a function call |
|
195 | # Now check with a function call | |
195 | cmd = 'a = f("%s' % prefix |
|
196 | cmd = 'a = f("%s' % prefix | |
196 | c = ip.complete(prefix, cmd)[1] |
|
197 | c = ip.complete(prefix, cmd)[1] | |
197 | comp = [prefix+s for s in suffixes] |
|
198 | comp = [prefix+s for s in suffixes] | |
198 | nt.assert_equal(c, comp) |
|
199 | nt.assert_equal(c, comp) | |
199 | finally: |
|
200 | finally: | |
200 | # prevent failures from making chdir stick |
|
201 | # prevent failures from making chdir stick | |
201 | os.chdir(cwd) |
|
202 | os.chdir(cwd) | |
202 |
|
203 | |||
203 |
|
204 | |||
204 | def test_greedy_completions(): |
|
205 | def test_greedy_completions(): | |
205 | ip = get_ipython() |
|
206 | ip = get_ipython() | |
206 | greedy_original = ip.Completer.greedy |
|
207 | greedy_original = ip.Completer.greedy | |
207 | try: |
|
208 | try: | |
208 | ip.Completer.greedy = False |
|
209 | ip.Completer.greedy = False | |
209 | ip.ex('a=list(range(5))') |
|
210 | ip.ex('a=list(range(5))') | |
210 | _,c = ip.complete('.',line='a[0].') |
|
211 | _,c = ip.complete('.',line='a[0].') | |
211 | nt.assert_false('a[0].real' in c, |
|
212 | nt.assert_false('a[0].real' in c, | |
212 | "Shouldn't have completed on a[0]: %s"%c) |
|
213 | "Shouldn't have completed on a[0]: %s"%c) | |
213 | ip.Completer.greedy = True |
|
214 | ip.Completer.greedy = True | |
214 | _,c = ip.complete('.',line='a[0].') |
|
215 | _,c = ip.complete('.',line='a[0].') | |
215 | nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c) |
|
216 | nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c) | |
216 | finally: |
|
217 | finally: | |
217 | ip.Completer.greedy = greedy_original |
|
218 | ip.Completer.greedy = greedy_original | |
218 |
|
219 | |||
219 |
|
220 | |||
220 | def test_omit__names(): |
|
221 | def test_omit__names(): | |
221 | # also happens to test IPCompleter as a configurable |
|
222 | # also happens to test IPCompleter as a configurable | |
222 | ip = get_ipython() |
|
223 | ip = get_ipython() | |
223 | ip._hidden_attr = 1 |
|
224 | ip._hidden_attr = 1 | |
224 | c = ip.Completer |
|
225 | c = ip.Completer | |
225 | ip.ex('ip=get_ipython()') |
|
226 | ip.ex('ip=get_ipython()') | |
226 | cfg = Config() |
|
227 | cfg = Config() | |
227 | cfg.IPCompleter.omit__names = 0 |
|
228 | cfg.IPCompleter.omit__names = 0 | |
228 | c.update_config(cfg) |
|
229 | c.update_config(cfg) | |
229 | s,matches = c.complete('ip.') |
|
230 | s,matches = c.complete('ip.') | |
230 | nt.assert_in('ip.__str__', matches) |
|
231 | nt.assert_in('ip.__str__', matches) | |
231 | nt.assert_in('ip._hidden_attr', matches) |
|
232 | nt.assert_in('ip._hidden_attr', matches) | |
232 | cfg.IPCompleter.omit__names = 1 |
|
233 | cfg.IPCompleter.omit__names = 1 | |
233 | c.update_config(cfg) |
|
234 | c.update_config(cfg) | |
234 | s,matches = c.complete('ip.') |
|
235 | s,matches = c.complete('ip.') | |
235 | nt.assert_not_in('ip.__str__', matches) |
|
236 | nt.assert_not_in('ip.__str__', matches) | |
236 | nt.assert_in('ip._hidden_attr', matches) |
|
237 | nt.assert_in('ip._hidden_attr', matches) | |
237 | cfg.IPCompleter.omit__names = 2 |
|
238 | cfg.IPCompleter.omit__names = 2 | |
238 | c.update_config(cfg) |
|
239 | c.update_config(cfg) | |
239 | s,matches = c.complete('ip.') |
|
240 | s,matches = c.complete('ip.') | |
240 | nt.assert_not_in('ip.__str__', matches) |
|
241 | nt.assert_not_in('ip.__str__', matches) | |
241 | nt.assert_not_in('ip._hidden_attr', matches) |
|
242 | nt.assert_not_in('ip._hidden_attr', matches) | |
242 | del ip._hidden_attr |
|
243 | del ip._hidden_attr | |
243 |
|
244 | |||
244 |
|
245 | |||
245 | def test_limit_to__all__False_ok(): |
|
246 | def test_limit_to__all__False_ok(): | |
246 | ip = get_ipython() |
|
247 | ip = get_ipython() | |
247 | c = ip.Completer |
|
248 | c = ip.Completer | |
248 | ip.ex('class D: x=24') |
|
249 | ip.ex('class D: x=24') | |
249 | ip.ex('d=D()') |
|
250 | ip.ex('d=D()') | |
250 | cfg = Config() |
|
251 | cfg = Config() | |
251 | cfg.IPCompleter.limit_to__all__ = False |
|
252 | cfg.IPCompleter.limit_to__all__ = False | |
252 | c.update_config(cfg) |
|
253 | c.update_config(cfg) | |
253 | s, matches = c.complete('d.') |
|
254 | s, matches = c.complete('d.') | |
254 | nt.assert_in('d.x', matches) |
|
255 | nt.assert_in('d.x', matches) | |
255 |
|
256 | |||
256 |
|
257 | |||
257 | def test_limit_to__all__True_ok(): |
|
258 | def test_limit_to__all__True_ok(): | |
258 | ip = get_ipython() |
|
259 | ip = get_ipython() | |
259 | c = ip.Completer |
|
260 | c = ip.Completer | |
260 | ip.ex('class D: x=24') |
|
261 | ip.ex('class D: x=24') | |
261 | ip.ex('d=D()') |
|
262 | ip.ex('d=D()') | |
262 | ip.ex("d.__all__=['z']") |
|
263 | ip.ex("d.__all__=['z']") | |
263 | cfg = Config() |
|
264 | cfg = Config() | |
264 | cfg.IPCompleter.limit_to__all__ = True |
|
265 | cfg.IPCompleter.limit_to__all__ = True | |
265 | c.update_config(cfg) |
|
266 | c.update_config(cfg) | |
266 | s, matches = c.complete('d.') |
|
267 | s, matches = c.complete('d.') | |
267 | nt.assert_in('d.z', matches) |
|
268 | nt.assert_in('d.z', matches) | |
268 | nt.assert_not_in('d.x', matches) |
|
269 | nt.assert_not_in('d.x', matches) | |
269 |
|
270 | |||
270 |
|
271 | |||
271 | def test_get__all__entries_ok(): |
|
272 | def test_get__all__entries_ok(): | |
272 | class A(object): |
|
273 | class A(object): | |
273 | __all__ = ['x', 1] |
|
274 | __all__ = ['x', 1] | |
274 | words = completer.get__all__entries(A()) |
|
275 | words = completer.get__all__entries(A()) | |
275 | nt.assert_equal(words, ['x']) |
|
276 | nt.assert_equal(words, ['x']) | |
276 |
|
277 | |||
277 |
|
278 | |||
278 | def test_get__all__entries_no__all__ok(): |
|
279 | def test_get__all__entries_no__all__ok(): | |
279 | class A(object): |
|
280 | class A(object): | |
280 | pass |
|
281 | pass | |
281 | words = completer.get__all__entries(A()) |
|
282 | words = completer.get__all__entries(A()) | |
282 | nt.assert_equal(words, []) |
|
283 | nt.assert_equal(words, []) | |
283 |
|
284 | |||
284 |
|
285 | |||
285 | def test_func_kw_completions(): |
|
286 | def test_func_kw_completions(): | |
286 | ip = get_ipython() |
|
287 | ip = get_ipython() | |
287 | c = ip.Completer |
|
288 | c = ip.Completer | |
288 | ip.ex('def myfunc(a=1,b=2): return a+b') |
|
289 | ip.ex('def myfunc(a=1,b=2): return a+b') | |
289 | s, matches = c.complete(None, 'myfunc(1,b') |
|
290 | s, matches = c.complete(None, 'myfunc(1,b') | |
290 | nt.assert_in('b=', matches) |
|
291 | nt.assert_in('b=', matches) | |
291 | # Simulate completing with cursor right after b (pos==10): |
|
292 | # Simulate completing with cursor right after b (pos==10): | |
292 | s, matches = c.complete(None, 'myfunc(1,b)', 10) |
|
293 | s, matches = c.complete(None, 'myfunc(1,b)', 10) | |
293 | nt.assert_in('b=', matches) |
|
294 | nt.assert_in('b=', matches) | |
294 | s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b') |
|
295 | s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b') | |
295 | nt.assert_in('b=', matches) |
|
296 | nt.assert_in('b=', matches) | |
296 | #builtin function |
|
297 | #builtin function | |
297 | s, matches = c.complete(None, 'min(k, k') |
|
298 | s, matches = c.complete(None, 'min(k, k') | |
298 | nt.assert_in('key=', matches) |
|
299 | nt.assert_in('key=', matches) | |
299 |
|
300 | |||
300 |
|
301 | |||
301 | def test_default_arguments_from_docstring(): |
|
302 | def test_default_arguments_from_docstring(): | |
302 | doc = min.__doc__ |
|
303 | doc = min.__doc__ | |
303 | ip = get_ipython() |
|
304 | ip = get_ipython() | |
304 | c = ip.Completer |
|
305 | c = ip.Completer | |
305 | kwd = c._default_arguments_from_docstring( |
|
306 | kwd = c._default_arguments_from_docstring( | |
306 | 'min(iterable[, key=func]) -> value') |
|
307 | 'min(iterable[, key=func]) -> value') | |
307 | nt.assert_equal(kwd, ['key']) |
|
308 | nt.assert_equal(kwd, ['key']) | |
308 | #with cython type etc |
|
309 | #with cython type etc | |
309 | kwd = c._default_arguments_from_docstring( |
|
310 | kwd = c._default_arguments_from_docstring( | |
310 | 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n') |
|
311 | 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n') | |
311 | nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit']) |
|
312 | nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit']) | |
312 | #white spaces |
|
313 | #white spaces | |
313 | kwd = c._default_arguments_from_docstring( |
|
314 | kwd = c._default_arguments_from_docstring( | |
314 | '\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n') |
|
315 | '\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n') | |
315 | nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit']) |
|
316 | nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit']) | |
316 |
|
317 | |||
317 | def test_line_magics(): |
|
318 | def test_line_magics(): | |
318 | ip = get_ipython() |
|
319 | ip = get_ipython() | |
319 | c = ip.Completer |
|
320 | c = ip.Completer | |
320 | s, matches = c.complete(None, 'lsmag') |
|
321 | s, matches = c.complete(None, 'lsmag') | |
321 | nt.assert_in('%lsmagic', matches) |
|
322 | nt.assert_in('%lsmagic', matches) | |
322 | s, matches = c.complete(None, '%lsmag') |
|
323 | s, matches = c.complete(None, '%lsmag') | |
323 | nt.assert_in('%lsmagic', matches) |
|
324 | nt.assert_in('%lsmagic', matches) | |
324 |
|
325 | |||
325 |
|
326 | |||
326 | def test_cell_magics(): |
|
327 | def test_cell_magics(): | |
327 | from IPython.core.magic import register_cell_magic |
|
328 | from IPython.core.magic import register_cell_magic | |
328 |
|
329 | |||
329 | @register_cell_magic |
|
330 | @register_cell_magic | |
330 | def _foo_cellm(line, cell): |
|
331 | def _foo_cellm(line, cell): | |
331 | pass |
|
332 | pass | |
332 |
|
333 | |||
333 | ip = get_ipython() |
|
334 | ip = get_ipython() | |
334 | c = ip.Completer |
|
335 | c = ip.Completer | |
335 |
|
336 | |||
336 | s, matches = c.complete(None, '_foo_ce') |
|
337 | s, matches = c.complete(None, '_foo_ce') | |
337 | nt.assert_in('%%_foo_cellm', matches) |
|
338 | nt.assert_in('%%_foo_cellm', matches) | |
338 | s, matches = c.complete(None, '%%_foo_ce') |
|
339 | s, matches = c.complete(None, '%%_foo_ce') | |
339 | nt.assert_in('%%_foo_cellm', matches) |
|
340 | nt.assert_in('%%_foo_cellm', matches) | |
340 |
|
341 | |||
341 |
|
342 | |||
342 | def test_line_cell_magics(): |
|
343 | def test_line_cell_magics(): | |
343 | from IPython.core.magic import register_line_cell_magic |
|
344 | from IPython.core.magic import register_line_cell_magic | |
344 |
|
345 | |||
345 | @register_line_cell_magic |
|
346 | @register_line_cell_magic | |
346 | def _bar_cellm(line, cell): |
|
347 | def _bar_cellm(line, cell): | |
347 | pass |
|
348 | pass | |
348 |
|
349 | |||
349 | ip = get_ipython() |
|
350 | ip = get_ipython() | |
350 | c = ip.Completer |
|
351 | c = ip.Completer | |
351 |
|
352 | |||
352 | # The policy here is trickier, see comments in completion code. The |
|
353 | # The policy here is trickier, see comments in completion code. The | |
353 | # returned values depend on whether the user passes %% or not explicitly, |
|
354 | # returned values depend on whether the user passes %% or not explicitly, | |
354 | # and this will show a difference if the same name is both a line and cell |
|
355 | # and this will show a difference if the same name is both a line and cell | |
355 | # magic. |
|
356 | # magic. | |
356 | s, matches = c.complete(None, '_bar_ce') |
|
357 | s, matches = c.complete(None, '_bar_ce') | |
357 | nt.assert_in('%_bar_cellm', matches) |
|
358 | nt.assert_in('%_bar_cellm', matches) | |
358 | nt.assert_in('%%_bar_cellm', matches) |
|
359 | nt.assert_in('%%_bar_cellm', matches) | |
359 | s, matches = c.complete(None, '%_bar_ce') |
|
360 | s, matches = c.complete(None, '%_bar_ce') | |
360 | nt.assert_in('%_bar_cellm', matches) |
|
361 | nt.assert_in('%_bar_cellm', matches) | |
361 | nt.assert_in('%%_bar_cellm', matches) |
|
362 | nt.assert_in('%%_bar_cellm', matches) | |
362 | s, matches = c.complete(None, '%%_bar_ce') |
|
363 | s, matches = c.complete(None, '%%_bar_ce') | |
363 | nt.assert_not_in('%_bar_cellm', matches) |
|
364 | nt.assert_not_in('%_bar_cellm', matches) | |
364 | nt.assert_in('%%_bar_cellm', matches) |
|
365 | nt.assert_in('%%_bar_cellm', matches) | |
365 |
|
366 | |||
366 |
|
367 | |||
367 | def test_magic_completion_order(): |
|
368 | def test_magic_completion_order(): | |
368 |
|
369 | |||
369 | ip = get_ipython() |
|
370 | ip = get_ipython() | |
370 | c = ip.Completer |
|
371 | c = ip.Completer | |
371 |
|
372 | |||
372 | # Test ordering of magics and non-magics with the same name |
|
373 | # Test ordering of magics and non-magics with the same name | |
373 | # We want the non-magic first |
|
374 | # We want the non-magic first | |
374 |
|
375 | |||
375 | # Before importing matplotlib, there should only be one option: |
|
376 | # Before importing matplotlib, there should only be one option: | |
376 |
|
377 | |||
377 | text, matches = c.complete('mat') |
|
378 | text, matches = c.complete('mat') | |
378 | nt.assert_equal(matches, ["%matplotlib"]) |
|
379 | nt.assert_equal(matches, ["%matplotlib"]) | |
379 |
|
380 | |||
380 |
|
381 | |||
381 | ip.run_cell("matplotlib = 1") # introduce name into namespace |
|
382 | ip.run_cell("matplotlib = 1") # introduce name into namespace | |
382 |
|
383 | |||
383 | # After the import, there should be two options, ordered like this: |
|
384 | # After the import, there should be two options, ordered like this: | |
384 | text, matches = c.complete('mat') |
|
385 | text, matches = c.complete('mat') | |
385 | nt.assert_equal(matches, ["matplotlib", "%matplotlib"]) |
|
386 | nt.assert_equal(matches, ["matplotlib", "%matplotlib"]) | |
386 |
|
387 | |||
387 |
|
388 | |||
388 | ip.run_cell("timeit = 1") # define a user variable called 'timeit' |
|
389 | ip.run_cell("timeit = 1") # define a user variable called 'timeit' | |
389 |
|
390 | |||
390 | # Order of user variable and line and cell magics with same name: |
|
391 | # Order of user variable and line and cell magics with same name: | |
391 | text, matches = c.complete('timeit') |
|
392 | text, matches = c.complete('timeit') | |
392 | nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"]) |
|
393 | nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"]) | |
393 |
|
394 |
@@ -1,120 +1,121 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Tests for completerlib. |
|
2 | """Tests for completerlib. | |
3 |
|
3 | |||
4 | """ |
|
4 | """ | |
5 | from __future__ import absolute_import |
|
5 | from __future__ import absolute_import | |
6 |
|
6 | |||
7 | #----------------------------------------------------------------------------- |
|
7 | #----------------------------------------------------------------------------- | |
8 | # Imports |
|
8 | # Imports | |
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 |
|
10 | |||
11 | import os |
|
11 | import os | |
12 | import shutil |
|
12 | import shutil | |
13 | import sys |
|
13 | import sys | |
14 | import tempfile |
|
14 | import tempfile | |
15 | import unittest |
|
15 | import unittest | |
16 | from os.path import join |
|
16 | from os.path import join | |
17 |
|
17 | |||
18 | from IPython.core.completerlib import magic_run_completer, module_completion |
|
18 | from IPython.core.completerlib import magic_run_completer, module_completion | |
|
19 | from IPython.utils import py3compat | |||
19 | from IPython.utils.tempdir import TemporaryDirectory |
|
20 | from IPython.utils.tempdir import TemporaryDirectory | |
20 | from IPython.testing.decorators import onlyif_unicode_paths |
|
21 | from IPython.testing.decorators import onlyif_unicode_paths | |
21 |
|
22 | |||
22 |
|
23 | |||
23 | class MockEvent(object): |
|
24 | class MockEvent(object): | |
24 | def __init__(self, line): |
|
25 | def __init__(self, line): | |
25 | self.line = line |
|
26 | self.line = line | |
26 |
|
27 | |||
27 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
28 | # Test functions begin |
|
29 | # Test functions begin | |
29 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
30 | class Test_magic_run_completer(unittest.TestCase): |
|
31 | class Test_magic_run_completer(unittest.TestCase): | |
31 | def setUp(self): |
|
32 | def setUp(self): | |
32 | self.BASETESTDIR = tempfile.mkdtemp() |
|
33 | self.BASETESTDIR = tempfile.mkdtemp() | |
33 | for fil in [u"aao.py", u"a.py", u"b.py"]: |
|
34 | for fil in [u"aao.py", u"a.py", u"b.py"]: | |
34 | with open(join(self.BASETESTDIR, fil), "w") as sfile: |
|
35 | with open(join(self.BASETESTDIR, fil), "w") as sfile: | |
35 | sfile.write("pass\n") |
|
36 | sfile.write("pass\n") | |
36 |
self.oldpath = |
|
37 | self.oldpath = py3compat.getcwd() | |
37 | os.chdir(self.BASETESTDIR) |
|
38 | os.chdir(self.BASETESTDIR) | |
38 |
|
39 | |||
39 | def tearDown(self): |
|
40 | def tearDown(self): | |
40 | os.chdir(self.oldpath) |
|
41 | os.chdir(self.oldpath) | |
41 | shutil.rmtree(self.BASETESTDIR) |
|
42 | shutil.rmtree(self.BASETESTDIR) | |
42 |
|
43 | |||
43 | def test_1(self): |
|
44 | def test_1(self): | |
44 | """Test magic_run_completer, should match two alterntives |
|
45 | """Test magic_run_completer, should match two alterntives | |
45 | """ |
|
46 | """ | |
46 | event = MockEvent(u"%run a") |
|
47 | event = MockEvent(u"%run a") | |
47 | mockself = None |
|
48 | mockself = None | |
48 | match = set(magic_run_completer(mockself, event)) |
|
49 | match = set(magic_run_completer(mockself, event)) | |
49 | self.assertEqual(match, set([u"a.py", u"aao.py"])) |
|
50 | self.assertEqual(match, set([u"a.py", u"aao.py"])) | |
50 |
|
51 | |||
51 | def test_2(self): |
|
52 | def test_2(self): | |
52 | """Test magic_run_completer, should match one alterntive |
|
53 | """Test magic_run_completer, should match one alterntive | |
53 | """ |
|
54 | """ | |
54 | event = MockEvent(u"%run aa") |
|
55 | event = MockEvent(u"%run aa") | |
55 | mockself = None |
|
56 | mockself = None | |
56 | match = set(magic_run_completer(mockself, event)) |
|
57 | match = set(magic_run_completer(mockself, event)) | |
57 | self.assertEqual(match, set([u"aao.py"])) |
|
58 | self.assertEqual(match, set([u"aao.py"])) | |
58 |
|
59 | |||
59 | def test_3(self): |
|
60 | def test_3(self): | |
60 | """Test magic_run_completer with unterminated " """ |
|
61 | """Test magic_run_completer with unterminated " """ | |
61 | event = MockEvent(u'%run "a') |
|
62 | event = MockEvent(u'%run "a') | |
62 | mockself = None |
|
63 | mockself = None | |
63 | match = set(magic_run_completer(mockself, event)) |
|
64 | match = set(magic_run_completer(mockself, event)) | |
64 | self.assertEqual(match, set([u"a.py", u"aao.py"])) |
|
65 | self.assertEqual(match, set([u"a.py", u"aao.py"])) | |
65 |
|
66 | |||
66 | def test_import_invalid_module(self): |
|
67 | def test_import_invalid_module(self): | |
67 | """Testing of issue https://github.com/ipython/ipython/issues/1107""" |
|
68 | """Testing of issue https://github.com/ipython/ipython/issues/1107""" | |
68 | invalid_module_names = set(['foo-bar', 'foo:bar', '10foo']) |
|
69 | invalid_module_names = set(['foo-bar', 'foo:bar', '10foo']) | |
69 | valid_module_names = set(['foobar']) |
|
70 | valid_module_names = set(['foobar']) | |
70 | with TemporaryDirectory() as tmpdir: |
|
71 | with TemporaryDirectory() as tmpdir: | |
71 | sys.path.insert( 0, tmpdir ) |
|
72 | sys.path.insert( 0, tmpdir ) | |
72 | for name in invalid_module_names | valid_module_names: |
|
73 | for name in invalid_module_names | valid_module_names: | |
73 | filename = os.path.join(tmpdir, name + '.py') |
|
74 | filename = os.path.join(tmpdir, name + '.py') | |
74 | open(filename, 'w').close() |
|
75 | open(filename, 'w').close() | |
75 |
|
76 | |||
76 | s = set( module_completion('import foo') ) |
|
77 | s = set( module_completion('import foo') ) | |
77 | intersection = s.intersection(invalid_module_names) |
|
78 | intersection = s.intersection(invalid_module_names) | |
78 | self.assertFalse(intersection, intersection) |
|
79 | self.assertFalse(intersection, intersection) | |
79 |
|
80 | |||
80 | assert valid_module_names.issubset(s), valid_module_names.intersection(s) |
|
81 | assert valid_module_names.issubset(s), valid_module_names.intersection(s) | |
81 |
|
82 | |||
82 | class Test_magic_run_completer_nonascii(unittest.TestCase): |
|
83 | class Test_magic_run_completer_nonascii(unittest.TestCase): | |
83 | @onlyif_unicode_paths |
|
84 | @onlyif_unicode_paths | |
84 | def setUp(self): |
|
85 | def setUp(self): | |
85 | self.BASETESTDIR = tempfile.mkdtemp() |
|
86 | self.BASETESTDIR = tempfile.mkdtemp() | |
86 | for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]: |
|
87 | for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]: | |
87 | with open(join(self.BASETESTDIR, fil), "w") as sfile: |
|
88 | with open(join(self.BASETESTDIR, fil), "w") as sfile: | |
88 | sfile.write("pass\n") |
|
89 | sfile.write("pass\n") | |
89 |
self.oldpath = |
|
90 | self.oldpath = py3compat.getcwd() | |
90 | os.chdir(self.BASETESTDIR) |
|
91 | os.chdir(self.BASETESTDIR) | |
91 |
|
92 | |||
92 | def tearDown(self): |
|
93 | def tearDown(self): | |
93 | os.chdir(self.oldpath) |
|
94 | os.chdir(self.oldpath) | |
94 | shutil.rmtree(self.BASETESTDIR) |
|
95 | shutil.rmtree(self.BASETESTDIR) | |
95 |
|
96 | |||
96 | @onlyif_unicode_paths |
|
97 | @onlyif_unicode_paths | |
97 | def test_1(self): |
|
98 | def test_1(self): | |
98 | """Test magic_run_completer, should match two alterntives |
|
99 | """Test magic_run_completer, should match two alterntives | |
99 | """ |
|
100 | """ | |
100 | event = MockEvent(u"%run a") |
|
101 | event = MockEvent(u"%run a") | |
101 | mockself = None |
|
102 | mockself = None | |
102 | match = set(magic_run_completer(mockself, event)) |
|
103 | match = set(magic_run_completer(mockself, event)) | |
103 | self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"])) |
|
104 | self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"])) | |
104 |
|
105 | |||
105 | @onlyif_unicode_paths |
|
106 | @onlyif_unicode_paths | |
106 | def test_2(self): |
|
107 | def test_2(self): | |
107 | """Test magic_run_completer, should match one alterntive |
|
108 | """Test magic_run_completer, should match one alterntive | |
108 | """ |
|
109 | """ | |
109 | event = MockEvent(u"%run aa") |
|
110 | event = MockEvent(u"%run aa") | |
110 | mockself = None |
|
111 | mockself = None | |
111 | match = set(magic_run_completer(mockself, event)) |
|
112 | match = set(magic_run_completer(mockself, event)) | |
112 | self.assertEqual(match, set([u"aaΓΈ.py"])) |
|
113 | self.assertEqual(match, set([u"aaΓΈ.py"])) | |
113 |
|
114 | |||
114 | @onlyif_unicode_paths |
|
115 | @onlyif_unicode_paths | |
115 | def test_3(self): |
|
116 | def test_3(self): | |
116 | """Test magic_run_completer with unterminated " """ |
|
117 | """Test magic_run_completer with unterminated " """ | |
117 | event = MockEvent(u'%run "a') |
|
118 | event = MockEvent(u'%run "a') | |
118 | mockself = None |
|
119 | mockself = None | |
119 | match = set(magic_run_completer(mockself, event)) |
|
120 | match = set(magic_run_completer(mockself, event)) | |
120 | self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"])) |
|
121 | self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"])) |
@@ -1,676 +1,677 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Tests for the key interactiveshell module. |
|
2 | """Tests for the key interactiveshell module. | |
3 |
|
3 | |||
4 | Historically the main classes in interactiveshell have been under-tested. This |
|
4 | Historically the main classes in interactiveshell have been under-tested. This | |
5 | module should grow as many single-method tests as possible to trap many of the |
|
5 | module should grow as many single-method tests as possible to trap many of the | |
6 | recurring bugs we seem to encounter with high-level interaction. |
|
6 | recurring bugs we seem to encounter with high-level interaction. | |
7 |
|
7 | |||
8 | Authors |
|
8 | Authors | |
9 | ------- |
|
9 | ------- | |
10 | * Fernando Perez |
|
10 | * Fernando Perez | |
11 | """ |
|
11 | """ | |
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 | # Copyright (C) 2011 The IPython Development Team |
|
13 | # Copyright (C) 2011 The IPython Development Team | |
14 | # |
|
14 | # | |
15 | # Distributed under the terms of the BSD License. The full license is in |
|
15 | # Distributed under the terms of the BSD License. The full license is in | |
16 | # the file COPYING, distributed as part of this software. |
|
16 | # the file COPYING, distributed as part of this software. | |
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 |
|
18 | |||
19 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
20 | # Imports |
|
20 | # Imports | |
21 | #----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
22 | # stdlib |
|
22 | # stdlib | |
23 | import ast |
|
23 | import ast | |
24 | import os |
|
24 | import os | |
25 | import signal |
|
25 | import signal | |
26 | import shutil |
|
26 | import shutil | |
27 | import sys |
|
27 | import sys | |
28 | import tempfile |
|
28 | import tempfile | |
29 | import unittest |
|
29 | import unittest | |
30 | from os.path import join |
|
30 | from os.path import join | |
31 |
|
31 | |||
32 | # third-party |
|
32 | # third-party | |
33 | import nose.tools as nt |
|
33 | import nose.tools as nt | |
34 |
|
34 | |||
35 | # Our own |
|
35 | # Our own | |
36 | from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths |
|
36 | from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths | |
37 | from IPython.testing import tools as tt |
|
37 | from IPython.testing import tools as tt | |
38 | from IPython.utils import io |
|
38 | from IPython.utils import io | |
|
39 | from IPython.utils import py3compat | |||
39 | from IPython.utils.py3compat import unicode_type, PY3 |
|
40 | from IPython.utils.py3compat import unicode_type, PY3 | |
40 |
|
41 | |||
41 | if PY3: |
|
42 | if PY3: | |
42 | from io import StringIO |
|
43 | from io import StringIO | |
43 | else: |
|
44 | else: | |
44 | from StringIO import StringIO |
|
45 | from StringIO import StringIO | |
45 |
|
46 | |||
46 | #----------------------------------------------------------------------------- |
|
47 | #----------------------------------------------------------------------------- | |
47 | # Globals |
|
48 | # Globals | |
48 | #----------------------------------------------------------------------------- |
|
49 | #----------------------------------------------------------------------------- | |
49 | # This is used by every single test, no point repeating it ad nauseam |
|
50 | # This is used by every single test, no point repeating it ad nauseam | |
50 | ip = get_ipython() |
|
51 | ip = get_ipython() | |
51 |
|
52 | |||
52 | #----------------------------------------------------------------------------- |
|
53 | #----------------------------------------------------------------------------- | |
53 | # Tests |
|
54 | # Tests | |
54 | #----------------------------------------------------------------------------- |
|
55 | #----------------------------------------------------------------------------- | |
55 |
|
56 | |||
56 | class InteractiveShellTestCase(unittest.TestCase): |
|
57 | class InteractiveShellTestCase(unittest.TestCase): | |
57 | def test_naked_string_cells(self): |
|
58 | def test_naked_string_cells(self): | |
58 | """Test that cells with only naked strings are fully executed""" |
|
59 | """Test that cells with only naked strings are fully executed""" | |
59 | # First, single-line inputs |
|
60 | # First, single-line inputs | |
60 | ip.run_cell('"a"\n') |
|
61 | ip.run_cell('"a"\n') | |
61 | self.assertEqual(ip.user_ns['_'], 'a') |
|
62 | self.assertEqual(ip.user_ns['_'], 'a') | |
62 | # And also multi-line cells |
|
63 | # And also multi-line cells | |
63 | ip.run_cell('"""a\nb"""\n') |
|
64 | ip.run_cell('"""a\nb"""\n') | |
64 | self.assertEqual(ip.user_ns['_'], 'a\nb') |
|
65 | self.assertEqual(ip.user_ns['_'], 'a\nb') | |
65 |
|
66 | |||
66 | def test_run_empty_cell(self): |
|
67 | def test_run_empty_cell(self): | |
67 | """Just make sure we don't get a horrible error with a blank |
|
68 | """Just make sure we don't get a horrible error with a blank | |
68 | cell of input. Yes, I did overlook that.""" |
|
69 | cell of input. Yes, I did overlook that.""" | |
69 | old_xc = ip.execution_count |
|
70 | old_xc = ip.execution_count | |
70 | ip.run_cell('') |
|
71 | ip.run_cell('') | |
71 | self.assertEqual(ip.execution_count, old_xc) |
|
72 | self.assertEqual(ip.execution_count, old_xc) | |
72 |
|
73 | |||
73 | def test_run_cell_multiline(self): |
|
74 | def test_run_cell_multiline(self): | |
74 | """Multi-block, multi-line cells must execute correctly. |
|
75 | """Multi-block, multi-line cells must execute correctly. | |
75 | """ |
|
76 | """ | |
76 | src = '\n'.join(["x=1", |
|
77 | src = '\n'.join(["x=1", | |
77 | "y=2", |
|
78 | "y=2", | |
78 | "if 1:", |
|
79 | "if 1:", | |
79 | " x += 1", |
|
80 | " x += 1", | |
80 | " y += 1",]) |
|
81 | " y += 1",]) | |
81 | ip.run_cell(src) |
|
82 | ip.run_cell(src) | |
82 | self.assertEqual(ip.user_ns['x'], 2) |
|
83 | self.assertEqual(ip.user_ns['x'], 2) | |
83 | self.assertEqual(ip.user_ns['y'], 3) |
|
84 | self.assertEqual(ip.user_ns['y'], 3) | |
84 |
|
85 | |||
85 | def test_multiline_string_cells(self): |
|
86 | def test_multiline_string_cells(self): | |
86 | "Code sprinkled with multiline strings should execute (GH-306)" |
|
87 | "Code sprinkled with multiline strings should execute (GH-306)" | |
87 | ip.run_cell('tmp=0') |
|
88 | ip.run_cell('tmp=0') | |
88 | self.assertEqual(ip.user_ns['tmp'], 0) |
|
89 | self.assertEqual(ip.user_ns['tmp'], 0) | |
89 | ip.run_cell('tmp=1;"""a\nb"""\n') |
|
90 | ip.run_cell('tmp=1;"""a\nb"""\n') | |
90 | self.assertEqual(ip.user_ns['tmp'], 1) |
|
91 | self.assertEqual(ip.user_ns['tmp'], 1) | |
91 |
|
92 | |||
92 | def test_dont_cache_with_semicolon(self): |
|
93 | def test_dont_cache_with_semicolon(self): | |
93 | "Ending a line with semicolon should not cache the returned object (GH-307)" |
|
94 | "Ending a line with semicolon should not cache the returned object (GH-307)" | |
94 | oldlen = len(ip.user_ns['Out']) |
|
95 | oldlen = len(ip.user_ns['Out']) | |
95 | a = ip.run_cell('1;', store_history=True) |
|
96 | a = ip.run_cell('1;', store_history=True) | |
96 | newlen = len(ip.user_ns['Out']) |
|
97 | newlen = len(ip.user_ns['Out']) | |
97 | self.assertEqual(oldlen, newlen) |
|
98 | self.assertEqual(oldlen, newlen) | |
98 | #also test the default caching behavior |
|
99 | #also test the default caching behavior | |
99 | ip.run_cell('1', store_history=True) |
|
100 | ip.run_cell('1', store_history=True) | |
100 | newlen = len(ip.user_ns['Out']) |
|
101 | newlen = len(ip.user_ns['Out']) | |
101 | self.assertEqual(oldlen+1, newlen) |
|
102 | self.assertEqual(oldlen+1, newlen) | |
102 |
|
103 | |||
103 | def test_In_variable(self): |
|
104 | def test_In_variable(self): | |
104 | "Verify that In variable grows with user input (GH-284)" |
|
105 | "Verify that In variable grows with user input (GH-284)" | |
105 | oldlen = len(ip.user_ns['In']) |
|
106 | oldlen = len(ip.user_ns['In']) | |
106 | ip.run_cell('1;', store_history=True) |
|
107 | ip.run_cell('1;', store_history=True) | |
107 | newlen = len(ip.user_ns['In']) |
|
108 | newlen = len(ip.user_ns['In']) | |
108 | self.assertEqual(oldlen+1, newlen) |
|
109 | self.assertEqual(oldlen+1, newlen) | |
109 | self.assertEqual(ip.user_ns['In'][-1],'1;') |
|
110 | self.assertEqual(ip.user_ns['In'][-1],'1;') | |
110 |
|
111 | |||
111 | def test_magic_names_in_string(self): |
|
112 | def test_magic_names_in_string(self): | |
112 | ip.run_cell('a = """\n%exit\n"""') |
|
113 | ip.run_cell('a = """\n%exit\n"""') | |
113 | self.assertEqual(ip.user_ns['a'], '\n%exit\n') |
|
114 | self.assertEqual(ip.user_ns['a'], '\n%exit\n') | |
114 |
|
115 | |||
115 | def test_trailing_newline(self): |
|
116 | def test_trailing_newline(self): | |
116 | """test that running !(command) does not raise a SyntaxError""" |
|
117 | """test that running !(command) does not raise a SyntaxError""" | |
117 | ip.run_cell('!(true)\n', False) |
|
118 | ip.run_cell('!(true)\n', False) | |
118 | ip.run_cell('!(true)\n\n\n', False) |
|
119 | ip.run_cell('!(true)\n\n\n', False) | |
119 |
|
120 | |||
120 | def test_gh_597(self): |
|
121 | def test_gh_597(self): | |
121 | """Pretty-printing lists of objects with non-ascii reprs may cause |
|
122 | """Pretty-printing lists of objects with non-ascii reprs may cause | |
122 | problems.""" |
|
123 | problems.""" | |
123 | class Spam(object): |
|
124 | class Spam(object): | |
124 | def __repr__(self): |
|
125 | def __repr__(self): | |
125 | return "\xe9"*50 |
|
126 | return "\xe9"*50 | |
126 | import IPython.core.formatters |
|
127 | import IPython.core.formatters | |
127 | f = IPython.core.formatters.PlainTextFormatter() |
|
128 | f = IPython.core.formatters.PlainTextFormatter() | |
128 | f([Spam(),Spam()]) |
|
129 | f([Spam(),Spam()]) | |
129 |
|
130 | |||
130 |
|
131 | |||
131 | def test_future_flags(self): |
|
132 | def test_future_flags(self): | |
132 | """Check that future flags are used for parsing code (gh-777)""" |
|
133 | """Check that future flags are used for parsing code (gh-777)""" | |
133 | ip.run_cell('from __future__ import print_function') |
|
134 | ip.run_cell('from __future__ import print_function') | |
134 | try: |
|
135 | try: | |
135 | ip.run_cell('prfunc_return_val = print(1,2, sep=" ")') |
|
136 | ip.run_cell('prfunc_return_val = print(1,2, sep=" ")') | |
136 | assert 'prfunc_return_val' in ip.user_ns |
|
137 | assert 'prfunc_return_val' in ip.user_ns | |
137 | finally: |
|
138 | finally: | |
138 | # Reset compiler flags so we don't mess up other tests. |
|
139 | # Reset compiler flags so we don't mess up other tests. | |
139 | ip.compile.reset_compiler_flags() |
|
140 | ip.compile.reset_compiler_flags() | |
140 |
|
141 | |||
141 | def test_future_unicode(self): |
|
142 | def test_future_unicode(self): | |
142 | """Check that unicode_literals is imported from __future__ (gh #786)""" |
|
143 | """Check that unicode_literals is imported from __future__ (gh #786)""" | |
143 | try: |
|
144 | try: | |
144 | ip.run_cell(u'byte_str = "a"') |
|
145 | ip.run_cell(u'byte_str = "a"') | |
145 | assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default |
|
146 | assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default | |
146 | ip.run_cell('from __future__ import unicode_literals') |
|
147 | ip.run_cell('from __future__ import unicode_literals') | |
147 | ip.run_cell(u'unicode_str = "a"') |
|
148 | ip.run_cell(u'unicode_str = "a"') | |
148 | assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode |
|
149 | assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode | |
149 | finally: |
|
150 | finally: | |
150 | # Reset compiler flags so we don't mess up other tests. |
|
151 | # Reset compiler flags so we don't mess up other tests. | |
151 | ip.compile.reset_compiler_flags() |
|
152 | ip.compile.reset_compiler_flags() | |
152 |
|
153 | |||
153 | def test_can_pickle(self): |
|
154 | def test_can_pickle(self): | |
154 | "Can we pickle objects defined interactively (GH-29)" |
|
155 | "Can we pickle objects defined interactively (GH-29)" | |
155 | ip = get_ipython() |
|
156 | ip = get_ipython() | |
156 | ip.reset() |
|
157 | ip.reset() | |
157 | ip.run_cell(("class Mylist(list):\n" |
|
158 | ip.run_cell(("class Mylist(list):\n" | |
158 | " def __init__(self,x=[]):\n" |
|
159 | " def __init__(self,x=[]):\n" | |
159 | " list.__init__(self,x)")) |
|
160 | " list.__init__(self,x)")) | |
160 | ip.run_cell("w=Mylist([1,2,3])") |
|
161 | ip.run_cell("w=Mylist([1,2,3])") | |
161 |
|
162 | |||
162 | from pickle import dumps |
|
163 | from pickle import dumps | |
163 |
|
164 | |||
164 | # We need to swap in our main module - this is only necessary |
|
165 | # We need to swap in our main module - this is only necessary | |
165 | # inside the test framework, because IPython puts the interactive module |
|
166 | # inside the test framework, because IPython puts the interactive module | |
166 | # in place (but the test framework undoes this). |
|
167 | # in place (but the test framework undoes this). | |
167 | _main = sys.modules['__main__'] |
|
168 | _main = sys.modules['__main__'] | |
168 | sys.modules['__main__'] = ip.user_module |
|
169 | sys.modules['__main__'] = ip.user_module | |
169 | try: |
|
170 | try: | |
170 | res = dumps(ip.user_ns["w"]) |
|
171 | res = dumps(ip.user_ns["w"]) | |
171 | finally: |
|
172 | finally: | |
172 | sys.modules['__main__'] = _main |
|
173 | sys.modules['__main__'] = _main | |
173 | self.assertTrue(isinstance(res, bytes)) |
|
174 | self.assertTrue(isinstance(res, bytes)) | |
174 |
|
175 | |||
175 | def test_global_ns(self): |
|
176 | def test_global_ns(self): | |
176 | "Code in functions must be able to access variables outside them." |
|
177 | "Code in functions must be able to access variables outside them." | |
177 | ip = get_ipython() |
|
178 | ip = get_ipython() | |
178 | ip.run_cell("a = 10") |
|
179 | ip.run_cell("a = 10") | |
179 | ip.run_cell(("def f(x):\n" |
|
180 | ip.run_cell(("def f(x):\n" | |
180 | " return x + a")) |
|
181 | " return x + a")) | |
181 | ip.run_cell("b = f(12)") |
|
182 | ip.run_cell("b = f(12)") | |
182 | self.assertEqual(ip.user_ns["b"], 22) |
|
183 | self.assertEqual(ip.user_ns["b"], 22) | |
183 |
|
184 | |||
184 | def test_bad_custom_tb(self): |
|
185 | def test_bad_custom_tb(self): | |
185 | """Check that InteractiveShell is protected from bad custom exception handlers""" |
|
186 | """Check that InteractiveShell is protected from bad custom exception handlers""" | |
186 | from IPython.utils import io |
|
187 | from IPython.utils import io | |
187 | save_stderr = io.stderr |
|
188 | save_stderr = io.stderr | |
188 | try: |
|
189 | try: | |
189 | # capture stderr |
|
190 | # capture stderr | |
190 | io.stderr = StringIO() |
|
191 | io.stderr = StringIO() | |
191 | ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0) |
|
192 | ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0) | |
192 | self.assertEqual(ip.custom_exceptions, (IOError,)) |
|
193 | self.assertEqual(ip.custom_exceptions, (IOError,)) | |
193 | ip.run_cell(u'raise IOError("foo")') |
|
194 | ip.run_cell(u'raise IOError("foo")') | |
194 | self.assertEqual(ip.custom_exceptions, ()) |
|
195 | self.assertEqual(ip.custom_exceptions, ()) | |
195 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) |
|
196 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) | |
196 | finally: |
|
197 | finally: | |
197 | io.stderr = save_stderr |
|
198 | io.stderr = save_stderr | |
198 |
|
199 | |||
199 | def test_bad_custom_tb_return(self): |
|
200 | def test_bad_custom_tb_return(self): | |
200 | """Check that InteractiveShell is protected from bad return types in custom exception handlers""" |
|
201 | """Check that InteractiveShell is protected from bad return types in custom exception handlers""" | |
201 | from IPython.utils import io |
|
202 | from IPython.utils import io | |
202 | save_stderr = io.stderr |
|
203 | save_stderr = io.stderr | |
203 | try: |
|
204 | try: | |
204 | # capture stderr |
|
205 | # capture stderr | |
205 | io.stderr = StringIO() |
|
206 | io.stderr = StringIO() | |
206 | ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1) |
|
207 | ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1) | |
207 | self.assertEqual(ip.custom_exceptions, (NameError,)) |
|
208 | self.assertEqual(ip.custom_exceptions, (NameError,)) | |
208 | ip.run_cell(u'a=abracadabra') |
|
209 | ip.run_cell(u'a=abracadabra') | |
209 | self.assertEqual(ip.custom_exceptions, ()) |
|
210 | self.assertEqual(ip.custom_exceptions, ()) | |
210 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) |
|
211 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) | |
211 | finally: |
|
212 | finally: | |
212 | io.stderr = save_stderr |
|
213 | io.stderr = save_stderr | |
213 |
|
214 | |||
214 | def test_drop_by_id(self): |
|
215 | def test_drop_by_id(self): | |
215 | myvars = {"a":object(), "b":object(), "c": object()} |
|
216 | myvars = {"a":object(), "b":object(), "c": object()} | |
216 | ip.push(myvars, interactive=False) |
|
217 | ip.push(myvars, interactive=False) | |
217 | for name in myvars: |
|
218 | for name in myvars: | |
218 | assert name in ip.user_ns, name |
|
219 | assert name in ip.user_ns, name | |
219 | assert name in ip.user_ns_hidden, name |
|
220 | assert name in ip.user_ns_hidden, name | |
220 | ip.user_ns['b'] = 12 |
|
221 | ip.user_ns['b'] = 12 | |
221 | ip.drop_by_id(myvars) |
|
222 | ip.drop_by_id(myvars) | |
222 | for name in ["a", "c"]: |
|
223 | for name in ["a", "c"]: | |
223 | assert name not in ip.user_ns, name |
|
224 | assert name not in ip.user_ns, name | |
224 | assert name not in ip.user_ns_hidden, name |
|
225 | assert name not in ip.user_ns_hidden, name | |
225 | assert ip.user_ns['b'] == 12 |
|
226 | assert ip.user_ns['b'] == 12 | |
226 | ip.reset() |
|
227 | ip.reset() | |
227 |
|
228 | |||
228 | def test_var_expand(self): |
|
229 | def test_var_expand(self): | |
229 | ip.user_ns['f'] = u'Ca\xf1o' |
|
230 | ip.user_ns['f'] = u'Ca\xf1o' | |
230 | self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o') |
|
231 | self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o') | |
231 | self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o') |
|
232 | self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o') | |
232 | self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1') |
|
233 | self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1') | |
233 | self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2') |
|
234 | self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2') | |
234 |
|
235 | |||
235 | ip.user_ns['f'] = b'Ca\xc3\xb1o' |
|
236 | ip.user_ns['f'] = b'Ca\xc3\xb1o' | |
236 | # This should not raise any exception: |
|
237 | # This should not raise any exception: | |
237 | ip.var_expand(u'echo $f') |
|
238 | ip.var_expand(u'echo $f') | |
238 |
|
239 | |||
239 | def test_var_expand_local(self): |
|
240 | def test_var_expand_local(self): | |
240 | """Test local variable expansion in !system and %magic calls""" |
|
241 | """Test local variable expansion in !system and %magic calls""" | |
241 | # !system |
|
242 | # !system | |
242 | ip.run_cell('def test():\n' |
|
243 | ip.run_cell('def test():\n' | |
243 | ' lvar = "ttt"\n' |
|
244 | ' lvar = "ttt"\n' | |
244 | ' ret = !echo {lvar}\n' |
|
245 | ' ret = !echo {lvar}\n' | |
245 | ' return ret[0]\n') |
|
246 | ' return ret[0]\n') | |
246 | res = ip.user_ns['test']() |
|
247 | res = ip.user_ns['test']() | |
247 | nt.assert_in('ttt', res) |
|
248 | nt.assert_in('ttt', res) | |
248 |
|
249 | |||
249 | # %magic |
|
250 | # %magic | |
250 | ip.run_cell('def makemacro():\n' |
|
251 | ip.run_cell('def makemacro():\n' | |
251 | ' macroname = "macro_var_expand_locals"\n' |
|
252 | ' macroname = "macro_var_expand_locals"\n' | |
252 | ' %macro {macroname} codestr\n') |
|
253 | ' %macro {macroname} codestr\n') | |
253 | ip.user_ns['codestr'] = "str(12)" |
|
254 | ip.user_ns['codestr'] = "str(12)" | |
254 | ip.run_cell('makemacro()') |
|
255 | ip.run_cell('makemacro()') | |
255 | nt.assert_in('macro_var_expand_locals', ip.user_ns) |
|
256 | nt.assert_in('macro_var_expand_locals', ip.user_ns) | |
256 |
|
257 | |||
257 | def test_var_expand_self(self): |
|
258 | def test_var_expand_self(self): | |
258 | """Test variable expansion with the name 'self', which was failing. |
|
259 | """Test variable expansion with the name 'self', which was failing. | |
259 |
|
260 | |||
260 | See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218 |
|
261 | See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218 | |
261 | """ |
|
262 | """ | |
262 | ip.run_cell('class cTest:\n' |
|
263 | ip.run_cell('class cTest:\n' | |
263 | ' classvar="see me"\n' |
|
264 | ' classvar="see me"\n' | |
264 | ' def test(self):\n' |
|
265 | ' def test(self):\n' | |
265 | ' res = !echo Variable: {self.classvar}\n' |
|
266 | ' res = !echo Variable: {self.classvar}\n' | |
266 | ' return res[0]\n') |
|
267 | ' return res[0]\n') | |
267 | nt.assert_in('see me', ip.user_ns['cTest']().test()) |
|
268 | nt.assert_in('see me', ip.user_ns['cTest']().test()) | |
268 |
|
269 | |||
269 | def test_bad_var_expand(self): |
|
270 | def test_bad_var_expand(self): | |
270 | """var_expand on invalid formats shouldn't raise""" |
|
271 | """var_expand on invalid formats shouldn't raise""" | |
271 | # SyntaxError |
|
272 | # SyntaxError | |
272 | self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}") |
|
273 | self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}") | |
273 | # NameError |
|
274 | # NameError | |
274 | self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}") |
|
275 | self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}") | |
275 | # ZeroDivisionError |
|
276 | # ZeroDivisionError | |
276 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") |
|
277 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") | |
277 |
|
278 | |||
278 | def test_silent_nopostexec(self): |
|
279 | def test_silent_nopostexec(self): | |
279 | """run_cell(silent=True) doesn't invoke post-exec funcs""" |
|
280 | """run_cell(silent=True) doesn't invoke post-exec funcs""" | |
280 | d = dict(called=False) |
|
281 | d = dict(called=False) | |
281 | def set_called(): |
|
282 | def set_called(): | |
282 | d['called'] = True |
|
283 | d['called'] = True | |
283 |
|
284 | |||
284 | ip.register_post_execute(set_called) |
|
285 | ip.register_post_execute(set_called) | |
285 | ip.run_cell("1", silent=True) |
|
286 | ip.run_cell("1", silent=True) | |
286 | self.assertFalse(d['called']) |
|
287 | self.assertFalse(d['called']) | |
287 | # double-check that non-silent exec did what we expected |
|
288 | # double-check that non-silent exec did what we expected | |
288 | # silent to avoid |
|
289 | # silent to avoid | |
289 | ip.run_cell("1") |
|
290 | ip.run_cell("1") | |
290 | self.assertTrue(d['called']) |
|
291 | self.assertTrue(d['called']) | |
291 | # remove post-exec |
|
292 | # remove post-exec | |
292 | ip._post_execute.pop(set_called) |
|
293 | ip._post_execute.pop(set_called) | |
293 |
|
294 | |||
294 | def test_silent_noadvance(self): |
|
295 | def test_silent_noadvance(self): | |
295 | """run_cell(silent=True) doesn't advance execution_count""" |
|
296 | """run_cell(silent=True) doesn't advance execution_count""" | |
296 | ec = ip.execution_count |
|
297 | ec = ip.execution_count | |
297 | # silent should force store_history=False |
|
298 | # silent should force store_history=False | |
298 | ip.run_cell("1", store_history=True, silent=True) |
|
299 | ip.run_cell("1", store_history=True, silent=True) | |
299 |
|
300 | |||
300 | self.assertEqual(ec, ip.execution_count) |
|
301 | self.assertEqual(ec, ip.execution_count) | |
301 | # double-check that non-silent exec did what we expected |
|
302 | # double-check that non-silent exec did what we expected | |
302 | # silent to avoid |
|
303 | # silent to avoid | |
303 | ip.run_cell("1", store_history=True) |
|
304 | ip.run_cell("1", store_history=True) | |
304 | self.assertEqual(ec+1, ip.execution_count) |
|
305 | self.assertEqual(ec+1, ip.execution_count) | |
305 |
|
306 | |||
306 | def test_silent_nodisplayhook(self): |
|
307 | def test_silent_nodisplayhook(self): | |
307 | """run_cell(silent=True) doesn't trigger displayhook""" |
|
308 | """run_cell(silent=True) doesn't trigger displayhook""" | |
308 | d = dict(called=False) |
|
309 | d = dict(called=False) | |
309 |
|
310 | |||
310 | trap = ip.display_trap |
|
311 | trap = ip.display_trap | |
311 | save_hook = trap.hook |
|
312 | save_hook = trap.hook | |
312 |
|
313 | |||
313 | def failing_hook(*args, **kwargs): |
|
314 | def failing_hook(*args, **kwargs): | |
314 | d['called'] = True |
|
315 | d['called'] = True | |
315 |
|
316 | |||
316 | try: |
|
317 | try: | |
317 | trap.hook = failing_hook |
|
318 | trap.hook = failing_hook | |
318 | ip.run_cell("1", silent=True) |
|
319 | ip.run_cell("1", silent=True) | |
319 | self.assertFalse(d['called']) |
|
320 | self.assertFalse(d['called']) | |
320 | # double-check that non-silent exec did what we expected |
|
321 | # double-check that non-silent exec did what we expected | |
321 | # silent to avoid |
|
322 | # silent to avoid | |
322 | ip.run_cell("1") |
|
323 | ip.run_cell("1") | |
323 | self.assertTrue(d['called']) |
|
324 | self.assertTrue(d['called']) | |
324 | finally: |
|
325 | finally: | |
325 | trap.hook = save_hook |
|
326 | trap.hook = save_hook | |
326 |
|
327 | |||
327 | @skipif(sys.version_info[0] >= 3, "softspace removed in py3") |
|
328 | @skipif(sys.version_info[0] >= 3, "softspace removed in py3") | |
328 | def test_print_softspace(self): |
|
329 | def test_print_softspace(self): | |
329 | """Verify that softspace is handled correctly when executing multiple |
|
330 | """Verify that softspace is handled correctly when executing multiple | |
330 | statements. |
|
331 | statements. | |
331 |
|
332 | |||
332 | In [1]: print 1; print 2 |
|
333 | In [1]: print 1; print 2 | |
333 | 1 |
|
334 | 1 | |
334 | 2 |
|
335 | 2 | |
335 |
|
336 | |||
336 | In [2]: print 1,; print 2 |
|
337 | In [2]: print 1,; print 2 | |
337 | 1 2 |
|
338 | 1 2 | |
338 | """ |
|
339 | """ | |
339 |
|
340 | |||
340 | def test_ofind_line_magic(self): |
|
341 | def test_ofind_line_magic(self): | |
341 | from IPython.core.magic import register_line_magic |
|
342 | from IPython.core.magic import register_line_magic | |
342 |
|
343 | |||
343 | @register_line_magic |
|
344 | @register_line_magic | |
344 | def lmagic(line): |
|
345 | def lmagic(line): | |
345 | "A line magic" |
|
346 | "A line magic" | |
346 |
|
347 | |||
347 | # Get info on line magic |
|
348 | # Get info on line magic | |
348 | lfind = ip._ofind('lmagic') |
|
349 | lfind = ip._ofind('lmagic') | |
349 | info = dict(found=True, isalias=False, ismagic=True, |
|
350 | info = dict(found=True, isalias=False, ismagic=True, | |
350 | namespace = 'IPython internal', obj= lmagic.__wrapped__, |
|
351 | namespace = 'IPython internal', obj= lmagic.__wrapped__, | |
351 | parent = None) |
|
352 | parent = None) | |
352 | nt.assert_equal(lfind, info) |
|
353 | nt.assert_equal(lfind, info) | |
353 |
|
354 | |||
354 | def test_ofind_cell_magic(self): |
|
355 | def test_ofind_cell_magic(self): | |
355 | from IPython.core.magic import register_cell_magic |
|
356 | from IPython.core.magic import register_cell_magic | |
356 |
|
357 | |||
357 | @register_cell_magic |
|
358 | @register_cell_magic | |
358 | def cmagic(line, cell): |
|
359 | def cmagic(line, cell): | |
359 | "A cell magic" |
|
360 | "A cell magic" | |
360 |
|
361 | |||
361 | # Get info on cell magic |
|
362 | # Get info on cell magic | |
362 | find = ip._ofind('cmagic') |
|
363 | find = ip._ofind('cmagic') | |
363 | info = dict(found=True, isalias=False, ismagic=True, |
|
364 | info = dict(found=True, isalias=False, ismagic=True, | |
364 | namespace = 'IPython internal', obj= cmagic.__wrapped__, |
|
365 | namespace = 'IPython internal', obj= cmagic.__wrapped__, | |
365 | parent = None) |
|
366 | parent = None) | |
366 | nt.assert_equal(find, info) |
|
367 | nt.assert_equal(find, info) | |
367 |
|
368 | |||
368 | def test_custom_exception(self): |
|
369 | def test_custom_exception(self): | |
369 | called = [] |
|
370 | called = [] | |
370 | def my_handler(shell, etype, value, tb, tb_offset=None): |
|
371 | def my_handler(shell, etype, value, tb, tb_offset=None): | |
371 | called.append(etype) |
|
372 | called.append(etype) | |
372 | shell.showtraceback((etype, value, tb), tb_offset=tb_offset) |
|
373 | shell.showtraceback((etype, value, tb), tb_offset=tb_offset) | |
373 |
|
374 | |||
374 | ip.set_custom_exc((ValueError,), my_handler) |
|
375 | ip.set_custom_exc((ValueError,), my_handler) | |
375 | try: |
|
376 | try: | |
376 | ip.run_cell("raise ValueError('test')") |
|
377 | ip.run_cell("raise ValueError('test')") | |
377 | # Check that this was called, and only once. |
|
378 | # Check that this was called, and only once. | |
378 | self.assertEqual(called, [ValueError]) |
|
379 | self.assertEqual(called, [ValueError]) | |
379 | finally: |
|
380 | finally: | |
380 | # Reset the custom exception hook |
|
381 | # Reset the custom exception hook | |
381 | ip.set_custom_exc((), None) |
|
382 | ip.set_custom_exc((), None) | |
382 |
|
383 | |||
383 | @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3") |
|
384 | @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3") | |
384 | def test_future_environment(self): |
|
385 | def test_future_environment(self): | |
385 | "Can we run code with & without the shell's __future__ imports?" |
|
386 | "Can we run code with & without the shell's __future__ imports?" | |
386 | ip.run_cell("from __future__ import division") |
|
387 | ip.run_cell("from __future__ import division") | |
387 | ip.run_cell("a = 1/2", shell_futures=True) |
|
388 | ip.run_cell("a = 1/2", shell_futures=True) | |
388 | self.assertEqual(ip.user_ns['a'], 0.5) |
|
389 | self.assertEqual(ip.user_ns['a'], 0.5) | |
389 | ip.run_cell("b = 1/2", shell_futures=False) |
|
390 | ip.run_cell("b = 1/2", shell_futures=False) | |
390 | self.assertEqual(ip.user_ns['b'], 0) |
|
391 | self.assertEqual(ip.user_ns['b'], 0) | |
391 |
|
392 | |||
392 | ip.compile.reset_compiler_flags() |
|
393 | ip.compile.reset_compiler_flags() | |
393 | # This shouldn't leak to the shell's compiler |
|
394 | # This shouldn't leak to the shell's compiler | |
394 | ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False) |
|
395 | ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False) | |
395 | self.assertEqual(ip.user_ns['c'], 0.5) |
|
396 | self.assertEqual(ip.user_ns['c'], 0.5) | |
396 | ip.run_cell("d = 1/2", shell_futures=True) |
|
397 | ip.run_cell("d = 1/2", shell_futures=True) | |
397 | self.assertEqual(ip.user_ns['d'], 0) |
|
398 | self.assertEqual(ip.user_ns['d'], 0) | |
398 |
|
399 | |||
399 |
|
400 | |||
400 | class TestSafeExecfileNonAsciiPath(unittest.TestCase): |
|
401 | class TestSafeExecfileNonAsciiPath(unittest.TestCase): | |
401 |
|
402 | |||
402 | @onlyif_unicode_paths |
|
403 | @onlyif_unicode_paths | |
403 | def setUp(self): |
|
404 | def setUp(self): | |
404 | self.BASETESTDIR = tempfile.mkdtemp() |
|
405 | self.BASETESTDIR = tempfile.mkdtemp() | |
405 | self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ") |
|
406 | self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ") | |
406 | os.mkdir(self.TESTDIR) |
|
407 | os.mkdir(self.TESTDIR) | |
407 | with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile: |
|
408 | with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile: | |
408 | sfile.write("pass\n") |
|
409 | sfile.write("pass\n") | |
409 |
self.oldpath = |
|
410 | self.oldpath = py3compat.getcwd() | |
410 | os.chdir(self.TESTDIR) |
|
411 | os.chdir(self.TESTDIR) | |
411 | self.fname = u"Γ₯Àâtestscript.py" |
|
412 | self.fname = u"Γ₯Àâtestscript.py" | |
412 |
|
413 | |||
413 | def tearDown(self): |
|
414 | def tearDown(self): | |
414 | os.chdir(self.oldpath) |
|
415 | os.chdir(self.oldpath) | |
415 | shutil.rmtree(self.BASETESTDIR) |
|
416 | shutil.rmtree(self.BASETESTDIR) | |
416 |
|
417 | |||
417 | @onlyif_unicode_paths |
|
418 | @onlyif_unicode_paths | |
418 | def test_1(self): |
|
419 | def test_1(self): | |
419 | """Test safe_execfile with non-ascii path |
|
420 | """Test safe_execfile with non-ascii path | |
420 | """ |
|
421 | """ | |
421 | ip.safe_execfile(self.fname, {}, raise_exceptions=True) |
|
422 | ip.safe_execfile(self.fname, {}, raise_exceptions=True) | |
422 |
|
423 | |||
423 | class ExitCodeChecks(tt.TempFileMixin): |
|
424 | class ExitCodeChecks(tt.TempFileMixin): | |
424 | def test_exit_code_ok(self): |
|
425 | def test_exit_code_ok(self): | |
425 | self.system('exit 0') |
|
426 | self.system('exit 0') | |
426 | self.assertEqual(ip.user_ns['_exit_code'], 0) |
|
427 | self.assertEqual(ip.user_ns['_exit_code'], 0) | |
427 |
|
428 | |||
428 | def test_exit_code_error(self): |
|
429 | def test_exit_code_error(self): | |
429 | self.system('exit 1') |
|
430 | self.system('exit 1') | |
430 | self.assertEqual(ip.user_ns['_exit_code'], 1) |
|
431 | self.assertEqual(ip.user_ns['_exit_code'], 1) | |
431 |
|
432 | |||
432 | @skipif(not hasattr(signal, 'SIGALRM')) |
|
433 | @skipif(not hasattr(signal, 'SIGALRM')) | |
433 | def test_exit_code_signal(self): |
|
434 | def test_exit_code_signal(self): | |
434 | self.mktmp("import signal, time\n" |
|
435 | self.mktmp("import signal, time\n" | |
435 | "signal.setitimer(signal.ITIMER_REAL, 0.1)\n" |
|
436 | "signal.setitimer(signal.ITIMER_REAL, 0.1)\n" | |
436 | "time.sleep(1)\n") |
|
437 | "time.sleep(1)\n") | |
437 | self.system("%s %s" % (sys.executable, self.fname)) |
|
438 | self.system("%s %s" % (sys.executable, self.fname)) | |
438 | self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM) |
|
439 | self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM) | |
439 |
|
440 | |||
440 | class TestSystemRaw(unittest.TestCase, ExitCodeChecks): |
|
441 | class TestSystemRaw(unittest.TestCase, ExitCodeChecks): | |
441 | system = ip.system_raw |
|
442 | system = ip.system_raw | |
442 |
|
443 | |||
443 | @onlyif_unicode_paths |
|
444 | @onlyif_unicode_paths | |
444 | def test_1(self): |
|
445 | def test_1(self): | |
445 | """Test system_raw with non-ascii cmd |
|
446 | """Test system_raw with non-ascii cmd | |
446 | """ |
|
447 | """ | |
447 | cmd = u'''python -c "'Γ₯Àâ'" ''' |
|
448 | cmd = u'''python -c "'Γ₯Àâ'" ''' | |
448 | ip.system_raw(cmd) |
|
449 | ip.system_raw(cmd) | |
449 |
|
450 | |||
450 | # TODO: Exit codes are currently ignored on Windows. |
|
451 | # TODO: Exit codes are currently ignored on Windows. | |
451 | class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks): |
|
452 | class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks): | |
452 | system = ip.system_piped |
|
453 | system = ip.system_piped | |
453 |
|
454 | |||
454 | @skip_win32 |
|
455 | @skip_win32 | |
455 | def test_exit_code_ok(self): |
|
456 | def test_exit_code_ok(self): | |
456 | ExitCodeChecks.test_exit_code_ok(self) |
|
457 | ExitCodeChecks.test_exit_code_ok(self) | |
457 |
|
458 | |||
458 | @skip_win32 |
|
459 | @skip_win32 | |
459 | def test_exit_code_error(self): |
|
460 | def test_exit_code_error(self): | |
460 | ExitCodeChecks.test_exit_code_error(self) |
|
461 | ExitCodeChecks.test_exit_code_error(self) | |
461 |
|
462 | |||
462 | @skip_win32 |
|
463 | @skip_win32 | |
463 | def test_exit_code_signal(self): |
|
464 | def test_exit_code_signal(self): | |
464 | ExitCodeChecks.test_exit_code_signal(self) |
|
465 | ExitCodeChecks.test_exit_code_signal(self) | |
465 |
|
466 | |||
466 | class TestModules(unittest.TestCase, tt.TempFileMixin): |
|
467 | class TestModules(unittest.TestCase, tt.TempFileMixin): | |
467 | def test_extraneous_loads(self): |
|
468 | def test_extraneous_loads(self): | |
468 | """Test we're not loading modules on startup that we shouldn't. |
|
469 | """Test we're not loading modules on startup that we shouldn't. | |
469 | """ |
|
470 | """ | |
470 | self.mktmp("import sys\n" |
|
471 | self.mktmp("import sys\n" | |
471 | "print('numpy' in sys.modules)\n" |
|
472 | "print('numpy' in sys.modules)\n" | |
472 | "print('IPython.parallel' in sys.modules)\n" |
|
473 | "print('IPython.parallel' in sys.modules)\n" | |
473 | "print('IPython.kernel.zmq' in sys.modules)\n" |
|
474 | "print('IPython.kernel.zmq' in sys.modules)\n" | |
474 | ) |
|
475 | ) | |
475 | out = "False\nFalse\nFalse\n" |
|
476 | out = "False\nFalse\nFalse\n" | |
476 | tt.ipexec_validate(self.fname, out) |
|
477 | tt.ipexec_validate(self.fname, out) | |
477 |
|
478 | |||
478 | class Negator(ast.NodeTransformer): |
|
479 | class Negator(ast.NodeTransformer): | |
479 | """Negates all number literals in an AST.""" |
|
480 | """Negates all number literals in an AST.""" | |
480 | def visit_Num(self, node): |
|
481 | def visit_Num(self, node): | |
481 | node.n = -node.n |
|
482 | node.n = -node.n | |
482 | return node |
|
483 | return node | |
483 |
|
484 | |||
484 | class TestAstTransform(unittest.TestCase): |
|
485 | class TestAstTransform(unittest.TestCase): | |
485 | def setUp(self): |
|
486 | def setUp(self): | |
486 | self.negator = Negator() |
|
487 | self.negator = Negator() | |
487 | ip.ast_transformers.append(self.negator) |
|
488 | ip.ast_transformers.append(self.negator) | |
488 |
|
489 | |||
489 | def tearDown(self): |
|
490 | def tearDown(self): | |
490 | ip.ast_transformers.remove(self.negator) |
|
491 | ip.ast_transformers.remove(self.negator) | |
491 |
|
492 | |||
492 | def test_run_cell(self): |
|
493 | def test_run_cell(self): | |
493 | with tt.AssertPrints('-34'): |
|
494 | with tt.AssertPrints('-34'): | |
494 | ip.run_cell('print (12 + 22)') |
|
495 | ip.run_cell('print (12 + 22)') | |
495 |
|
496 | |||
496 | # A named reference to a number shouldn't be transformed. |
|
497 | # A named reference to a number shouldn't be transformed. | |
497 | ip.user_ns['n'] = 55 |
|
498 | ip.user_ns['n'] = 55 | |
498 | with tt.AssertNotPrints('-55'): |
|
499 | with tt.AssertNotPrints('-55'): | |
499 | ip.run_cell('print (n)') |
|
500 | ip.run_cell('print (n)') | |
500 |
|
501 | |||
501 | def test_timeit(self): |
|
502 | def test_timeit(self): | |
502 | called = set() |
|
503 | called = set() | |
503 | def f(x): |
|
504 | def f(x): | |
504 | called.add(x) |
|
505 | called.add(x) | |
505 | ip.push({'f':f}) |
|
506 | ip.push({'f':f}) | |
506 |
|
507 | |||
507 | with tt.AssertPrints("best of "): |
|
508 | with tt.AssertPrints("best of "): | |
508 | ip.run_line_magic("timeit", "-n1 f(1)") |
|
509 | ip.run_line_magic("timeit", "-n1 f(1)") | |
509 | self.assertEqual(called, set([-1])) |
|
510 | self.assertEqual(called, set([-1])) | |
510 | called.clear() |
|
511 | called.clear() | |
511 |
|
512 | |||
512 | with tt.AssertPrints("best of "): |
|
513 | with tt.AssertPrints("best of "): | |
513 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") |
|
514 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") | |
514 | self.assertEqual(called, set([-2, -3])) |
|
515 | self.assertEqual(called, set([-2, -3])) | |
515 |
|
516 | |||
516 | def test_time(self): |
|
517 | def test_time(self): | |
517 | called = [] |
|
518 | called = [] | |
518 | def f(x): |
|
519 | def f(x): | |
519 | called.append(x) |
|
520 | called.append(x) | |
520 | ip.push({'f':f}) |
|
521 | ip.push({'f':f}) | |
521 |
|
522 | |||
522 | # Test with an expression |
|
523 | # Test with an expression | |
523 | with tt.AssertPrints("Wall time: "): |
|
524 | with tt.AssertPrints("Wall time: "): | |
524 | ip.run_line_magic("time", "f(5+9)") |
|
525 | ip.run_line_magic("time", "f(5+9)") | |
525 | self.assertEqual(called, [-14]) |
|
526 | self.assertEqual(called, [-14]) | |
526 | called[:] = [] |
|
527 | called[:] = [] | |
527 |
|
528 | |||
528 | # Test with a statement (different code path) |
|
529 | # Test with a statement (different code path) | |
529 | with tt.AssertPrints("Wall time: "): |
|
530 | with tt.AssertPrints("Wall time: "): | |
530 | ip.run_line_magic("time", "a = f(-3 + -2)") |
|
531 | ip.run_line_magic("time", "a = f(-3 + -2)") | |
531 | self.assertEqual(called, [5]) |
|
532 | self.assertEqual(called, [5]) | |
532 |
|
533 | |||
533 | def test_macro(self): |
|
534 | def test_macro(self): | |
534 | ip.push({'a':10}) |
|
535 | ip.push({'a':10}) | |
535 | # The AST transformation makes this do a+=-1 |
|
536 | # The AST transformation makes this do a+=-1 | |
536 | ip.define_macro("amacro", "a+=1\nprint(a)") |
|
537 | ip.define_macro("amacro", "a+=1\nprint(a)") | |
537 |
|
538 | |||
538 | with tt.AssertPrints("9"): |
|
539 | with tt.AssertPrints("9"): | |
539 | ip.run_cell("amacro") |
|
540 | ip.run_cell("amacro") | |
540 | with tt.AssertPrints("8"): |
|
541 | with tt.AssertPrints("8"): | |
541 | ip.run_cell("amacro") |
|
542 | ip.run_cell("amacro") | |
542 |
|
543 | |||
543 | class IntegerWrapper(ast.NodeTransformer): |
|
544 | class IntegerWrapper(ast.NodeTransformer): | |
544 | """Wraps all integers in a call to Integer()""" |
|
545 | """Wraps all integers in a call to Integer()""" | |
545 | def visit_Num(self, node): |
|
546 | def visit_Num(self, node): | |
546 | if isinstance(node.n, int): |
|
547 | if isinstance(node.n, int): | |
547 | return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()), |
|
548 | return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()), | |
548 | args=[node], keywords=[]) |
|
549 | args=[node], keywords=[]) | |
549 | return node |
|
550 | return node | |
550 |
|
551 | |||
551 | class TestAstTransform2(unittest.TestCase): |
|
552 | class TestAstTransform2(unittest.TestCase): | |
552 | def setUp(self): |
|
553 | def setUp(self): | |
553 | self.intwrapper = IntegerWrapper() |
|
554 | self.intwrapper = IntegerWrapper() | |
554 | ip.ast_transformers.append(self.intwrapper) |
|
555 | ip.ast_transformers.append(self.intwrapper) | |
555 |
|
556 | |||
556 | self.calls = [] |
|
557 | self.calls = [] | |
557 | def Integer(*args): |
|
558 | def Integer(*args): | |
558 | self.calls.append(args) |
|
559 | self.calls.append(args) | |
559 | return args |
|
560 | return args | |
560 | ip.push({"Integer": Integer}) |
|
561 | ip.push({"Integer": Integer}) | |
561 |
|
562 | |||
562 | def tearDown(self): |
|
563 | def tearDown(self): | |
563 | ip.ast_transformers.remove(self.intwrapper) |
|
564 | ip.ast_transformers.remove(self.intwrapper) | |
564 | del ip.user_ns['Integer'] |
|
565 | del ip.user_ns['Integer'] | |
565 |
|
566 | |||
566 | def test_run_cell(self): |
|
567 | def test_run_cell(self): | |
567 | ip.run_cell("n = 2") |
|
568 | ip.run_cell("n = 2") | |
568 | self.assertEqual(self.calls, [(2,)]) |
|
569 | self.assertEqual(self.calls, [(2,)]) | |
569 |
|
570 | |||
570 | # This shouldn't throw an error |
|
571 | # This shouldn't throw an error | |
571 | ip.run_cell("o = 2.0") |
|
572 | ip.run_cell("o = 2.0") | |
572 | self.assertEqual(ip.user_ns['o'], 2.0) |
|
573 | self.assertEqual(ip.user_ns['o'], 2.0) | |
573 |
|
574 | |||
574 | def test_timeit(self): |
|
575 | def test_timeit(self): | |
575 | called = set() |
|
576 | called = set() | |
576 | def f(x): |
|
577 | def f(x): | |
577 | called.add(x) |
|
578 | called.add(x) | |
578 | ip.push({'f':f}) |
|
579 | ip.push({'f':f}) | |
579 |
|
580 | |||
580 | with tt.AssertPrints("best of "): |
|
581 | with tt.AssertPrints("best of "): | |
581 | ip.run_line_magic("timeit", "-n1 f(1)") |
|
582 | ip.run_line_magic("timeit", "-n1 f(1)") | |
582 | self.assertEqual(called, set([(1,)])) |
|
583 | self.assertEqual(called, set([(1,)])) | |
583 | called.clear() |
|
584 | called.clear() | |
584 |
|
585 | |||
585 | with tt.AssertPrints("best of "): |
|
586 | with tt.AssertPrints("best of "): | |
586 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") |
|
587 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") | |
587 | self.assertEqual(called, set([(2,), (3,)])) |
|
588 | self.assertEqual(called, set([(2,), (3,)])) | |
588 |
|
589 | |||
589 | class ErrorTransformer(ast.NodeTransformer): |
|
590 | class ErrorTransformer(ast.NodeTransformer): | |
590 | """Throws an error when it sees a number.""" |
|
591 | """Throws an error when it sees a number.""" | |
591 | def visit_Num(self): |
|
592 | def visit_Num(self): | |
592 | raise ValueError("test") |
|
593 | raise ValueError("test") | |
593 |
|
594 | |||
594 | class TestAstTransformError(unittest.TestCase): |
|
595 | class TestAstTransformError(unittest.TestCase): | |
595 | def test_unregistering(self): |
|
596 | def test_unregistering(self): | |
596 | err_transformer = ErrorTransformer() |
|
597 | err_transformer = ErrorTransformer() | |
597 | ip.ast_transformers.append(err_transformer) |
|
598 | ip.ast_transformers.append(err_transformer) | |
598 |
|
599 | |||
599 | with tt.AssertPrints("unregister", channel='stderr'): |
|
600 | with tt.AssertPrints("unregister", channel='stderr'): | |
600 | ip.run_cell("1 + 2") |
|
601 | ip.run_cell("1 + 2") | |
601 |
|
602 | |||
602 | # This should have been removed. |
|
603 | # This should have been removed. | |
603 | nt.assert_not_in(err_transformer, ip.ast_transformers) |
|
604 | nt.assert_not_in(err_transformer, ip.ast_transformers) | |
604 |
|
605 | |||
605 | def test__IPYTHON__(): |
|
606 | def test__IPYTHON__(): | |
606 | # This shouldn't raise a NameError, that's all |
|
607 | # This shouldn't raise a NameError, that's all | |
607 | __IPYTHON__ |
|
608 | __IPYTHON__ | |
608 |
|
609 | |||
609 |
|
610 | |||
610 | class DummyRepr(object): |
|
611 | class DummyRepr(object): | |
611 | def __repr__(self): |
|
612 | def __repr__(self): | |
612 | return "DummyRepr" |
|
613 | return "DummyRepr" | |
613 |
|
614 | |||
614 | def _repr_html_(self): |
|
615 | def _repr_html_(self): | |
615 | return "<b>dummy</b>" |
|
616 | return "<b>dummy</b>" | |
616 |
|
617 | |||
617 | def _repr_javascript_(self): |
|
618 | def _repr_javascript_(self): | |
618 | return "console.log('hi');", {'key': 'value'} |
|
619 | return "console.log('hi');", {'key': 'value'} | |
619 |
|
620 | |||
620 |
|
621 | |||
621 | def test_user_variables(): |
|
622 | def test_user_variables(): | |
622 | # enable all formatters |
|
623 | # enable all formatters | |
623 | ip.display_formatter.active_types = ip.display_formatter.format_types |
|
624 | ip.display_formatter.active_types = ip.display_formatter.format_types | |
624 |
|
625 | |||
625 | ip.user_ns['dummy'] = d = DummyRepr() |
|
626 | ip.user_ns['dummy'] = d = DummyRepr() | |
626 | keys = set(['dummy', 'doesnotexist']) |
|
627 | keys = set(['dummy', 'doesnotexist']) | |
627 | r = ip.user_variables(keys) |
|
628 | r = ip.user_variables(keys) | |
628 |
|
629 | |||
629 | nt.assert_equal(keys, set(r.keys())) |
|
630 | nt.assert_equal(keys, set(r.keys())) | |
630 | dummy = r['dummy'] |
|
631 | dummy = r['dummy'] | |
631 | nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys())) |
|
632 | nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys())) | |
632 | nt.assert_equal(dummy['status'], 'ok') |
|
633 | nt.assert_equal(dummy['status'], 'ok') | |
633 | data = dummy['data'] |
|
634 | data = dummy['data'] | |
634 | metadata = dummy['metadata'] |
|
635 | metadata = dummy['metadata'] | |
635 | nt.assert_equal(data.get('text/html'), d._repr_html_()) |
|
636 | nt.assert_equal(data.get('text/html'), d._repr_html_()) | |
636 | js, jsmd = d._repr_javascript_() |
|
637 | js, jsmd = d._repr_javascript_() | |
637 | nt.assert_equal(data.get('application/javascript'), js) |
|
638 | nt.assert_equal(data.get('application/javascript'), js) | |
638 | nt.assert_equal(metadata.get('application/javascript'), jsmd) |
|
639 | nt.assert_equal(metadata.get('application/javascript'), jsmd) | |
639 |
|
640 | |||
640 | dne = r['doesnotexist'] |
|
641 | dne = r['doesnotexist'] | |
641 | nt.assert_equal(dne['status'], 'error') |
|
642 | nt.assert_equal(dne['status'], 'error') | |
642 | nt.assert_equal(dne['ename'], 'KeyError') |
|
643 | nt.assert_equal(dne['ename'], 'KeyError') | |
643 |
|
644 | |||
644 | # back to text only |
|
645 | # back to text only | |
645 | ip.display_formatter.active_types = ['text/plain'] |
|
646 | ip.display_formatter.active_types = ['text/plain'] | |
646 |
|
647 | |||
647 | def test_user_expression(): |
|
648 | def test_user_expression(): | |
648 | # enable all formatters |
|
649 | # enable all formatters | |
649 | ip.display_formatter.active_types = ip.display_formatter.format_types |
|
650 | ip.display_formatter.active_types = ip.display_formatter.format_types | |
650 | query = { |
|
651 | query = { | |
651 | 'a' : '1 + 2', |
|
652 | 'a' : '1 + 2', | |
652 | 'b' : '1/0', |
|
653 | 'b' : '1/0', | |
653 | } |
|
654 | } | |
654 | r = ip.user_expressions(query) |
|
655 | r = ip.user_expressions(query) | |
655 | import pprint |
|
656 | import pprint | |
656 | pprint.pprint(r) |
|
657 | pprint.pprint(r) | |
657 | nt.assert_equal(r.keys(), query.keys()) |
|
658 | nt.assert_equal(r.keys(), query.keys()) | |
658 | a = r['a'] |
|
659 | a = r['a'] | |
659 | nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys())) |
|
660 | nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys())) | |
660 | nt.assert_equal(a['status'], 'ok') |
|
661 | nt.assert_equal(a['status'], 'ok') | |
661 | data = a['data'] |
|
662 | data = a['data'] | |
662 | metadata = a['metadata'] |
|
663 | metadata = a['metadata'] | |
663 | nt.assert_equal(data.get('text/plain'), '3') |
|
664 | nt.assert_equal(data.get('text/plain'), '3') | |
664 |
|
665 | |||
665 | b = r['b'] |
|
666 | b = r['b'] | |
666 | nt.assert_equal(b['status'], 'error') |
|
667 | nt.assert_equal(b['status'], 'error') | |
667 | nt.assert_equal(b['ename'], 'ZeroDivisionError') |
|
668 | nt.assert_equal(b['ename'], 'ZeroDivisionError') | |
668 |
|
669 | |||
669 | # back to text only |
|
670 | # back to text only | |
670 | ip.display_formatter.active_types = ['text/plain'] |
|
671 | ip.display_formatter.active_types = ['text/plain'] | |
671 |
|
672 | |||
672 |
|
673 | |||
673 |
|
674 | |||
674 |
|
675 | |||
675 |
|
676 | |||
676 |
|
677 |
@@ -1,944 +1,944 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Tests for various magic functions. |
|
2 | """Tests for various magic functions. | |
3 |
|
3 | |||
4 | Needs to be run by nose (to make ipython session available). |
|
4 | Needs to be run by nose (to make ipython session available). | |
5 | """ |
|
5 | """ | |
6 | from __future__ import absolute_import |
|
6 | from __future__ import absolute_import | |
7 |
|
7 | |||
8 | #----------------------------------------------------------------------------- |
|
8 | #----------------------------------------------------------------------------- | |
9 | # Imports |
|
9 | # Imports | |
10 | #----------------------------------------------------------------------------- |
|
10 | #----------------------------------------------------------------------------- | |
11 |
|
11 | |||
12 | import io |
|
12 | import io | |
13 | import os |
|
13 | import os | |
14 | import sys |
|
14 | import sys | |
15 | from unittest import TestCase |
|
15 | from unittest import TestCase | |
16 |
|
16 | |||
17 | try: |
|
17 | try: | |
18 | from importlib import invalidate_caches # Required from Python 3.3 |
|
18 | from importlib import invalidate_caches # Required from Python 3.3 | |
19 | except ImportError: |
|
19 | except ImportError: | |
20 | def invalidate_caches(): |
|
20 | def invalidate_caches(): | |
21 | pass |
|
21 | pass | |
22 |
|
22 | |||
23 | import nose.tools as nt |
|
23 | import nose.tools as nt | |
24 |
|
24 | |||
25 | from IPython.core import magic |
|
25 | from IPython.core import magic | |
26 | from IPython.core.magic import (Magics, magics_class, line_magic, |
|
26 | from IPython.core.magic import (Magics, magics_class, line_magic, | |
27 | cell_magic, line_cell_magic, |
|
27 | cell_magic, line_cell_magic, | |
28 | register_line_magic, register_cell_magic, |
|
28 | register_line_magic, register_cell_magic, | |
29 | register_line_cell_magic) |
|
29 | register_line_cell_magic) | |
30 | from IPython.core.magics import execution, script, code |
|
30 | from IPython.core.magics import execution, script, code | |
31 | from IPython.nbformat.v3.tests.nbexamples import nb0 |
|
31 | from IPython.nbformat.v3.tests.nbexamples import nb0 | |
32 | from IPython.nbformat import current |
|
32 | from IPython.nbformat import current | |
33 | from IPython.testing import decorators as dec |
|
33 | from IPython.testing import decorators as dec | |
34 | from IPython.testing import tools as tt |
|
34 | from IPython.testing import tools as tt | |
35 | from IPython.utils import py3compat |
|
35 | from IPython.utils import py3compat | |
36 | from IPython.utils.io import capture_output |
|
36 | from IPython.utils.io import capture_output | |
37 | from IPython.utils.tempdir import TemporaryDirectory |
|
37 | from IPython.utils.tempdir import TemporaryDirectory | |
38 | from IPython.utils.process import find_cmd |
|
38 | from IPython.utils.process import find_cmd | |
39 |
|
39 | |||
40 | if py3compat.PY3: |
|
40 | if py3compat.PY3: | |
41 | from io import StringIO |
|
41 | from io import StringIO | |
42 | else: |
|
42 | else: | |
43 | from StringIO import StringIO |
|
43 | from StringIO import StringIO | |
44 |
|
44 | |||
45 | #----------------------------------------------------------------------------- |
|
45 | #----------------------------------------------------------------------------- | |
46 | # Test functions begin |
|
46 | # Test functions begin | |
47 | #----------------------------------------------------------------------------- |
|
47 | #----------------------------------------------------------------------------- | |
48 |
|
48 | |||
49 | @magic.magics_class |
|
49 | @magic.magics_class | |
50 | class DummyMagics(magic.Magics): pass |
|
50 | class DummyMagics(magic.Magics): pass | |
51 |
|
51 | |||
52 | def test_extract_code_ranges(): |
|
52 | def test_extract_code_ranges(): | |
53 | instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" |
|
53 | instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" | |
54 | expected = [(0, 1), |
|
54 | expected = [(0, 1), | |
55 | (2, 3), |
|
55 | (2, 3), | |
56 | (4, 6), |
|
56 | (4, 6), | |
57 | (6, 9), |
|
57 | (6, 9), | |
58 | (9, 14), |
|
58 | (9, 14), | |
59 | (16, None), |
|
59 | (16, None), | |
60 | (None, 9), |
|
60 | (None, 9), | |
61 | (9, None), |
|
61 | (9, None), | |
62 | (None, 13), |
|
62 | (None, 13), | |
63 | (None, None)] |
|
63 | (None, None)] | |
64 | actual = list(code.extract_code_ranges(instr)) |
|
64 | actual = list(code.extract_code_ranges(instr)) | |
65 | nt.assert_equal(actual, expected) |
|
65 | nt.assert_equal(actual, expected) | |
66 |
|
66 | |||
67 | def test_extract_symbols(): |
|
67 | def test_extract_symbols(): | |
68 | source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" |
|
68 | source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" | |
69 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] |
|
69 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] | |
70 | expected = [([], ['a']), |
|
70 | expected = [([], ['a']), | |
71 | (["def b():\n return 42\n"], []), |
|
71 | (["def b():\n return 42\n"], []), | |
72 | (["class A: pass\n"], []), |
|
72 | (["class A: pass\n"], []), | |
73 | (["class A: pass\n", "def b():\n return 42\n"], []), |
|
73 | (["class A: pass\n", "def b():\n return 42\n"], []), | |
74 | (["class A: pass\n"], ['a']), |
|
74 | (["class A: pass\n"], ['a']), | |
75 | ([], ['z'])] |
|
75 | ([], ['z'])] | |
76 | for symbols, exp in zip(symbols_args, expected): |
|
76 | for symbols, exp in zip(symbols_args, expected): | |
77 | nt.assert_equal(code.extract_symbols(source, symbols), exp) |
|
77 | nt.assert_equal(code.extract_symbols(source, symbols), exp) | |
78 |
|
78 | |||
79 |
|
79 | |||
80 | def test_extract_symbols_raises_exception_with_non_python_code(): |
|
80 | def test_extract_symbols_raises_exception_with_non_python_code(): | |
81 | source = ("=begin A Ruby program :)=end\n" |
|
81 | source = ("=begin A Ruby program :)=end\n" | |
82 | "def hello\n" |
|
82 | "def hello\n" | |
83 | "puts 'Hello world'\n" |
|
83 | "puts 'Hello world'\n" | |
84 | "end") |
|
84 | "end") | |
85 | with nt.assert_raises(SyntaxError): |
|
85 | with nt.assert_raises(SyntaxError): | |
86 | code.extract_symbols(source, "hello") |
|
86 | code.extract_symbols(source, "hello") | |
87 |
|
87 | |||
88 | def test_config(): |
|
88 | def test_config(): | |
89 | """ test that config magic does not raise |
|
89 | """ test that config magic does not raise | |
90 | can happen if Configurable init is moved too early into |
|
90 | can happen if Configurable init is moved too early into | |
91 | Magics.__init__ as then a Config object will be registerd as a |
|
91 | Magics.__init__ as then a Config object will be registerd as a | |
92 | magic. |
|
92 | magic. | |
93 | """ |
|
93 | """ | |
94 | ## should not raise. |
|
94 | ## should not raise. | |
95 | _ip.magic('config') |
|
95 | _ip.magic('config') | |
96 |
|
96 | |||
97 | def test_rehashx(): |
|
97 | def test_rehashx(): | |
98 | # clear up everything |
|
98 | # clear up everything | |
99 | _ip = get_ipython() |
|
99 | _ip = get_ipython() | |
100 | _ip.alias_manager.clear_aliases() |
|
100 | _ip.alias_manager.clear_aliases() | |
101 | del _ip.db['syscmdlist'] |
|
101 | del _ip.db['syscmdlist'] | |
102 |
|
102 | |||
103 | _ip.magic('rehashx') |
|
103 | _ip.magic('rehashx') | |
104 | # Practically ALL ipython development systems will have more than 10 aliases |
|
104 | # Practically ALL ipython development systems will have more than 10 aliases | |
105 |
|
105 | |||
106 | nt.assert_true(len(_ip.alias_manager.aliases) > 10) |
|
106 | nt.assert_true(len(_ip.alias_manager.aliases) > 10) | |
107 | for name, cmd in _ip.alias_manager.aliases: |
|
107 | for name, cmd in _ip.alias_manager.aliases: | |
108 | # we must strip dots from alias names |
|
108 | # we must strip dots from alias names | |
109 | nt.assert_not_in('.', name) |
|
109 | nt.assert_not_in('.', name) | |
110 |
|
110 | |||
111 | # rehashx must fill up syscmdlist |
|
111 | # rehashx must fill up syscmdlist | |
112 | scoms = _ip.db['syscmdlist'] |
|
112 | scoms = _ip.db['syscmdlist'] | |
113 | nt.assert_true(len(scoms) > 10) |
|
113 | nt.assert_true(len(scoms) > 10) | |
114 |
|
114 | |||
115 |
|
115 | |||
116 | def test_magic_parse_options(): |
|
116 | def test_magic_parse_options(): | |
117 | """Test that we don't mangle paths when parsing magic options.""" |
|
117 | """Test that we don't mangle paths when parsing magic options.""" | |
118 | ip = get_ipython() |
|
118 | ip = get_ipython() | |
119 | path = 'c:\\x' |
|
119 | path = 'c:\\x' | |
120 | m = DummyMagics(ip) |
|
120 | m = DummyMagics(ip) | |
121 | opts = m.parse_options('-f %s' % path,'f:')[0] |
|
121 | opts = m.parse_options('-f %s' % path,'f:')[0] | |
122 | # argv splitting is os-dependent |
|
122 | # argv splitting is os-dependent | |
123 | if os.name == 'posix': |
|
123 | if os.name == 'posix': | |
124 | expected = 'c:x' |
|
124 | expected = 'c:x' | |
125 | else: |
|
125 | else: | |
126 | expected = path |
|
126 | expected = path | |
127 | nt.assert_equal(opts['f'], expected) |
|
127 | nt.assert_equal(opts['f'], expected) | |
128 |
|
128 | |||
129 | def test_magic_parse_long_options(): |
|
129 | def test_magic_parse_long_options(): | |
130 | """Magic.parse_options can handle --foo=bar long options""" |
|
130 | """Magic.parse_options can handle --foo=bar long options""" | |
131 | ip = get_ipython() |
|
131 | ip = get_ipython() | |
132 | m = DummyMagics(ip) |
|
132 | m = DummyMagics(ip) | |
133 | opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=') |
|
133 | opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=') | |
134 | nt.assert_in('foo', opts) |
|
134 | nt.assert_in('foo', opts) | |
135 | nt.assert_in('bar', opts) |
|
135 | nt.assert_in('bar', opts) | |
136 | nt.assert_equal(opts['bar'], "bubble") |
|
136 | nt.assert_equal(opts['bar'], "bubble") | |
137 |
|
137 | |||
138 |
|
138 | |||
139 | @dec.skip_without('sqlite3') |
|
139 | @dec.skip_without('sqlite3') | |
140 | def doctest_hist_f(): |
|
140 | def doctest_hist_f(): | |
141 | """Test %hist -f with temporary filename. |
|
141 | """Test %hist -f with temporary filename. | |
142 |
|
142 | |||
143 | In [9]: import tempfile |
|
143 | In [9]: import tempfile | |
144 |
|
144 | |||
145 | In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') |
|
145 | In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') | |
146 |
|
146 | |||
147 | In [11]: %hist -nl -f $tfile 3 |
|
147 | In [11]: %hist -nl -f $tfile 3 | |
148 |
|
148 | |||
149 | In [13]: import os; os.unlink(tfile) |
|
149 | In [13]: import os; os.unlink(tfile) | |
150 | """ |
|
150 | """ | |
151 |
|
151 | |||
152 |
|
152 | |||
153 | @dec.skip_without('sqlite3') |
|
153 | @dec.skip_without('sqlite3') | |
154 | def doctest_hist_r(): |
|
154 | def doctest_hist_r(): | |
155 | """Test %hist -r |
|
155 | """Test %hist -r | |
156 |
|
156 | |||
157 | XXX - This test is not recording the output correctly. For some reason, in |
|
157 | XXX - This test is not recording the output correctly. For some reason, in | |
158 | testing mode the raw history isn't getting populated. No idea why. |
|
158 | testing mode the raw history isn't getting populated. No idea why. | |
159 | Disabling the output checking for now, though at least we do run it. |
|
159 | Disabling the output checking for now, though at least we do run it. | |
160 |
|
160 | |||
161 | In [1]: 'hist' in _ip.lsmagic() |
|
161 | In [1]: 'hist' in _ip.lsmagic() | |
162 | Out[1]: True |
|
162 | Out[1]: True | |
163 |
|
163 | |||
164 | In [2]: x=1 |
|
164 | In [2]: x=1 | |
165 |
|
165 | |||
166 | In [3]: %hist -rl 2 |
|
166 | In [3]: %hist -rl 2 | |
167 | x=1 # random |
|
167 | x=1 # random | |
168 | %hist -r 2 |
|
168 | %hist -r 2 | |
169 | """ |
|
169 | """ | |
170 |
|
170 | |||
171 |
|
171 | |||
172 | @dec.skip_without('sqlite3') |
|
172 | @dec.skip_without('sqlite3') | |
173 | def doctest_hist_op(): |
|
173 | def doctest_hist_op(): | |
174 | """Test %hist -op |
|
174 | """Test %hist -op | |
175 |
|
175 | |||
176 | In [1]: class b(float): |
|
176 | In [1]: class b(float): | |
177 | ...: pass |
|
177 | ...: pass | |
178 | ...: |
|
178 | ...: | |
179 |
|
179 | |||
180 | In [2]: class s(object): |
|
180 | In [2]: class s(object): | |
181 | ...: def __str__(self): |
|
181 | ...: def __str__(self): | |
182 | ...: return 's' |
|
182 | ...: return 's' | |
183 | ...: |
|
183 | ...: | |
184 |
|
184 | |||
185 | In [3]: |
|
185 | In [3]: | |
186 |
|
186 | |||
187 | In [4]: class r(b): |
|
187 | In [4]: class r(b): | |
188 | ...: def __repr__(self): |
|
188 | ...: def __repr__(self): | |
189 | ...: return 'r' |
|
189 | ...: return 'r' | |
190 | ...: |
|
190 | ...: | |
191 |
|
191 | |||
192 | In [5]: class sr(s,r): pass |
|
192 | In [5]: class sr(s,r): pass | |
193 | ...: |
|
193 | ...: | |
194 |
|
194 | |||
195 | In [6]: |
|
195 | In [6]: | |
196 |
|
196 | |||
197 | In [7]: bb=b() |
|
197 | In [7]: bb=b() | |
198 |
|
198 | |||
199 | In [8]: ss=s() |
|
199 | In [8]: ss=s() | |
200 |
|
200 | |||
201 | In [9]: rr=r() |
|
201 | In [9]: rr=r() | |
202 |
|
202 | |||
203 | In [10]: ssrr=sr() |
|
203 | In [10]: ssrr=sr() | |
204 |
|
204 | |||
205 | In [11]: 4.5 |
|
205 | In [11]: 4.5 | |
206 | Out[11]: 4.5 |
|
206 | Out[11]: 4.5 | |
207 |
|
207 | |||
208 | In [12]: str(ss) |
|
208 | In [12]: str(ss) | |
209 | Out[12]: 's' |
|
209 | Out[12]: 's' | |
210 |
|
210 | |||
211 | In [13]: |
|
211 | In [13]: | |
212 |
|
212 | |||
213 | In [14]: %hist -op |
|
213 | In [14]: %hist -op | |
214 | >>> class b: |
|
214 | >>> class b: | |
215 | ... pass |
|
215 | ... pass | |
216 | ... |
|
216 | ... | |
217 | >>> class s(b): |
|
217 | >>> class s(b): | |
218 | ... def __str__(self): |
|
218 | ... def __str__(self): | |
219 | ... return 's' |
|
219 | ... return 's' | |
220 | ... |
|
220 | ... | |
221 | >>> |
|
221 | >>> | |
222 | >>> class r(b): |
|
222 | >>> class r(b): | |
223 | ... def __repr__(self): |
|
223 | ... def __repr__(self): | |
224 | ... return 'r' |
|
224 | ... return 'r' | |
225 | ... |
|
225 | ... | |
226 | >>> class sr(s,r): pass |
|
226 | >>> class sr(s,r): pass | |
227 | >>> |
|
227 | >>> | |
228 | >>> bb=b() |
|
228 | >>> bb=b() | |
229 | >>> ss=s() |
|
229 | >>> ss=s() | |
230 | >>> rr=r() |
|
230 | >>> rr=r() | |
231 | >>> ssrr=sr() |
|
231 | >>> ssrr=sr() | |
232 | >>> 4.5 |
|
232 | >>> 4.5 | |
233 | 4.5 |
|
233 | 4.5 | |
234 | >>> str(ss) |
|
234 | >>> str(ss) | |
235 | 's' |
|
235 | 's' | |
236 | >>> |
|
236 | >>> | |
237 | """ |
|
237 | """ | |
238 |
|
238 | |||
239 |
|
239 | |||
240 | @dec.skip_without('sqlite3') |
|
240 | @dec.skip_without('sqlite3') | |
241 | def test_macro(): |
|
241 | def test_macro(): | |
242 | ip = get_ipython() |
|
242 | ip = get_ipython() | |
243 | ip.history_manager.reset() # Clear any existing history. |
|
243 | ip.history_manager.reset() # Clear any existing history. | |
244 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] |
|
244 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] | |
245 | for i, cmd in enumerate(cmds, start=1): |
|
245 | for i, cmd in enumerate(cmds, start=1): | |
246 | ip.history_manager.store_inputs(i, cmd) |
|
246 | ip.history_manager.store_inputs(i, cmd) | |
247 | ip.magic("macro test 1-3") |
|
247 | ip.magic("macro test 1-3") | |
248 | nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n") |
|
248 | nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n") | |
249 |
|
249 | |||
250 | # List macros |
|
250 | # List macros | |
251 | nt.assert_in("test", ip.magic("macro")) |
|
251 | nt.assert_in("test", ip.magic("macro")) | |
252 |
|
252 | |||
253 |
|
253 | |||
254 | @dec.skip_without('sqlite3') |
|
254 | @dec.skip_without('sqlite3') | |
255 | def test_macro_run(): |
|
255 | def test_macro_run(): | |
256 | """Test that we can run a multi-line macro successfully.""" |
|
256 | """Test that we can run a multi-line macro successfully.""" | |
257 | ip = get_ipython() |
|
257 | ip = get_ipython() | |
258 | ip.history_manager.reset() |
|
258 | ip.history_manager.reset() | |
259 | cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"), |
|
259 | cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"), | |
260 | "%macro test 2-3"] |
|
260 | "%macro test 2-3"] | |
261 | for cmd in cmds: |
|
261 | for cmd in cmds: | |
262 | ip.run_cell(cmd, store_history=True) |
|
262 | ip.run_cell(cmd, store_history=True) | |
263 | nt.assert_equal(ip.user_ns["test"].value, |
|
263 | nt.assert_equal(ip.user_ns["test"].value, | |
264 | py3compat.doctest_refactor_print("a+=1\nprint a\n")) |
|
264 | py3compat.doctest_refactor_print("a+=1\nprint a\n")) | |
265 | with tt.AssertPrints("12"): |
|
265 | with tt.AssertPrints("12"): | |
266 | ip.run_cell("test") |
|
266 | ip.run_cell("test") | |
267 | with tt.AssertPrints("13"): |
|
267 | with tt.AssertPrints("13"): | |
268 | ip.run_cell("test") |
|
268 | ip.run_cell("test") | |
269 |
|
269 | |||
270 |
|
270 | |||
271 | def test_magic_magic(): |
|
271 | def test_magic_magic(): | |
272 | """Test %magic""" |
|
272 | """Test %magic""" | |
273 | ip = get_ipython() |
|
273 | ip = get_ipython() | |
274 | with capture_output() as captured: |
|
274 | with capture_output() as captured: | |
275 | ip.magic("magic") |
|
275 | ip.magic("magic") | |
276 |
|
276 | |||
277 | stdout = captured.stdout |
|
277 | stdout = captured.stdout | |
278 | nt.assert_in('%magic', stdout) |
|
278 | nt.assert_in('%magic', stdout) | |
279 | nt.assert_in('IPython', stdout) |
|
279 | nt.assert_in('IPython', stdout) | |
280 | nt.assert_in('Available', stdout) |
|
280 | nt.assert_in('Available', stdout) | |
281 |
|
281 | |||
282 |
|
282 | |||
283 | @dec.skipif_not_numpy |
|
283 | @dec.skipif_not_numpy | |
284 | def test_numpy_reset_array_undec(): |
|
284 | def test_numpy_reset_array_undec(): | |
285 | "Test '%reset array' functionality" |
|
285 | "Test '%reset array' functionality" | |
286 | _ip.ex('import numpy as np') |
|
286 | _ip.ex('import numpy as np') | |
287 | _ip.ex('a = np.empty(2)') |
|
287 | _ip.ex('a = np.empty(2)') | |
288 | nt.assert_in('a', _ip.user_ns) |
|
288 | nt.assert_in('a', _ip.user_ns) | |
289 | _ip.magic('reset -f array') |
|
289 | _ip.magic('reset -f array') | |
290 | nt.assert_not_in('a', _ip.user_ns) |
|
290 | nt.assert_not_in('a', _ip.user_ns) | |
291 |
|
291 | |||
292 | def test_reset_out(): |
|
292 | def test_reset_out(): | |
293 | "Test '%reset out' magic" |
|
293 | "Test '%reset out' magic" | |
294 | _ip.run_cell("parrot = 'dead'", store_history=True) |
|
294 | _ip.run_cell("parrot = 'dead'", store_history=True) | |
295 | # test '%reset -f out', make an Out prompt |
|
295 | # test '%reset -f out', make an Out prompt | |
296 | _ip.run_cell("parrot", store_history=True) |
|
296 | _ip.run_cell("parrot", store_history=True) | |
297 | nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) |
|
297 | nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) | |
298 | _ip.magic('reset -f out') |
|
298 | _ip.magic('reset -f out') | |
299 | nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) |
|
299 | nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) | |
300 | nt.assert_equal(len(_ip.user_ns['Out']), 0) |
|
300 | nt.assert_equal(len(_ip.user_ns['Out']), 0) | |
301 |
|
301 | |||
302 | def test_reset_in(): |
|
302 | def test_reset_in(): | |
303 | "Test '%reset in' magic" |
|
303 | "Test '%reset in' magic" | |
304 | # test '%reset -f in' |
|
304 | # test '%reset -f in' | |
305 | _ip.run_cell("parrot", store_history=True) |
|
305 | _ip.run_cell("parrot", store_history=True) | |
306 | nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) |
|
306 | nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) | |
307 | _ip.magic('%reset -f in') |
|
307 | _ip.magic('%reset -f in') | |
308 | nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) |
|
308 | nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) | |
309 | nt.assert_equal(len(set(_ip.user_ns['In'])), 1) |
|
309 | nt.assert_equal(len(set(_ip.user_ns['In'])), 1) | |
310 |
|
310 | |||
311 | def test_reset_dhist(): |
|
311 | def test_reset_dhist(): | |
312 | "Test '%reset dhist' magic" |
|
312 | "Test '%reset dhist' magic" | |
313 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing |
|
313 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing | |
314 | _ip.magic('cd ' + os.path.dirname(nt.__file__)) |
|
314 | _ip.magic('cd ' + os.path.dirname(nt.__file__)) | |
315 | _ip.magic('cd -') |
|
315 | _ip.magic('cd -') | |
316 | nt.assert_true(len(_ip.user_ns['_dh']) > 0) |
|
316 | nt.assert_true(len(_ip.user_ns['_dh']) > 0) | |
317 | _ip.magic('reset -f dhist') |
|
317 | _ip.magic('reset -f dhist') | |
318 | nt.assert_equal(len(_ip.user_ns['_dh']), 0) |
|
318 | nt.assert_equal(len(_ip.user_ns['_dh']), 0) | |
319 | _ip.run_cell("_dh = [d for d in tmp]") #restore |
|
319 | _ip.run_cell("_dh = [d for d in tmp]") #restore | |
320 |
|
320 | |||
321 | def test_reset_in_length(): |
|
321 | def test_reset_in_length(): | |
322 | "Test that '%reset in' preserves In[] length" |
|
322 | "Test that '%reset in' preserves In[] length" | |
323 | _ip.run_cell("print 'foo'") |
|
323 | _ip.run_cell("print 'foo'") | |
324 | _ip.run_cell("reset -f in") |
|
324 | _ip.run_cell("reset -f in") | |
325 | nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1) |
|
325 | nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1) | |
326 |
|
326 | |||
327 | def test_tb_syntaxerror(): |
|
327 | def test_tb_syntaxerror(): | |
328 | """test %tb after a SyntaxError""" |
|
328 | """test %tb after a SyntaxError""" | |
329 | ip = get_ipython() |
|
329 | ip = get_ipython() | |
330 | ip.run_cell("for") |
|
330 | ip.run_cell("for") | |
331 |
|
331 | |||
332 | # trap and validate stdout |
|
332 | # trap and validate stdout | |
333 | save_stdout = sys.stdout |
|
333 | save_stdout = sys.stdout | |
334 | try: |
|
334 | try: | |
335 | sys.stdout = StringIO() |
|
335 | sys.stdout = StringIO() | |
336 | ip.run_cell("%tb") |
|
336 | ip.run_cell("%tb") | |
337 | out = sys.stdout.getvalue() |
|
337 | out = sys.stdout.getvalue() | |
338 | finally: |
|
338 | finally: | |
339 | sys.stdout = save_stdout |
|
339 | sys.stdout = save_stdout | |
340 | # trim output, and only check the last line |
|
340 | # trim output, and only check the last line | |
341 | last_line = out.rstrip().splitlines()[-1].strip() |
|
341 | last_line = out.rstrip().splitlines()[-1].strip() | |
342 | nt.assert_equal(last_line, "SyntaxError: invalid syntax") |
|
342 | nt.assert_equal(last_line, "SyntaxError: invalid syntax") | |
343 |
|
343 | |||
344 |
|
344 | |||
345 | def test_time(): |
|
345 | def test_time(): | |
346 | ip = get_ipython() |
|
346 | ip = get_ipython() | |
347 |
|
347 | |||
348 | with tt.AssertPrints("Wall time: "): |
|
348 | with tt.AssertPrints("Wall time: "): | |
349 | ip.run_cell("%time None") |
|
349 | ip.run_cell("%time None") | |
350 |
|
350 | |||
351 | ip.run_cell("def f(kmjy):\n" |
|
351 | ip.run_cell("def f(kmjy):\n" | |
352 | " %time print (2*kmjy)") |
|
352 | " %time print (2*kmjy)") | |
353 |
|
353 | |||
354 | with tt.AssertPrints("Wall time: "): |
|
354 | with tt.AssertPrints("Wall time: "): | |
355 | with tt.AssertPrints("hihi", suppress=False): |
|
355 | with tt.AssertPrints("hihi", suppress=False): | |
356 | ip.run_cell("f('hi')") |
|
356 | ip.run_cell("f('hi')") | |
357 |
|
357 | |||
358 |
|
358 | |||
359 | @dec.skip_win32 |
|
359 | @dec.skip_win32 | |
360 | def test_time2(): |
|
360 | def test_time2(): | |
361 | ip = get_ipython() |
|
361 | ip = get_ipython() | |
362 |
|
362 | |||
363 | with tt.AssertPrints("CPU times: user "): |
|
363 | with tt.AssertPrints("CPU times: user "): | |
364 | ip.run_cell("%time None") |
|
364 | ip.run_cell("%time None") | |
365 |
|
365 | |||
366 | def test_time3(): |
|
366 | def test_time3(): | |
367 | """Erroneous magic function calls, issue gh-3334""" |
|
367 | """Erroneous magic function calls, issue gh-3334""" | |
368 | ip = get_ipython() |
|
368 | ip = get_ipython() | |
369 | ip.user_ns.pop('run', None) |
|
369 | ip.user_ns.pop('run', None) | |
370 |
|
370 | |||
371 | with tt.AssertNotPrints("not found", channel='stderr'): |
|
371 | with tt.AssertNotPrints("not found", channel='stderr'): | |
372 | ip.run_cell("%%time\n" |
|
372 | ip.run_cell("%%time\n" | |
373 | "run = 0\n" |
|
373 | "run = 0\n" | |
374 | "run += 1") |
|
374 | "run += 1") | |
375 |
|
375 | |||
376 | def test_doctest_mode(): |
|
376 | def test_doctest_mode(): | |
377 | "Toggle doctest_mode twice, it should be a no-op and run without error" |
|
377 | "Toggle doctest_mode twice, it should be a no-op and run without error" | |
378 | _ip.magic('doctest_mode') |
|
378 | _ip.magic('doctest_mode') | |
379 | _ip.magic('doctest_mode') |
|
379 | _ip.magic('doctest_mode') | |
380 |
|
380 | |||
381 |
|
381 | |||
382 | def test_parse_options(): |
|
382 | def test_parse_options(): | |
383 | """Tests for basic options parsing in magics.""" |
|
383 | """Tests for basic options parsing in magics.""" | |
384 | # These are only the most minimal of tests, more should be added later. At |
|
384 | # These are only the most minimal of tests, more should be added later. At | |
385 | # the very least we check that basic text/unicode calls work OK. |
|
385 | # the very least we check that basic text/unicode calls work OK. | |
386 | m = DummyMagics(_ip) |
|
386 | m = DummyMagics(_ip) | |
387 | nt.assert_equal(m.parse_options('foo', '')[1], 'foo') |
|
387 | nt.assert_equal(m.parse_options('foo', '')[1], 'foo') | |
388 | nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo') |
|
388 | nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo') | |
389 |
|
389 | |||
390 |
|
390 | |||
391 | def test_dirops(): |
|
391 | def test_dirops(): | |
392 | """Test various directory handling operations.""" |
|
392 | """Test various directory handling operations.""" | |
393 |
# curpath = lambda :os.path.splitdrive( |
|
393 | # curpath = lambda :os.path.splitdrive(py3compat.getcwd())[1].replace('\\','/') | |
394 |
curpath = |
|
394 | curpath = py3compat.getcwd | |
395 |
startdir = |
|
395 | startdir = py3compat.getcwd() | |
396 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
396 | ipdir = os.path.realpath(_ip.ipython_dir) | |
397 | try: |
|
397 | try: | |
398 | _ip.magic('cd "%s"' % ipdir) |
|
398 | _ip.magic('cd "%s"' % ipdir) | |
399 | nt.assert_equal(curpath(), ipdir) |
|
399 | nt.assert_equal(curpath(), ipdir) | |
400 | _ip.magic('cd -') |
|
400 | _ip.magic('cd -') | |
401 | nt.assert_equal(curpath(), startdir) |
|
401 | nt.assert_equal(curpath(), startdir) | |
402 | _ip.magic('pushd "%s"' % ipdir) |
|
402 | _ip.magic('pushd "%s"' % ipdir) | |
403 | nt.assert_equal(curpath(), ipdir) |
|
403 | nt.assert_equal(curpath(), ipdir) | |
404 | _ip.magic('popd') |
|
404 | _ip.magic('popd') | |
405 | nt.assert_equal(curpath(), startdir) |
|
405 | nt.assert_equal(curpath(), startdir) | |
406 | finally: |
|
406 | finally: | |
407 | os.chdir(startdir) |
|
407 | os.chdir(startdir) | |
408 |
|
408 | |||
409 |
|
409 | |||
410 | def test_xmode(): |
|
410 | def test_xmode(): | |
411 | # Calling xmode three times should be a no-op |
|
411 | # Calling xmode three times should be a no-op | |
412 | xmode = _ip.InteractiveTB.mode |
|
412 | xmode = _ip.InteractiveTB.mode | |
413 | for i in range(3): |
|
413 | for i in range(3): | |
414 | _ip.magic("xmode") |
|
414 | _ip.magic("xmode") | |
415 | nt.assert_equal(_ip.InteractiveTB.mode, xmode) |
|
415 | nt.assert_equal(_ip.InteractiveTB.mode, xmode) | |
416 |
|
416 | |||
417 | def test_reset_hard(): |
|
417 | def test_reset_hard(): | |
418 | monitor = [] |
|
418 | monitor = [] | |
419 | class A(object): |
|
419 | class A(object): | |
420 | def __del__(self): |
|
420 | def __del__(self): | |
421 | monitor.append(1) |
|
421 | monitor.append(1) | |
422 | def __repr__(self): |
|
422 | def __repr__(self): | |
423 | return "<A instance>" |
|
423 | return "<A instance>" | |
424 |
|
424 | |||
425 | _ip.user_ns["a"] = A() |
|
425 | _ip.user_ns["a"] = A() | |
426 | _ip.run_cell("a") |
|
426 | _ip.run_cell("a") | |
427 |
|
427 | |||
428 | nt.assert_equal(monitor, []) |
|
428 | nt.assert_equal(monitor, []) | |
429 | _ip.magic("reset -f") |
|
429 | _ip.magic("reset -f") | |
430 | nt.assert_equal(monitor, [1]) |
|
430 | nt.assert_equal(monitor, [1]) | |
431 |
|
431 | |||
432 | class TestXdel(tt.TempFileMixin): |
|
432 | class TestXdel(tt.TempFileMixin): | |
433 | def test_xdel(self): |
|
433 | def test_xdel(self): | |
434 | """Test that references from %run are cleared by xdel.""" |
|
434 | """Test that references from %run are cleared by xdel.""" | |
435 | src = ("class A(object):\n" |
|
435 | src = ("class A(object):\n" | |
436 | " monitor = []\n" |
|
436 | " monitor = []\n" | |
437 | " def __del__(self):\n" |
|
437 | " def __del__(self):\n" | |
438 | " self.monitor.append(1)\n" |
|
438 | " self.monitor.append(1)\n" | |
439 | "a = A()\n") |
|
439 | "a = A()\n") | |
440 | self.mktmp(src) |
|
440 | self.mktmp(src) | |
441 | # %run creates some hidden references... |
|
441 | # %run creates some hidden references... | |
442 | _ip.magic("run %s" % self.fname) |
|
442 | _ip.magic("run %s" % self.fname) | |
443 | # ... as does the displayhook. |
|
443 | # ... as does the displayhook. | |
444 | _ip.run_cell("a") |
|
444 | _ip.run_cell("a") | |
445 |
|
445 | |||
446 | monitor = _ip.user_ns["A"].monitor |
|
446 | monitor = _ip.user_ns["A"].monitor | |
447 | nt.assert_equal(monitor, []) |
|
447 | nt.assert_equal(monitor, []) | |
448 |
|
448 | |||
449 | _ip.magic("xdel a") |
|
449 | _ip.magic("xdel a") | |
450 |
|
450 | |||
451 | # Check that a's __del__ method has been called. |
|
451 | # Check that a's __del__ method has been called. | |
452 | nt.assert_equal(monitor, [1]) |
|
452 | nt.assert_equal(monitor, [1]) | |
453 |
|
453 | |||
454 | def doctest_who(): |
|
454 | def doctest_who(): | |
455 | """doctest for %who |
|
455 | """doctest for %who | |
456 |
|
456 | |||
457 | In [1]: %reset -f |
|
457 | In [1]: %reset -f | |
458 |
|
458 | |||
459 | In [2]: alpha = 123 |
|
459 | In [2]: alpha = 123 | |
460 |
|
460 | |||
461 | In [3]: beta = 'beta' |
|
461 | In [3]: beta = 'beta' | |
462 |
|
462 | |||
463 | In [4]: %who int |
|
463 | In [4]: %who int | |
464 | alpha |
|
464 | alpha | |
465 |
|
465 | |||
466 | In [5]: %who str |
|
466 | In [5]: %who str | |
467 | beta |
|
467 | beta | |
468 |
|
468 | |||
469 | In [6]: %whos |
|
469 | In [6]: %whos | |
470 | Variable Type Data/Info |
|
470 | Variable Type Data/Info | |
471 | ---------------------------- |
|
471 | ---------------------------- | |
472 | alpha int 123 |
|
472 | alpha int 123 | |
473 | beta str beta |
|
473 | beta str beta | |
474 |
|
474 | |||
475 | In [7]: %who_ls |
|
475 | In [7]: %who_ls | |
476 | Out[7]: ['alpha', 'beta'] |
|
476 | Out[7]: ['alpha', 'beta'] | |
477 | """ |
|
477 | """ | |
478 |
|
478 | |||
479 | def test_whos(): |
|
479 | def test_whos(): | |
480 | """Check that whos is protected against objects where repr() fails.""" |
|
480 | """Check that whos is protected against objects where repr() fails.""" | |
481 | class A(object): |
|
481 | class A(object): | |
482 | def __repr__(self): |
|
482 | def __repr__(self): | |
483 | raise Exception() |
|
483 | raise Exception() | |
484 | _ip.user_ns['a'] = A() |
|
484 | _ip.user_ns['a'] = A() | |
485 | _ip.magic("whos") |
|
485 | _ip.magic("whos") | |
486 |
|
486 | |||
487 | @py3compat.u_format |
|
487 | @py3compat.u_format | |
488 | def doctest_precision(): |
|
488 | def doctest_precision(): | |
489 | """doctest for %precision |
|
489 | """doctest for %precision | |
490 |
|
490 | |||
491 | In [1]: f = get_ipython().display_formatter.formatters['text/plain'] |
|
491 | In [1]: f = get_ipython().display_formatter.formatters['text/plain'] | |
492 |
|
492 | |||
493 | In [2]: %precision 5 |
|
493 | In [2]: %precision 5 | |
494 | Out[2]: {u}'%.5f' |
|
494 | Out[2]: {u}'%.5f' | |
495 |
|
495 | |||
496 | In [3]: f.float_format |
|
496 | In [3]: f.float_format | |
497 | Out[3]: {u}'%.5f' |
|
497 | Out[3]: {u}'%.5f' | |
498 |
|
498 | |||
499 | In [4]: %precision %e |
|
499 | In [4]: %precision %e | |
500 | Out[4]: {u}'%e' |
|
500 | Out[4]: {u}'%e' | |
501 |
|
501 | |||
502 | In [5]: f(3.1415927) |
|
502 | In [5]: f(3.1415927) | |
503 | Out[5]: {u}'3.141593e+00' |
|
503 | Out[5]: {u}'3.141593e+00' | |
504 | """ |
|
504 | """ | |
505 |
|
505 | |||
506 | def test_psearch(): |
|
506 | def test_psearch(): | |
507 | with tt.AssertPrints("dict.fromkeys"): |
|
507 | with tt.AssertPrints("dict.fromkeys"): | |
508 | _ip.run_cell("dict.fr*?") |
|
508 | _ip.run_cell("dict.fr*?") | |
509 |
|
509 | |||
510 | def test_timeit_shlex(): |
|
510 | def test_timeit_shlex(): | |
511 | """test shlex issues with timeit (#1109)""" |
|
511 | """test shlex issues with timeit (#1109)""" | |
512 | _ip.ex("def f(*a,**kw): pass") |
|
512 | _ip.ex("def f(*a,**kw): pass") | |
513 | _ip.magic('timeit -n1 "this is a bug".count(" ")') |
|
513 | _ip.magic('timeit -n1 "this is a bug".count(" ")') | |
514 | _ip.magic('timeit -r1 -n1 f(" ", 1)') |
|
514 | _ip.magic('timeit -r1 -n1 f(" ", 1)') | |
515 | _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")') |
|
515 | _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")') | |
516 | _ip.magic('timeit -r1 -n1 ("a " + "b")') |
|
516 | _ip.magic('timeit -r1 -n1 ("a " + "b")') | |
517 | _ip.magic('timeit -r1 -n1 f("a " + "b")') |
|
517 | _ip.magic('timeit -r1 -n1 f("a " + "b")') | |
518 | _ip.magic('timeit -r1 -n1 f("a " + "b ")') |
|
518 | _ip.magic('timeit -r1 -n1 f("a " + "b ")') | |
519 |
|
519 | |||
520 |
|
520 | |||
521 | def test_timeit_arguments(): |
|
521 | def test_timeit_arguments(): | |
522 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" |
|
522 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" | |
523 | _ip.magic("timeit ('#')") |
|
523 | _ip.magic("timeit ('#')") | |
524 |
|
524 | |||
525 |
|
525 | |||
526 | def test_timeit_special_syntax(): |
|
526 | def test_timeit_special_syntax(): | |
527 | "Test %%timeit with IPython special syntax" |
|
527 | "Test %%timeit with IPython special syntax" | |
528 | @register_line_magic |
|
528 | @register_line_magic | |
529 | def lmagic(line): |
|
529 | def lmagic(line): | |
530 | ip = get_ipython() |
|
530 | ip = get_ipython() | |
531 | ip.user_ns['lmagic_out'] = line |
|
531 | ip.user_ns['lmagic_out'] = line | |
532 |
|
532 | |||
533 | # line mode test |
|
533 | # line mode test | |
534 | _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line') |
|
534 | _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line') | |
535 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') |
|
535 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') | |
536 | # cell mode test |
|
536 | # cell mode test | |
537 | _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2') |
|
537 | _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2') | |
538 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') |
|
538 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') | |
539 |
|
539 | |||
540 | def test_timeit_return(): |
|
540 | def test_timeit_return(): | |
541 | """ |
|
541 | """ | |
542 | test wether timeit -o return object |
|
542 | test wether timeit -o return object | |
543 | """ |
|
543 | """ | |
544 |
|
544 | |||
545 | res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') |
|
545 | res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') | |
546 | assert(res is not None) |
|
546 | assert(res is not None) | |
547 |
|
547 | |||
548 | def test_timeit_quiet(): |
|
548 | def test_timeit_quiet(): | |
549 | """ |
|
549 | """ | |
550 | test quiet option of timeit magic |
|
550 | test quiet option of timeit magic | |
551 | """ |
|
551 | """ | |
552 | with tt.AssertNotPrints("loops"): |
|
552 | with tt.AssertNotPrints("loops"): | |
553 | _ip.run_cell("%timeit -n1 -r1 -q 1") |
|
553 | _ip.run_cell("%timeit -n1 -r1 -q 1") | |
554 |
|
554 | |||
555 | @dec.skipif(execution.profile is None) |
|
555 | @dec.skipif(execution.profile is None) | |
556 | def test_prun_special_syntax(): |
|
556 | def test_prun_special_syntax(): | |
557 | "Test %%prun with IPython special syntax" |
|
557 | "Test %%prun with IPython special syntax" | |
558 | @register_line_magic |
|
558 | @register_line_magic | |
559 | def lmagic(line): |
|
559 | def lmagic(line): | |
560 | ip = get_ipython() |
|
560 | ip = get_ipython() | |
561 | ip.user_ns['lmagic_out'] = line |
|
561 | ip.user_ns['lmagic_out'] = line | |
562 |
|
562 | |||
563 | # line mode test |
|
563 | # line mode test | |
564 | _ip.run_line_magic('prun', '-q %lmagic my line') |
|
564 | _ip.run_line_magic('prun', '-q %lmagic my line') | |
565 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') |
|
565 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') | |
566 | # cell mode test |
|
566 | # cell mode test | |
567 | _ip.run_cell_magic('prun', '-q', '%lmagic my line2') |
|
567 | _ip.run_cell_magic('prun', '-q', '%lmagic my line2') | |
568 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') |
|
568 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') | |
569 |
|
569 | |||
570 | @dec.skipif(execution.profile is None) |
|
570 | @dec.skipif(execution.profile is None) | |
571 | def test_prun_quotes(): |
|
571 | def test_prun_quotes(): | |
572 | "Test that prun does not clobber string escapes (GH #1302)" |
|
572 | "Test that prun does not clobber string escapes (GH #1302)" | |
573 | _ip.magic(r"prun -q x = '\t'") |
|
573 | _ip.magic(r"prun -q x = '\t'") | |
574 | nt.assert_equal(_ip.user_ns['x'], '\t') |
|
574 | nt.assert_equal(_ip.user_ns['x'], '\t') | |
575 |
|
575 | |||
576 | def test_extension(): |
|
576 | def test_extension(): | |
577 | tmpdir = TemporaryDirectory() |
|
577 | tmpdir = TemporaryDirectory() | |
578 | orig_ipython_dir = _ip.ipython_dir |
|
578 | orig_ipython_dir = _ip.ipython_dir | |
579 | try: |
|
579 | try: | |
580 | _ip.ipython_dir = tmpdir.name |
|
580 | _ip.ipython_dir = tmpdir.name | |
581 | nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension") |
|
581 | nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension") | |
582 | url = os.path.join(os.path.dirname(__file__), "daft_extension.py") |
|
582 | url = os.path.join(os.path.dirname(__file__), "daft_extension.py") | |
583 | _ip.magic("install_ext %s" % url) |
|
583 | _ip.magic("install_ext %s" % url) | |
584 | _ip.user_ns.pop('arq', None) |
|
584 | _ip.user_ns.pop('arq', None) | |
585 | invalidate_caches() # Clear import caches |
|
585 | invalidate_caches() # Clear import caches | |
586 | _ip.magic("load_ext daft_extension") |
|
586 | _ip.magic("load_ext daft_extension") | |
587 | nt.assert_equal(_ip.user_ns['arq'], 185) |
|
587 | nt.assert_equal(_ip.user_ns['arq'], 185) | |
588 | _ip.magic("unload_ext daft_extension") |
|
588 | _ip.magic("unload_ext daft_extension") | |
589 | assert 'arq' not in _ip.user_ns |
|
589 | assert 'arq' not in _ip.user_ns | |
590 | finally: |
|
590 | finally: | |
591 | _ip.ipython_dir = orig_ipython_dir |
|
591 | _ip.ipython_dir = orig_ipython_dir | |
592 | tmpdir.cleanup() |
|
592 | tmpdir.cleanup() | |
593 |
|
593 | |||
594 | def test_notebook_export_json(): |
|
594 | def test_notebook_export_json(): | |
595 | with TemporaryDirectory() as td: |
|
595 | with TemporaryDirectory() as td: | |
596 | outfile = os.path.join(td, "nb.ipynb") |
|
596 | outfile = os.path.join(td, "nb.ipynb") | |
597 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
597 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) | |
598 | _ip.magic("notebook -e %s" % outfile) |
|
598 | _ip.magic("notebook -e %s" % outfile) | |
599 |
|
599 | |||
600 | def test_notebook_export_py(): |
|
600 | def test_notebook_export_py(): | |
601 | with TemporaryDirectory() as td: |
|
601 | with TemporaryDirectory() as td: | |
602 | outfile = os.path.join(td, "nb.py") |
|
602 | outfile = os.path.join(td, "nb.py") | |
603 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
603 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) | |
604 | _ip.magic("notebook -e %s" % outfile) |
|
604 | _ip.magic("notebook -e %s" % outfile) | |
605 |
|
605 | |||
606 | def test_notebook_reformat_py(): |
|
606 | def test_notebook_reformat_py(): | |
607 | with TemporaryDirectory() as td: |
|
607 | with TemporaryDirectory() as td: | |
608 | infile = os.path.join(td, "nb.ipynb") |
|
608 | infile = os.path.join(td, "nb.ipynb") | |
609 | with io.open(infile, 'w', encoding='utf-8') as f: |
|
609 | with io.open(infile, 'w', encoding='utf-8') as f: | |
610 | current.write(nb0, f, 'json') |
|
610 | current.write(nb0, f, 'json') | |
611 |
|
611 | |||
612 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
612 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) | |
613 | _ip.magic("notebook -f py %s" % infile) |
|
613 | _ip.magic("notebook -f py %s" % infile) | |
614 |
|
614 | |||
615 | def test_notebook_reformat_json(): |
|
615 | def test_notebook_reformat_json(): | |
616 | with TemporaryDirectory() as td: |
|
616 | with TemporaryDirectory() as td: | |
617 | infile = os.path.join(td, "nb.py") |
|
617 | infile = os.path.join(td, "nb.py") | |
618 | with io.open(infile, 'w', encoding='utf-8') as f: |
|
618 | with io.open(infile, 'w', encoding='utf-8') as f: | |
619 | current.write(nb0, f, 'py') |
|
619 | current.write(nb0, f, 'py') | |
620 |
|
620 | |||
621 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) |
|
621 | _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'")) | |
622 | _ip.magic("notebook -f ipynb %s" % infile) |
|
622 | _ip.magic("notebook -f ipynb %s" % infile) | |
623 | _ip.magic("notebook -f json %s" % infile) |
|
623 | _ip.magic("notebook -f json %s" % infile) | |
624 |
|
624 | |||
625 | def test_env(): |
|
625 | def test_env(): | |
626 | env = _ip.magic("env") |
|
626 | env = _ip.magic("env") | |
627 | assert isinstance(env, dict), type(env) |
|
627 | assert isinstance(env, dict), type(env) | |
628 |
|
628 | |||
629 |
|
629 | |||
630 | class CellMagicTestCase(TestCase): |
|
630 | class CellMagicTestCase(TestCase): | |
631 |
|
631 | |||
632 | def check_ident(self, magic): |
|
632 | def check_ident(self, magic): | |
633 | # Manually called, we get the result |
|
633 | # Manually called, we get the result | |
634 | out = _ip.run_cell_magic(magic, 'a', 'b') |
|
634 | out = _ip.run_cell_magic(magic, 'a', 'b') | |
635 | nt.assert_equal(out, ('a','b')) |
|
635 | nt.assert_equal(out, ('a','b')) | |
636 | # Via run_cell, it goes into the user's namespace via displayhook |
|
636 | # Via run_cell, it goes into the user's namespace via displayhook | |
637 | _ip.run_cell('%%' + magic +' c\nd') |
|
637 | _ip.run_cell('%%' + magic +' c\nd') | |
638 | nt.assert_equal(_ip.user_ns['_'], ('c','d')) |
|
638 | nt.assert_equal(_ip.user_ns['_'], ('c','d')) | |
639 |
|
639 | |||
640 | def test_cell_magic_func_deco(self): |
|
640 | def test_cell_magic_func_deco(self): | |
641 | "Cell magic using simple decorator" |
|
641 | "Cell magic using simple decorator" | |
642 | @register_cell_magic |
|
642 | @register_cell_magic | |
643 | def cellm(line, cell): |
|
643 | def cellm(line, cell): | |
644 | return line, cell |
|
644 | return line, cell | |
645 |
|
645 | |||
646 | self.check_ident('cellm') |
|
646 | self.check_ident('cellm') | |
647 |
|
647 | |||
648 | def test_cell_magic_reg(self): |
|
648 | def test_cell_magic_reg(self): | |
649 | "Cell magic manually registered" |
|
649 | "Cell magic manually registered" | |
650 | def cellm(line, cell): |
|
650 | def cellm(line, cell): | |
651 | return line, cell |
|
651 | return line, cell | |
652 |
|
652 | |||
653 | _ip.register_magic_function(cellm, 'cell', 'cellm2') |
|
653 | _ip.register_magic_function(cellm, 'cell', 'cellm2') | |
654 | self.check_ident('cellm2') |
|
654 | self.check_ident('cellm2') | |
655 |
|
655 | |||
656 | def test_cell_magic_class(self): |
|
656 | def test_cell_magic_class(self): | |
657 | "Cell magics declared via a class" |
|
657 | "Cell magics declared via a class" | |
658 | @magics_class |
|
658 | @magics_class | |
659 | class MyMagics(Magics): |
|
659 | class MyMagics(Magics): | |
660 |
|
660 | |||
661 | @cell_magic |
|
661 | @cell_magic | |
662 | def cellm3(self, line, cell): |
|
662 | def cellm3(self, line, cell): | |
663 | return line, cell |
|
663 | return line, cell | |
664 |
|
664 | |||
665 | _ip.register_magics(MyMagics) |
|
665 | _ip.register_magics(MyMagics) | |
666 | self.check_ident('cellm3') |
|
666 | self.check_ident('cellm3') | |
667 |
|
667 | |||
668 | def test_cell_magic_class2(self): |
|
668 | def test_cell_magic_class2(self): | |
669 | "Cell magics declared via a class, #2" |
|
669 | "Cell magics declared via a class, #2" | |
670 | @magics_class |
|
670 | @magics_class | |
671 | class MyMagics2(Magics): |
|
671 | class MyMagics2(Magics): | |
672 |
|
672 | |||
673 | @cell_magic('cellm4') |
|
673 | @cell_magic('cellm4') | |
674 | def cellm33(self, line, cell): |
|
674 | def cellm33(self, line, cell): | |
675 | return line, cell |
|
675 | return line, cell | |
676 |
|
676 | |||
677 | _ip.register_magics(MyMagics2) |
|
677 | _ip.register_magics(MyMagics2) | |
678 | self.check_ident('cellm4') |
|
678 | self.check_ident('cellm4') | |
679 | # Check that nothing is registered as 'cellm33' |
|
679 | # Check that nothing is registered as 'cellm33' | |
680 | c33 = _ip.find_cell_magic('cellm33') |
|
680 | c33 = _ip.find_cell_magic('cellm33') | |
681 | nt.assert_equal(c33, None) |
|
681 | nt.assert_equal(c33, None) | |
682 |
|
682 | |||
683 | def test_file(): |
|
683 | def test_file(): | |
684 | """Basic %%file""" |
|
684 | """Basic %%file""" | |
685 | ip = get_ipython() |
|
685 | ip = get_ipython() | |
686 | with TemporaryDirectory() as td: |
|
686 | with TemporaryDirectory() as td: | |
687 | fname = os.path.join(td, 'file1') |
|
687 | fname = os.path.join(td, 'file1') | |
688 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
688 | ip.run_cell_magic("file", fname, u'\n'.join([ | |
689 | 'line1', |
|
689 | 'line1', | |
690 | 'line2', |
|
690 | 'line2', | |
691 | ])) |
|
691 | ])) | |
692 | with open(fname) as f: |
|
692 | with open(fname) as f: | |
693 | s = f.read() |
|
693 | s = f.read() | |
694 | nt.assert_in('line1\n', s) |
|
694 | nt.assert_in('line1\n', s) | |
695 | nt.assert_in('line2', s) |
|
695 | nt.assert_in('line2', s) | |
696 |
|
696 | |||
697 | def test_file_var_expand(): |
|
697 | def test_file_var_expand(): | |
698 | """%%file $filename""" |
|
698 | """%%file $filename""" | |
699 | ip = get_ipython() |
|
699 | ip = get_ipython() | |
700 | with TemporaryDirectory() as td: |
|
700 | with TemporaryDirectory() as td: | |
701 | fname = os.path.join(td, 'file1') |
|
701 | fname = os.path.join(td, 'file1') | |
702 | ip.user_ns['filename'] = fname |
|
702 | ip.user_ns['filename'] = fname | |
703 | ip.run_cell_magic("file", '$filename', u'\n'.join([ |
|
703 | ip.run_cell_magic("file", '$filename', u'\n'.join([ | |
704 | 'line1', |
|
704 | 'line1', | |
705 | 'line2', |
|
705 | 'line2', | |
706 | ])) |
|
706 | ])) | |
707 | with open(fname) as f: |
|
707 | with open(fname) as f: | |
708 | s = f.read() |
|
708 | s = f.read() | |
709 | nt.assert_in('line1\n', s) |
|
709 | nt.assert_in('line1\n', s) | |
710 | nt.assert_in('line2', s) |
|
710 | nt.assert_in('line2', s) | |
711 |
|
711 | |||
712 | def test_file_unicode(): |
|
712 | def test_file_unicode(): | |
713 | """%%file with unicode cell""" |
|
713 | """%%file with unicode cell""" | |
714 | ip = get_ipython() |
|
714 | ip = get_ipython() | |
715 | with TemporaryDirectory() as td: |
|
715 | with TemporaryDirectory() as td: | |
716 | fname = os.path.join(td, 'file1') |
|
716 | fname = os.path.join(td, 'file1') | |
717 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
717 | ip.run_cell_magic("file", fname, u'\n'.join([ | |
718 | u'linΓ©1', |
|
718 | u'linΓ©1', | |
719 | u'linΓ©2', |
|
719 | u'linΓ©2', | |
720 | ])) |
|
720 | ])) | |
721 | with io.open(fname, encoding='utf-8') as f: |
|
721 | with io.open(fname, encoding='utf-8') as f: | |
722 | s = f.read() |
|
722 | s = f.read() | |
723 | nt.assert_in(u'linΓ©1\n', s) |
|
723 | nt.assert_in(u'linΓ©1\n', s) | |
724 | nt.assert_in(u'linΓ©2', s) |
|
724 | nt.assert_in(u'linΓ©2', s) | |
725 |
|
725 | |||
726 | def test_file_amend(): |
|
726 | def test_file_amend(): | |
727 | """%%file -a amends files""" |
|
727 | """%%file -a amends files""" | |
728 | ip = get_ipython() |
|
728 | ip = get_ipython() | |
729 | with TemporaryDirectory() as td: |
|
729 | with TemporaryDirectory() as td: | |
730 | fname = os.path.join(td, 'file2') |
|
730 | fname = os.path.join(td, 'file2') | |
731 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
731 | ip.run_cell_magic("file", fname, u'\n'.join([ | |
732 | 'line1', |
|
732 | 'line1', | |
733 | 'line2', |
|
733 | 'line2', | |
734 | ])) |
|
734 | ])) | |
735 | ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([ |
|
735 | ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([ | |
736 | 'line3', |
|
736 | 'line3', | |
737 | 'line4', |
|
737 | 'line4', | |
738 | ])) |
|
738 | ])) | |
739 | with open(fname) as f: |
|
739 | with open(fname) as f: | |
740 | s = f.read() |
|
740 | s = f.read() | |
741 | nt.assert_in('line1\n', s) |
|
741 | nt.assert_in('line1\n', s) | |
742 | nt.assert_in('line3\n', s) |
|
742 | nt.assert_in('line3\n', s) | |
743 |
|
743 | |||
744 |
|
744 | |||
745 | def test_script_config(): |
|
745 | def test_script_config(): | |
746 | ip = get_ipython() |
|
746 | ip = get_ipython() | |
747 | ip.config.ScriptMagics.script_magics = ['whoda'] |
|
747 | ip.config.ScriptMagics.script_magics = ['whoda'] | |
748 | sm = script.ScriptMagics(shell=ip) |
|
748 | sm = script.ScriptMagics(shell=ip) | |
749 | nt.assert_in('whoda', sm.magics['cell']) |
|
749 | nt.assert_in('whoda', sm.magics['cell']) | |
750 |
|
750 | |||
751 | @dec.skip_win32 |
|
751 | @dec.skip_win32 | |
752 | def test_script_out(): |
|
752 | def test_script_out(): | |
753 | ip = get_ipython() |
|
753 | ip = get_ipython() | |
754 | ip.run_cell_magic("script", "--out output sh", "echo 'hi'") |
|
754 | ip.run_cell_magic("script", "--out output sh", "echo 'hi'") | |
755 | nt.assert_equal(ip.user_ns['output'], 'hi\n') |
|
755 | nt.assert_equal(ip.user_ns['output'], 'hi\n') | |
756 |
|
756 | |||
757 | @dec.skip_win32 |
|
757 | @dec.skip_win32 | |
758 | def test_script_err(): |
|
758 | def test_script_err(): | |
759 | ip = get_ipython() |
|
759 | ip = get_ipython() | |
760 | ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2") |
|
760 | ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2") | |
761 | nt.assert_equal(ip.user_ns['error'], 'hello\n') |
|
761 | nt.assert_equal(ip.user_ns['error'], 'hello\n') | |
762 |
|
762 | |||
763 | @dec.skip_win32 |
|
763 | @dec.skip_win32 | |
764 | def test_script_out_err(): |
|
764 | def test_script_out_err(): | |
765 | ip = get_ipython() |
|
765 | ip = get_ipython() | |
766 | ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2") |
|
766 | ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2") | |
767 | nt.assert_equal(ip.user_ns['output'], 'hi\n') |
|
767 | nt.assert_equal(ip.user_ns['output'], 'hi\n') | |
768 | nt.assert_equal(ip.user_ns['error'], 'hello\n') |
|
768 | nt.assert_equal(ip.user_ns['error'], 'hello\n') | |
769 |
|
769 | |||
770 | @dec.skip_win32 |
|
770 | @dec.skip_win32 | |
771 | def test_script_bg_out(): |
|
771 | def test_script_bg_out(): | |
772 | ip = get_ipython() |
|
772 | ip = get_ipython() | |
773 | ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") |
|
773 | ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") | |
774 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') |
|
774 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') | |
775 |
|
775 | |||
776 | @dec.skip_win32 |
|
776 | @dec.skip_win32 | |
777 | def test_script_bg_err(): |
|
777 | def test_script_bg_err(): | |
778 | ip = get_ipython() |
|
778 | ip = get_ipython() | |
779 | ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2") |
|
779 | ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2") | |
780 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') |
|
780 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') | |
781 |
|
781 | |||
782 | @dec.skip_win32 |
|
782 | @dec.skip_win32 | |
783 | def test_script_bg_out_err(): |
|
783 | def test_script_bg_out_err(): | |
784 | ip = get_ipython() |
|
784 | ip = get_ipython() | |
785 | ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2") |
|
785 | ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2") | |
786 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') |
|
786 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') | |
787 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') |
|
787 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') | |
788 |
|
788 | |||
789 | def test_script_defaults(): |
|
789 | def test_script_defaults(): | |
790 | ip = get_ipython() |
|
790 | ip = get_ipython() | |
791 | for cmd in ['sh', 'bash', 'perl', 'ruby']: |
|
791 | for cmd in ['sh', 'bash', 'perl', 'ruby']: | |
792 | try: |
|
792 | try: | |
793 | find_cmd(cmd) |
|
793 | find_cmd(cmd) | |
794 | except Exception: |
|
794 | except Exception: | |
795 | pass |
|
795 | pass | |
796 | else: |
|
796 | else: | |
797 | nt.assert_in(cmd, ip.magics_manager.magics['cell']) |
|
797 | nt.assert_in(cmd, ip.magics_manager.magics['cell']) | |
798 |
|
798 | |||
799 |
|
799 | |||
800 | @magics_class |
|
800 | @magics_class | |
801 | class FooFoo(Magics): |
|
801 | class FooFoo(Magics): | |
802 | """class with both %foo and %%foo magics""" |
|
802 | """class with both %foo and %%foo magics""" | |
803 | @line_magic('foo') |
|
803 | @line_magic('foo') | |
804 | def line_foo(self, line): |
|
804 | def line_foo(self, line): | |
805 | "I am line foo" |
|
805 | "I am line foo" | |
806 | pass |
|
806 | pass | |
807 |
|
807 | |||
808 | @cell_magic("foo") |
|
808 | @cell_magic("foo") | |
809 | def cell_foo(self, line, cell): |
|
809 | def cell_foo(self, line, cell): | |
810 | "I am cell foo, not line foo" |
|
810 | "I am cell foo, not line foo" | |
811 | pass |
|
811 | pass | |
812 |
|
812 | |||
813 | def test_line_cell_info(): |
|
813 | def test_line_cell_info(): | |
814 | """%%foo and %foo magics are distinguishable to inspect""" |
|
814 | """%%foo and %foo magics are distinguishable to inspect""" | |
815 | ip = get_ipython() |
|
815 | ip = get_ipython() | |
816 | ip.magics_manager.register(FooFoo) |
|
816 | ip.magics_manager.register(FooFoo) | |
817 | oinfo = ip.object_inspect('foo') |
|
817 | oinfo = ip.object_inspect('foo') | |
818 | nt.assert_true(oinfo['found']) |
|
818 | nt.assert_true(oinfo['found']) | |
819 | nt.assert_true(oinfo['ismagic']) |
|
819 | nt.assert_true(oinfo['ismagic']) | |
820 |
|
820 | |||
821 | oinfo = ip.object_inspect('%%foo') |
|
821 | oinfo = ip.object_inspect('%%foo') | |
822 | nt.assert_true(oinfo['found']) |
|
822 | nt.assert_true(oinfo['found']) | |
823 | nt.assert_true(oinfo['ismagic']) |
|
823 | nt.assert_true(oinfo['ismagic']) | |
824 | nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__) |
|
824 | nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__) | |
825 |
|
825 | |||
826 | oinfo = ip.object_inspect('%foo') |
|
826 | oinfo = ip.object_inspect('%foo') | |
827 | nt.assert_true(oinfo['found']) |
|
827 | nt.assert_true(oinfo['found']) | |
828 | nt.assert_true(oinfo['ismagic']) |
|
828 | nt.assert_true(oinfo['ismagic']) | |
829 | nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__) |
|
829 | nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__) | |
830 |
|
830 | |||
831 | def test_multiple_magics(): |
|
831 | def test_multiple_magics(): | |
832 | ip = get_ipython() |
|
832 | ip = get_ipython() | |
833 | foo1 = FooFoo(ip) |
|
833 | foo1 = FooFoo(ip) | |
834 | foo2 = FooFoo(ip) |
|
834 | foo2 = FooFoo(ip) | |
835 | mm = ip.magics_manager |
|
835 | mm = ip.magics_manager | |
836 | mm.register(foo1) |
|
836 | mm.register(foo1) | |
837 | nt.assert_true(mm.magics['line']['foo'].__self__ is foo1) |
|
837 | nt.assert_true(mm.magics['line']['foo'].__self__ is foo1) | |
838 | mm.register(foo2) |
|
838 | mm.register(foo2) | |
839 | nt.assert_true(mm.magics['line']['foo'].__self__ is foo2) |
|
839 | nt.assert_true(mm.magics['line']['foo'].__self__ is foo2) | |
840 |
|
840 | |||
841 | def test_alias_magic(): |
|
841 | def test_alias_magic(): | |
842 | """Test %alias_magic.""" |
|
842 | """Test %alias_magic.""" | |
843 | ip = get_ipython() |
|
843 | ip = get_ipython() | |
844 | mm = ip.magics_manager |
|
844 | mm = ip.magics_manager | |
845 |
|
845 | |||
846 | # Basic operation: both cell and line magics are created, if possible. |
|
846 | # Basic operation: both cell and line magics are created, if possible. | |
847 | ip.run_line_magic('alias_magic', 'timeit_alias timeit') |
|
847 | ip.run_line_magic('alias_magic', 'timeit_alias timeit') | |
848 | nt.assert_in('timeit_alias', mm.magics['line']) |
|
848 | nt.assert_in('timeit_alias', mm.magics['line']) | |
849 | nt.assert_in('timeit_alias', mm.magics['cell']) |
|
849 | nt.assert_in('timeit_alias', mm.magics['cell']) | |
850 |
|
850 | |||
851 | # --cell is specified, line magic not created. |
|
851 | # --cell is specified, line magic not created. | |
852 | ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit') |
|
852 | ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit') | |
853 | nt.assert_not_in('timeit_cell_alias', mm.magics['line']) |
|
853 | nt.assert_not_in('timeit_cell_alias', mm.magics['line']) | |
854 | nt.assert_in('timeit_cell_alias', mm.magics['cell']) |
|
854 | nt.assert_in('timeit_cell_alias', mm.magics['cell']) | |
855 |
|
855 | |||
856 | # Test that line alias is created successfully. |
|
856 | # Test that line alias is created successfully. | |
857 | ip.run_line_magic('alias_magic', '--line env_alias env') |
|
857 | ip.run_line_magic('alias_magic', '--line env_alias env') | |
858 | nt.assert_equal(ip.run_line_magic('env', ''), |
|
858 | nt.assert_equal(ip.run_line_magic('env', ''), | |
859 | ip.run_line_magic('env_alias', '')) |
|
859 | ip.run_line_magic('env_alias', '')) | |
860 |
|
860 | |||
861 | def test_save(): |
|
861 | def test_save(): | |
862 | """Test %save.""" |
|
862 | """Test %save.""" | |
863 | ip = get_ipython() |
|
863 | ip = get_ipython() | |
864 | ip.history_manager.reset() # Clear any existing history. |
|
864 | ip.history_manager.reset() # Clear any existing history. | |
865 | cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"] |
|
865 | cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"] | |
866 | for i, cmd in enumerate(cmds, start=1): |
|
866 | for i, cmd in enumerate(cmds, start=1): | |
867 | ip.history_manager.store_inputs(i, cmd) |
|
867 | ip.history_manager.store_inputs(i, cmd) | |
868 | with TemporaryDirectory() as tmpdir: |
|
868 | with TemporaryDirectory() as tmpdir: | |
869 | file = os.path.join(tmpdir, "testsave.py") |
|
869 | file = os.path.join(tmpdir, "testsave.py") | |
870 | ip.run_line_magic("save", "%s 1-10" % file) |
|
870 | ip.run_line_magic("save", "%s 1-10" % file) | |
871 | with open(file) as f: |
|
871 | with open(file) as f: | |
872 | content = f.read() |
|
872 | content = f.read() | |
873 | nt.assert_equal(content.count(cmds[0]), 1) |
|
873 | nt.assert_equal(content.count(cmds[0]), 1) | |
874 | nt.assert_in('coding: utf-8', content) |
|
874 | nt.assert_in('coding: utf-8', content) | |
875 | ip.run_line_magic("save", "-a %s 1-10" % file) |
|
875 | ip.run_line_magic("save", "-a %s 1-10" % file) | |
876 | with open(file) as f: |
|
876 | with open(file) as f: | |
877 | content = f.read() |
|
877 | content = f.read() | |
878 | nt.assert_equal(content.count(cmds[0]), 2) |
|
878 | nt.assert_equal(content.count(cmds[0]), 2) | |
879 | nt.assert_in('coding: utf-8', content) |
|
879 | nt.assert_in('coding: utf-8', content) | |
880 |
|
880 | |||
881 |
|
881 | |||
882 | def test_store(): |
|
882 | def test_store(): | |
883 | """Test %store.""" |
|
883 | """Test %store.""" | |
884 | ip = get_ipython() |
|
884 | ip = get_ipython() | |
885 | ip.run_line_magic('load_ext', 'storemagic') |
|
885 | ip.run_line_magic('load_ext', 'storemagic') | |
886 |
|
886 | |||
887 | # make sure the storage is empty |
|
887 | # make sure the storage is empty | |
888 | ip.run_line_magic('store', '-z') |
|
888 | ip.run_line_magic('store', '-z') | |
889 | ip.user_ns['var'] = 42 |
|
889 | ip.user_ns['var'] = 42 | |
890 | ip.run_line_magic('store', 'var') |
|
890 | ip.run_line_magic('store', 'var') | |
891 | ip.user_ns['var'] = 39 |
|
891 | ip.user_ns['var'] = 39 | |
892 | ip.run_line_magic('store', '-r') |
|
892 | ip.run_line_magic('store', '-r') | |
893 | nt.assert_equal(ip.user_ns['var'], 42) |
|
893 | nt.assert_equal(ip.user_ns['var'], 42) | |
894 |
|
894 | |||
895 | ip.run_line_magic('store', '-d var') |
|
895 | ip.run_line_magic('store', '-d var') | |
896 | ip.user_ns['var'] = 39 |
|
896 | ip.user_ns['var'] = 39 | |
897 | ip.run_line_magic('store' , '-r') |
|
897 | ip.run_line_magic('store' , '-r') | |
898 | nt.assert_equal(ip.user_ns['var'], 39) |
|
898 | nt.assert_equal(ip.user_ns['var'], 39) | |
899 |
|
899 | |||
900 |
|
900 | |||
901 | def _run_edit_test(arg_s, exp_filename=None, |
|
901 | def _run_edit_test(arg_s, exp_filename=None, | |
902 | exp_lineno=-1, |
|
902 | exp_lineno=-1, | |
903 | exp_contents=None, |
|
903 | exp_contents=None, | |
904 | exp_is_temp=None): |
|
904 | exp_is_temp=None): | |
905 | ip = get_ipython() |
|
905 | ip = get_ipython() | |
906 | M = code.CodeMagics(ip) |
|
906 | M = code.CodeMagics(ip) | |
907 | last_call = ['',''] |
|
907 | last_call = ['',''] | |
908 | opts,args = M.parse_options(arg_s,'prxn:') |
|
908 | opts,args = M.parse_options(arg_s,'prxn:') | |
909 | filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) |
|
909 | filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) | |
910 |
|
910 | |||
911 | if exp_filename is not None: |
|
911 | if exp_filename is not None: | |
912 | nt.assert_equal(exp_filename, filename) |
|
912 | nt.assert_equal(exp_filename, filename) | |
913 | if exp_contents is not None: |
|
913 | if exp_contents is not None: | |
914 | with io.open(filename, 'r') as f: |
|
914 | with io.open(filename, 'r') as f: | |
915 | contents = f.read() |
|
915 | contents = f.read() | |
916 | nt.assert_equal(exp_contents, contents) |
|
916 | nt.assert_equal(exp_contents, contents) | |
917 | if exp_lineno != -1: |
|
917 | if exp_lineno != -1: | |
918 | nt.assert_equal(exp_lineno, lineno) |
|
918 | nt.assert_equal(exp_lineno, lineno) | |
919 | if exp_is_temp is not None: |
|
919 | if exp_is_temp is not None: | |
920 | nt.assert_equal(exp_is_temp, is_temp) |
|
920 | nt.assert_equal(exp_is_temp, is_temp) | |
921 |
|
921 | |||
922 |
|
922 | |||
923 | def test_edit_interactive(): |
|
923 | def test_edit_interactive(): | |
924 | """%edit on interactively defined objects""" |
|
924 | """%edit on interactively defined objects""" | |
925 | ip = get_ipython() |
|
925 | ip = get_ipython() | |
926 | n = ip.execution_count |
|
926 | n = ip.execution_count | |
927 | ip.run_cell(u"def foo(): return 1", store_history=True) |
|
927 | ip.run_cell(u"def foo(): return 1", store_history=True) | |
928 |
|
928 | |||
929 | try: |
|
929 | try: | |
930 | _run_edit_test("foo") |
|
930 | _run_edit_test("foo") | |
931 | except code.InteractivelyDefined as e: |
|
931 | except code.InteractivelyDefined as e: | |
932 | nt.assert_equal(e.index, n) |
|
932 | nt.assert_equal(e.index, n) | |
933 | else: |
|
933 | else: | |
934 | raise AssertionError("Should have raised InteractivelyDefined") |
|
934 | raise AssertionError("Should have raised InteractivelyDefined") | |
935 |
|
935 | |||
936 |
|
936 | |||
937 | def test_edit_cell(): |
|
937 | def test_edit_cell(): | |
938 | """%edit [cell id]""" |
|
938 | """%edit [cell id]""" | |
939 | ip = get_ipython() |
|
939 | ip = get_ipython() | |
940 |
|
940 | |||
941 | ip.run_cell(u"def foo(): return 1", store_history=True) |
|
941 | ip.run_cell(u"def foo(): return 1", store_history=True) | |
942 |
|
942 | |||
943 | # test |
|
943 | # test | |
944 | _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) |
|
944 | _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) |
@@ -1,111 +1,112 b'' | |||||
1 | # -*- coding: utf-8 |
|
1 | # -*- coding: utf-8 | |
2 | """Tests for prompt generation.""" |
|
2 | """Tests for prompt generation.""" | |
3 |
|
3 | |||
4 | import unittest |
|
4 | import unittest | |
5 |
|
5 | |||
6 | import os |
|
6 | import os | |
7 |
|
7 | |||
8 | from IPython.testing import tools as tt, decorators as dec |
|
8 | from IPython.testing import tools as tt, decorators as dec | |
9 | from IPython.core.prompts import PromptManager, LazyEvaluate |
|
9 | from IPython.core.prompts import PromptManager, LazyEvaluate | |
10 | from IPython.testing.globalipapp import get_ipython |
|
10 | from IPython.testing.globalipapp import get_ipython | |
11 | from IPython.utils.tempdir import TemporaryDirectory |
|
11 | from IPython.utils.tempdir import TemporaryDirectory | |
|
12 | from IPython.utils import py3compat | |||
12 | from IPython.utils.py3compat import unicode_type |
|
13 | from IPython.utils.py3compat import unicode_type | |
13 |
|
14 | |||
14 | ip = get_ipython() |
|
15 | ip = get_ipython() | |
15 |
|
16 | |||
16 |
|
17 | |||
17 | class PromptTests(unittest.TestCase): |
|
18 | class PromptTests(unittest.TestCase): | |
18 | def setUp(self): |
|
19 | def setUp(self): | |
19 | self.pm = PromptManager(shell=ip, config=ip.config) |
|
20 | self.pm = PromptManager(shell=ip, config=ip.config) | |
20 |
|
21 | |||
21 | def test_multiline_prompt(self): |
|
22 | def test_multiline_prompt(self): | |
22 | self.pm.in_template = "[In]\n>>>" |
|
23 | self.pm.in_template = "[In]\n>>>" | |
23 | self.pm.render('in') |
|
24 | self.pm.render('in') | |
24 | self.assertEqual(self.pm.width, 3) |
|
25 | self.assertEqual(self.pm.width, 3) | |
25 | self.assertEqual(self.pm.txtwidth, 3) |
|
26 | self.assertEqual(self.pm.txtwidth, 3) | |
26 |
|
27 | |||
27 | self.pm.in_template = '[In]\n' |
|
28 | self.pm.in_template = '[In]\n' | |
28 | self.pm.render('in') |
|
29 | self.pm.render('in') | |
29 | self.assertEqual(self.pm.width, 0) |
|
30 | self.assertEqual(self.pm.width, 0) | |
30 | self.assertEqual(self.pm.txtwidth, 0) |
|
31 | self.assertEqual(self.pm.txtwidth, 0) | |
31 |
|
32 | |||
32 | def test_translate_abbreviations(self): |
|
33 | def test_translate_abbreviations(self): | |
33 | def do_translate(template): |
|
34 | def do_translate(template): | |
34 | self.pm.in_template = template |
|
35 | self.pm.in_template = template | |
35 | return self.pm.templates['in'] |
|
36 | return self.pm.templates['in'] | |
36 |
|
37 | |||
37 | pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'), |
|
38 | pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'), | |
38 | (r'\T', '{time}'), |
|
39 | (r'\T', '{time}'), | |
39 | (r'\n', '\n') |
|
40 | (r'\n', '\n') | |
40 | ] |
|
41 | ] | |
41 |
|
42 | |||
42 | tt.check_pairs(do_translate, pairs) |
|
43 | tt.check_pairs(do_translate, pairs) | |
43 |
|
44 | |||
44 | def test_user_ns(self): |
|
45 | def test_user_ns(self): | |
45 | self.pm.color_scheme = 'NoColor' |
|
46 | self.pm.color_scheme = 'NoColor' | |
46 | ip.ex("foo='bar'") |
|
47 | ip.ex("foo='bar'") | |
47 | self.pm.in_template = "In [{foo}]" |
|
48 | self.pm.in_template = "In [{foo}]" | |
48 | prompt = self.pm.render('in') |
|
49 | prompt = self.pm.render('in') | |
49 | self.assertEqual(prompt, u'In [bar]') |
|
50 | self.assertEqual(prompt, u'In [bar]') | |
50 |
|
51 | |||
51 | def test_builtins(self): |
|
52 | def test_builtins(self): | |
52 | self.pm.color_scheme = 'NoColor' |
|
53 | self.pm.color_scheme = 'NoColor' | |
53 | self.pm.in_template = "In [{int}]" |
|
54 | self.pm.in_template = "In [{int}]" | |
54 | prompt = self.pm.render('in') |
|
55 | prompt = self.pm.render('in') | |
55 | self.assertEqual(prompt, u"In [%r]" % int) |
|
56 | self.assertEqual(prompt, u"In [%r]" % int) | |
56 |
|
57 | |||
57 | def test_undefined(self): |
|
58 | def test_undefined(self): | |
58 | self.pm.color_scheme = 'NoColor' |
|
59 | self.pm.color_scheme = 'NoColor' | |
59 | self.pm.in_template = "In [{foo_dne}]" |
|
60 | self.pm.in_template = "In [{foo_dne}]" | |
60 | prompt = self.pm.render('in') |
|
61 | prompt = self.pm.render('in') | |
61 | self.assertEqual(prompt, u"In [<ERROR: 'foo_dne' not found>]") |
|
62 | self.assertEqual(prompt, u"In [<ERROR: 'foo_dne' not found>]") | |
62 |
|
63 | |||
63 | def test_render(self): |
|
64 | def test_render(self): | |
64 | self.pm.in_template = r'\#>' |
|
65 | self.pm.in_template = r'\#>' | |
65 | self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count) |
|
66 | self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count) | |
66 |
|
67 | |||
67 | @dec.onlyif_unicode_paths |
|
68 | @dec.onlyif_unicode_paths | |
68 | def test_render_unicode_cwd(self): |
|
69 | def test_render_unicode_cwd(self): | |
69 |
save = |
|
70 | save = py3compat.getcwd() | |
70 | with TemporaryDirectory(u'ΓΌnicΓΈdΓ©') as td: |
|
71 | with TemporaryDirectory(u'ΓΌnicΓΈdΓ©') as td: | |
71 | os.chdir(td) |
|
72 | os.chdir(td) | |
72 | self.pm.in_template = r'\w [\#]' |
|
73 | self.pm.in_template = r'\w [\#]' | |
73 | p = self.pm.render('in', color=False) |
|
74 | p = self.pm.render('in', color=False) | |
74 |
self.assertEqual(p, u"%s [%i]" % ( |
|
75 | self.assertEqual(p, u"%s [%i]" % (py3compat.getcwd(), ip.execution_count)) | |
75 | os.chdir(save) |
|
76 | os.chdir(save) | |
76 |
|
77 | |||
77 | def test_lazy_eval_unicode(self): |
|
78 | def test_lazy_eval_unicode(self): | |
78 | u = u'ΓΌnicΓΈdΓ©' |
|
79 | u = u'ΓΌnicΓΈdΓ©' | |
79 | lz = LazyEvaluate(lambda : u) |
|
80 | lz = LazyEvaluate(lambda : u) | |
80 | # str(lz) would fail |
|
81 | # str(lz) would fail | |
81 | self.assertEqual(unicode_type(lz), u) |
|
82 | self.assertEqual(unicode_type(lz), u) | |
82 | self.assertEqual(format(lz), u) |
|
83 | self.assertEqual(format(lz), u) | |
83 |
|
84 | |||
84 | def test_lazy_eval_nonascii_bytes(self): |
|
85 | def test_lazy_eval_nonascii_bytes(self): | |
85 | u = u'ΓΌnicΓΈdΓ©' |
|
86 | u = u'ΓΌnicΓΈdΓ©' | |
86 | b = u.encode('utf8') |
|
87 | b = u.encode('utf8') | |
87 | lz = LazyEvaluate(lambda : b) |
|
88 | lz = LazyEvaluate(lambda : b) | |
88 | # unicode(lz) would fail |
|
89 | # unicode(lz) would fail | |
89 | self.assertEqual(str(lz), str(b)) |
|
90 | self.assertEqual(str(lz), str(b)) | |
90 | self.assertEqual(format(lz), str(b)) |
|
91 | self.assertEqual(format(lz), str(b)) | |
91 |
|
92 | |||
92 | def test_lazy_eval_float(self): |
|
93 | def test_lazy_eval_float(self): | |
93 | f = 0.503 |
|
94 | f = 0.503 | |
94 | lz = LazyEvaluate(lambda : f) |
|
95 | lz = LazyEvaluate(lambda : f) | |
95 |
|
96 | |||
96 | self.assertEqual(str(lz), str(f)) |
|
97 | self.assertEqual(str(lz), str(f)) | |
97 | self.assertEqual(unicode_type(lz), unicode_type(f)) |
|
98 | self.assertEqual(unicode_type(lz), unicode_type(f)) | |
98 | self.assertEqual(format(lz), str(f)) |
|
99 | self.assertEqual(format(lz), str(f)) | |
99 | self.assertEqual(format(lz, '.1'), '0.5') |
|
100 | self.assertEqual(format(lz, '.1'), '0.5') | |
100 |
|
101 | |||
101 | @dec.skip_win32 |
|
102 | @dec.skip_win32 | |
102 | def test_cwd_x(self): |
|
103 | def test_cwd_x(self): | |
103 | self.pm.in_template = r"\X0" |
|
104 | self.pm.in_template = r"\X0" | |
104 |
save = |
|
105 | save = py3compat.getcwd() | |
105 | os.chdir(os.path.expanduser('~')) |
|
106 | os.chdir(os.path.expanduser('~')) | |
106 | p = self.pm.render('in', color=False) |
|
107 | p = self.pm.render('in', color=False) | |
107 | try: |
|
108 | try: | |
108 | self.assertEqual(p, '~') |
|
109 | self.assertEqual(p, '~') | |
109 | finally: |
|
110 | finally: | |
110 | os.chdir(save) |
|
111 | os.chdir(save) | |
111 |
|
112 |
@@ -1,458 +1,458 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """Tests for code execution (%run and related), which is particularly tricky. |
|
2 | """Tests for code execution (%run and related), which is particularly tricky. | |
3 |
|
3 | |||
4 | Because of how %run manages namespaces, and the fact that we are trying here to |
|
4 | Because of how %run manages namespaces, and the fact that we are trying here to | |
5 | verify subtle object deletion and reference counting issues, the %run tests |
|
5 | verify subtle object deletion and reference counting issues, the %run tests | |
6 | will be kept in this separate file. This makes it easier to aggregate in one |
|
6 | will be kept in this separate file. This makes it easier to aggregate in one | |
7 | place the tricks needed to handle it; most other magics are much easier to test |
|
7 | place the tricks needed to handle it; most other magics are much easier to test | |
8 | and we do so in a common test_magic file. |
|
8 | and we do so in a common test_magic file. | |
9 | """ |
|
9 | """ | |
10 | from __future__ import absolute_import |
|
10 | from __future__ import absolute_import | |
11 |
|
11 | |||
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 | # Imports |
|
13 | # Imports | |
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 |
|
15 | |||
16 | import functools |
|
16 | import functools | |
17 | import os |
|
17 | import os | |
18 | from os.path import join as pjoin |
|
18 | from os.path import join as pjoin | |
19 | import random |
|
19 | import random | |
20 | import sys |
|
20 | import sys | |
21 | import tempfile |
|
21 | import tempfile | |
22 | import textwrap |
|
22 | import textwrap | |
23 | import unittest |
|
23 | import unittest | |
24 |
|
24 | |||
25 | import nose.tools as nt |
|
25 | import nose.tools as nt | |
26 | from nose import SkipTest |
|
26 | from nose import SkipTest | |
27 |
|
27 | |||
28 | from IPython.testing import decorators as dec |
|
28 | from IPython.testing import decorators as dec | |
29 | from IPython.testing import tools as tt |
|
29 | from IPython.testing import tools as tt | |
30 | from IPython.utils import py3compat |
|
30 | from IPython.utils import py3compat | |
31 | from IPython.utils.tempdir import TemporaryDirectory |
|
31 | from IPython.utils.tempdir import TemporaryDirectory | |
32 | from IPython.core import debugger |
|
32 | from IPython.core import debugger | |
33 |
|
33 | |||
34 | #----------------------------------------------------------------------------- |
|
34 | #----------------------------------------------------------------------------- | |
35 | # Test functions begin |
|
35 | # Test functions begin | |
36 | #----------------------------------------------------------------------------- |
|
36 | #----------------------------------------------------------------------------- | |
37 |
|
37 | |||
38 | def doctest_refbug(): |
|
38 | def doctest_refbug(): | |
39 | """Very nasty problem with references held by multiple runs of a script. |
|
39 | """Very nasty problem with references held by multiple runs of a script. | |
40 | See: https://github.com/ipython/ipython/issues/141 |
|
40 | See: https://github.com/ipython/ipython/issues/141 | |
41 |
|
41 | |||
42 | In [1]: _ip.clear_main_mod_cache() |
|
42 | In [1]: _ip.clear_main_mod_cache() | |
43 | # random |
|
43 | # random | |
44 |
|
44 | |||
45 | In [2]: %run refbug |
|
45 | In [2]: %run refbug | |
46 |
|
46 | |||
47 | In [3]: call_f() |
|
47 | In [3]: call_f() | |
48 | lowercased: hello |
|
48 | lowercased: hello | |
49 |
|
49 | |||
50 | In [4]: %run refbug |
|
50 | In [4]: %run refbug | |
51 |
|
51 | |||
52 | In [5]: call_f() |
|
52 | In [5]: call_f() | |
53 | lowercased: hello |
|
53 | lowercased: hello | |
54 | lowercased: hello |
|
54 | lowercased: hello | |
55 | """ |
|
55 | """ | |
56 |
|
56 | |||
57 |
|
57 | |||
58 | def doctest_run_builtins(): |
|
58 | def doctest_run_builtins(): | |
59 | r"""Check that %run doesn't damage __builtins__. |
|
59 | r"""Check that %run doesn't damage __builtins__. | |
60 |
|
60 | |||
61 | In [1]: import tempfile |
|
61 | In [1]: import tempfile | |
62 |
|
62 | |||
63 | In [2]: bid1 = id(__builtins__) |
|
63 | In [2]: bid1 = id(__builtins__) | |
64 |
|
64 | |||
65 | In [3]: fname = tempfile.mkstemp('.py')[1] |
|
65 | In [3]: fname = tempfile.mkstemp('.py')[1] | |
66 |
|
66 | |||
67 | In [3]: f = open(fname,'w') |
|
67 | In [3]: f = open(fname,'w') | |
68 |
|
68 | |||
69 | In [4]: dummy= f.write('pass\n') |
|
69 | In [4]: dummy= f.write('pass\n') | |
70 |
|
70 | |||
71 | In [5]: f.flush() |
|
71 | In [5]: f.flush() | |
72 |
|
72 | |||
73 | In [6]: t1 = type(__builtins__) |
|
73 | In [6]: t1 = type(__builtins__) | |
74 |
|
74 | |||
75 | In [7]: %run $fname |
|
75 | In [7]: %run $fname | |
76 |
|
76 | |||
77 | In [7]: f.close() |
|
77 | In [7]: f.close() | |
78 |
|
78 | |||
79 | In [8]: bid2 = id(__builtins__) |
|
79 | In [8]: bid2 = id(__builtins__) | |
80 |
|
80 | |||
81 | In [9]: t2 = type(__builtins__) |
|
81 | In [9]: t2 = type(__builtins__) | |
82 |
|
82 | |||
83 | In [10]: t1 == t2 |
|
83 | In [10]: t1 == t2 | |
84 | Out[10]: True |
|
84 | Out[10]: True | |
85 |
|
85 | |||
86 | In [10]: bid1 == bid2 |
|
86 | In [10]: bid1 == bid2 | |
87 | Out[10]: True |
|
87 | Out[10]: True | |
88 |
|
88 | |||
89 | In [12]: try: |
|
89 | In [12]: try: | |
90 | ....: os.unlink(fname) |
|
90 | ....: os.unlink(fname) | |
91 | ....: except: |
|
91 | ....: except: | |
92 | ....: pass |
|
92 | ....: pass | |
93 | ....: |
|
93 | ....: | |
94 | """ |
|
94 | """ | |
95 |
|
95 | |||
96 |
|
96 | |||
97 | def doctest_run_option_parser(): |
|
97 | def doctest_run_option_parser(): | |
98 | r"""Test option parser in %run. |
|
98 | r"""Test option parser in %run. | |
99 |
|
99 | |||
100 | In [1]: %run print_argv.py |
|
100 | In [1]: %run print_argv.py | |
101 | [] |
|
101 | [] | |
102 |
|
102 | |||
103 | In [2]: %run print_argv.py print*.py |
|
103 | In [2]: %run print_argv.py print*.py | |
104 | ['print_argv.py'] |
|
104 | ['print_argv.py'] | |
105 |
|
105 | |||
106 | In [3]: %run -G print_argv.py print*.py |
|
106 | In [3]: %run -G print_argv.py print*.py | |
107 | ['print*.py'] |
|
107 | ['print*.py'] | |
108 |
|
108 | |||
109 | """ |
|
109 | """ | |
110 |
|
110 | |||
111 |
|
111 | |||
112 | @dec.skip_win32 |
|
112 | @dec.skip_win32 | |
113 | def doctest_run_option_parser_for_posix(): |
|
113 | def doctest_run_option_parser_for_posix(): | |
114 | r"""Test option parser in %run (Linux/OSX specific). |
|
114 | r"""Test option parser in %run (Linux/OSX specific). | |
115 |
|
115 | |||
116 | You need double quote to escape glob in POSIX systems: |
|
116 | You need double quote to escape glob in POSIX systems: | |
117 |
|
117 | |||
118 | In [1]: %run print_argv.py print\\*.py |
|
118 | In [1]: %run print_argv.py print\\*.py | |
119 | ['print*.py'] |
|
119 | ['print*.py'] | |
120 |
|
120 | |||
121 | You can't use quote to escape glob in POSIX systems: |
|
121 | You can't use quote to escape glob in POSIX systems: | |
122 |
|
122 | |||
123 | In [2]: %run print_argv.py 'print*.py' |
|
123 | In [2]: %run print_argv.py 'print*.py' | |
124 | ['print_argv.py'] |
|
124 | ['print_argv.py'] | |
125 |
|
125 | |||
126 | """ |
|
126 | """ | |
127 |
|
127 | |||
128 |
|
128 | |||
129 | @dec.skip_if_not_win32 |
|
129 | @dec.skip_if_not_win32 | |
130 | def doctest_run_option_parser_for_windows(): |
|
130 | def doctest_run_option_parser_for_windows(): | |
131 | r"""Test option parser in %run (Windows specific). |
|
131 | r"""Test option parser in %run (Windows specific). | |
132 |
|
132 | |||
133 | In Windows, you can't escape ``*` `by backslash: |
|
133 | In Windows, you can't escape ``*` `by backslash: | |
134 |
|
134 | |||
135 | In [1]: %run print_argv.py print\\*.py |
|
135 | In [1]: %run print_argv.py print\\*.py | |
136 | ['print\\*.py'] |
|
136 | ['print\\*.py'] | |
137 |
|
137 | |||
138 | You can use quote to escape glob: |
|
138 | You can use quote to escape glob: | |
139 |
|
139 | |||
140 | In [2]: %run print_argv.py 'print*.py' |
|
140 | In [2]: %run print_argv.py 'print*.py' | |
141 | ['print*.py'] |
|
141 | ['print*.py'] | |
142 |
|
142 | |||
143 | """ |
|
143 | """ | |
144 |
|
144 | |||
145 |
|
145 | |||
146 | @py3compat.doctest_refactor_print |
|
146 | @py3compat.doctest_refactor_print | |
147 | def doctest_reset_del(): |
|
147 | def doctest_reset_del(): | |
148 | """Test that resetting doesn't cause errors in __del__ methods. |
|
148 | """Test that resetting doesn't cause errors in __del__ methods. | |
149 |
|
149 | |||
150 | In [2]: class A(object): |
|
150 | In [2]: class A(object): | |
151 | ...: def __del__(self): |
|
151 | ...: def __del__(self): | |
152 | ...: print str("Hi") |
|
152 | ...: print str("Hi") | |
153 | ...: |
|
153 | ...: | |
154 |
|
154 | |||
155 | In [3]: a = A() |
|
155 | In [3]: a = A() | |
156 |
|
156 | |||
157 | In [4]: get_ipython().reset() |
|
157 | In [4]: get_ipython().reset() | |
158 | Hi |
|
158 | Hi | |
159 |
|
159 | |||
160 | In [5]: 1+1 |
|
160 | In [5]: 1+1 | |
161 | Out[5]: 2 |
|
161 | Out[5]: 2 | |
162 | """ |
|
162 | """ | |
163 |
|
163 | |||
164 | # For some tests, it will be handy to organize them in a class with a common |
|
164 | # For some tests, it will be handy to organize them in a class with a common | |
165 | # setup that makes a temp file |
|
165 | # setup that makes a temp file | |
166 |
|
166 | |||
167 | class TestMagicRunPass(tt.TempFileMixin): |
|
167 | class TestMagicRunPass(tt.TempFileMixin): | |
168 |
|
168 | |||
169 | def setup(self): |
|
169 | def setup(self): | |
170 | """Make a valid python temp file.""" |
|
170 | """Make a valid python temp file.""" | |
171 | self.mktmp('pass\n') |
|
171 | self.mktmp('pass\n') | |
172 |
|
172 | |||
173 | def run_tmpfile(self): |
|
173 | def run_tmpfile(self): | |
174 | _ip = get_ipython() |
|
174 | _ip = get_ipython() | |
175 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. |
|
175 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. | |
176 | # See below and ticket https://bugs.launchpad.net/bugs/366353 |
|
176 | # See below and ticket https://bugs.launchpad.net/bugs/366353 | |
177 | _ip.magic('run %s' % self.fname) |
|
177 | _ip.magic('run %s' % self.fname) | |
178 |
|
178 | |||
179 | def run_tmpfile_p(self): |
|
179 | def run_tmpfile_p(self): | |
180 | _ip = get_ipython() |
|
180 | _ip = get_ipython() | |
181 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. |
|
181 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. | |
182 | # See below and ticket https://bugs.launchpad.net/bugs/366353 |
|
182 | # See below and ticket https://bugs.launchpad.net/bugs/366353 | |
183 | _ip.magic('run -p %s' % self.fname) |
|
183 | _ip.magic('run -p %s' % self.fname) | |
184 |
|
184 | |||
185 | def test_builtins_id(self): |
|
185 | def test_builtins_id(self): | |
186 | """Check that %run doesn't damage __builtins__ """ |
|
186 | """Check that %run doesn't damage __builtins__ """ | |
187 | _ip = get_ipython() |
|
187 | _ip = get_ipython() | |
188 | # Test that the id of __builtins__ is not modified by %run |
|
188 | # Test that the id of __builtins__ is not modified by %run | |
189 | bid1 = id(_ip.user_ns['__builtins__']) |
|
189 | bid1 = id(_ip.user_ns['__builtins__']) | |
190 | self.run_tmpfile() |
|
190 | self.run_tmpfile() | |
191 | bid2 = id(_ip.user_ns['__builtins__']) |
|
191 | bid2 = id(_ip.user_ns['__builtins__']) | |
192 | nt.assert_equal(bid1, bid2) |
|
192 | nt.assert_equal(bid1, bid2) | |
193 |
|
193 | |||
194 | def test_builtins_type(self): |
|
194 | def test_builtins_type(self): | |
195 | """Check that the type of __builtins__ doesn't change with %run. |
|
195 | """Check that the type of __builtins__ doesn't change with %run. | |
196 |
|
196 | |||
197 | However, the above could pass if __builtins__ was already modified to |
|
197 | However, the above could pass if __builtins__ was already modified to | |
198 | be a dict (it should be a module) by a previous use of %run. So we |
|
198 | be a dict (it should be a module) by a previous use of %run. So we | |
199 | also check explicitly that it really is a module: |
|
199 | also check explicitly that it really is a module: | |
200 | """ |
|
200 | """ | |
201 | _ip = get_ipython() |
|
201 | _ip = get_ipython() | |
202 | self.run_tmpfile() |
|
202 | self.run_tmpfile() | |
203 | nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys)) |
|
203 | nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys)) | |
204 |
|
204 | |||
205 | def test_prompts(self): |
|
205 | def test_prompts(self): | |
206 | """Test that prompts correctly generate after %run""" |
|
206 | """Test that prompts correctly generate after %run""" | |
207 | self.run_tmpfile() |
|
207 | self.run_tmpfile() | |
208 | _ip = get_ipython() |
|
208 | _ip = get_ipython() | |
209 | p2 = _ip.prompt_manager.render('in2').strip() |
|
209 | p2 = _ip.prompt_manager.render('in2').strip() | |
210 | nt.assert_equal(p2[:3], '...') |
|
210 | nt.assert_equal(p2[:3], '...') | |
211 |
|
211 | |||
212 | def test_run_profile( self ): |
|
212 | def test_run_profile( self ): | |
213 | """Test that the option -p, which invokes the profiler, do not |
|
213 | """Test that the option -p, which invokes the profiler, do not | |
214 | crash by invoking execfile""" |
|
214 | crash by invoking execfile""" | |
215 | _ip = get_ipython() |
|
215 | _ip = get_ipython() | |
216 | self.run_tmpfile_p() |
|
216 | self.run_tmpfile_p() | |
217 |
|
217 | |||
218 |
|
218 | |||
219 | class TestMagicRunSimple(tt.TempFileMixin): |
|
219 | class TestMagicRunSimple(tt.TempFileMixin): | |
220 |
|
220 | |||
221 | def test_simpledef(self): |
|
221 | def test_simpledef(self): | |
222 | """Test that simple class definitions work.""" |
|
222 | """Test that simple class definitions work.""" | |
223 | src = ("class foo: pass\n" |
|
223 | src = ("class foo: pass\n" | |
224 | "def f(): return foo()") |
|
224 | "def f(): return foo()") | |
225 | self.mktmp(src) |
|
225 | self.mktmp(src) | |
226 | _ip.magic('run %s' % self.fname) |
|
226 | _ip.magic('run %s' % self.fname) | |
227 | _ip.run_cell('t = isinstance(f(), foo)') |
|
227 | _ip.run_cell('t = isinstance(f(), foo)') | |
228 | nt.assert_true(_ip.user_ns['t']) |
|
228 | nt.assert_true(_ip.user_ns['t']) | |
229 |
|
229 | |||
230 | def test_obj_del(self): |
|
230 | def test_obj_del(self): | |
231 | """Test that object's __del__ methods are called on exit.""" |
|
231 | """Test that object's __del__ methods are called on exit.""" | |
232 | if sys.platform == 'win32': |
|
232 | if sys.platform == 'win32': | |
233 | try: |
|
233 | try: | |
234 | import win32api |
|
234 | import win32api | |
235 | except ImportError: |
|
235 | except ImportError: | |
236 | raise SkipTest("Test requires pywin32") |
|
236 | raise SkipTest("Test requires pywin32") | |
237 | src = ("class A(object):\n" |
|
237 | src = ("class A(object):\n" | |
238 | " def __del__(self):\n" |
|
238 | " def __del__(self):\n" | |
239 | " print 'object A deleted'\n" |
|
239 | " print 'object A deleted'\n" | |
240 | "a = A()\n") |
|
240 | "a = A()\n") | |
241 | self.mktmp(py3compat.doctest_refactor_print(src)) |
|
241 | self.mktmp(py3compat.doctest_refactor_print(src)) | |
242 | if dec.module_not_available('sqlite3'): |
|
242 | if dec.module_not_available('sqlite3'): | |
243 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' |
|
243 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' | |
244 | else: |
|
244 | else: | |
245 | err = None |
|
245 | err = None | |
246 | tt.ipexec_validate(self.fname, 'object A deleted', err) |
|
246 | tt.ipexec_validate(self.fname, 'object A deleted', err) | |
247 |
|
247 | |||
248 | def test_aggressive_namespace_cleanup(self): |
|
248 | def test_aggressive_namespace_cleanup(self): | |
249 | """Test that namespace cleanup is not too aggressive GH-238 |
|
249 | """Test that namespace cleanup is not too aggressive GH-238 | |
250 |
|
250 | |||
251 | Returning from another run magic deletes the namespace""" |
|
251 | Returning from another run magic deletes the namespace""" | |
252 | # see ticket https://github.com/ipython/ipython/issues/238 |
|
252 | # see ticket https://github.com/ipython/ipython/issues/238 | |
253 | class secondtmp(tt.TempFileMixin): pass |
|
253 | class secondtmp(tt.TempFileMixin): pass | |
254 | empty = secondtmp() |
|
254 | empty = secondtmp() | |
255 | empty.mktmp('') |
|
255 | empty.mktmp('') | |
256 | # On Windows, the filename will have \users in it, so we need to use the |
|
256 | # On Windows, the filename will have \users in it, so we need to use the | |
257 | # repr so that the \u becomes \\u. |
|
257 | # repr so that the \u becomes \\u. | |
258 | src = ("ip = get_ipython()\n" |
|
258 | src = ("ip = get_ipython()\n" | |
259 | "for i in range(5):\n" |
|
259 | "for i in range(5):\n" | |
260 | " try:\n" |
|
260 | " try:\n" | |
261 | " ip.magic(%r)\n" |
|
261 | " ip.magic(%r)\n" | |
262 | " except NameError as e:\n" |
|
262 | " except NameError as e:\n" | |
263 | " print(i)\n" |
|
263 | " print(i)\n" | |
264 | " break\n" % ('run ' + empty.fname)) |
|
264 | " break\n" % ('run ' + empty.fname)) | |
265 | self.mktmp(src) |
|
265 | self.mktmp(src) | |
266 | _ip.magic('run %s' % self.fname) |
|
266 | _ip.magic('run %s' % self.fname) | |
267 | _ip.run_cell('ip == get_ipython()') |
|
267 | _ip.run_cell('ip == get_ipython()') | |
268 | nt.assert_equal(_ip.user_ns['i'], 4) |
|
268 | nt.assert_equal(_ip.user_ns['i'], 4) | |
269 |
|
269 | |||
270 | def test_run_second(self): |
|
270 | def test_run_second(self): | |
271 | """Test that running a second file doesn't clobber the first, gh-3547 |
|
271 | """Test that running a second file doesn't clobber the first, gh-3547 | |
272 | """ |
|
272 | """ | |
273 | self.mktmp("avar = 1\n" |
|
273 | self.mktmp("avar = 1\n" | |
274 | "def afunc():\n" |
|
274 | "def afunc():\n" | |
275 | " return avar\n") |
|
275 | " return avar\n") | |
276 |
|
276 | |||
277 | empty = tt.TempFileMixin() |
|
277 | empty = tt.TempFileMixin() | |
278 | empty.mktmp("") |
|
278 | empty.mktmp("") | |
279 |
|
279 | |||
280 | _ip.magic('run %s' % self.fname) |
|
280 | _ip.magic('run %s' % self.fname) | |
281 | _ip.magic('run %s' % empty.fname) |
|
281 | _ip.magic('run %s' % empty.fname) | |
282 | nt.assert_equal(_ip.user_ns['afunc'](), 1) |
|
282 | nt.assert_equal(_ip.user_ns['afunc'](), 1) | |
283 |
|
283 | |||
284 | @dec.skip_win32 |
|
284 | @dec.skip_win32 | |
285 | def test_tclass(self): |
|
285 | def test_tclass(self): | |
286 | mydir = os.path.dirname(__file__) |
|
286 | mydir = os.path.dirname(__file__) | |
287 | tc = os.path.join(mydir, 'tclass') |
|
287 | tc = os.path.join(mydir, 'tclass') | |
288 | src = ("%%run '%s' C-first\n" |
|
288 | src = ("%%run '%s' C-first\n" | |
289 | "%%run '%s' C-second\n" |
|
289 | "%%run '%s' C-second\n" | |
290 | "%%run '%s' C-third\n") % (tc, tc, tc) |
|
290 | "%%run '%s' C-third\n") % (tc, tc, tc) | |
291 | self.mktmp(src, '.ipy') |
|
291 | self.mktmp(src, '.ipy') | |
292 | out = """\ |
|
292 | out = """\ | |
293 | ARGV 1-: ['C-first'] |
|
293 | ARGV 1-: ['C-first'] | |
294 | ARGV 1-: ['C-second'] |
|
294 | ARGV 1-: ['C-second'] | |
295 | tclass.py: deleting object: C-first |
|
295 | tclass.py: deleting object: C-first | |
296 | ARGV 1-: ['C-third'] |
|
296 | ARGV 1-: ['C-third'] | |
297 | tclass.py: deleting object: C-second |
|
297 | tclass.py: deleting object: C-second | |
298 | tclass.py: deleting object: C-third |
|
298 | tclass.py: deleting object: C-third | |
299 | """ |
|
299 | """ | |
300 | if dec.module_not_available('sqlite3'): |
|
300 | if dec.module_not_available('sqlite3'): | |
301 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' |
|
301 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' | |
302 | else: |
|
302 | else: | |
303 | err = None |
|
303 | err = None | |
304 | tt.ipexec_validate(self.fname, out, err) |
|
304 | tt.ipexec_validate(self.fname, out, err) | |
305 |
|
305 | |||
306 | def test_run_i_after_reset(self): |
|
306 | def test_run_i_after_reset(self): | |
307 | """Check that %run -i still works after %reset (gh-693)""" |
|
307 | """Check that %run -i still works after %reset (gh-693)""" | |
308 | src = "yy = zz\n" |
|
308 | src = "yy = zz\n" | |
309 | self.mktmp(src) |
|
309 | self.mktmp(src) | |
310 | _ip.run_cell("zz = 23") |
|
310 | _ip.run_cell("zz = 23") | |
311 | _ip.magic('run -i %s' % self.fname) |
|
311 | _ip.magic('run -i %s' % self.fname) | |
312 | nt.assert_equal(_ip.user_ns['yy'], 23) |
|
312 | nt.assert_equal(_ip.user_ns['yy'], 23) | |
313 | _ip.magic('reset -f') |
|
313 | _ip.magic('reset -f') | |
314 | _ip.run_cell("zz = 23") |
|
314 | _ip.run_cell("zz = 23") | |
315 | _ip.magic('run -i %s' % self.fname) |
|
315 | _ip.magic('run -i %s' % self.fname) | |
316 | nt.assert_equal(_ip.user_ns['yy'], 23) |
|
316 | nt.assert_equal(_ip.user_ns['yy'], 23) | |
317 |
|
317 | |||
318 | def test_unicode(self): |
|
318 | def test_unicode(self): | |
319 | """Check that files in odd encodings are accepted.""" |
|
319 | """Check that files in odd encodings are accepted.""" | |
320 | mydir = os.path.dirname(__file__) |
|
320 | mydir = os.path.dirname(__file__) | |
321 | na = os.path.join(mydir, 'nonascii.py') |
|
321 | na = os.path.join(mydir, 'nonascii.py') | |
322 | _ip.magic('run "%s"' % na) |
|
322 | _ip.magic('run "%s"' % na) | |
323 | nt.assert_equal(_ip.user_ns['u'], u'ΠΡβΠ€') |
|
323 | nt.assert_equal(_ip.user_ns['u'], u'ΠΡβΠ€') | |
324 |
|
324 | |||
325 | def test_run_py_file_attribute(self): |
|
325 | def test_run_py_file_attribute(self): | |
326 | """Test handling of `__file__` attribute in `%run <file>.py`.""" |
|
326 | """Test handling of `__file__` attribute in `%run <file>.py`.""" | |
327 | src = "t = __file__\n" |
|
327 | src = "t = __file__\n" | |
328 | self.mktmp(src) |
|
328 | self.mktmp(src) | |
329 | _missing = object() |
|
329 | _missing = object() | |
330 | file1 = _ip.user_ns.get('__file__', _missing) |
|
330 | file1 = _ip.user_ns.get('__file__', _missing) | |
331 | _ip.magic('run %s' % self.fname) |
|
331 | _ip.magic('run %s' % self.fname) | |
332 | file2 = _ip.user_ns.get('__file__', _missing) |
|
332 | file2 = _ip.user_ns.get('__file__', _missing) | |
333 |
|
333 | |||
334 | # Check that __file__ was equal to the filename in the script's |
|
334 | # Check that __file__ was equal to the filename in the script's | |
335 | # namespace. |
|
335 | # namespace. | |
336 | nt.assert_equal(_ip.user_ns['t'], self.fname) |
|
336 | nt.assert_equal(_ip.user_ns['t'], self.fname) | |
337 |
|
337 | |||
338 | # Check that __file__ was not leaked back into user_ns. |
|
338 | # Check that __file__ was not leaked back into user_ns. | |
339 | nt.assert_equal(file1, file2) |
|
339 | nt.assert_equal(file1, file2) | |
340 |
|
340 | |||
341 | def test_run_ipy_file_attribute(self): |
|
341 | def test_run_ipy_file_attribute(self): | |
342 | """Test handling of `__file__` attribute in `%run <file.ipy>`.""" |
|
342 | """Test handling of `__file__` attribute in `%run <file.ipy>`.""" | |
343 | src = "t = __file__\n" |
|
343 | src = "t = __file__\n" | |
344 | self.mktmp(src, ext='.ipy') |
|
344 | self.mktmp(src, ext='.ipy') | |
345 | _missing = object() |
|
345 | _missing = object() | |
346 | file1 = _ip.user_ns.get('__file__', _missing) |
|
346 | file1 = _ip.user_ns.get('__file__', _missing) | |
347 | _ip.magic('run %s' % self.fname) |
|
347 | _ip.magic('run %s' % self.fname) | |
348 | file2 = _ip.user_ns.get('__file__', _missing) |
|
348 | file2 = _ip.user_ns.get('__file__', _missing) | |
349 |
|
349 | |||
350 | # Check that __file__ was equal to the filename in the script's |
|
350 | # Check that __file__ was equal to the filename in the script's | |
351 | # namespace. |
|
351 | # namespace. | |
352 | nt.assert_equal(_ip.user_ns['t'], self.fname) |
|
352 | nt.assert_equal(_ip.user_ns['t'], self.fname) | |
353 |
|
353 | |||
354 | # Check that __file__ was not leaked back into user_ns. |
|
354 | # Check that __file__ was not leaked back into user_ns. | |
355 | nt.assert_equal(file1, file2) |
|
355 | nt.assert_equal(file1, file2) | |
356 |
|
356 | |||
357 | def test_run_formatting(self): |
|
357 | def test_run_formatting(self): | |
358 | """ Test that %run -t -N<N> does not raise a TypeError for N > 1.""" |
|
358 | """ Test that %run -t -N<N> does not raise a TypeError for N > 1.""" | |
359 | src = "pass" |
|
359 | src = "pass" | |
360 | self.mktmp(src) |
|
360 | self.mktmp(src) | |
361 | _ip.magic('run -t -N 1 %s' % self.fname) |
|
361 | _ip.magic('run -t -N 1 %s' % self.fname) | |
362 | _ip.magic('run -t -N 10 %s' % self.fname) |
|
362 | _ip.magic('run -t -N 10 %s' % self.fname) | |
363 |
|
363 | |||
364 | def test_ignore_sys_exit(self): |
|
364 | def test_ignore_sys_exit(self): | |
365 | """Test the -e option to ignore sys.exit()""" |
|
365 | """Test the -e option to ignore sys.exit()""" | |
366 | src = "import sys; sys.exit(1)" |
|
366 | src = "import sys; sys.exit(1)" | |
367 | self.mktmp(src) |
|
367 | self.mktmp(src) | |
368 | with tt.AssertPrints('SystemExit'): |
|
368 | with tt.AssertPrints('SystemExit'): | |
369 | _ip.magic('run %s' % self.fname) |
|
369 | _ip.magic('run %s' % self.fname) | |
370 |
|
370 | |||
371 | with tt.AssertNotPrints('SystemExit'): |
|
371 | with tt.AssertNotPrints('SystemExit'): | |
372 | _ip.magic('run -e %s' % self.fname) |
|
372 | _ip.magic('run -e %s' % self.fname) | |
373 |
|
373 | |||
374 |
|
374 | |||
375 |
|
375 | |||
376 | class TestMagicRunWithPackage(unittest.TestCase): |
|
376 | class TestMagicRunWithPackage(unittest.TestCase): | |
377 |
|
377 | |||
378 | def writefile(self, name, content): |
|
378 | def writefile(self, name, content): | |
379 | path = os.path.join(self.tempdir.name, name) |
|
379 | path = os.path.join(self.tempdir.name, name) | |
380 | d = os.path.dirname(path) |
|
380 | d = os.path.dirname(path) | |
381 | if not os.path.isdir(d): |
|
381 | if not os.path.isdir(d): | |
382 | os.makedirs(d) |
|
382 | os.makedirs(d) | |
383 | with open(path, 'w') as f: |
|
383 | with open(path, 'w') as f: | |
384 | f.write(textwrap.dedent(content)) |
|
384 | f.write(textwrap.dedent(content)) | |
385 |
|
385 | |||
386 | def setUp(self): |
|
386 | def setUp(self): | |
387 | self.package = package = 'tmp{0}'.format(repr(random.random())[2:]) |
|
387 | self.package = package = 'tmp{0}'.format(repr(random.random())[2:]) | |
388 | """Temporary valid python package name.""" |
|
388 | """Temporary valid python package name.""" | |
389 |
|
389 | |||
390 | self.value = int(random.random() * 10000) |
|
390 | self.value = int(random.random() * 10000) | |
391 |
|
391 | |||
392 | self.tempdir = TemporaryDirectory() |
|
392 | self.tempdir = TemporaryDirectory() | |
393 |
self.__orig_cwd = |
|
393 | self.__orig_cwd = py3compat.getcwd() | |
394 | sys.path.insert(0, self.tempdir.name) |
|
394 | sys.path.insert(0, self.tempdir.name) | |
395 |
|
395 | |||
396 | self.writefile(os.path.join(package, '__init__.py'), '') |
|
396 | self.writefile(os.path.join(package, '__init__.py'), '') | |
397 | self.writefile(os.path.join(package, 'sub.py'), """ |
|
397 | self.writefile(os.path.join(package, 'sub.py'), """ | |
398 | x = {0!r} |
|
398 | x = {0!r} | |
399 | """.format(self.value)) |
|
399 | """.format(self.value)) | |
400 | self.writefile(os.path.join(package, 'relative.py'), """ |
|
400 | self.writefile(os.path.join(package, 'relative.py'), """ | |
401 | from .sub import x |
|
401 | from .sub import x | |
402 | """) |
|
402 | """) | |
403 | self.writefile(os.path.join(package, 'absolute.py'), """ |
|
403 | self.writefile(os.path.join(package, 'absolute.py'), """ | |
404 | from {0}.sub import x |
|
404 | from {0}.sub import x | |
405 | """.format(package)) |
|
405 | """.format(package)) | |
406 |
|
406 | |||
407 | def tearDown(self): |
|
407 | def tearDown(self): | |
408 | os.chdir(self.__orig_cwd) |
|
408 | os.chdir(self.__orig_cwd) | |
409 | sys.path[:] = [p for p in sys.path if p != self.tempdir.name] |
|
409 | sys.path[:] = [p for p in sys.path if p != self.tempdir.name] | |
410 | self.tempdir.cleanup() |
|
410 | self.tempdir.cleanup() | |
411 |
|
411 | |||
412 | def check_run_submodule(self, submodule, opts=''): |
|
412 | def check_run_submodule(self, submodule, opts=''): | |
413 | _ip.user_ns.pop('x', None) |
|
413 | _ip.user_ns.pop('x', None) | |
414 | _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts)) |
|
414 | _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts)) | |
415 | self.assertEqual(_ip.user_ns['x'], self.value, |
|
415 | self.assertEqual(_ip.user_ns['x'], self.value, | |
416 | 'Variable `x` is not loaded from module `{0}`.' |
|
416 | 'Variable `x` is not loaded from module `{0}`.' | |
417 | .format(submodule)) |
|
417 | .format(submodule)) | |
418 |
|
418 | |||
419 | def test_run_submodule_with_absolute_import(self): |
|
419 | def test_run_submodule_with_absolute_import(self): | |
420 | self.check_run_submodule('absolute') |
|
420 | self.check_run_submodule('absolute') | |
421 |
|
421 | |||
422 | def test_run_submodule_with_relative_import(self): |
|
422 | def test_run_submodule_with_relative_import(self): | |
423 | """Run submodule that has a relative import statement (#2727).""" |
|
423 | """Run submodule that has a relative import statement (#2727).""" | |
424 | self.check_run_submodule('relative') |
|
424 | self.check_run_submodule('relative') | |
425 |
|
425 | |||
426 | def test_prun_submodule_with_absolute_import(self): |
|
426 | def test_prun_submodule_with_absolute_import(self): | |
427 | self.check_run_submodule('absolute', '-p') |
|
427 | self.check_run_submodule('absolute', '-p') | |
428 |
|
428 | |||
429 | def test_prun_submodule_with_relative_import(self): |
|
429 | def test_prun_submodule_with_relative_import(self): | |
430 | self.check_run_submodule('relative', '-p') |
|
430 | self.check_run_submodule('relative', '-p') | |
431 |
|
431 | |||
432 | def with_fake_debugger(func): |
|
432 | def with_fake_debugger(func): | |
433 | @functools.wraps(func) |
|
433 | @functools.wraps(func) | |
434 | def wrapper(*args, **kwds): |
|
434 | def wrapper(*args, **kwds): | |
435 | with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)): |
|
435 | with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)): | |
436 | return func(*args, **kwds) |
|
436 | return func(*args, **kwds) | |
437 | return wrapper |
|
437 | return wrapper | |
438 |
|
438 | |||
439 | @with_fake_debugger |
|
439 | @with_fake_debugger | |
440 | def test_debug_run_submodule_with_absolute_import(self): |
|
440 | def test_debug_run_submodule_with_absolute_import(self): | |
441 | self.check_run_submodule('absolute', '-d') |
|
441 | self.check_run_submodule('absolute', '-d') | |
442 |
|
442 | |||
443 | @with_fake_debugger |
|
443 | @with_fake_debugger | |
444 | def test_debug_run_submodule_with_relative_import(self): |
|
444 | def test_debug_run_submodule_with_relative_import(self): | |
445 | self.check_run_submodule('relative', '-d') |
|
445 | self.check_run_submodule('relative', '-d') | |
446 |
|
446 | |||
447 | def test_run__name__(): |
|
447 | def test_run__name__(): | |
448 | with TemporaryDirectory() as td: |
|
448 | with TemporaryDirectory() as td: | |
449 | path = pjoin(td, 'foo.py') |
|
449 | path = pjoin(td, 'foo.py') | |
450 | with open(path, 'w') as f: |
|
450 | with open(path, 'w') as f: | |
451 | f.write("q = __name__") |
|
451 | f.write("q = __name__") | |
452 |
|
452 | |||
453 | _ip.user_ns.pop('q', None) |
|
453 | _ip.user_ns.pop('q', None) | |
454 | _ip.magic('run {}'.format(path)) |
|
454 | _ip.magic('run {}'.format(path)) | |
455 | nt.assert_equal(_ip.user_ns.pop('q'), '__main__') |
|
455 | nt.assert_equal(_ip.user_ns.pop('q'), '__main__') | |
456 |
|
456 | |||
457 | _ip.magic('run -n {}'.format(path)) |
|
457 | _ip.magic('run -n {}'.format(path)) | |
458 | nt.assert_equal(_ip.user_ns.pop('q'), 'foo') |
|
458 | nt.assert_equal(_ip.user_ns.pop('q'), 'foo') |
@@ -1,172 +1,171 b'' | |||||
1 | """Manage IPython.parallel clusters in the notebook. |
|
1 | """Manage IPython.parallel clusters in the notebook. | |
2 |
|
2 | |||
3 | Authors: |
|
3 | Authors: | |
4 |
|
4 | |||
5 | * Brian Granger |
|
5 | * Brian Granger | |
6 | """ |
|
6 | """ | |
7 |
|
7 | |||
8 | #----------------------------------------------------------------------------- |
|
8 | #----------------------------------------------------------------------------- | |
9 | # Copyright (C) 2008-2011 The IPython Development Team |
|
9 | # Copyright (C) 2008-2011 The IPython Development Team | |
10 | # |
|
10 | # | |
11 | # Distributed under the terms of the BSD License. The full license is in |
|
11 | # Distributed under the terms of the BSD License. The full license is in | |
12 | # the file COPYING, distributed as part of this software. |
|
12 | # the file COPYING, distributed as part of this software. | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | # Imports |
|
16 | # Imports | |
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 |
|
18 | |||
19 | import os |
|
|||
20 |
|
||||
21 | from tornado import web |
|
19 | from tornado import web | |
22 | from zmq.eventloop import ioloop |
|
20 | from zmq.eventloop import ioloop | |
23 |
|
21 | |||
24 | from IPython.config.configurable import LoggingConfigurable |
|
22 | from IPython.config.configurable import LoggingConfigurable | |
25 | from IPython.utils.traitlets import Dict, Instance, CFloat |
|
23 | from IPython.utils.traitlets import Dict, Instance, CFloat | |
26 | from IPython.parallel.apps.ipclusterapp import IPClusterStart |
|
24 | from IPython.parallel.apps.ipclusterapp import IPClusterStart | |
27 | from IPython.core.profileapp import list_profiles_in |
|
25 | from IPython.core.profileapp import list_profiles_in | |
28 | from IPython.core.profiledir import ProfileDir |
|
26 | from IPython.core.profiledir import ProfileDir | |
|
27 | from IPython.utils import py3compat | |||
29 | from IPython.utils.path import get_ipython_dir |
|
28 | from IPython.utils.path import get_ipython_dir | |
30 |
|
29 | |||
31 |
|
30 | |||
32 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
33 | # Classes |
|
32 | # Classes | |
34 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
35 |
|
34 | |||
36 |
|
35 | |||
37 | class DummyIPClusterStart(IPClusterStart): |
|
36 | class DummyIPClusterStart(IPClusterStart): | |
38 | """Dummy subclass to skip init steps that conflict with global app. |
|
37 | """Dummy subclass to skip init steps that conflict with global app. | |
39 |
|
38 | |||
40 | Instantiating and initializing this class should result in fully configured |
|
39 | Instantiating and initializing this class should result in fully configured | |
41 | launchers, but no other side effects or state. |
|
40 | launchers, but no other side effects or state. | |
42 | """ |
|
41 | """ | |
43 |
|
42 | |||
44 | def init_signal(self): |
|
43 | def init_signal(self): | |
45 | pass |
|
44 | pass | |
46 | def reinit_logging(self): |
|
45 | def reinit_logging(self): | |
47 | pass |
|
46 | pass | |
48 |
|
47 | |||
49 |
|
48 | |||
50 | class ClusterManager(LoggingConfigurable): |
|
49 | class ClusterManager(LoggingConfigurable): | |
51 |
|
50 | |||
52 | profiles = Dict() |
|
51 | profiles = Dict() | |
53 |
|
52 | |||
54 | delay = CFloat(1., config=True, |
|
53 | delay = CFloat(1., config=True, | |
55 | help="delay (in s) between starting the controller and the engines") |
|
54 | help="delay (in s) between starting the controller and the engines") | |
56 |
|
55 | |||
57 | loop = Instance('zmq.eventloop.ioloop.IOLoop') |
|
56 | loop = Instance('zmq.eventloop.ioloop.IOLoop') | |
58 | def _loop_default(self): |
|
57 | def _loop_default(self): | |
59 | from zmq.eventloop.ioloop import IOLoop |
|
58 | from zmq.eventloop.ioloop import IOLoop | |
60 | return IOLoop.instance() |
|
59 | return IOLoop.instance() | |
61 |
|
60 | |||
62 | def build_launchers(self, profile_dir): |
|
61 | def build_launchers(self, profile_dir): | |
63 | starter = DummyIPClusterStart(log=self.log) |
|
62 | starter = DummyIPClusterStart(log=self.log) | |
64 | starter.initialize(['--profile-dir', profile_dir]) |
|
63 | starter.initialize(['--profile-dir', profile_dir]) | |
65 | cl = starter.controller_launcher |
|
64 | cl = starter.controller_launcher | |
66 | esl = starter.engine_launcher |
|
65 | esl = starter.engine_launcher | |
67 | n = starter.n |
|
66 | n = starter.n | |
68 | return cl, esl, n |
|
67 | return cl, esl, n | |
69 |
|
68 | |||
70 | def get_profile_dir(self, name, path): |
|
69 | def get_profile_dir(self, name, path): | |
71 | p = ProfileDir.find_profile_dir_by_name(path,name=name) |
|
70 | p = ProfileDir.find_profile_dir_by_name(path,name=name) | |
72 | return p.location |
|
71 | return p.location | |
73 |
|
72 | |||
74 | def update_profiles(self): |
|
73 | def update_profiles(self): | |
75 | """List all profiles in the ipython_dir and cwd. |
|
74 | """List all profiles in the ipython_dir and cwd. | |
76 | """ |
|
75 | """ | |
77 |
for path in [get_ipython_dir(), |
|
76 | for path in [get_ipython_dir(), py3compat.getcwd()]: | |
78 | for profile in list_profiles_in(path): |
|
77 | for profile in list_profiles_in(path): | |
79 | pd = self.get_profile_dir(profile, path) |
|
78 | pd = self.get_profile_dir(profile, path) | |
80 | if profile not in self.profiles: |
|
79 | if profile not in self.profiles: | |
81 | self.log.debug("Adding cluster profile '%s'" % profile) |
|
80 | self.log.debug("Adding cluster profile '%s'" % profile) | |
82 | self.profiles[profile] = { |
|
81 | self.profiles[profile] = { | |
83 | 'profile': profile, |
|
82 | 'profile': profile, | |
84 | 'profile_dir': pd, |
|
83 | 'profile_dir': pd, | |
85 | 'status': 'stopped' |
|
84 | 'status': 'stopped' | |
86 | } |
|
85 | } | |
87 |
|
86 | |||
88 | def list_profiles(self): |
|
87 | def list_profiles(self): | |
89 | self.update_profiles() |
|
88 | self.update_profiles() | |
90 | # sorted list, but ensure that 'default' always comes first |
|
89 | # sorted list, but ensure that 'default' always comes first | |
91 | default_first = lambda name: name if name != 'default' else '' |
|
90 | default_first = lambda name: name if name != 'default' else '' | |
92 | result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)] |
|
91 | result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)] | |
93 | return result |
|
92 | return result | |
94 |
|
93 | |||
95 | def check_profile(self, profile): |
|
94 | def check_profile(self, profile): | |
96 | if profile not in self.profiles: |
|
95 | if profile not in self.profiles: | |
97 | raise web.HTTPError(404, u'profile not found') |
|
96 | raise web.HTTPError(404, u'profile not found') | |
98 |
|
97 | |||
99 | def profile_info(self, profile): |
|
98 | def profile_info(self, profile): | |
100 | self.check_profile(profile) |
|
99 | self.check_profile(profile) | |
101 | result = {} |
|
100 | result = {} | |
102 | data = self.profiles.get(profile) |
|
101 | data = self.profiles.get(profile) | |
103 | result['profile'] = profile |
|
102 | result['profile'] = profile | |
104 | result['profile_dir'] = data['profile_dir'] |
|
103 | result['profile_dir'] = data['profile_dir'] | |
105 | result['status'] = data['status'] |
|
104 | result['status'] = data['status'] | |
106 | if 'n' in data: |
|
105 | if 'n' in data: | |
107 | result['n'] = data['n'] |
|
106 | result['n'] = data['n'] | |
108 | return result |
|
107 | return result | |
109 |
|
108 | |||
110 | def start_cluster(self, profile, n=None): |
|
109 | def start_cluster(self, profile, n=None): | |
111 | """Start a cluster for a given profile.""" |
|
110 | """Start a cluster for a given profile.""" | |
112 | self.check_profile(profile) |
|
111 | self.check_profile(profile) | |
113 | data = self.profiles[profile] |
|
112 | data = self.profiles[profile] | |
114 | if data['status'] == 'running': |
|
113 | if data['status'] == 'running': | |
115 | raise web.HTTPError(409, u'cluster already running') |
|
114 | raise web.HTTPError(409, u'cluster already running') | |
116 | cl, esl, default_n = self.build_launchers(data['profile_dir']) |
|
115 | cl, esl, default_n = self.build_launchers(data['profile_dir']) | |
117 | n = n if n is not None else default_n |
|
116 | n = n if n is not None else default_n | |
118 | def clean_data(): |
|
117 | def clean_data(): | |
119 | data.pop('controller_launcher',None) |
|
118 | data.pop('controller_launcher',None) | |
120 | data.pop('engine_set_launcher',None) |
|
119 | data.pop('engine_set_launcher',None) | |
121 | data.pop('n',None) |
|
120 | data.pop('n',None) | |
122 | data['status'] = 'stopped' |
|
121 | data['status'] = 'stopped' | |
123 | def engines_stopped(r): |
|
122 | def engines_stopped(r): | |
124 | self.log.debug('Engines stopped') |
|
123 | self.log.debug('Engines stopped') | |
125 | if cl.running: |
|
124 | if cl.running: | |
126 | cl.stop() |
|
125 | cl.stop() | |
127 | clean_data() |
|
126 | clean_data() | |
128 | esl.on_stop(engines_stopped) |
|
127 | esl.on_stop(engines_stopped) | |
129 | def controller_stopped(r): |
|
128 | def controller_stopped(r): | |
130 | self.log.debug('Controller stopped') |
|
129 | self.log.debug('Controller stopped') | |
131 | if esl.running: |
|
130 | if esl.running: | |
132 | esl.stop() |
|
131 | esl.stop() | |
133 | clean_data() |
|
132 | clean_data() | |
134 | cl.on_stop(controller_stopped) |
|
133 | cl.on_stop(controller_stopped) | |
135 |
|
134 | |||
136 | dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop) |
|
135 | dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop) | |
137 | dc.start() |
|
136 | dc.start() | |
138 | dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop) |
|
137 | dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop) | |
139 | dc.start() |
|
138 | dc.start() | |
140 |
|
139 | |||
141 | self.log.debug('Cluster started') |
|
140 | self.log.debug('Cluster started') | |
142 | data['controller_launcher'] = cl |
|
141 | data['controller_launcher'] = cl | |
143 | data['engine_set_launcher'] = esl |
|
142 | data['engine_set_launcher'] = esl | |
144 | data['n'] = n |
|
143 | data['n'] = n | |
145 | data['status'] = 'running' |
|
144 | data['status'] = 'running' | |
146 | return self.profile_info(profile) |
|
145 | return self.profile_info(profile) | |
147 |
|
146 | |||
148 | def stop_cluster(self, profile): |
|
147 | def stop_cluster(self, profile): | |
149 | """Stop a cluster for a given profile.""" |
|
148 | """Stop a cluster for a given profile.""" | |
150 | self.check_profile(profile) |
|
149 | self.check_profile(profile) | |
151 | data = self.profiles[profile] |
|
150 | data = self.profiles[profile] | |
152 | if data['status'] == 'stopped': |
|
151 | if data['status'] == 'stopped': | |
153 | raise web.HTTPError(409, u'cluster not running') |
|
152 | raise web.HTTPError(409, u'cluster not running') | |
154 | data = self.profiles[profile] |
|
153 | data = self.profiles[profile] | |
155 | cl = data['controller_launcher'] |
|
154 | cl = data['controller_launcher'] | |
156 | esl = data['engine_set_launcher'] |
|
155 | esl = data['engine_set_launcher'] | |
157 | if cl.running: |
|
156 | if cl.running: | |
158 | cl.stop() |
|
157 | cl.stop() | |
159 | if esl.running: |
|
158 | if esl.running: | |
160 | esl.stop() |
|
159 | esl.stop() | |
161 | # Return a temp info dict, the real one is updated in the on_stop |
|
160 | # Return a temp info dict, the real one is updated in the on_stop | |
162 | # logic above. |
|
161 | # logic above. | |
163 | result = { |
|
162 | result = { | |
164 | 'profile': data['profile'], |
|
163 | 'profile': data['profile'], | |
165 | 'profile_dir': data['profile_dir'], |
|
164 | 'profile_dir': data['profile_dir'], | |
166 | 'status': 'stopped' |
|
165 | 'status': 'stopped' | |
167 | } |
|
166 | } | |
168 | return result |
|
167 | return result | |
169 |
|
168 | |||
170 | def stop_all_clusters(self): |
|
169 | def stop_all_clusters(self): | |
171 | for p in self.profiles.keys(): |
|
170 | for p in self.profiles.keys(): | |
172 | self.stop_cluster(p) |
|
171 | self.stop_cluster(p) |
@@ -1,173 +1,174 b'' | |||||
1 | """A base class notebook manager. |
|
1 | """A base class notebook manager. | |
2 |
|
2 | |||
3 | Authors: |
|
3 | Authors: | |
4 |
|
4 | |||
5 | * Brian Granger |
|
5 | * Brian Granger | |
6 | * Zach Sailer |
|
6 | * Zach Sailer | |
7 | """ |
|
7 | """ | |
8 |
|
8 | |||
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 | # Copyright (C) 2011 The IPython Development Team |
|
10 | # Copyright (C) 2011 The IPython Development Team | |
11 | # |
|
11 | # | |
12 | # Distributed under the terms of the BSD License. The full license is in |
|
12 | # Distributed under the terms of the BSD License. The full license is in | |
13 | # the file COPYING, distributed as part of this software. |
|
13 | # the file COPYING, distributed as part of this software. | |
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 |
|
15 | |||
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 | # Imports |
|
17 | # Imports | |
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 |
|
19 | |||
20 | import os |
|
20 | import os | |
21 |
|
21 | |||
22 | from IPython.config.configurable import LoggingConfigurable |
|
22 | from IPython.config.configurable import LoggingConfigurable | |
23 | from IPython.nbformat import current |
|
23 | from IPython.nbformat import current | |
24 | from IPython.utils.traitlets import List, Dict, Unicode, TraitError |
|
24 | from IPython.utils import py3compat | |
|
25 | from IPython.utils.traitlets import Unicode, TraitError | |||
25 |
|
26 | |||
26 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
27 | # Classes |
|
28 | # Classes | |
28 | #----------------------------------------------------------------------------- |
|
29 | #----------------------------------------------------------------------------- | |
29 |
|
30 | |||
30 | class NotebookManager(LoggingConfigurable): |
|
31 | class NotebookManager(LoggingConfigurable): | |
31 |
|
32 | |||
32 | # Todo: |
|
33 | # Todo: | |
33 | # The notebook_dir attribute is used to mean a couple of different things: |
|
34 | # The notebook_dir attribute is used to mean a couple of different things: | |
34 | # 1. Where the notebooks are stored if FileNotebookManager is used. |
|
35 | # 1. Where the notebooks are stored if FileNotebookManager is used. | |
35 | # 2. The cwd of the kernel for a project. |
|
36 | # 2. The cwd of the kernel for a project. | |
36 | # Right now we use this attribute in a number of different places and |
|
37 | # Right now we use this attribute in a number of different places and | |
37 | # we are going to have to disentangle all of this. |
|
38 | # we are going to have to disentangle all of this. | |
38 |
notebook_dir = Unicode( |
|
39 | notebook_dir = Unicode(py3compat.getcwd(), config=True, help=""" | |
39 | The directory to use for notebooks. |
|
40 | The directory to use for notebooks. | |
40 | """) |
|
41 | """) | |
41 |
|
42 | |||
42 | filename_ext = Unicode(u'.ipynb') |
|
43 | filename_ext = Unicode(u'.ipynb') | |
43 |
|
44 | |||
44 | def path_exists(self, path): |
|
45 | def path_exists(self, path): | |
45 | """Does the API-style path (directory) actually exist? |
|
46 | """Does the API-style path (directory) actually exist? | |
46 |
|
47 | |||
47 | Override this method in subclasses. |
|
48 | Override this method in subclasses. | |
48 |
|
49 | |||
49 | Parameters |
|
50 | Parameters | |
50 | ---------- |
|
51 | ---------- | |
51 | path : string |
|
52 | path : string | |
52 | The |
|
53 | The | |
53 |
|
54 | |||
54 | Returns |
|
55 | Returns | |
55 | ------- |
|
56 | ------- | |
56 | exists : bool |
|
57 | exists : bool | |
57 | Whether the path does indeed exist. |
|
58 | Whether the path does indeed exist. | |
58 | """ |
|
59 | """ | |
59 | raise NotImplementedError |
|
60 | raise NotImplementedError | |
60 |
|
61 | |||
61 | def _notebook_dir_changed(self, name, old, new): |
|
62 | def _notebook_dir_changed(self, name, old, new): | |
62 | """Do a bit of validation of the notebook dir.""" |
|
63 | """Do a bit of validation of the notebook dir.""" | |
63 | if not os.path.isabs(new): |
|
64 | if not os.path.isabs(new): | |
64 | # If we receive a non-absolute path, make it absolute. |
|
65 | # If we receive a non-absolute path, make it absolute. | |
65 | self.notebook_dir = os.path.abspath(new) |
|
66 | self.notebook_dir = os.path.abspath(new) | |
66 | return |
|
67 | return | |
67 | if os.path.exists(new) and not os.path.isdir(new): |
|
68 | if os.path.exists(new) and not os.path.isdir(new): | |
68 | raise TraitError("notebook dir %r is not a directory" % new) |
|
69 | raise TraitError("notebook dir %r is not a directory" % new) | |
69 | if not os.path.exists(new): |
|
70 | if not os.path.exists(new): | |
70 | self.log.info("Creating notebook dir %s", new) |
|
71 | self.log.info("Creating notebook dir %s", new) | |
71 | try: |
|
72 | try: | |
72 | os.mkdir(new) |
|
73 | os.mkdir(new) | |
73 | except: |
|
74 | except: | |
74 | raise TraitError("Couldn't create notebook dir %r" % new) |
|
75 | raise TraitError("Couldn't create notebook dir %r" % new) | |
75 |
|
76 | |||
76 | # Main notebook API |
|
77 | # Main notebook API | |
77 |
|
78 | |||
78 | def increment_filename(self, basename, path=''): |
|
79 | def increment_filename(self, basename, path=''): | |
79 | """Increment a notebook filename without the .ipynb to make it unique. |
|
80 | """Increment a notebook filename without the .ipynb to make it unique. | |
80 |
|
81 | |||
81 | Parameters |
|
82 | Parameters | |
82 | ---------- |
|
83 | ---------- | |
83 | basename : unicode |
|
84 | basename : unicode | |
84 | The name of a notebook without the ``.ipynb`` file extension. |
|
85 | The name of a notebook without the ``.ipynb`` file extension. | |
85 | path : unicode |
|
86 | path : unicode | |
86 | The URL path of the notebooks directory |
|
87 | The URL path of the notebooks directory | |
87 | """ |
|
88 | """ | |
88 | return basename |
|
89 | return basename | |
89 |
|
90 | |||
90 | def list_notebooks(self, path=''): |
|
91 | def list_notebooks(self, path=''): | |
91 | """Return a list of notebook dicts without content. |
|
92 | """Return a list of notebook dicts without content. | |
92 |
|
93 | |||
93 | This returns a list of dicts, each of the form:: |
|
94 | This returns a list of dicts, each of the form:: | |
94 |
|
95 | |||
95 | dict(notebook_id=notebook,name=name) |
|
96 | dict(notebook_id=notebook,name=name) | |
96 |
|
97 | |||
97 | This list of dicts should be sorted by name:: |
|
98 | This list of dicts should be sorted by name:: | |
98 |
|
99 | |||
99 | data = sorted(data, key=lambda item: item['name']) |
|
100 | data = sorted(data, key=lambda item: item['name']) | |
100 | """ |
|
101 | """ | |
101 | raise NotImplementedError('must be implemented in a subclass') |
|
102 | raise NotImplementedError('must be implemented in a subclass') | |
102 |
|
103 | |||
103 | def get_notebook_model(self, name, path='', content=True): |
|
104 | def get_notebook_model(self, name, path='', content=True): | |
104 | """Get the notebook model with or without content.""" |
|
105 | """Get the notebook model with or without content.""" | |
105 | raise NotImplementedError('must be implemented in a subclass') |
|
106 | raise NotImplementedError('must be implemented in a subclass') | |
106 |
|
107 | |||
107 | def save_notebook_model(self, model, name, path=''): |
|
108 | def save_notebook_model(self, model, name, path=''): | |
108 | """Save the notebook model and return the model with no content.""" |
|
109 | """Save the notebook model and return the model with no content.""" | |
109 | raise NotImplementedError('must be implemented in a subclass') |
|
110 | raise NotImplementedError('must be implemented in a subclass') | |
110 |
|
111 | |||
111 | def update_notebook_model(self, model, name, path=''): |
|
112 | def update_notebook_model(self, model, name, path=''): | |
112 | """Update the notebook model and return the model with no content.""" |
|
113 | """Update the notebook model and return the model with no content.""" | |
113 | raise NotImplementedError('must be implemented in a subclass') |
|
114 | raise NotImplementedError('must be implemented in a subclass') | |
114 |
|
115 | |||
115 | def delete_notebook_model(self, name, path=''): |
|
116 | def delete_notebook_model(self, name, path=''): | |
116 | """Delete notebook by name and path.""" |
|
117 | """Delete notebook by name and path.""" | |
117 | raise NotImplementedError('must be implemented in a subclass') |
|
118 | raise NotImplementedError('must be implemented in a subclass') | |
118 |
|
119 | |||
119 | def create_notebook_model(self, model=None, path=''): |
|
120 | def create_notebook_model(self, model=None, path=''): | |
120 | """Create a new notebook and return its model with no content.""" |
|
121 | """Create a new notebook and return its model with no content.""" | |
121 | path = path.strip('/') |
|
122 | path = path.strip('/') | |
122 | if model is None: |
|
123 | if model is None: | |
123 | model = {} |
|
124 | model = {} | |
124 | if 'content' not in model: |
|
125 | if 'content' not in model: | |
125 | metadata = current.new_metadata(name=u'') |
|
126 | metadata = current.new_metadata(name=u'') | |
126 | model['content'] = current.new_notebook(metadata=metadata) |
|
127 | model['content'] = current.new_notebook(metadata=metadata) | |
127 | if 'name' not in model: |
|
128 | if 'name' not in model: | |
128 | model['name'] = self.increment_filename('Untitled', path) |
|
129 | model['name'] = self.increment_filename('Untitled', path) | |
129 |
|
130 | |||
130 | model['path'] = path |
|
131 | model['path'] = path | |
131 | model = self.save_notebook_model(model, model['name'], model['path']) |
|
132 | model = self.save_notebook_model(model, model['name'], model['path']) | |
132 | return model |
|
133 | return model | |
133 |
|
134 | |||
134 | def copy_notebook(self, from_name, to_name=None, path=''): |
|
135 | def copy_notebook(self, from_name, to_name=None, path=''): | |
135 | """Copy an existing notebook and return its new model. |
|
136 | """Copy an existing notebook and return its new model. | |
136 |
|
137 | |||
137 | If to_name not specified, increment `from_name-Copy#.ipynb`. |
|
138 | If to_name not specified, increment `from_name-Copy#.ipynb`. | |
138 | """ |
|
139 | """ | |
139 | path = path.strip('/') |
|
140 | path = path.strip('/') | |
140 | model = self.get_notebook_model(from_name, path) |
|
141 | model = self.get_notebook_model(from_name, path) | |
141 | if not to_name: |
|
142 | if not to_name: | |
142 | base = os.path.splitext(from_name)[0] + '-Copy' |
|
143 | base = os.path.splitext(from_name)[0] + '-Copy' | |
143 | to_name = self.increment_filename(base, path) |
|
144 | to_name = self.increment_filename(base, path) | |
144 | model['name'] = to_name |
|
145 | model['name'] = to_name | |
145 | model = self.save_notebook_model(model, to_name, path) |
|
146 | model = self.save_notebook_model(model, to_name, path) | |
146 | return model |
|
147 | return model | |
147 |
|
148 | |||
148 | # Checkpoint-related |
|
149 | # Checkpoint-related | |
149 |
|
150 | |||
150 | def create_checkpoint(self, name, path=''): |
|
151 | def create_checkpoint(self, name, path=''): | |
151 | """Create a checkpoint of the current state of a notebook |
|
152 | """Create a checkpoint of the current state of a notebook | |
152 |
|
153 | |||
153 | Returns a checkpoint_id for the new checkpoint. |
|
154 | Returns a checkpoint_id for the new checkpoint. | |
154 | """ |
|
155 | """ | |
155 | raise NotImplementedError("must be implemented in a subclass") |
|
156 | raise NotImplementedError("must be implemented in a subclass") | |
156 |
|
157 | |||
157 | def list_checkpoints(self, name, path=''): |
|
158 | def list_checkpoints(self, name, path=''): | |
158 | """Return a list of checkpoints for a given notebook""" |
|
159 | """Return a list of checkpoints for a given notebook""" | |
159 | return [] |
|
160 | return [] | |
160 |
|
161 | |||
161 | def restore_checkpoint(self, checkpoint_id, name, path=''): |
|
162 | def restore_checkpoint(self, checkpoint_id, name, path=''): | |
162 | """Restore a notebook from one of its checkpoints""" |
|
163 | """Restore a notebook from one of its checkpoints""" | |
163 | raise NotImplementedError("must be implemented in a subclass") |
|
164 | raise NotImplementedError("must be implemented in a subclass") | |
164 |
|
165 | |||
165 | def delete_checkpoint(self, checkpoint_id, name, path=''): |
|
166 | def delete_checkpoint(self, checkpoint_id, name, path=''): | |
166 | """delete a checkpoint for a notebook""" |
|
167 | """delete a checkpoint for a notebook""" | |
167 | raise NotImplementedError("must be implemented in a subclass") |
|
168 | raise NotImplementedError("must be implemented in a subclass") | |
168 |
|
169 | |||
169 | def log_info(self): |
|
170 | def log_info(self): | |
170 | self.log.info(self.info_string()) |
|
171 | self.log.info(self.info_string()) | |
171 |
|
172 | |||
172 | def info_string(self): |
|
173 | def info_string(self): | |
173 | return "Serving notebooks" |
|
174 | return "Serving notebooks" |
@@ -1,275 +1,276 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | The Base Application class for IPython.parallel apps |
|
3 | The Base Application class for IPython.parallel apps | |
4 |
|
4 | |||
5 | Authors: |
|
5 | Authors: | |
6 |
|
6 | |||
7 | * Brian Granger |
|
7 | * Brian Granger | |
8 | * Min RK |
|
8 | * Min RK | |
9 |
|
9 | |||
10 | """ |
|
10 | """ | |
11 |
|
11 | |||
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 | # Copyright (C) 2008-2011 The IPython Development Team |
|
13 | # Copyright (C) 2008-2011 The IPython Development Team | |
14 | # |
|
14 | # | |
15 | # Distributed under the terms of the BSD License. The full license is in |
|
15 | # Distributed under the terms of the BSD License. The full license is in | |
16 | # the file COPYING, distributed as part of this software. |
|
16 | # the file COPYING, distributed as part of this software. | |
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 |
|
18 | |||
19 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
20 | # Imports |
|
20 | # Imports | |
21 | #----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
22 |
|
22 | |||
23 | import os |
|
23 | import os | |
24 | import logging |
|
24 | import logging | |
25 | import re |
|
25 | import re | |
26 | import sys |
|
26 | import sys | |
27 |
|
27 | |||
28 | from subprocess import Popen, PIPE |
|
28 | from subprocess import Popen, PIPE | |
29 |
|
29 | |||
30 | from IPython.config.application import catch_config_error, LevelFormatter |
|
30 | from IPython.config.application import catch_config_error, LevelFormatter | |
31 | from IPython.core import release |
|
31 | from IPython.core import release | |
32 | from IPython.core.crashhandler import CrashHandler |
|
32 | from IPython.core.crashhandler import CrashHandler | |
33 | from IPython.core.application import ( |
|
33 | from IPython.core.application import ( | |
34 | BaseIPythonApplication, |
|
34 | BaseIPythonApplication, | |
35 | base_aliases as base_ip_aliases, |
|
35 | base_aliases as base_ip_aliases, | |
36 | base_flags as base_ip_flags |
|
36 | base_flags as base_ip_flags | |
37 | ) |
|
37 | ) | |
38 | from IPython.utils.path import expand_path |
|
38 | from IPython.utils.path import expand_path | |
|
39 | from IPython.utils import py3compat | |||
39 | from IPython.utils.py3compat import unicode_type |
|
40 | from IPython.utils.py3compat import unicode_type | |
40 |
|
41 | |||
41 | from IPython.utils.traitlets import Unicode, Bool, Instance, Dict |
|
42 | from IPython.utils.traitlets import Unicode, Bool, Instance, Dict | |
42 |
|
43 | |||
43 | #----------------------------------------------------------------------------- |
|
44 | #----------------------------------------------------------------------------- | |
44 | # Module errors |
|
45 | # Module errors | |
45 | #----------------------------------------------------------------------------- |
|
46 | #----------------------------------------------------------------------------- | |
46 |
|
47 | |||
47 | class PIDFileError(Exception): |
|
48 | class PIDFileError(Exception): | |
48 | pass |
|
49 | pass | |
49 |
|
50 | |||
50 |
|
51 | |||
51 | #----------------------------------------------------------------------------- |
|
52 | #----------------------------------------------------------------------------- | |
52 | # Crash handler for this application |
|
53 | # Crash handler for this application | |
53 | #----------------------------------------------------------------------------- |
|
54 | #----------------------------------------------------------------------------- | |
54 |
|
55 | |||
55 | class ParallelCrashHandler(CrashHandler): |
|
56 | class ParallelCrashHandler(CrashHandler): | |
56 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" |
|
57 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" | |
57 |
|
58 | |||
58 | def __init__(self, app): |
|
59 | def __init__(self, app): | |
59 | contact_name = release.authors['Min'][0] |
|
60 | contact_name = release.authors['Min'][0] | |
60 | contact_email = release.author_email |
|
61 | contact_email = release.author_email | |
61 | bug_tracker = 'https://github.com/ipython/ipython/issues' |
|
62 | bug_tracker = 'https://github.com/ipython/ipython/issues' | |
62 | super(ParallelCrashHandler,self).__init__( |
|
63 | super(ParallelCrashHandler,self).__init__( | |
63 | app, contact_name, contact_email, bug_tracker |
|
64 | app, contact_name, contact_email, bug_tracker | |
64 | ) |
|
65 | ) | |
65 |
|
66 | |||
66 |
|
67 | |||
67 | #----------------------------------------------------------------------------- |
|
68 | #----------------------------------------------------------------------------- | |
68 | # Main application |
|
69 | # Main application | |
69 | #----------------------------------------------------------------------------- |
|
70 | #----------------------------------------------------------------------------- | |
70 | base_aliases = {} |
|
71 | base_aliases = {} | |
71 | base_aliases.update(base_ip_aliases) |
|
72 | base_aliases.update(base_ip_aliases) | |
72 | base_aliases.update({ |
|
73 | base_aliases.update({ | |
73 | 'work-dir' : 'BaseParallelApplication.work_dir', |
|
74 | 'work-dir' : 'BaseParallelApplication.work_dir', | |
74 | 'log-to-file' : 'BaseParallelApplication.log_to_file', |
|
75 | 'log-to-file' : 'BaseParallelApplication.log_to_file', | |
75 | 'clean-logs' : 'BaseParallelApplication.clean_logs', |
|
76 | 'clean-logs' : 'BaseParallelApplication.clean_logs', | |
76 | 'log-url' : 'BaseParallelApplication.log_url', |
|
77 | 'log-url' : 'BaseParallelApplication.log_url', | |
77 | 'cluster-id' : 'BaseParallelApplication.cluster_id', |
|
78 | 'cluster-id' : 'BaseParallelApplication.cluster_id', | |
78 | }) |
|
79 | }) | |
79 |
|
80 | |||
80 | base_flags = { |
|
81 | base_flags = { | |
81 | 'log-to-file' : ( |
|
82 | 'log-to-file' : ( | |
82 | {'BaseParallelApplication' : {'log_to_file' : True}}, |
|
83 | {'BaseParallelApplication' : {'log_to_file' : True}}, | |
83 | "send log output to a file" |
|
84 | "send log output to a file" | |
84 | ) |
|
85 | ) | |
85 | } |
|
86 | } | |
86 | base_flags.update(base_ip_flags) |
|
87 | base_flags.update(base_ip_flags) | |
87 |
|
88 | |||
88 | class BaseParallelApplication(BaseIPythonApplication): |
|
89 | class BaseParallelApplication(BaseIPythonApplication): | |
89 | """The base Application for IPython.parallel apps |
|
90 | """The base Application for IPython.parallel apps | |
90 |
|
91 | |||
91 | Principle extensions to BaseIPyythonApplication: |
|
92 | Principle extensions to BaseIPyythonApplication: | |
92 |
|
93 | |||
93 | * work_dir |
|
94 | * work_dir | |
94 | * remote logging via pyzmq |
|
95 | * remote logging via pyzmq | |
95 | * IOLoop instance |
|
96 | * IOLoop instance | |
96 | """ |
|
97 | """ | |
97 |
|
98 | |||
98 | crash_handler_class = ParallelCrashHandler |
|
99 | crash_handler_class = ParallelCrashHandler | |
99 |
|
100 | |||
100 | def _log_level_default(self): |
|
101 | def _log_level_default(self): | |
101 | # temporarily override default_log_level to INFO |
|
102 | # temporarily override default_log_level to INFO | |
102 | return logging.INFO |
|
103 | return logging.INFO | |
103 |
|
104 | |||
104 | def _log_format_default(self): |
|
105 | def _log_format_default(self): | |
105 | """override default log format to include time""" |
|
106 | """override default log format to include time""" | |
106 | return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s" |
|
107 | return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s" | |
107 |
|
108 | |||
108 |
work_dir = Unicode( |
|
109 | work_dir = Unicode(py3compat.getcwd(), config=True, | |
109 | help='Set the working dir for the process.' |
|
110 | help='Set the working dir for the process.' | |
110 | ) |
|
111 | ) | |
111 | def _work_dir_changed(self, name, old, new): |
|
112 | def _work_dir_changed(self, name, old, new): | |
112 | self.work_dir = unicode_type(expand_path(new)) |
|
113 | self.work_dir = unicode_type(expand_path(new)) | |
113 |
|
114 | |||
114 | log_to_file = Bool(config=True, |
|
115 | log_to_file = Bool(config=True, | |
115 | help="whether to log to a file") |
|
116 | help="whether to log to a file") | |
116 |
|
117 | |||
117 | clean_logs = Bool(False, config=True, |
|
118 | clean_logs = Bool(False, config=True, | |
118 | help="whether to cleanup old logfiles before starting") |
|
119 | help="whether to cleanup old logfiles before starting") | |
119 |
|
120 | |||
120 | log_url = Unicode('', config=True, |
|
121 | log_url = Unicode('', config=True, | |
121 | help="The ZMQ URL of the iplogger to aggregate logging.") |
|
122 | help="The ZMQ URL of the iplogger to aggregate logging.") | |
122 |
|
123 | |||
123 | cluster_id = Unicode('', config=True, |
|
124 | cluster_id = Unicode('', config=True, | |
124 | help="""String id to add to runtime files, to prevent name collisions when |
|
125 | help="""String id to add to runtime files, to prevent name collisions when | |
125 | using multiple clusters with a single profile simultaneously. |
|
126 | using multiple clusters with a single profile simultaneously. | |
126 |
|
127 | |||
127 | When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json' |
|
128 | When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json' | |
128 |
|
129 | |||
129 | Since this is text inserted into filenames, typical recommendations apply: |
|
130 | Since this is text inserted into filenames, typical recommendations apply: | |
130 | Simple character strings are ideal, and spaces are not recommended (but should |
|
131 | Simple character strings are ideal, and spaces are not recommended (but should | |
131 | generally work). |
|
132 | generally work). | |
132 | """ |
|
133 | """ | |
133 | ) |
|
134 | ) | |
134 | def _cluster_id_changed(self, name, old, new): |
|
135 | def _cluster_id_changed(self, name, old, new): | |
135 | self.name = self.__class__.name |
|
136 | self.name = self.__class__.name | |
136 | if new: |
|
137 | if new: | |
137 | self.name += '-%s'%new |
|
138 | self.name += '-%s'%new | |
138 |
|
139 | |||
139 | def _config_files_default(self): |
|
140 | def _config_files_default(self): | |
140 | return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py'] |
|
141 | return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py'] | |
141 |
|
142 | |||
142 | loop = Instance('zmq.eventloop.ioloop.IOLoop') |
|
143 | loop = Instance('zmq.eventloop.ioloop.IOLoop') | |
143 | def _loop_default(self): |
|
144 | def _loop_default(self): | |
144 | from zmq.eventloop.ioloop import IOLoop |
|
145 | from zmq.eventloop.ioloop import IOLoop | |
145 | return IOLoop.instance() |
|
146 | return IOLoop.instance() | |
146 |
|
147 | |||
147 | aliases = Dict(base_aliases) |
|
148 | aliases = Dict(base_aliases) | |
148 | flags = Dict(base_flags) |
|
149 | flags = Dict(base_flags) | |
149 |
|
150 | |||
150 | @catch_config_error |
|
151 | @catch_config_error | |
151 | def initialize(self, argv=None): |
|
152 | def initialize(self, argv=None): | |
152 | """initialize the app""" |
|
153 | """initialize the app""" | |
153 | super(BaseParallelApplication, self).initialize(argv) |
|
154 | super(BaseParallelApplication, self).initialize(argv) | |
154 | self.to_work_dir() |
|
155 | self.to_work_dir() | |
155 | self.reinit_logging() |
|
156 | self.reinit_logging() | |
156 |
|
157 | |||
157 | def to_work_dir(self): |
|
158 | def to_work_dir(self): | |
158 | wd = self.work_dir |
|
159 | wd = self.work_dir | |
159 |
if unicode_type(wd) != |
|
160 | if unicode_type(wd) != py3compat.getcwd(): | |
160 | os.chdir(wd) |
|
161 | os.chdir(wd) | |
161 | self.log.info("Changing to working dir: %s" % wd) |
|
162 | self.log.info("Changing to working dir: %s" % wd) | |
162 | # This is the working dir by now. |
|
163 | # This is the working dir by now. | |
163 | sys.path.insert(0, '') |
|
164 | sys.path.insert(0, '') | |
164 |
|
165 | |||
165 | def reinit_logging(self): |
|
166 | def reinit_logging(self): | |
166 | # Remove old log files |
|
167 | # Remove old log files | |
167 | log_dir = self.profile_dir.log_dir |
|
168 | log_dir = self.profile_dir.log_dir | |
168 | if self.clean_logs: |
|
169 | if self.clean_logs: | |
169 | for f in os.listdir(log_dir): |
|
170 | for f in os.listdir(log_dir): | |
170 | if re.match(r'%s-\d+\.(log|err|out)' % self.name, f): |
|
171 | if re.match(r'%s-\d+\.(log|err|out)' % self.name, f): | |
171 | try: |
|
172 | try: | |
172 | os.remove(os.path.join(log_dir, f)) |
|
173 | os.remove(os.path.join(log_dir, f)) | |
173 | except (OSError, IOError): |
|
174 | except (OSError, IOError): | |
174 | # probably just conflict from sibling process |
|
175 | # probably just conflict from sibling process | |
175 | # already removing it |
|
176 | # already removing it | |
176 | pass |
|
177 | pass | |
177 | if self.log_to_file: |
|
178 | if self.log_to_file: | |
178 | # Start logging to the new log file |
|
179 | # Start logging to the new log file | |
179 | log_filename = self.name + u'-' + str(os.getpid()) + u'.log' |
|
180 | log_filename = self.name + u'-' + str(os.getpid()) + u'.log' | |
180 | logfile = os.path.join(log_dir, log_filename) |
|
181 | logfile = os.path.join(log_dir, log_filename) | |
181 | open_log_file = open(logfile, 'w') |
|
182 | open_log_file = open(logfile, 'w') | |
182 | else: |
|
183 | else: | |
183 | open_log_file = None |
|
184 | open_log_file = None | |
184 | if open_log_file is not None: |
|
185 | if open_log_file is not None: | |
185 | while self.log.handlers: |
|
186 | while self.log.handlers: | |
186 | self.log.removeHandler(self.log.handlers[0]) |
|
187 | self.log.removeHandler(self.log.handlers[0]) | |
187 | self._log_handler = logging.StreamHandler(open_log_file) |
|
188 | self._log_handler = logging.StreamHandler(open_log_file) | |
188 | self.log.addHandler(self._log_handler) |
|
189 | self.log.addHandler(self._log_handler) | |
189 | else: |
|
190 | else: | |
190 | self._log_handler = self.log.handlers[0] |
|
191 | self._log_handler = self.log.handlers[0] | |
191 | # Add timestamps to log format: |
|
192 | # Add timestamps to log format: | |
192 | self._log_formatter = LevelFormatter(self.log_format, |
|
193 | self._log_formatter = LevelFormatter(self.log_format, | |
193 | datefmt=self.log_datefmt) |
|
194 | datefmt=self.log_datefmt) | |
194 | self._log_handler.setFormatter(self._log_formatter) |
|
195 | self._log_handler.setFormatter(self._log_formatter) | |
195 | # do not propagate log messages to root logger |
|
196 | # do not propagate log messages to root logger | |
196 | # ipcluster app will sometimes print duplicate messages during shutdown |
|
197 | # ipcluster app will sometimes print duplicate messages during shutdown | |
197 | # if this is 1 (default): |
|
198 | # if this is 1 (default): | |
198 | self.log.propagate = False |
|
199 | self.log.propagate = False | |
199 |
|
200 | |||
200 | def write_pid_file(self, overwrite=False): |
|
201 | def write_pid_file(self, overwrite=False): | |
201 | """Create a .pid file in the pid_dir with my pid. |
|
202 | """Create a .pid file in the pid_dir with my pid. | |
202 |
|
203 | |||
203 | This must be called after pre_construct, which sets `self.pid_dir`. |
|
204 | This must be called after pre_construct, which sets `self.pid_dir`. | |
204 | This raises :exc:`PIDFileError` if the pid file exists already. |
|
205 | This raises :exc:`PIDFileError` if the pid file exists already. | |
205 | """ |
|
206 | """ | |
206 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') |
|
207 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') | |
207 | if os.path.isfile(pid_file): |
|
208 | if os.path.isfile(pid_file): | |
208 | pid = self.get_pid_from_file() |
|
209 | pid = self.get_pid_from_file() | |
209 | if not overwrite: |
|
210 | if not overwrite: | |
210 | raise PIDFileError( |
|
211 | raise PIDFileError( | |
211 | 'The pid file [%s] already exists. \nThis could mean that this ' |
|
212 | 'The pid file [%s] already exists. \nThis could mean that this ' | |
212 | 'server is already running with [pid=%s].' % (pid_file, pid) |
|
213 | 'server is already running with [pid=%s].' % (pid_file, pid) | |
213 | ) |
|
214 | ) | |
214 | with open(pid_file, 'w') as f: |
|
215 | with open(pid_file, 'w') as f: | |
215 | self.log.info("Creating pid file: %s" % pid_file) |
|
216 | self.log.info("Creating pid file: %s" % pid_file) | |
216 | f.write(repr(os.getpid())+'\n') |
|
217 | f.write(repr(os.getpid())+'\n') | |
217 |
|
218 | |||
218 | def remove_pid_file(self): |
|
219 | def remove_pid_file(self): | |
219 | """Remove the pid file. |
|
220 | """Remove the pid file. | |
220 |
|
221 | |||
221 | This should be called at shutdown by registering a callback with |
|
222 | This should be called at shutdown by registering a callback with | |
222 | :func:`reactor.addSystemEventTrigger`. This needs to return |
|
223 | :func:`reactor.addSystemEventTrigger`. This needs to return | |
223 | ``None``. |
|
224 | ``None``. | |
224 | """ |
|
225 | """ | |
225 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') |
|
226 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') | |
226 | if os.path.isfile(pid_file): |
|
227 | if os.path.isfile(pid_file): | |
227 | try: |
|
228 | try: | |
228 | self.log.info("Removing pid file: %s" % pid_file) |
|
229 | self.log.info("Removing pid file: %s" % pid_file) | |
229 | os.remove(pid_file) |
|
230 | os.remove(pid_file) | |
230 | except: |
|
231 | except: | |
231 | self.log.warn("Error removing the pid file: %s" % pid_file) |
|
232 | self.log.warn("Error removing the pid file: %s" % pid_file) | |
232 |
|
233 | |||
233 | def get_pid_from_file(self): |
|
234 | def get_pid_from_file(self): | |
234 | """Get the pid from the pid file. |
|
235 | """Get the pid from the pid file. | |
235 |
|
236 | |||
236 | If the pid file doesn't exist a :exc:`PIDFileError` is raised. |
|
237 | If the pid file doesn't exist a :exc:`PIDFileError` is raised. | |
237 | """ |
|
238 | """ | |
238 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') |
|
239 | pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') | |
239 | if os.path.isfile(pid_file): |
|
240 | if os.path.isfile(pid_file): | |
240 | with open(pid_file, 'r') as f: |
|
241 | with open(pid_file, 'r') as f: | |
241 | s = f.read().strip() |
|
242 | s = f.read().strip() | |
242 | try: |
|
243 | try: | |
243 | pid = int(s) |
|
244 | pid = int(s) | |
244 | except: |
|
245 | except: | |
245 | raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s)) |
|
246 | raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s)) | |
246 | return pid |
|
247 | return pid | |
247 | else: |
|
248 | else: | |
248 | raise PIDFileError('pid file not found: %s' % pid_file) |
|
249 | raise PIDFileError('pid file not found: %s' % pid_file) | |
249 |
|
250 | |||
250 | def check_pid(self, pid): |
|
251 | def check_pid(self, pid): | |
251 | if os.name == 'nt': |
|
252 | if os.name == 'nt': | |
252 | try: |
|
253 | try: | |
253 | import ctypes |
|
254 | import ctypes | |
254 | # returns 0 if no such process (of ours) exists |
|
255 | # returns 0 if no such process (of ours) exists | |
255 | # positive int otherwise |
|
256 | # positive int otherwise | |
256 | p = ctypes.windll.kernel32.OpenProcess(1,0,pid) |
|
257 | p = ctypes.windll.kernel32.OpenProcess(1,0,pid) | |
257 | except Exception: |
|
258 | except Exception: | |
258 | self.log.warn( |
|
259 | self.log.warn( | |
259 | "Could not determine whether pid %i is running via `OpenProcess`. " |
|
260 | "Could not determine whether pid %i is running via `OpenProcess`. " | |
260 | " Making the likely assumption that it is."%pid |
|
261 | " Making the likely assumption that it is."%pid | |
261 | ) |
|
262 | ) | |
262 | return True |
|
263 | return True | |
263 | return bool(p) |
|
264 | return bool(p) | |
264 | else: |
|
265 | else: | |
265 | try: |
|
266 | try: | |
266 | p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE) |
|
267 | p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE) | |
267 | output,_ = p.communicate() |
|
268 | output,_ = p.communicate() | |
268 | except OSError: |
|
269 | except OSError: | |
269 | self.log.warn( |
|
270 | self.log.warn( | |
270 | "Could not determine whether pid %i is running via `ps x`. " |
|
271 | "Could not determine whether pid %i is running via `ps x`. " | |
271 | " Making the likely assumption that it is."%pid |
|
272 | " Making the likely assumption that it is."%pid | |
272 | ) |
|
273 | ) | |
273 | return True |
|
274 | return True | |
274 | pids = list(map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))) |
|
275 | pids = list(map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))) | |
275 | return pid in pids |
|
276 | return pid in pids |
@@ -1,764 +1,764 b'' | |||||
1 | """Nose Plugin that supports IPython doctests. |
|
1 | """Nose Plugin that supports IPython doctests. | |
2 |
|
2 | |||
3 | Limitations: |
|
3 | Limitations: | |
4 |
|
4 | |||
5 | - When generating examples for use as doctests, make sure that you have |
|
5 | - When generating examples for use as doctests, make sure that you have | |
6 | pretty-printing OFF. This can be done either by setting the |
|
6 | pretty-printing OFF. This can be done either by setting the | |
7 | ``PlainTextFormatter.pprint`` option in your configuration file to False, or |
|
7 | ``PlainTextFormatter.pprint`` option in your configuration file to False, or | |
8 | by interactively disabling it with %Pprint. This is required so that IPython |
|
8 | by interactively disabling it with %Pprint. This is required so that IPython | |
9 | output matches that of normal Python, which is used by doctest for internal |
|
9 | output matches that of normal Python, which is used by doctest for internal | |
10 | execution. |
|
10 | execution. | |
11 |
|
11 | |||
12 | - Do not rely on specific prompt numbers for results (such as using |
|
12 | - Do not rely on specific prompt numbers for results (such as using | |
13 | '_34==True', for example). For IPython tests run via an external process the |
|
13 | '_34==True', for example). For IPython tests run via an external process the | |
14 | prompt numbers may be different, and IPython tests run as normal python code |
|
14 | prompt numbers may be different, and IPython tests run as normal python code | |
15 | won't even have these special _NN variables set at all. |
|
15 | won't even have these special _NN variables set at all. | |
16 | """ |
|
16 | """ | |
17 |
|
17 | |||
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 | # Module imports |
|
19 | # Module imports | |
20 |
|
20 | |||
21 | # From the standard library |
|
21 | # From the standard library | |
22 | import doctest |
|
22 | import doctest | |
23 | import inspect |
|
23 | import inspect | |
24 | import logging |
|
24 | import logging | |
25 | import os |
|
25 | import os | |
26 | import re |
|
26 | import re | |
27 | import sys |
|
27 | import sys | |
28 | import traceback |
|
28 | import traceback | |
29 | import unittest |
|
29 | import unittest | |
30 |
|
30 | |||
31 | from inspect import getmodule |
|
31 | from inspect import getmodule | |
32 |
|
32 | |||
33 | # We are overriding the default doctest runner, so we need to import a few |
|
33 | # We are overriding the default doctest runner, so we need to import a few | |
34 | # things from doctest directly |
|
34 | # things from doctest directly | |
35 | from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, |
|
35 | from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, | |
36 | _unittest_reportflags, DocTestRunner, |
|
36 | _unittest_reportflags, DocTestRunner, | |
37 | _extract_future_flags, pdb, _OutputRedirectingPdb, |
|
37 | _extract_future_flags, pdb, _OutputRedirectingPdb, | |
38 | _exception_traceback, |
|
38 | _exception_traceback, | |
39 | linecache) |
|
39 | linecache) | |
40 |
|
40 | |||
41 | # Third-party modules |
|
41 | # Third-party modules | |
42 | import nose.core |
|
42 | import nose.core | |
43 |
|
43 | |||
44 | from nose.plugins import doctests, Plugin |
|
44 | from nose.plugins import doctests, Plugin | |
45 | from nose.util import anyp, getpackage, test_address, resolve_name, tolist |
|
45 | from nose.util import anyp, getpackage, test_address, resolve_name, tolist | |
46 |
|
46 | |||
47 | # Our own imports |
|
47 | # Our own imports | |
48 | from IPython.utils.py3compat import builtin_mod, PY3 |
|
48 | from IPython.utils.py3compat import builtin_mod, PY3, getcwd | |
49 |
|
49 | |||
50 | if PY3: |
|
50 | if PY3: | |
51 | from io import StringIO |
|
51 | from io import StringIO | |
52 | else: |
|
52 | else: | |
53 | from StringIO import StringIO |
|
53 | from StringIO import StringIO | |
54 |
|
54 | |||
55 | #----------------------------------------------------------------------------- |
|
55 | #----------------------------------------------------------------------------- | |
56 | # Module globals and other constants |
|
56 | # Module globals and other constants | |
57 | #----------------------------------------------------------------------------- |
|
57 | #----------------------------------------------------------------------------- | |
58 |
|
58 | |||
59 | log = logging.getLogger(__name__) |
|
59 | log = logging.getLogger(__name__) | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | #----------------------------------------------------------------------------- |
|
62 | #----------------------------------------------------------------------------- | |
63 | # Classes and functions |
|
63 | # Classes and functions | |
64 | #----------------------------------------------------------------------------- |
|
64 | #----------------------------------------------------------------------------- | |
65 |
|
65 | |||
66 | def is_extension_module(filename): |
|
66 | def is_extension_module(filename): | |
67 | """Return whether the given filename is an extension module. |
|
67 | """Return whether the given filename is an extension module. | |
68 |
|
68 | |||
69 | This simply checks that the extension is either .so or .pyd. |
|
69 | This simply checks that the extension is either .so or .pyd. | |
70 | """ |
|
70 | """ | |
71 | return os.path.splitext(filename)[1].lower() in ('.so','.pyd') |
|
71 | return os.path.splitext(filename)[1].lower() in ('.so','.pyd') | |
72 |
|
72 | |||
73 |
|
73 | |||
74 | class DocTestSkip(object): |
|
74 | class DocTestSkip(object): | |
75 | """Object wrapper for doctests to be skipped.""" |
|
75 | """Object wrapper for doctests to be skipped.""" | |
76 |
|
76 | |||
77 | ds_skip = """Doctest to skip. |
|
77 | ds_skip = """Doctest to skip. | |
78 | >>> 1 #doctest: +SKIP |
|
78 | >>> 1 #doctest: +SKIP | |
79 | """ |
|
79 | """ | |
80 |
|
80 | |||
81 | def __init__(self,obj): |
|
81 | def __init__(self,obj): | |
82 | self.obj = obj |
|
82 | self.obj = obj | |
83 |
|
83 | |||
84 | def __getattribute__(self,key): |
|
84 | def __getattribute__(self,key): | |
85 | if key == '__doc__': |
|
85 | if key == '__doc__': | |
86 | return DocTestSkip.ds_skip |
|
86 | return DocTestSkip.ds_skip | |
87 | else: |
|
87 | else: | |
88 | return getattr(object.__getattribute__(self,'obj'),key) |
|
88 | return getattr(object.__getattribute__(self,'obj'),key) | |
89 |
|
89 | |||
90 | # Modified version of the one in the stdlib, that fixes a python bug (doctests |
|
90 | # Modified version of the one in the stdlib, that fixes a python bug (doctests | |
91 | # not found in extension modules, http://bugs.python.org/issue3158) |
|
91 | # not found in extension modules, http://bugs.python.org/issue3158) | |
92 | class DocTestFinder(doctest.DocTestFinder): |
|
92 | class DocTestFinder(doctest.DocTestFinder): | |
93 |
|
93 | |||
94 | def _from_module(self, module, object): |
|
94 | def _from_module(self, module, object): | |
95 | """ |
|
95 | """ | |
96 | Return true if the given object is defined in the given |
|
96 | Return true if the given object is defined in the given | |
97 | module. |
|
97 | module. | |
98 | """ |
|
98 | """ | |
99 | if module is None: |
|
99 | if module is None: | |
100 | return True |
|
100 | return True | |
101 | elif inspect.isfunction(object): |
|
101 | elif inspect.isfunction(object): | |
102 | return module.__dict__ is object.__globals__ |
|
102 | return module.__dict__ is object.__globals__ | |
103 | elif inspect.isbuiltin(object): |
|
103 | elif inspect.isbuiltin(object): | |
104 | return module.__name__ == object.__module__ |
|
104 | return module.__name__ == object.__module__ | |
105 | elif inspect.isclass(object): |
|
105 | elif inspect.isclass(object): | |
106 | return module.__name__ == object.__module__ |
|
106 | return module.__name__ == object.__module__ | |
107 | elif inspect.ismethod(object): |
|
107 | elif inspect.ismethod(object): | |
108 | # This one may be a bug in cython that fails to correctly set the |
|
108 | # This one may be a bug in cython that fails to correctly set the | |
109 | # __module__ attribute of methods, but since the same error is easy |
|
109 | # __module__ attribute of methods, but since the same error is easy | |
110 | # to make by extension code writers, having this safety in place |
|
110 | # to make by extension code writers, having this safety in place | |
111 | # isn't such a bad idea |
|
111 | # isn't such a bad idea | |
112 | return module.__name__ == object.__self__.__class__.__module__ |
|
112 | return module.__name__ == object.__self__.__class__.__module__ | |
113 | elif inspect.getmodule(object) is not None: |
|
113 | elif inspect.getmodule(object) is not None: | |
114 | return module is inspect.getmodule(object) |
|
114 | return module is inspect.getmodule(object) | |
115 | elif hasattr(object, '__module__'): |
|
115 | elif hasattr(object, '__module__'): | |
116 | return module.__name__ == object.__module__ |
|
116 | return module.__name__ == object.__module__ | |
117 | elif isinstance(object, property): |
|
117 | elif isinstance(object, property): | |
118 | return True # [XX] no way not be sure. |
|
118 | return True # [XX] no way not be sure. | |
119 | else: |
|
119 | else: | |
120 | raise ValueError("object must be a class or function, got %r" % object) |
|
120 | raise ValueError("object must be a class or function, got %r" % object) | |
121 |
|
121 | |||
122 | def _find(self, tests, obj, name, module, source_lines, globs, seen): |
|
122 | def _find(self, tests, obj, name, module, source_lines, globs, seen): | |
123 | """ |
|
123 | """ | |
124 | Find tests for the given object and any contained objects, and |
|
124 | Find tests for the given object and any contained objects, and | |
125 | add them to `tests`. |
|
125 | add them to `tests`. | |
126 | """ |
|
126 | """ | |
127 | #print '_find for:', obj, name, module # dbg |
|
127 | #print '_find for:', obj, name, module # dbg | |
128 | if hasattr(obj,"skip_doctest"): |
|
128 | if hasattr(obj,"skip_doctest"): | |
129 | #print 'SKIPPING DOCTEST FOR:',obj # dbg |
|
129 | #print 'SKIPPING DOCTEST FOR:',obj # dbg | |
130 | obj = DocTestSkip(obj) |
|
130 | obj = DocTestSkip(obj) | |
131 |
|
131 | |||
132 | doctest.DocTestFinder._find(self,tests, obj, name, module, |
|
132 | doctest.DocTestFinder._find(self,tests, obj, name, module, | |
133 | source_lines, globs, seen) |
|
133 | source_lines, globs, seen) | |
134 |
|
134 | |||
135 | # Below we re-run pieces of the above method with manual modifications, |
|
135 | # Below we re-run pieces of the above method with manual modifications, | |
136 | # because the original code is buggy and fails to correctly identify |
|
136 | # because the original code is buggy and fails to correctly identify | |
137 | # doctests in extension modules. |
|
137 | # doctests in extension modules. | |
138 |
|
138 | |||
139 | # Local shorthands |
|
139 | # Local shorthands | |
140 | from inspect import isroutine, isclass, ismodule |
|
140 | from inspect import isroutine, isclass, ismodule | |
141 |
|
141 | |||
142 | # Look for tests in a module's contained objects. |
|
142 | # Look for tests in a module's contained objects. | |
143 | if inspect.ismodule(obj) and self._recurse: |
|
143 | if inspect.ismodule(obj) and self._recurse: | |
144 | for valname, val in obj.__dict__.items(): |
|
144 | for valname, val in obj.__dict__.items(): | |
145 | valname1 = '%s.%s' % (name, valname) |
|
145 | valname1 = '%s.%s' % (name, valname) | |
146 | if ( (isroutine(val) or isclass(val)) |
|
146 | if ( (isroutine(val) or isclass(val)) | |
147 | and self._from_module(module, val) ): |
|
147 | and self._from_module(module, val) ): | |
148 |
|
148 | |||
149 | self._find(tests, val, valname1, module, source_lines, |
|
149 | self._find(tests, val, valname1, module, source_lines, | |
150 | globs, seen) |
|
150 | globs, seen) | |
151 |
|
151 | |||
152 | # Look for tests in a class's contained objects. |
|
152 | # Look for tests in a class's contained objects. | |
153 | if inspect.isclass(obj) and self._recurse: |
|
153 | if inspect.isclass(obj) and self._recurse: | |
154 | #print 'RECURSE into class:',obj # dbg |
|
154 | #print 'RECURSE into class:',obj # dbg | |
155 | for valname, val in obj.__dict__.items(): |
|
155 | for valname, val in obj.__dict__.items(): | |
156 | # Special handling for staticmethod/classmethod. |
|
156 | # Special handling for staticmethod/classmethod. | |
157 | if isinstance(val, staticmethod): |
|
157 | if isinstance(val, staticmethod): | |
158 | val = getattr(obj, valname) |
|
158 | val = getattr(obj, valname) | |
159 | if isinstance(val, classmethod): |
|
159 | if isinstance(val, classmethod): | |
160 | val = getattr(obj, valname).__func__ |
|
160 | val = getattr(obj, valname).__func__ | |
161 |
|
161 | |||
162 | # Recurse to methods, properties, and nested classes. |
|
162 | # Recurse to methods, properties, and nested classes. | |
163 | if ((inspect.isfunction(val) or inspect.isclass(val) or |
|
163 | if ((inspect.isfunction(val) or inspect.isclass(val) or | |
164 | inspect.ismethod(val) or |
|
164 | inspect.ismethod(val) or | |
165 | isinstance(val, property)) and |
|
165 | isinstance(val, property)) and | |
166 | self._from_module(module, val)): |
|
166 | self._from_module(module, val)): | |
167 | valname = '%s.%s' % (name, valname) |
|
167 | valname = '%s.%s' % (name, valname) | |
168 | self._find(tests, val, valname, module, source_lines, |
|
168 | self._find(tests, val, valname, module, source_lines, | |
169 | globs, seen) |
|
169 | globs, seen) | |
170 |
|
170 | |||
171 |
|
171 | |||
172 | class IPDoctestOutputChecker(doctest.OutputChecker): |
|
172 | class IPDoctestOutputChecker(doctest.OutputChecker): | |
173 | """Second-chance checker with support for random tests. |
|
173 | """Second-chance checker with support for random tests. | |
174 |
|
174 | |||
175 | If the default comparison doesn't pass, this checker looks in the expected |
|
175 | If the default comparison doesn't pass, this checker looks in the expected | |
176 | output string for flags that tell us to ignore the output. |
|
176 | output string for flags that tell us to ignore the output. | |
177 | """ |
|
177 | """ | |
178 |
|
178 | |||
179 | random_re = re.compile(r'#\s*random\s+') |
|
179 | random_re = re.compile(r'#\s*random\s+') | |
180 |
|
180 | |||
181 | def check_output(self, want, got, optionflags): |
|
181 | def check_output(self, want, got, optionflags): | |
182 | """Check output, accepting special markers embedded in the output. |
|
182 | """Check output, accepting special markers embedded in the output. | |
183 |
|
183 | |||
184 | If the output didn't pass the default validation but the special string |
|
184 | If the output didn't pass the default validation but the special string | |
185 | '#random' is included, we accept it.""" |
|
185 | '#random' is included, we accept it.""" | |
186 |
|
186 | |||
187 | # Let the original tester verify first, in case people have valid tests |
|
187 | # Let the original tester verify first, in case people have valid tests | |
188 | # that happen to have a comment saying '#random' embedded in. |
|
188 | # that happen to have a comment saying '#random' embedded in. | |
189 | ret = doctest.OutputChecker.check_output(self, want, got, |
|
189 | ret = doctest.OutputChecker.check_output(self, want, got, | |
190 | optionflags) |
|
190 | optionflags) | |
191 | if not ret and self.random_re.search(want): |
|
191 | if not ret and self.random_re.search(want): | |
192 | #print >> sys.stderr, 'RANDOM OK:',want # dbg |
|
192 | #print >> sys.stderr, 'RANDOM OK:',want # dbg | |
193 | return True |
|
193 | return True | |
194 |
|
194 | |||
195 | return ret |
|
195 | return ret | |
196 |
|
196 | |||
197 |
|
197 | |||
198 | class DocTestCase(doctests.DocTestCase): |
|
198 | class DocTestCase(doctests.DocTestCase): | |
199 | """Proxy for DocTestCase: provides an address() method that |
|
199 | """Proxy for DocTestCase: provides an address() method that | |
200 | returns the correct address for the doctest case. Otherwise |
|
200 | returns the correct address for the doctest case. Otherwise | |
201 | acts as a proxy to the test case. To provide hints for address(), |
|
201 | acts as a proxy to the test case. To provide hints for address(), | |
202 | an obj may also be passed -- this will be used as the test object |
|
202 | an obj may also be passed -- this will be used as the test object | |
203 | for purposes of determining the test address, if it is provided. |
|
203 | for purposes of determining the test address, if it is provided. | |
204 | """ |
|
204 | """ | |
205 |
|
205 | |||
206 | # Note: this method was taken from numpy's nosetester module. |
|
206 | # Note: this method was taken from numpy's nosetester module. | |
207 |
|
207 | |||
208 | # Subclass nose.plugins.doctests.DocTestCase to work around a bug in |
|
208 | # Subclass nose.plugins.doctests.DocTestCase to work around a bug in | |
209 | # its constructor that blocks non-default arguments from being passed |
|
209 | # its constructor that blocks non-default arguments from being passed | |
210 | # down into doctest.DocTestCase |
|
210 | # down into doctest.DocTestCase | |
211 |
|
211 | |||
212 | def __init__(self, test, optionflags=0, setUp=None, tearDown=None, |
|
212 | def __init__(self, test, optionflags=0, setUp=None, tearDown=None, | |
213 | checker=None, obj=None, result_var='_'): |
|
213 | checker=None, obj=None, result_var='_'): | |
214 | self._result_var = result_var |
|
214 | self._result_var = result_var | |
215 | doctests.DocTestCase.__init__(self, test, |
|
215 | doctests.DocTestCase.__init__(self, test, | |
216 | optionflags=optionflags, |
|
216 | optionflags=optionflags, | |
217 | setUp=setUp, tearDown=tearDown, |
|
217 | setUp=setUp, tearDown=tearDown, | |
218 | checker=checker) |
|
218 | checker=checker) | |
219 | # Now we must actually copy the original constructor from the stdlib |
|
219 | # Now we must actually copy the original constructor from the stdlib | |
220 | # doctest class, because we can't call it directly and a bug in nose |
|
220 | # doctest class, because we can't call it directly and a bug in nose | |
221 | # means it never gets passed the right arguments. |
|
221 | # means it never gets passed the right arguments. | |
222 |
|
222 | |||
223 | self._dt_optionflags = optionflags |
|
223 | self._dt_optionflags = optionflags | |
224 | self._dt_checker = checker |
|
224 | self._dt_checker = checker | |
225 | self._dt_test = test |
|
225 | self._dt_test = test | |
226 | self._dt_test_globs_ori = test.globs |
|
226 | self._dt_test_globs_ori = test.globs | |
227 | self._dt_setUp = setUp |
|
227 | self._dt_setUp = setUp | |
228 | self._dt_tearDown = tearDown |
|
228 | self._dt_tearDown = tearDown | |
229 |
|
229 | |||
230 | # XXX - store this runner once in the object! |
|
230 | # XXX - store this runner once in the object! | |
231 | runner = IPDocTestRunner(optionflags=optionflags, |
|
231 | runner = IPDocTestRunner(optionflags=optionflags, | |
232 | checker=checker, verbose=False) |
|
232 | checker=checker, verbose=False) | |
233 | self._dt_runner = runner |
|
233 | self._dt_runner = runner | |
234 |
|
234 | |||
235 |
|
235 | |||
236 | # Each doctest should remember the directory it was loaded from, so |
|
236 | # Each doctest should remember the directory it was loaded from, so | |
237 | # things like %run work without too many contortions |
|
237 | # things like %run work without too many contortions | |
238 | self._ori_dir = os.path.dirname(test.filename) |
|
238 | self._ori_dir = os.path.dirname(test.filename) | |
239 |
|
239 | |||
240 | # Modified runTest from the default stdlib |
|
240 | # Modified runTest from the default stdlib | |
241 | def runTest(self): |
|
241 | def runTest(self): | |
242 | test = self._dt_test |
|
242 | test = self._dt_test | |
243 | runner = self._dt_runner |
|
243 | runner = self._dt_runner | |
244 |
|
244 | |||
245 | old = sys.stdout |
|
245 | old = sys.stdout | |
246 | new = StringIO() |
|
246 | new = StringIO() | |
247 | optionflags = self._dt_optionflags |
|
247 | optionflags = self._dt_optionflags | |
248 |
|
248 | |||
249 | if not (optionflags & REPORTING_FLAGS): |
|
249 | if not (optionflags & REPORTING_FLAGS): | |
250 | # The option flags don't include any reporting flags, |
|
250 | # The option flags don't include any reporting flags, | |
251 | # so add the default reporting flags |
|
251 | # so add the default reporting flags | |
252 | optionflags |= _unittest_reportflags |
|
252 | optionflags |= _unittest_reportflags | |
253 |
|
253 | |||
254 | try: |
|
254 | try: | |
255 | # Save our current directory and switch out to the one where the |
|
255 | # Save our current directory and switch out to the one where the | |
256 | # test was originally created, in case another doctest did a |
|
256 | # test was originally created, in case another doctest did a | |
257 | # directory change. We'll restore this in the finally clause. |
|
257 | # directory change. We'll restore this in the finally clause. | |
258 |
curdir = |
|
258 | curdir = getcwd() | |
259 | #print 'runTest in dir:', self._ori_dir # dbg |
|
259 | #print 'runTest in dir:', self._ori_dir # dbg | |
260 | os.chdir(self._ori_dir) |
|
260 | os.chdir(self._ori_dir) | |
261 |
|
261 | |||
262 | runner.DIVIDER = "-"*70 |
|
262 | runner.DIVIDER = "-"*70 | |
263 | failures, tries = runner.run(test,out=new.write, |
|
263 | failures, tries = runner.run(test,out=new.write, | |
264 | clear_globs=False) |
|
264 | clear_globs=False) | |
265 | finally: |
|
265 | finally: | |
266 | sys.stdout = old |
|
266 | sys.stdout = old | |
267 | os.chdir(curdir) |
|
267 | os.chdir(curdir) | |
268 |
|
268 | |||
269 | if failures: |
|
269 | if failures: | |
270 | raise self.failureException(self.format_failure(new.getvalue())) |
|
270 | raise self.failureException(self.format_failure(new.getvalue())) | |
271 |
|
271 | |||
272 | def setUp(self): |
|
272 | def setUp(self): | |
273 | """Modified test setup that syncs with ipython namespace""" |
|
273 | """Modified test setup that syncs with ipython namespace""" | |
274 | #print "setUp test", self._dt_test.examples # dbg |
|
274 | #print "setUp test", self._dt_test.examples # dbg | |
275 | if isinstance(self._dt_test.examples[0], IPExample): |
|
275 | if isinstance(self._dt_test.examples[0], IPExample): | |
276 | # for IPython examples *only*, we swap the globals with the ipython |
|
276 | # for IPython examples *only*, we swap the globals with the ipython | |
277 | # namespace, after updating it with the globals (which doctest |
|
277 | # namespace, after updating it with the globals (which doctest | |
278 | # fills with the necessary info from the module being tested). |
|
278 | # fills with the necessary info from the module being tested). | |
279 | self.user_ns_orig = {} |
|
279 | self.user_ns_orig = {} | |
280 | self.user_ns_orig.update(_ip.user_ns) |
|
280 | self.user_ns_orig.update(_ip.user_ns) | |
281 | _ip.user_ns.update(self._dt_test.globs) |
|
281 | _ip.user_ns.update(self._dt_test.globs) | |
282 | # We must remove the _ key in the namespace, so that Python's |
|
282 | # We must remove the _ key in the namespace, so that Python's | |
283 | # doctest code sets it naturally |
|
283 | # doctest code sets it naturally | |
284 | _ip.user_ns.pop('_', None) |
|
284 | _ip.user_ns.pop('_', None) | |
285 | _ip.user_ns['__builtins__'] = builtin_mod |
|
285 | _ip.user_ns['__builtins__'] = builtin_mod | |
286 | self._dt_test.globs = _ip.user_ns |
|
286 | self._dt_test.globs = _ip.user_ns | |
287 |
|
287 | |||
288 | super(DocTestCase, self).setUp() |
|
288 | super(DocTestCase, self).setUp() | |
289 |
|
289 | |||
290 | def tearDown(self): |
|
290 | def tearDown(self): | |
291 |
|
291 | |||
292 | # Undo the test.globs reassignment we made, so that the parent class |
|
292 | # Undo the test.globs reassignment we made, so that the parent class | |
293 | # teardown doesn't destroy the ipython namespace |
|
293 | # teardown doesn't destroy the ipython namespace | |
294 | if isinstance(self._dt_test.examples[0], IPExample): |
|
294 | if isinstance(self._dt_test.examples[0], IPExample): | |
295 | self._dt_test.globs = self._dt_test_globs_ori |
|
295 | self._dt_test.globs = self._dt_test_globs_ori | |
296 | _ip.user_ns.clear() |
|
296 | _ip.user_ns.clear() | |
297 | _ip.user_ns.update(self.user_ns_orig) |
|
297 | _ip.user_ns.update(self.user_ns_orig) | |
298 |
|
298 | |||
299 | # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but |
|
299 | # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but | |
300 | # it does look like one to me: its tearDown method tries to run |
|
300 | # it does look like one to me: its tearDown method tries to run | |
301 | # |
|
301 | # | |
302 | # delattr(builtin_mod, self._result_var) |
|
302 | # delattr(builtin_mod, self._result_var) | |
303 | # |
|
303 | # | |
304 | # without checking that the attribute really is there; it implicitly |
|
304 | # without checking that the attribute really is there; it implicitly | |
305 | # assumes it should have been set via displayhook. But if the |
|
305 | # assumes it should have been set via displayhook. But if the | |
306 | # displayhook was never called, this doesn't necessarily happen. I |
|
306 | # displayhook was never called, this doesn't necessarily happen. I | |
307 | # haven't been able to find a little self-contained example outside of |
|
307 | # haven't been able to find a little self-contained example outside of | |
308 | # ipython that would show the problem so I can report it to the nose |
|
308 | # ipython that would show the problem so I can report it to the nose | |
309 | # team, but it does happen a lot in our code. |
|
309 | # team, but it does happen a lot in our code. | |
310 | # |
|
310 | # | |
311 | # So here, we just protect as narrowly as possible by trapping an |
|
311 | # So here, we just protect as narrowly as possible by trapping an | |
312 | # attribute error whose message would be the name of self._result_var, |
|
312 | # attribute error whose message would be the name of self._result_var, | |
313 | # and letting any other error propagate. |
|
313 | # and letting any other error propagate. | |
314 | try: |
|
314 | try: | |
315 | super(DocTestCase, self).tearDown() |
|
315 | super(DocTestCase, self).tearDown() | |
316 | except AttributeError as exc: |
|
316 | except AttributeError as exc: | |
317 | if exc.args[0] != self._result_var: |
|
317 | if exc.args[0] != self._result_var: | |
318 | raise |
|
318 | raise | |
319 |
|
319 | |||
320 |
|
320 | |||
321 | # A simple subclassing of the original with a different class name, so we can |
|
321 | # A simple subclassing of the original with a different class name, so we can | |
322 | # distinguish and treat differently IPython examples from pure python ones. |
|
322 | # distinguish and treat differently IPython examples from pure python ones. | |
323 | class IPExample(doctest.Example): pass |
|
323 | class IPExample(doctest.Example): pass | |
324 |
|
324 | |||
325 |
|
325 | |||
326 | class IPExternalExample(doctest.Example): |
|
326 | class IPExternalExample(doctest.Example): | |
327 | """Doctest examples to be run in an external process.""" |
|
327 | """Doctest examples to be run in an external process.""" | |
328 |
|
328 | |||
329 | def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, |
|
329 | def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, | |
330 | options=None): |
|
330 | options=None): | |
331 | # Parent constructor |
|
331 | # Parent constructor | |
332 | doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) |
|
332 | doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) | |
333 |
|
333 | |||
334 | # An EXTRA newline is needed to prevent pexpect hangs |
|
334 | # An EXTRA newline is needed to prevent pexpect hangs | |
335 | self.source += '\n' |
|
335 | self.source += '\n' | |
336 |
|
336 | |||
337 |
|
337 | |||
338 | class IPDocTestParser(doctest.DocTestParser): |
|
338 | class IPDocTestParser(doctest.DocTestParser): | |
339 | """ |
|
339 | """ | |
340 | A class used to parse strings containing doctest examples. |
|
340 | A class used to parse strings containing doctest examples. | |
341 |
|
341 | |||
342 | Note: This is a version modified to properly recognize IPython input and |
|
342 | Note: This is a version modified to properly recognize IPython input and | |
343 | convert any IPython examples into valid Python ones. |
|
343 | convert any IPython examples into valid Python ones. | |
344 | """ |
|
344 | """ | |
345 | # This regular expression is used to find doctest examples in a |
|
345 | # This regular expression is used to find doctest examples in a | |
346 | # string. It defines three groups: `source` is the source code |
|
346 | # string. It defines three groups: `source` is the source code | |
347 | # (including leading indentation and prompts); `indent` is the |
|
347 | # (including leading indentation and prompts); `indent` is the | |
348 | # indentation of the first (PS1) line of the source code; and |
|
348 | # indentation of the first (PS1) line of the source code; and | |
349 | # `want` is the expected output (including leading indentation). |
|
349 | # `want` is the expected output (including leading indentation). | |
350 |
|
350 | |||
351 | # Classic Python prompts or default IPython ones |
|
351 | # Classic Python prompts or default IPython ones | |
352 | _PS1_PY = r'>>>' |
|
352 | _PS1_PY = r'>>>' | |
353 | _PS2_PY = r'\.\.\.' |
|
353 | _PS2_PY = r'\.\.\.' | |
354 |
|
354 | |||
355 | _PS1_IP = r'In\ \[\d+\]:' |
|
355 | _PS1_IP = r'In\ \[\d+\]:' | |
356 | _PS2_IP = r'\ \ \ \.\.\.+:' |
|
356 | _PS2_IP = r'\ \ \ \.\.\.+:' | |
357 |
|
357 | |||
358 | _RE_TPL = r''' |
|
358 | _RE_TPL = r''' | |
359 | # Source consists of a PS1 line followed by zero or more PS2 lines. |
|
359 | # Source consists of a PS1 line followed by zero or more PS2 lines. | |
360 | (?P<source> |
|
360 | (?P<source> | |
361 | (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line |
|
361 | (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line | |
362 | (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines |
|
362 | (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines | |
363 | \n? # a newline |
|
363 | \n? # a newline | |
364 | # Want consists of any non-blank lines that do not start with PS1. |
|
364 | # Want consists of any non-blank lines that do not start with PS1. | |
365 | (?P<want> (?:(?![ ]*$) # Not a blank line |
|
365 | (?P<want> (?:(?![ ]*$) # Not a blank line | |
366 | (?![ ]*%s) # Not a line starting with PS1 |
|
366 | (?![ ]*%s) # Not a line starting with PS1 | |
367 | (?![ ]*%s) # Not a line starting with PS2 |
|
367 | (?![ ]*%s) # Not a line starting with PS2 | |
368 | .*$\n? # But any other line |
|
368 | .*$\n? # But any other line | |
369 | )*) |
|
369 | )*) | |
370 | ''' |
|
370 | ''' | |
371 |
|
371 | |||
372 | _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), |
|
372 | _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), | |
373 | re.MULTILINE | re.VERBOSE) |
|
373 | re.MULTILINE | re.VERBOSE) | |
374 |
|
374 | |||
375 | _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), |
|
375 | _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), | |
376 | re.MULTILINE | re.VERBOSE) |
|
376 | re.MULTILINE | re.VERBOSE) | |
377 |
|
377 | |||
378 | # Mark a test as being fully random. In this case, we simply append the |
|
378 | # Mark a test as being fully random. In this case, we simply append the | |
379 | # random marker ('#random') to each individual example's output. This way |
|
379 | # random marker ('#random') to each individual example's output. This way | |
380 | # we don't need to modify any other code. |
|
380 | # we don't need to modify any other code. | |
381 | _RANDOM_TEST = re.compile(r'#\s*all-random\s+') |
|
381 | _RANDOM_TEST = re.compile(r'#\s*all-random\s+') | |
382 |
|
382 | |||
383 | # Mark tests to be executed in an external process - currently unsupported. |
|
383 | # Mark tests to be executed in an external process - currently unsupported. | |
384 | _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') |
|
384 | _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') | |
385 |
|
385 | |||
386 | def ip2py(self,source): |
|
386 | def ip2py(self,source): | |
387 | """Convert input IPython source into valid Python.""" |
|
387 | """Convert input IPython source into valid Python.""" | |
388 | block = _ip.input_transformer_manager.transform_cell(source) |
|
388 | block = _ip.input_transformer_manager.transform_cell(source) | |
389 | if len(block.splitlines()) == 1: |
|
389 | if len(block.splitlines()) == 1: | |
390 | return _ip.prefilter(block) |
|
390 | return _ip.prefilter(block) | |
391 | else: |
|
391 | else: | |
392 | return block |
|
392 | return block | |
393 |
|
393 | |||
394 | def parse(self, string, name='<string>'): |
|
394 | def parse(self, string, name='<string>'): | |
395 | """ |
|
395 | """ | |
396 | Divide the given string into examples and intervening text, |
|
396 | Divide the given string into examples and intervening text, | |
397 | and return them as a list of alternating Examples and strings. |
|
397 | and return them as a list of alternating Examples and strings. | |
398 | Line numbers for the Examples are 0-based. The optional |
|
398 | Line numbers for the Examples are 0-based. The optional | |
399 | argument `name` is a name identifying this string, and is only |
|
399 | argument `name` is a name identifying this string, and is only | |
400 | used for error messages. |
|
400 | used for error messages. | |
401 | """ |
|
401 | """ | |
402 |
|
402 | |||
403 | #print 'Parse string:\n',string # dbg |
|
403 | #print 'Parse string:\n',string # dbg | |
404 |
|
404 | |||
405 | string = string.expandtabs() |
|
405 | string = string.expandtabs() | |
406 | # If all lines begin with the same indentation, then strip it. |
|
406 | # If all lines begin with the same indentation, then strip it. | |
407 | min_indent = self._min_indent(string) |
|
407 | min_indent = self._min_indent(string) | |
408 | if min_indent > 0: |
|
408 | if min_indent > 0: | |
409 | string = '\n'.join([l[min_indent:] for l in string.split('\n')]) |
|
409 | string = '\n'.join([l[min_indent:] for l in string.split('\n')]) | |
410 |
|
410 | |||
411 | output = [] |
|
411 | output = [] | |
412 | charno, lineno = 0, 0 |
|
412 | charno, lineno = 0, 0 | |
413 |
|
413 | |||
414 | # We make 'all random' tests by adding the '# random' mark to every |
|
414 | # We make 'all random' tests by adding the '# random' mark to every | |
415 | # block of output in the test. |
|
415 | # block of output in the test. | |
416 | if self._RANDOM_TEST.search(string): |
|
416 | if self._RANDOM_TEST.search(string): | |
417 | random_marker = '\n# random' |
|
417 | random_marker = '\n# random' | |
418 | else: |
|
418 | else: | |
419 | random_marker = '' |
|
419 | random_marker = '' | |
420 |
|
420 | |||
421 | # Whether to convert the input from ipython to python syntax |
|
421 | # Whether to convert the input from ipython to python syntax | |
422 | ip2py = False |
|
422 | ip2py = False | |
423 | # Find all doctest examples in the string. First, try them as Python |
|
423 | # Find all doctest examples in the string. First, try them as Python | |
424 | # examples, then as IPython ones |
|
424 | # examples, then as IPython ones | |
425 | terms = list(self._EXAMPLE_RE_PY.finditer(string)) |
|
425 | terms = list(self._EXAMPLE_RE_PY.finditer(string)) | |
426 | if terms: |
|
426 | if terms: | |
427 | # Normal Python example |
|
427 | # Normal Python example | |
428 | #print '-'*70 # dbg |
|
428 | #print '-'*70 # dbg | |
429 | #print 'PyExample, Source:\n',string # dbg |
|
429 | #print 'PyExample, Source:\n',string # dbg | |
430 | #print '-'*70 # dbg |
|
430 | #print '-'*70 # dbg | |
431 | Example = doctest.Example |
|
431 | Example = doctest.Example | |
432 | else: |
|
432 | else: | |
433 | # It's an ipython example. Note that IPExamples are run |
|
433 | # It's an ipython example. Note that IPExamples are run | |
434 | # in-process, so their syntax must be turned into valid python. |
|
434 | # in-process, so their syntax must be turned into valid python. | |
435 | # IPExternalExamples are run out-of-process (via pexpect) so they |
|
435 | # IPExternalExamples are run out-of-process (via pexpect) so they | |
436 | # don't need any filtering (a real ipython will be executing them). |
|
436 | # don't need any filtering (a real ipython will be executing them). | |
437 | terms = list(self._EXAMPLE_RE_IP.finditer(string)) |
|
437 | terms = list(self._EXAMPLE_RE_IP.finditer(string)) | |
438 | if self._EXTERNAL_IP.search(string): |
|
438 | if self._EXTERNAL_IP.search(string): | |
439 | #print '-'*70 # dbg |
|
439 | #print '-'*70 # dbg | |
440 | #print 'IPExternalExample, Source:\n',string # dbg |
|
440 | #print 'IPExternalExample, Source:\n',string # dbg | |
441 | #print '-'*70 # dbg |
|
441 | #print '-'*70 # dbg | |
442 | Example = IPExternalExample |
|
442 | Example = IPExternalExample | |
443 | else: |
|
443 | else: | |
444 | #print '-'*70 # dbg |
|
444 | #print '-'*70 # dbg | |
445 | #print 'IPExample, Source:\n',string # dbg |
|
445 | #print 'IPExample, Source:\n',string # dbg | |
446 | #print '-'*70 # dbg |
|
446 | #print '-'*70 # dbg | |
447 | Example = IPExample |
|
447 | Example = IPExample | |
448 | ip2py = True |
|
448 | ip2py = True | |
449 |
|
449 | |||
450 | for m in terms: |
|
450 | for m in terms: | |
451 | # Add the pre-example text to `output`. |
|
451 | # Add the pre-example text to `output`. | |
452 | output.append(string[charno:m.start()]) |
|
452 | output.append(string[charno:m.start()]) | |
453 | # Update lineno (lines before this example) |
|
453 | # Update lineno (lines before this example) | |
454 | lineno += string.count('\n', charno, m.start()) |
|
454 | lineno += string.count('\n', charno, m.start()) | |
455 | # Extract info from the regexp match. |
|
455 | # Extract info from the regexp match. | |
456 | (source, options, want, exc_msg) = \ |
|
456 | (source, options, want, exc_msg) = \ | |
457 | self._parse_example(m, name, lineno,ip2py) |
|
457 | self._parse_example(m, name, lineno,ip2py) | |
458 |
|
458 | |||
459 | # Append the random-output marker (it defaults to empty in most |
|
459 | # Append the random-output marker (it defaults to empty in most | |
460 | # cases, it's only non-empty for 'all-random' tests): |
|
460 | # cases, it's only non-empty for 'all-random' tests): | |
461 | want += random_marker |
|
461 | want += random_marker | |
462 |
|
462 | |||
463 | if Example is IPExternalExample: |
|
463 | if Example is IPExternalExample: | |
464 | options[doctest.NORMALIZE_WHITESPACE] = True |
|
464 | options[doctest.NORMALIZE_WHITESPACE] = True | |
465 | want += '\n' |
|
465 | want += '\n' | |
466 |
|
466 | |||
467 | # Create an Example, and add it to the list. |
|
467 | # Create an Example, and add it to the list. | |
468 | if not self._IS_BLANK_OR_COMMENT(source): |
|
468 | if not self._IS_BLANK_OR_COMMENT(source): | |
469 | output.append(Example(source, want, exc_msg, |
|
469 | output.append(Example(source, want, exc_msg, | |
470 | lineno=lineno, |
|
470 | lineno=lineno, | |
471 | indent=min_indent+len(m.group('indent')), |
|
471 | indent=min_indent+len(m.group('indent')), | |
472 | options=options)) |
|
472 | options=options)) | |
473 | # Update lineno (lines inside this example) |
|
473 | # Update lineno (lines inside this example) | |
474 | lineno += string.count('\n', m.start(), m.end()) |
|
474 | lineno += string.count('\n', m.start(), m.end()) | |
475 | # Update charno. |
|
475 | # Update charno. | |
476 | charno = m.end() |
|
476 | charno = m.end() | |
477 | # Add any remaining post-example text to `output`. |
|
477 | # Add any remaining post-example text to `output`. | |
478 | output.append(string[charno:]) |
|
478 | output.append(string[charno:]) | |
479 | return output |
|
479 | return output | |
480 |
|
480 | |||
481 | def _parse_example(self, m, name, lineno,ip2py=False): |
|
481 | def _parse_example(self, m, name, lineno,ip2py=False): | |
482 | """ |
|
482 | """ | |
483 | Given a regular expression match from `_EXAMPLE_RE` (`m`), |
|
483 | Given a regular expression match from `_EXAMPLE_RE` (`m`), | |
484 | return a pair `(source, want)`, where `source` is the matched |
|
484 | return a pair `(source, want)`, where `source` is the matched | |
485 | example's source code (with prompts and indentation stripped); |
|
485 | example's source code (with prompts and indentation stripped); | |
486 | and `want` is the example's expected output (with indentation |
|
486 | and `want` is the example's expected output (with indentation | |
487 | stripped). |
|
487 | stripped). | |
488 |
|
488 | |||
489 | `name` is the string's name, and `lineno` is the line number |
|
489 | `name` is the string's name, and `lineno` is the line number | |
490 | where the example starts; both are used for error messages. |
|
490 | where the example starts; both are used for error messages. | |
491 |
|
491 | |||
492 | Optional: |
|
492 | Optional: | |
493 | `ip2py`: if true, filter the input via IPython to convert the syntax |
|
493 | `ip2py`: if true, filter the input via IPython to convert the syntax | |
494 | into valid python. |
|
494 | into valid python. | |
495 | """ |
|
495 | """ | |
496 |
|
496 | |||
497 | # Get the example's indentation level. |
|
497 | # Get the example's indentation level. | |
498 | indent = len(m.group('indent')) |
|
498 | indent = len(m.group('indent')) | |
499 |
|
499 | |||
500 | # Divide source into lines; check that they're properly |
|
500 | # Divide source into lines; check that they're properly | |
501 | # indented; and then strip their indentation & prompts. |
|
501 | # indented; and then strip their indentation & prompts. | |
502 | source_lines = m.group('source').split('\n') |
|
502 | source_lines = m.group('source').split('\n') | |
503 |
|
503 | |||
504 | # We're using variable-length input prompts |
|
504 | # We're using variable-length input prompts | |
505 | ps1 = m.group('ps1') |
|
505 | ps1 = m.group('ps1') | |
506 | ps2 = m.group('ps2') |
|
506 | ps2 = m.group('ps2') | |
507 | ps1_len = len(ps1) |
|
507 | ps1_len = len(ps1) | |
508 |
|
508 | |||
509 | self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) |
|
509 | self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) | |
510 | if ps2: |
|
510 | if ps2: | |
511 | self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) |
|
511 | self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) | |
512 |
|
512 | |||
513 | source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) |
|
513 | source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) | |
514 |
|
514 | |||
515 | if ip2py: |
|
515 | if ip2py: | |
516 | # Convert source input from IPython into valid Python syntax |
|
516 | # Convert source input from IPython into valid Python syntax | |
517 | source = self.ip2py(source) |
|
517 | source = self.ip2py(source) | |
518 |
|
518 | |||
519 | # Divide want into lines; check that it's properly indented; and |
|
519 | # Divide want into lines; check that it's properly indented; and | |
520 | # then strip the indentation. Spaces before the last newline should |
|
520 | # then strip the indentation. Spaces before the last newline should | |
521 | # be preserved, so plain rstrip() isn't good enough. |
|
521 | # be preserved, so plain rstrip() isn't good enough. | |
522 | want = m.group('want') |
|
522 | want = m.group('want') | |
523 | want_lines = want.split('\n') |
|
523 | want_lines = want.split('\n') | |
524 | if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): |
|
524 | if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): | |
525 | del want_lines[-1] # forget final newline & spaces after it |
|
525 | del want_lines[-1] # forget final newline & spaces after it | |
526 | self._check_prefix(want_lines, ' '*indent, name, |
|
526 | self._check_prefix(want_lines, ' '*indent, name, | |
527 | lineno + len(source_lines)) |
|
527 | lineno + len(source_lines)) | |
528 |
|
528 | |||
529 | # Remove ipython output prompt that might be present in the first line |
|
529 | # Remove ipython output prompt that might be present in the first line | |
530 | want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) |
|
530 | want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) | |
531 |
|
531 | |||
532 | want = '\n'.join([wl[indent:] for wl in want_lines]) |
|
532 | want = '\n'.join([wl[indent:] for wl in want_lines]) | |
533 |
|
533 | |||
534 | # If `want` contains a traceback message, then extract it. |
|
534 | # If `want` contains a traceback message, then extract it. | |
535 | m = self._EXCEPTION_RE.match(want) |
|
535 | m = self._EXCEPTION_RE.match(want) | |
536 | if m: |
|
536 | if m: | |
537 | exc_msg = m.group('msg') |
|
537 | exc_msg = m.group('msg') | |
538 | else: |
|
538 | else: | |
539 | exc_msg = None |
|
539 | exc_msg = None | |
540 |
|
540 | |||
541 | # Extract options from the source. |
|
541 | # Extract options from the source. | |
542 | options = self._find_options(source, name, lineno) |
|
542 | options = self._find_options(source, name, lineno) | |
543 |
|
543 | |||
544 | return source, options, want, exc_msg |
|
544 | return source, options, want, exc_msg | |
545 |
|
545 | |||
546 | def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): |
|
546 | def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): | |
547 | """ |
|
547 | """ | |
548 | Given the lines of a source string (including prompts and |
|
548 | Given the lines of a source string (including prompts and | |
549 | leading indentation), check to make sure that every prompt is |
|
549 | leading indentation), check to make sure that every prompt is | |
550 | followed by a space character. If any line is not followed by |
|
550 | followed by a space character. If any line is not followed by | |
551 | a space character, then raise ValueError. |
|
551 | a space character, then raise ValueError. | |
552 |
|
552 | |||
553 | Note: IPython-modified version which takes the input prompt length as a |
|
553 | Note: IPython-modified version which takes the input prompt length as a | |
554 | parameter, so that prompts of variable length can be dealt with. |
|
554 | parameter, so that prompts of variable length can be dealt with. | |
555 | """ |
|
555 | """ | |
556 | space_idx = indent+ps1_len |
|
556 | space_idx = indent+ps1_len | |
557 | min_len = space_idx+1 |
|
557 | min_len = space_idx+1 | |
558 | for i, line in enumerate(lines): |
|
558 | for i, line in enumerate(lines): | |
559 | if len(line) >= min_len and line[space_idx] != ' ': |
|
559 | if len(line) >= min_len and line[space_idx] != ' ': | |
560 | raise ValueError('line %r of the docstring for %s ' |
|
560 | raise ValueError('line %r of the docstring for %s ' | |
561 | 'lacks blank after %s: %r' % |
|
561 | 'lacks blank after %s: %r' % | |
562 | (lineno+i+1, name, |
|
562 | (lineno+i+1, name, | |
563 | line[indent:space_idx], line)) |
|
563 | line[indent:space_idx], line)) | |
564 |
|
564 | |||
565 |
|
565 | |||
566 | SKIP = doctest.register_optionflag('SKIP') |
|
566 | SKIP = doctest.register_optionflag('SKIP') | |
567 |
|
567 | |||
568 |
|
568 | |||
569 | class IPDocTestRunner(doctest.DocTestRunner,object): |
|
569 | class IPDocTestRunner(doctest.DocTestRunner,object): | |
570 | """Test runner that synchronizes the IPython namespace with test globals. |
|
570 | """Test runner that synchronizes the IPython namespace with test globals. | |
571 | """ |
|
571 | """ | |
572 |
|
572 | |||
573 | def run(self, test, compileflags=None, out=None, clear_globs=True): |
|
573 | def run(self, test, compileflags=None, out=None, clear_globs=True): | |
574 |
|
574 | |||
575 | # Hack: ipython needs access to the execution context of the example, |
|
575 | # Hack: ipython needs access to the execution context of the example, | |
576 | # so that it can propagate user variables loaded by %run into |
|
576 | # so that it can propagate user variables loaded by %run into | |
577 | # test.globs. We put them here into our modified %run as a function |
|
577 | # test.globs. We put them here into our modified %run as a function | |
578 | # attribute. Our new %run will then only make the namespace update |
|
578 | # attribute. Our new %run will then only make the namespace update | |
579 | # when called (rather than unconconditionally updating test.globs here |
|
579 | # when called (rather than unconconditionally updating test.globs here | |
580 | # for all examples, most of which won't be calling %run anyway). |
|
580 | # for all examples, most of which won't be calling %run anyway). | |
581 | #_ip._ipdoctest_test_globs = test.globs |
|
581 | #_ip._ipdoctest_test_globs = test.globs | |
582 | #_ip._ipdoctest_test_filename = test.filename |
|
582 | #_ip._ipdoctest_test_filename = test.filename | |
583 |
|
583 | |||
584 | test.globs.update(_ip.user_ns) |
|
584 | test.globs.update(_ip.user_ns) | |
585 |
|
585 | |||
586 | return super(IPDocTestRunner,self).run(test, |
|
586 | return super(IPDocTestRunner,self).run(test, | |
587 | compileflags,out,clear_globs) |
|
587 | compileflags,out,clear_globs) | |
588 |
|
588 | |||
589 |
|
589 | |||
590 | class DocFileCase(doctest.DocFileCase): |
|
590 | class DocFileCase(doctest.DocFileCase): | |
591 | """Overrides to provide filename |
|
591 | """Overrides to provide filename | |
592 | """ |
|
592 | """ | |
593 | def address(self): |
|
593 | def address(self): | |
594 | return (self._dt_test.filename, None, None) |
|
594 | return (self._dt_test.filename, None, None) | |
595 |
|
595 | |||
596 |
|
596 | |||
597 | class ExtensionDoctest(doctests.Doctest): |
|
597 | class ExtensionDoctest(doctests.Doctest): | |
598 | """Nose Plugin that supports doctests in extension modules. |
|
598 | """Nose Plugin that supports doctests in extension modules. | |
599 | """ |
|
599 | """ | |
600 | name = 'extdoctest' # call nosetests with --with-extdoctest |
|
600 | name = 'extdoctest' # call nosetests with --with-extdoctest | |
601 | enabled = True |
|
601 | enabled = True | |
602 |
|
602 | |||
603 | def options(self, parser, env=os.environ): |
|
603 | def options(self, parser, env=os.environ): | |
604 | Plugin.options(self, parser, env) |
|
604 | Plugin.options(self, parser, env) | |
605 | parser.add_option('--doctest-tests', action='store_true', |
|
605 | parser.add_option('--doctest-tests', action='store_true', | |
606 | dest='doctest_tests', |
|
606 | dest='doctest_tests', | |
607 | default=env.get('NOSE_DOCTEST_TESTS',True), |
|
607 | default=env.get('NOSE_DOCTEST_TESTS',True), | |
608 | help="Also look for doctests in test modules. " |
|
608 | help="Also look for doctests in test modules. " | |
609 | "Note that classes, methods and functions should " |
|
609 | "Note that classes, methods and functions should " | |
610 | "have either doctests or non-doctest tests, " |
|
610 | "have either doctests or non-doctest tests, " | |
611 | "not both. [NOSE_DOCTEST_TESTS]") |
|
611 | "not both. [NOSE_DOCTEST_TESTS]") | |
612 | parser.add_option('--doctest-extension', action="append", |
|
612 | parser.add_option('--doctest-extension', action="append", | |
613 | dest="doctestExtension", |
|
613 | dest="doctestExtension", | |
614 | help="Also look for doctests in files with " |
|
614 | help="Also look for doctests in files with " | |
615 | "this extension [NOSE_DOCTEST_EXTENSION]") |
|
615 | "this extension [NOSE_DOCTEST_EXTENSION]") | |
616 | # Set the default as a list, if given in env; otherwise |
|
616 | # Set the default as a list, if given in env; otherwise | |
617 | # an additional value set on the command line will cause |
|
617 | # an additional value set on the command line will cause | |
618 | # an error. |
|
618 | # an error. | |
619 | env_setting = env.get('NOSE_DOCTEST_EXTENSION') |
|
619 | env_setting = env.get('NOSE_DOCTEST_EXTENSION') | |
620 | if env_setting is not None: |
|
620 | if env_setting is not None: | |
621 | parser.set_defaults(doctestExtension=tolist(env_setting)) |
|
621 | parser.set_defaults(doctestExtension=tolist(env_setting)) | |
622 |
|
622 | |||
623 |
|
623 | |||
624 | def configure(self, options, config): |
|
624 | def configure(self, options, config): | |
625 | Plugin.configure(self, options, config) |
|
625 | Plugin.configure(self, options, config) | |
626 | # Pull standard doctest plugin out of config; we will do doctesting |
|
626 | # Pull standard doctest plugin out of config; we will do doctesting | |
627 | config.plugins.plugins = [p for p in config.plugins.plugins |
|
627 | config.plugins.plugins = [p for p in config.plugins.plugins | |
628 | if p.name != 'doctest'] |
|
628 | if p.name != 'doctest'] | |
629 | self.doctest_tests = options.doctest_tests |
|
629 | self.doctest_tests = options.doctest_tests | |
630 | self.extension = tolist(options.doctestExtension) |
|
630 | self.extension = tolist(options.doctestExtension) | |
631 |
|
631 | |||
632 | self.parser = doctest.DocTestParser() |
|
632 | self.parser = doctest.DocTestParser() | |
633 | self.finder = DocTestFinder() |
|
633 | self.finder = DocTestFinder() | |
634 | self.checker = IPDoctestOutputChecker() |
|
634 | self.checker = IPDoctestOutputChecker() | |
635 | self.globs = None |
|
635 | self.globs = None | |
636 | self.extraglobs = None |
|
636 | self.extraglobs = None | |
637 |
|
637 | |||
638 |
|
638 | |||
639 | def loadTestsFromExtensionModule(self,filename): |
|
639 | def loadTestsFromExtensionModule(self,filename): | |
640 | bpath,mod = os.path.split(filename) |
|
640 | bpath,mod = os.path.split(filename) | |
641 | modname = os.path.splitext(mod)[0] |
|
641 | modname = os.path.splitext(mod)[0] | |
642 | try: |
|
642 | try: | |
643 | sys.path.append(bpath) |
|
643 | sys.path.append(bpath) | |
644 | module = __import__(modname) |
|
644 | module = __import__(modname) | |
645 | tests = list(self.loadTestsFromModule(module)) |
|
645 | tests = list(self.loadTestsFromModule(module)) | |
646 | finally: |
|
646 | finally: | |
647 | sys.path.pop() |
|
647 | sys.path.pop() | |
648 | return tests |
|
648 | return tests | |
649 |
|
649 | |||
650 | # NOTE: the method below is almost a copy of the original one in nose, with |
|
650 | # NOTE: the method below is almost a copy of the original one in nose, with | |
651 | # a few modifications to control output checking. |
|
651 | # a few modifications to control output checking. | |
652 |
|
652 | |||
653 | def loadTestsFromModule(self, module): |
|
653 | def loadTestsFromModule(self, module): | |
654 | #print '*** ipdoctest - lTM',module # dbg |
|
654 | #print '*** ipdoctest - lTM',module # dbg | |
655 |
|
655 | |||
656 | if not self.matches(module.__name__): |
|
656 | if not self.matches(module.__name__): | |
657 | log.debug("Doctest doesn't want module %s", module) |
|
657 | log.debug("Doctest doesn't want module %s", module) | |
658 | return |
|
658 | return | |
659 |
|
659 | |||
660 | tests = self.finder.find(module,globs=self.globs, |
|
660 | tests = self.finder.find(module,globs=self.globs, | |
661 | extraglobs=self.extraglobs) |
|
661 | extraglobs=self.extraglobs) | |
662 | if not tests: |
|
662 | if not tests: | |
663 | return |
|
663 | return | |
664 |
|
664 | |||
665 | # always use whitespace and ellipsis options |
|
665 | # always use whitespace and ellipsis options | |
666 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |
|
666 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | |
667 |
|
667 | |||
668 | tests.sort() |
|
668 | tests.sort() | |
669 | module_file = module.__file__ |
|
669 | module_file = module.__file__ | |
670 | if module_file[-4:] in ('.pyc', '.pyo'): |
|
670 | if module_file[-4:] in ('.pyc', '.pyo'): | |
671 | module_file = module_file[:-1] |
|
671 | module_file = module_file[:-1] | |
672 | for test in tests: |
|
672 | for test in tests: | |
673 | if not test.examples: |
|
673 | if not test.examples: | |
674 | continue |
|
674 | continue | |
675 | if not test.filename: |
|
675 | if not test.filename: | |
676 | test.filename = module_file |
|
676 | test.filename = module_file | |
677 |
|
677 | |||
678 | yield DocTestCase(test, |
|
678 | yield DocTestCase(test, | |
679 | optionflags=optionflags, |
|
679 | optionflags=optionflags, | |
680 | checker=self.checker) |
|
680 | checker=self.checker) | |
681 |
|
681 | |||
682 |
|
682 | |||
683 | def loadTestsFromFile(self, filename): |
|
683 | def loadTestsFromFile(self, filename): | |
684 | #print "ipdoctest - from file", filename # dbg |
|
684 | #print "ipdoctest - from file", filename # dbg | |
685 | if is_extension_module(filename): |
|
685 | if is_extension_module(filename): | |
686 | for t in self.loadTestsFromExtensionModule(filename): |
|
686 | for t in self.loadTestsFromExtensionModule(filename): | |
687 | yield t |
|
687 | yield t | |
688 | else: |
|
688 | else: | |
689 | if self.extension and anyp(filename.endswith, self.extension): |
|
689 | if self.extension and anyp(filename.endswith, self.extension): | |
690 | name = os.path.basename(filename) |
|
690 | name = os.path.basename(filename) | |
691 | dh = open(filename) |
|
691 | dh = open(filename) | |
692 | try: |
|
692 | try: | |
693 | doc = dh.read() |
|
693 | doc = dh.read() | |
694 | finally: |
|
694 | finally: | |
695 | dh.close() |
|
695 | dh.close() | |
696 | test = self.parser.get_doctest( |
|
696 | test = self.parser.get_doctest( | |
697 | doc, globs={'__file__': filename}, name=name, |
|
697 | doc, globs={'__file__': filename}, name=name, | |
698 | filename=filename, lineno=0) |
|
698 | filename=filename, lineno=0) | |
699 | if test.examples: |
|
699 | if test.examples: | |
700 | #print 'FileCase:',test.examples # dbg |
|
700 | #print 'FileCase:',test.examples # dbg | |
701 | yield DocFileCase(test) |
|
701 | yield DocFileCase(test) | |
702 | else: |
|
702 | else: | |
703 | yield False # no tests to load |
|
703 | yield False # no tests to load | |
704 |
|
704 | |||
705 |
|
705 | |||
706 | class IPythonDoctest(ExtensionDoctest): |
|
706 | class IPythonDoctest(ExtensionDoctest): | |
707 | """Nose Plugin that supports doctests in extension modules. |
|
707 | """Nose Plugin that supports doctests in extension modules. | |
708 | """ |
|
708 | """ | |
709 | name = 'ipdoctest' # call nosetests with --with-ipdoctest |
|
709 | name = 'ipdoctest' # call nosetests with --with-ipdoctest | |
710 | enabled = True |
|
710 | enabled = True | |
711 |
|
711 | |||
712 | def makeTest(self, obj, parent): |
|
712 | def makeTest(self, obj, parent): | |
713 | """Look for doctests in the given object, which will be a |
|
713 | """Look for doctests in the given object, which will be a | |
714 | function, method or class. |
|
714 | function, method or class. | |
715 | """ |
|
715 | """ | |
716 | #print 'Plugin analyzing:', obj, parent # dbg |
|
716 | #print 'Plugin analyzing:', obj, parent # dbg | |
717 | # always use whitespace and ellipsis options |
|
717 | # always use whitespace and ellipsis options | |
718 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |
|
718 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | |
719 |
|
719 | |||
720 | doctests = self.finder.find(obj, module=getmodule(parent)) |
|
720 | doctests = self.finder.find(obj, module=getmodule(parent)) | |
721 | if doctests: |
|
721 | if doctests: | |
722 | for test in doctests: |
|
722 | for test in doctests: | |
723 | if len(test.examples) == 0: |
|
723 | if len(test.examples) == 0: | |
724 | continue |
|
724 | continue | |
725 |
|
725 | |||
726 | yield DocTestCase(test, obj=obj, |
|
726 | yield DocTestCase(test, obj=obj, | |
727 | optionflags=optionflags, |
|
727 | optionflags=optionflags, | |
728 | checker=self.checker) |
|
728 | checker=self.checker) | |
729 |
|
729 | |||
730 | def options(self, parser, env=os.environ): |
|
730 | def options(self, parser, env=os.environ): | |
731 | #print "Options for nose plugin:", self.name # dbg |
|
731 | #print "Options for nose plugin:", self.name # dbg | |
732 | Plugin.options(self, parser, env) |
|
732 | Plugin.options(self, parser, env) | |
733 | parser.add_option('--ipdoctest-tests', action='store_true', |
|
733 | parser.add_option('--ipdoctest-tests', action='store_true', | |
734 | dest='ipdoctest_tests', |
|
734 | dest='ipdoctest_tests', | |
735 | default=env.get('NOSE_IPDOCTEST_TESTS',True), |
|
735 | default=env.get('NOSE_IPDOCTEST_TESTS',True), | |
736 | help="Also look for doctests in test modules. " |
|
736 | help="Also look for doctests in test modules. " | |
737 | "Note that classes, methods and functions should " |
|
737 | "Note that classes, methods and functions should " | |
738 | "have either doctests or non-doctest tests, " |
|
738 | "have either doctests or non-doctest tests, " | |
739 | "not both. [NOSE_IPDOCTEST_TESTS]") |
|
739 | "not both. [NOSE_IPDOCTEST_TESTS]") | |
740 | parser.add_option('--ipdoctest-extension', action="append", |
|
740 | parser.add_option('--ipdoctest-extension', action="append", | |
741 | dest="ipdoctest_extension", |
|
741 | dest="ipdoctest_extension", | |
742 | help="Also look for doctests in files with " |
|
742 | help="Also look for doctests in files with " | |
743 | "this extension [NOSE_IPDOCTEST_EXTENSION]") |
|
743 | "this extension [NOSE_IPDOCTEST_EXTENSION]") | |
744 | # Set the default as a list, if given in env; otherwise |
|
744 | # Set the default as a list, if given in env; otherwise | |
745 | # an additional value set on the command line will cause |
|
745 | # an additional value set on the command line will cause | |
746 | # an error. |
|
746 | # an error. | |
747 | env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') |
|
747 | env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') | |
748 | if env_setting is not None: |
|
748 | if env_setting is not None: | |
749 | parser.set_defaults(ipdoctest_extension=tolist(env_setting)) |
|
749 | parser.set_defaults(ipdoctest_extension=tolist(env_setting)) | |
750 |
|
750 | |||
751 | def configure(self, options, config): |
|
751 | def configure(self, options, config): | |
752 | #print "Configuring nose plugin:", self.name # dbg |
|
752 | #print "Configuring nose plugin:", self.name # dbg | |
753 | Plugin.configure(self, options, config) |
|
753 | Plugin.configure(self, options, config) | |
754 | # Pull standard doctest plugin out of config; we will do doctesting |
|
754 | # Pull standard doctest plugin out of config; we will do doctesting | |
755 | config.plugins.plugins = [p for p in config.plugins.plugins |
|
755 | config.plugins.plugins = [p for p in config.plugins.plugins | |
756 | if p.name != 'doctest'] |
|
756 | if p.name != 'doctest'] | |
757 | self.doctest_tests = options.ipdoctest_tests |
|
757 | self.doctest_tests = options.ipdoctest_tests | |
758 | self.extension = tolist(options.ipdoctest_extension) |
|
758 | self.extension = tolist(options.ipdoctest_extension) | |
759 |
|
759 | |||
760 | self.parser = IPDocTestParser() |
|
760 | self.parser = IPDocTestParser() | |
761 | self.finder = DocTestFinder(parser=self.parser) |
|
761 | self.finder = DocTestFinder(parser=self.parser) | |
762 | self.checker = IPDoctestOutputChecker() |
|
762 | self.checker = IPDoctestOutputChecker() | |
763 | self.globs = None |
|
763 | self.globs = None | |
764 | self.extraglobs = None |
|
764 | self.extraglobs = None |
@@ -1,187 +1,187 b'' | |||||
1 | """Windows-specific implementation of process utilities. |
|
1 | """Windows-specific implementation of process utilities. | |
2 |
|
2 | |||
3 | This file is only meant to be imported by process.py, not by end-users. |
|
3 | This file is only meant to be imported by process.py, not by end-users. | |
4 | """ |
|
4 | """ | |
5 |
|
5 | |||
6 | #----------------------------------------------------------------------------- |
|
6 | #----------------------------------------------------------------------------- | |
7 | # Copyright (C) 2010-2011 The IPython Development Team |
|
7 | # Copyright (C) 2010-2011 The IPython Development Team | |
8 | # |
|
8 | # | |
9 | # Distributed under the terms of the BSD License. The full license is in |
|
9 | # Distributed under the terms of the BSD License. The full license is in | |
10 | # the file COPYING, distributed as part of this software. |
|
10 | # the file COPYING, distributed as part of this software. | |
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 |
|
12 | |||
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 | # Imports |
|
14 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | from __future__ import print_function |
|
16 | from __future__ import print_function | |
17 |
|
17 | |||
18 | # stdlib |
|
18 | # stdlib | |
19 | import os |
|
19 | import os | |
20 | import sys |
|
20 | import sys | |
21 | import ctypes |
|
21 | import ctypes | |
22 |
|
22 | |||
23 | from ctypes import c_int, POINTER |
|
23 | from ctypes import c_int, POINTER | |
24 | from ctypes.wintypes import LPCWSTR, HLOCAL |
|
24 | from ctypes.wintypes import LPCWSTR, HLOCAL | |
25 | from subprocess import STDOUT |
|
25 | from subprocess import STDOUT | |
26 |
|
26 | |||
27 | # our own imports |
|
27 | # our own imports | |
28 | from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split |
|
28 | from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split | |
29 | from . import py3compat |
|
29 | from . import py3compat | |
30 | from .encoding import DEFAULT_ENCODING |
|
30 | from .encoding import DEFAULT_ENCODING | |
31 |
|
31 | |||
32 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
33 | # Function definitions |
|
33 | # Function definitions | |
34 | #----------------------------------------------------------------------------- |
|
34 | #----------------------------------------------------------------------------- | |
35 |
|
35 | |||
36 | class AvoidUNCPath(object): |
|
36 | class AvoidUNCPath(object): | |
37 | """A context manager to protect command execution from UNC paths. |
|
37 | """A context manager to protect command execution from UNC paths. | |
38 |
|
38 | |||
39 | In the Win32 API, commands can't be invoked with the cwd being a UNC path. |
|
39 | In the Win32 API, commands can't be invoked with the cwd being a UNC path. | |
40 | This context manager temporarily changes directory to the 'C:' drive on |
|
40 | This context manager temporarily changes directory to the 'C:' drive on | |
41 | entering, and restores the original working directory on exit. |
|
41 | entering, and restores the original working directory on exit. | |
42 |
|
42 | |||
43 | The context manager returns the starting working directory *if* it made a |
|
43 | The context manager returns the starting working directory *if* it made a | |
44 | change and None otherwise, so that users can apply the necessary adjustment |
|
44 | change and None otherwise, so that users can apply the necessary adjustment | |
45 | to their system calls in the event of a change. |
|
45 | to their system calls in the event of a change. | |
46 |
|
46 | |||
47 | Examples |
|
47 | Examples | |
48 | -------- |
|
48 | -------- | |
49 | :: |
|
49 | :: | |
50 | cmd = 'dir' |
|
50 | cmd = 'dir' | |
51 | with AvoidUNCPath() as path: |
|
51 | with AvoidUNCPath() as path: | |
52 | if path is not None: |
|
52 | if path is not None: | |
53 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
53 | cmd = '"pushd %s &&"%s' % (path, cmd) | |
54 | os.system(cmd) |
|
54 | os.system(cmd) | |
55 | """ |
|
55 | """ | |
56 | def __enter__(self): |
|
56 | def __enter__(self): | |
57 |
self.path = |
|
57 | self.path = py3compat.getcwd() | |
58 | self.is_unc_path = self.path.startswith(r"\\") |
|
58 | self.is_unc_path = self.path.startswith(r"\\") | |
59 | if self.is_unc_path: |
|
59 | if self.is_unc_path: | |
60 | # change to c drive (as cmd.exe cannot handle UNC addresses) |
|
60 | # change to c drive (as cmd.exe cannot handle UNC addresses) | |
61 | os.chdir("C:") |
|
61 | os.chdir("C:") | |
62 | return self.path |
|
62 | return self.path | |
63 | else: |
|
63 | else: | |
64 | # We return None to signal that there was no change in the working |
|
64 | # We return None to signal that there was no change in the working | |
65 | # directory |
|
65 | # directory | |
66 | return None |
|
66 | return None | |
67 |
|
67 | |||
68 | def __exit__(self, exc_type, exc_value, traceback): |
|
68 | def __exit__(self, exc_type, exc_value, traceback): | |
69 | if self.is_unc_path: |
|
69 | if self.is_unc_path: | |
70 | os.chdir(self.path) |
|
70 | os.chdir(self.path) | |
71 |
|
71 | |||
72 |
|
72 | |||
73 | def _find_cmd(cmd): |
|
73 | def _find_cmd(cmd): | |
74 | """Find the full path to a .bat or .exe using the win32api module.""" |
|
74 | """Find the full path to a .bat or .exe using the win32api module.""" | |
75 | try: |
|
75 | try: | |
76 | from win32api import SearchPath |
|
76 | from win32api import SearchPath | |
77 | except ImportError: |
|
77 | except ImportError: | |
78 | raise ImportError('you need to have pywin32 installed for this to work') |
|
78 | raise ImportError('you need to have pywin32 installed for this to work') | |
79 | else: |
|
79 | else: | |
80 | PATH = os.environ['PATH'] |
|
80 | PATH = os.environ['PATH'] | |
81 | extensions = ['.exe', '.com', '.bat', '.py'] |
|
81 | extensions = ['.exe', '.com', '.bat', '.py'] | |
82 | path = None |
|
82 | path = None | |
83 | for ext in extensions: |
|
83 | for ext in extensions: | |
84 | try: |
|
84 | try: | |
85 | path = SearchPath(PATH, cmd, ext)[0] |
|
85 | path = SearchPath(PATH, cmd, ext)[0] | |
86 | except: |
|
86 | except: | |
87 | pass |
|
87 | pass | |
88 | if path is None: |
|
88 | if path is None: | |
89 | raise OSError("command %r not found" % cmd) |
|
89 | raise OSError("command %r not found" % cmd) | |
90 | else: |
|
90 | else: | |
91 | return path |
|
91 | return path | |
92 |
|
92 | |||
93 |
|
93 | |||
94 | def _system_body(p): |
|
94 | def _system_body(p): | |
95 | """Callback for _system.""" |
|
95 | """Callback for _system.""" | |
96 | enc = DEFAULT_ENCODING |
|
96 | enc = DEFAULT_ENCODING | |
97 | for line in read_no_interrupt(p.stdout).splitlines(): |
|
97 | for line in read_no_interrupt(p.stdout).splitlines(): | |
98 | line = line.decode(enc, 'replace') |
|
98 | line = line.decode(enc, 'replace') | |
99 | print(line, file=sys.stdout) |
|
99 | print(line, file=sys.stdout) | |
100 | for line in read_no_interrupt(p.stderr).splitlines(): |
|
100 | for line in read_no_interrupt(p.stderr).splitlines(): | |
101 | line = line.decode(enc, 'replace') |
|
101 | line = line.decode(enc, 'replace') | |
102 | print(line, file=sys.stderr) |
|
102 | print(line, file=sys.stderr) | |
103 |
|
103 | |||
104 | # Wait to finish for returncode |
|
104 | # Wait to finish for returncode | |
105 | return p.wait() |
|
105 | return p.wait() | |
106 |
|
106 | |||
107 |
|
107 | |||
108 | def system(cmd): |
|
108 | def system(cmd): | |
109 | """Win32 version of os.system() that works with network shares. |
|
109 | """Win32 version of os.system() that works with network shares. | |
110 |
|
110 | |||
111 | Note that this implementation returns None, as meant for use in IPython. |
|
111 | Note that this implementation returns None, as meant for use in IPython. | |
112 |
|
112 | |||
113 | Parameters |
|
113 | Parameters | |
114 | ---------- |
|
114 | ---------- | |
115 | cmd : str |
|
115 | cmd : str | |
116 | A command to be executed in the system shell. |
|
116 | A command to be executed in the system shell. | |
117 |
|
117 | |||
118 | Returns |
|
118 | Returns | |
119 | ------- |
|
119 | ------- | |
120 | None : we explicitly do NOT return the subprocess status code, as this |
|
120 | None : we explicitly do NOT return the subprocess status code, as this | |
121 | utility is meant to be used extensively in IPython, where any return value |
|
121 | utility is meant to be used extensively in IPython, where any return value | |
122 | would trigger :func:`sys.displayhook` calls. |
|
122 | would trigger :func:`sys.displayhook` calls. | |
123 | """ |
|
123 | """ | |
124 | # The controller provides interactivity with both |
|
124 | # The controller provides interactivity with both | |
125 | # stdin and stdout |
|
125 | # stdin and stdout | |
126 | #import _process_win32_controller |
|
126 | #import _process_win32_controller | |
127 | #_process_win32_controller.system(cmd) |
|
127 | #_process_win32_controller.system(cmd) | |
128 |
|
128 | |||
129 | with AvoidUNCPath() as path: |
|
129 | with AvoidUNCPath() as path: | |
130 | if path is not None: |
|
130 | if path is not None: | |
131 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
131 | cmd = '"pushd %s &&"%s' % (path, cmd) | |
132 | return process_handler(cmd, _system_body) |
|
132 | return process_handler(cmd, _system_body) | |
133 |
|
133 | |||
134 | def getoutput(cmd): |
|
134 | def getoutput(cmd): | |
135 | """Return standard output of executing cmd in a shell. |
|
135 | """Return standard output of executing cmd in a shell. | |
136 |
|
136 | |||
137 | Accepts the same arguments as os.system(). |
|
137 | Accepts the same arguments as os.system(). | |
138 |
|
138 | |||
139 | Parameters |
|
139 | Parameters | |
140 | ---------- |
|
140 | ---------- | |
141 | cmd : str |
|
141 | cmd : str | |
142 | A command to be executed in the system shell. |
|
142 | A command to be executed in the system shell. | |
143 |
|
143 | |||
144 | Returns |
|
144 | Returns | |
145 | ------- |
|
145 | ------- | |
146 | stdout : str |
|
146 | stdout : str | |
147 | """ |
|
147 | """ | |
148 |
|
148 | |||
149 | with AvoidUNCPath() as path: |
|
149 | with AvoidUNCPath() as path: | |
150 | if path is not None: |
|
150 | if path is not None: | |
151 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
151 | cmd = '"pushd %s &&"%s' % (path, cmd) | |
152 | out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT) |
|
152 | out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT) | |
153 |
|
153 | |||
154 | if out is None: |
|
154 | if out is None: | |
155 | out = b'' |
|
155 | out = b'' | |
156 | return py3compat.bytes_to_str(out) |
|
156 | return py3compat.bytes_to_str(out) | |
157 |
|
157 | |||
158 | try: |
|
158 | try: | |
159 | CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW |
|
159 | CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW | |
160 | CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)] |
|
160 | CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)] | |
161 | CommandLineToArgvW.restype = POINTER(LPCWSTR) |
|
161 | CommandLineToArgvW.restype = POINTER(LPCWSTR) | |
162 | LocalFree = ctypes.windll.kernel32.LocalFree |
|
162 | LocalFree = ctypes.windll.kernel32.LocalFree | |
163 | LocalFree.res_type = HLOCAL |
|
163 | LocalFree.res_type = HLOCAL | |
164 | LocalFree.arg_types = [HLOCAL] |
|
164 | LocalFree.arg_types = [HLOCAL] | |
165 |
|
165 | |||
166 | def arg_split(commandline, posix=False, strict=True): |
|
166 | def arg_split(commandline, posix=False, strict=True): | |
167 | """Split a command line's arguments in a shell-like manner. |
|
167 | """Split a command line's arguments in a shell-like manner. | |
168 |
|
168 | |||
169 | This is a special version for windows that use a ctypes call to CommandLineToArgvW |
|
169 | This is a special version for windows that use a ctypes call to CommandLineToArgvW | |
170 | to do the argv splitting. The posix paramter is ignored. |
|
170 | to do the argv splitting. The posix paramter is ignored. | |
171 |
|
171 | |||
172 | If strict=False, process_common.arg_split(...strict=False) is used instead. |
|
172 | If strict=False, process_common.arg_split(...strict=False) is used instead. | |
173 | """ |
|
173 | """ | |
174 | #CommandLineToArgvW returns path to executable if called with empty string. |
|
174 | #CommandLineToArgvW returns path to executable if called with empty string. | |
175 | if commandline.strip() == "": |
|
175 | if commandline.strip() == "": | |
176 | return [] |
|
176 | return [] | |
177 | if not strict: |
|
177 | if not strict: | |
178 | # not really a cl-arg, fallback on _process_common |
|
178 | # not really a cl-arg, fallback on _process_common | |
179 | return py_arg_split(commandline, posix=posix, strict=strict) |
|
179 | return py_arg_split(commandline, posix=posix, strict=strict) | |
180 | argvn = c_int() |
|
180 | argvn = c_int() | |
181 | result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn)) |
|
181 | result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn)) | |
182 | result_array_type = LPCWSTR * argvn.value |
|
182 | result_array_type = LPCWSTR * argvn.value | |
183 | result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))] |
|
183 | result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))] | |
184 | retval = LocalFree(result_pointer) |
|
184 | retval = LocalFree(result_pointer) | |
185 | return result |
|
185 | return result | |
186 | except AttributeError: |
|
186 | except AttributeError: | |
187 | arg_split = py_arg_split |
|
187 | arg_split = py_arg_split |
@@ -1,577 +1,577 b'' | |||||
1 | """Windows-specific implementation of process utilities with direct WinAPI. |
|
1 | """Windows-specific implementation of process utilities with direct WinAPI. | |
2 |
|
2 | |||
3 | This file is meant to be used by process.py |
|
3 | This file is meant to be used by process.py | |
4 | """ |
|
4 | """ | |
5 |
|
5 | |||
6 | #----------------------------------------------------------------------------- |
|
6 | #----------------------------------------------------------------------------- | |
7 | # Copyright (C) 2010-2011 The IPython Development Team |
|
7 | # Copyright (C) 2010-2011 The IPython Development Team | |
8 | # |
|
8 | # | |
9 | # Distributed under the terms of the BSD License. The full license is in |
|
9 | # Distributed under the terms of the BSD License. The full license is in | |
10 | # the file COPYING, distributed as part of this software. |
|
10 | # the file COPYING, distributed as part of this software. | |
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 |
|
12 | |||
13 | from __future__ import print_function |
|
13 | from __future__ import print_function | |
14 |
|
14 | |||
15 | # stdlib |
|
15 | # stdlib | |
16 | import os, sys, threading |
|
16 | import os, sys, threading | |
17 | import ctypes, msvcrt |
|
17 | import ctypes, msvcrt | |
18 |
|
18 | |||
19 | # local imports |
|
19 | # local imports | |
20 | from .py3compat import unicode_type |
|
20 | from . import py3compat | |
21 |
|
21 | |||
22 | # Win32 API types needed for the API calls |
|
22 | # Win32 API types needed for the API calls | |
23 | from ctypes import POINTER |
|
23 | from ctypes import POINTER | |
24 | from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \ |
|
24 | from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \ | |
25 | ULONG, LPCWSTR |
|
25 | ULONG, LPCWSTR | |
26 | LPDWORD = POINTER(DWORD) |
|
26 | LPDWORD = POINTER(DWORD) | |
27 | LPHANDLE = POINTER(HANDLE) |
|
27 | LPHANDLE = POINTER(HANDLE) | |
28 | ULONG_PTR = POINTER(ULONG) |
|
28 | ULONG_PTR = POINTER(ULONG) | |
29 | class SECURITY_ATTRIBUTES(ctypes.Structure): |
|
29 | class SECURITY_ATTRIBUTES(ctypes.Structure): | |
30 | _fields_ = [("nLength", DWORD), |
|
30 | _fields_ = [("nLength", DWORD), | |
31 | ("lpSecurityDescriptor", LPVOID), |
|
31 | ("lpSecurityDescriptor", LPVOID), | |
32 | ("bInheritHandle", BOOL)] |
|
32 | ("bInheritHandle", BOOL)] | |
33 | LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES) |
|
33 | LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES) | |
34 | class STARTUPINFO(ctypes.Structure): |
|
34 | class STARTUPINFO(ctypes.Structure): | |
35 | _fields_ = [("cb", DWORD), |
|
35 | _fields_ = [("cb", DWORD), | |
36 | ("lpReserved", LPCWSTR), |
|
36 | ("lpReserved", LPCWSTR), | |
37 | ("lpDesktop", LPCWSTR), |
|
37 | ("lpDesktop", LPCWSTR), | |
38 | ("lpTitle", LPCWSTR), |
|
38 | ("lpTitle", LPCWSTR), | |
39 | ("dwX", DWORD), |
|
39 | ("dwX", DWORD), | |
40 | ("dwY", DWORD), |
|
40 | ("dwY", DWORD), | |
41 | ("dwXSize", DWORD), |
|
41 | ("dwXSize", DWORD), | |
42 | ("dwYSize", DWORD), |
|
42 | ("dwYSize", DWORD), | |
43 | ("dwXCountChars", DWORD), |
|
43 | ("dwXCountChars", DWORD), | |
44 | ("dwYCountChars", DWORD), |
|
44 | ("dwYCountChars", DWORD), | |
45 | ("dwFillAttribute", DWORD), |
|
45 | ("dwFillAttribute", DWORD), | |
46 | ("dwFlags", DWORD), |
|
46 | ("dwFlags", DWORD), | |
47 | ("wShowWindow", WORD), |
|
47 | ("wShowWindow", WORD), | |
48 | ("cbReserved2", WORD), |
|
48 | ("cbReserved2", WORD), | |
49 | ("lpReserved2", LPVOID), |
|
49 | ("lpReserved2", LPVOID), | |
50 | ("hStdInput", HANDLE), |
|
50 | ("hStdInput", HANDLE), | |
51 | ("hStdOutput", HANDLE), |
|
51 | ("hStdOutput", HANDLE), | |
52 | ("hStdError", HANDLE)] |
|
52 | ("hStdError", HANDLE)] | |
53 | LPSTARTUPINFO = POINTER(STARTUPINFO) |
|
53 | LPSTARTUPINFO = POINTER(STARTUPINFO) | |
54 | class PROCESS_INFORMATION(ctypes.Structure): |
|
54 | class PROCESS_INFORMATION(ctypes.Structure): | |
55 | _fields_ = [("hProcess", HANDLE), |
|
55 | _fields_ = [("hProcess", HANDLE), | |
56 | ("hThread", HANDLE), |
|
56 | ("hThread", HANDLE), | |
57 | ("dwProcessId", DWORD), |
|
57 | ("dwProcessId", DWORD), | |
58 | ("dwThreadId", DWORD)] |
|
58 | ("dwThreadId", DWORD)] | |
59 | LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) |
|
59 | LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) | |
60 |
|
60 | |||
61 | # Win32 API constants needed |
|
61 | # Win32 API constants needed | |
62 | ERROR_HANDLE_EOF = 38 |
|
62 | ERROR_HANDLE_EOF = 38 | |
63 | ERROR_BROKEN_PIPE = 109 |
|
63 | ERROR_BROKEN_PIPE = 109 | |
64 | ERROR_NO_DATA = 232 |
|
64 | ERROR_NO_DATA = 232 | |
65 | HANDLE_FLAG_INHERIT = 0x0001 |
|
65 | HANDLE_FLAG_INHERIT = 0x0001 | |
66 | STARTF_USESTDHANDLES = 0x0100 |
|
66 | STARTF_USESTDHANDLES = 0x0100 | |
67 | CREATE_SUSPENDED = 0x0004 |
|
67 | CREATE_SUSPENDED = 0x0004 | |
68 | CREATE_NEW_CONSOLE = 0x0010 |
|
68 | CREATE_NEW_CONSOLE = 0x0010 | |
69 | CREATE_NO_WINDOW = 0x08000000 |
|
69 | CREATE_NO_WINDOW = 0x08000000 | |
70 | STILL_ACTIVE = 259 |
|
70 | STILL_ACTIVE = 259 | |
71 | WAIT_TIMEOUT = 0x0102 |
|
71 | WAIT_TIMEOUT = 0x0102 | |
72 | WAIT_FAILED = 0xFFFFFFFF |
|
72 | WAIT_FAILED = 0xFFFFFFFF | |
73 | INFINITE = 0xFFFFFFFF |
|
73 | INFINITE = 0xFFFFFFFF | |
74 | DUPLICATE_SAME_ACCESS = 0x00000002 |
|
74 | DUPLICATE_SAME_ACCESS = 0x00000002 | |
75 | ENABLE_ECHO_INPUT = 0x0004 |
|
75 | ENABLE_ECHO_INPUT = 0x0004 | |
76 | ENABLE_LINE_INPUT = 0x0002 |
|
76 | ENABLE_LINE_INPUT = 0x0002 | |
77 | ENABLE_PROCESSED_INPUT = 0x0001 |
|
77 | ENABLE_PROCESSED_INPUT = 0x0001 | |
78 |
|
78 | |||
79 | # Win32 API functions needed |
|
79 | # Win32 API functions needed | |
80 | GetLastError = ctypes.windll.kernel32.GetLastError |
|
80 | GetLastError = ctypes.windll.kernel32.GetLastError | |
81 | GetLastError.argtypes = [] |
|
81 | GetLastError.argtypes = [] | |
82 | GetLastError.restype = DWORD |
|
82 | GetLastError.restype = DWORD | |
83 |
|
83 | |||
84 | CreateFile = ctypes.windll.kernel32.CreateFileW |
|
84 | CreateFile = ctypes.windll.kernel32.CreateFileW | |
85 | CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE] |
|
85 | CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE] | |
86 | CreateFile.restype = HANDLE |
|
86 | CreateFile.restype = HANDLE | |
87 |
|
87 | |||
88 | CreatePipe = ctypes.windll.kernel32.CreatePipe |
|
88 | CreatePipe = ctypes.windll.kernel32.CreatePipe | |
89 | CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE), |
|
89 | CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE), | |
90 | LPSECURITY_ATTRIBUTES, DWORD] |
|
90 | LPSECURITY_ATTRIBUTES, DWORD] | |
91 | CreatePipe.restype = BOOL |
|
91 | CreatePipe.restype = BOOL | |
92 |
|
92 | |||
93 | CreateProcess = ctypes.windll.kernel32.CreateProcessW |
|
93 | CreateProcess = ctypes.windll.kernel32.CreateProcessW | |
94 | CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES, |
|
94 | CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES, | |
95 | LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO, |
|
95 | LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO, | |
96 | LPPROCESS_INFORMATION] |
|
96 | LPPROCESS_INFORMATION] | |
97 | CreateProcess.restype = BOOL |
|
97 | CreateProcess.restype = BOOL | |
98 |
|
98 | |||
99 | GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess |
|
99 | GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess | |
100 | GetExitCodeProcess.argtypes = [HANDLE, LPDWORD] |
|
100 | GetExitCodeProcess.argtypes = [HANDLE, LPDWORD] | |
101 | GetExitCodeProcess.restype = BOOL |
|
101 | GetExitCodeProcess.restype = BOOL | |
102 |
|
102 | |||
103 | GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess |
|
103 | GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess | |
104 | GetCurrentProcess.argtypes = [] |
|
104 | GetCurrentProcess.argtypes = [] | |
105 | GetCurrentProcess.restype = HANDLE |
|
105 | GetCurrentProcess.restype = HANDLE | |
106 |
|
106 | |||
107 | ResumeThread = ctypes.windll.kernel32.ResumeThread |
|
107 | ResumeThread = ctypes.windll.kernel32.ResumeThread | |
108 | ResumeThread.argtypes = [HANDLE] |
|
108 | ResumeThread.argtypes = [HANDLE] | |
109 | ResumeThread.restype = DWORD |
|
109 | ResumeThread.restype = DWORD | |
110 |
|
110 | |||
111 | ReadFile = ctypes.windll.kernel32.ReadFile |
|
111 | ReadFile = ctypes.windll.kernel32.ReadFile | |
112 | ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] |
|
112 | ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] | |
113 | ReadFile.restype = BOOL |
|
113 | ReadFile.restype = BOOL | |
114 |
|
114 | |||
115 | WriteFile = ctypes.windll.kernel32.WriteFile |
|
115 | WriteFile = ctypes.windll.kernel32.WriteFile | |
116 | WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] |
|
116 | WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] | |
117 | WriteFile.restype = BOOL |
|
117 | WriteFile.restype = BOOL | |
118 |
|
118 | |||
119 | GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode |
|
119 | GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode | |
120 | GetConsoleMode.argtypes = [HANDLE, LPDWORD] |
|
120 | GetConsoleMode.argtypes = [HANDLE, LPDWORD] | |
121 | GetConsoleMode.restype = BOOL |
|
121 | GetConsoleMode.restype = BOOL | |
122 |
|
122 | |||
123 | SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode |
|
123 | SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode | |
124 | SetConsoleMode.argtypes = [HANDLE, DWORD] |
|
124 | SetConsoleMode.argtypes = [HANDLE, DWORD] | |
125 | SetConsoleMode.restype = BOOL |
|
125 | SetConsoleMode.restype = BOOL | |
126 |
|
126 | |||
127 | FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer |
|
127 | FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer | |
128 | FlushConsoleInputBuffer.argtypes = [HANDLE] |
|
128 | FlushConsoleInputBuffer.argtypes = [HANDLE] | |
129 | FlushConsoleInputBuffer.restype = BOOL |
|
129 | FlushConsoleInputBuffer.restype = BOOL | |
130 |
|
130 | |||
131 | WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject |
|
131 | WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject | |
132 | WaitForSingleObject.argtypes = [HANDLE, DWORD] |
|
132 | WaitForSingleObject.argtypes = [HANDLE, DWORD] | |
133 | WaitForSingleObject.restype = DWORD |
|
133 | WaitForSingleObject.restype = DWORD | |
134 |
|
134 | |||
135 | DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle |
|
135 | DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle | |
136 | DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE, |
|
136 | DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE, | |
137 | DWORD, BOOL, DWORD] |
|
137 | DWORD, BOOL, DWORD] | |
138 | DuplicateHandle.restype = BOOL |
|
138 | DuplicateHandle.restype = BOOL | |
139 |
|
139 | |||
140 | SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation |
|
140 | SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation | |
141 | SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD] |
|
141 | SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD] | |
142 | SetHandleInformation.restype = BOOL |
|
142 | SetHandleInformation.restype = BOOL | |
143 |
|
143 | |||
144 | CloseHandle = ctypes.windll.kernel32.CloseHandle |
|
144 | CloseHandle = ctypes.windll.kernel32.CloseHandle | |
145 | CloseHandle.argtypes = [HANDLE] |
|
145 | CloseHandle.argtypes = [HANDLE] | |
146 | CloseHandle.restype = BOOL |
|
146 | CloseHandle.restype = BOOL | |
147 |
|
147 | |||
148 | CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW |
|
148 | CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW | |
149 | CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)] |
|
149 | CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)] | |
150 | CommandLineToArgvW.restype = POINTER(LPCWSTR) |
|
150 | CommandLineToArgvW.restype = POINTER(LPCWSTR) | |
151 |
|
151 | |||
152 | LocalFree = ctypes.windll.kernel32.LocalFree |
|
152 | LocalFree = ctypes.windll.kernel32.LocalFree | |
153 | LocalFree.argtypes = [HLOCAL] |
|
153 | LocalFree.argtypes = [HLOCAL] | |
154 | LocalFree.restype = HLOCAL |
|
154 | LocalFree.restype = HLOCAL | |
155 |
|
155 | |||
156 | class AvoidUNCPath(object): |
|
156 | class AvoidUNCPath(object): | |
157 | """A context manager to protect command execution from UNC paths. |
|
157 | """A context manager to protect command execution from UNC paths. | |
158 |
|
158 | |||
159 | In the Win32 API, commands can't be invoked with the cwd being a UNC path. |
|
159 | In the Win32 API, commands can't be invoked with the cwd being a UNC path. | |
160 | This context manager temporarily changes directory to the 'C:' drive on |
|
160 | This context manager temporarily changes directory to the 'C:' drive on | |
161 | entering, and restores the original working directory on exit. |
|
161 | entering, and restores the original working directory on exit. | |
162 |
|
162 | |||
163 | The context manager returns the starting working directory *if* it made a |
|
163 | The context manager returns the starting working directory *if* it made a | |
164 | change and None otherwise, so that users can apply the necessary adjustment |
|
164 | change and None otherwise, so that users can apply the necessary adjustment | |
165 | to their system calls in the event of a change. |
|
165 | to their system calls in the event of a change. | |
166 |
|
166 | |||
167 | Examples |
|
167 | Examples | |
168 | -------- |
|
168 | -------- | |
169 | :: |
|
169 | :: | |
170 | cmd = 'dir' |
|
170 | cmd = 'dir' | |
171 | with AvoidUNCPath() as path: |
|
171 | with AvoidUNCPath() as path: | |
172 | if path is not None: |
|
172 | if path is not None: | |
173 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
173 | cmd = '"pushd %s &&"%s' % (path, cmd) | |
174 | os.system(cmd) |
|
174 | os.system(cmd) | |
175 | """ |
|
175 | """ | |
176 | def __enter__(self): |
|
176 | def __enter__(self): | |
177 |
self.path = |
|
177 | self.path = py3compat.getcwd() | |
178 | self.is_unc_path = self.path.startswith(r"\\") |
|
178 | self.is_unc_path = self.path.startswith(r"\\") | |
179 | if self.is_unc_path: |
|
179 | if self.is_unc_path: | |
180 | # change to c drive (as cmd.exe cannot handle UNC addresses) |
|
180 | # change to c drive (as cmd.exe cannot handle UNC addresses) | |
181 | os.chdir("C:") |
|
181 | os.chdir("C:") | |
182 | return self.path |
|
182 | return self.path | |
183 | else: |
|
183 | else: | |
184 | # We return None to signal that there was no change in the working |
|
184 | # We return None to signal that there was no change in the working | |
185 | # directory |
|
185 | # directory | |
186 | return None |
|
186 | return None | |
187 |
|
187 | |||
188 | def __exit__(self, exc_type, exc_value, traceback): |
|
188 | def __exit__(self, exc_type, exc_value, traceback): | |
189 | if self.is_unc_path: |
|
189 | if self.is_unc_path: | |
190 | os.chdir(self.path) |
|
190 | os.chdir(self.path) | |
191 |
|
191 | |||
192 |
|
192 | |||
193 | class Win32ShellCommandController(object): |
|
193 | class Win32ShellCommandController(object): | |
194 | """Runs a shell command in a 'with' context. |
|
194 | """Runs a shell command in a 'with' context. | |
195 |
|
195 | |||
196 | This implementation is Win32-specific. |
|
196 | This implementation is Win32-specific. | |
197 |
|
197 | |||
198 | Example: |
|
198 | Example: | |
199 | # Runs the command interactively with default console stdin/stdout |
|
199 | # Runs the command interactively with default console stdin/stdout | |
200 | with ShellCommandController('python -i') as scc: |
|
200 | with ShellCommandController('python -i') as scc: | |
201 | scc.run() |
|
201 | scc.run() | |
202 |
|
202 | |||
203 | # Runs the command using the provided functions for stdin/stdout |
|
203 | # Runs the command using the provided functions for stdin/stdout | |
204 | def my_stdout_func(s): |
|
204 | def my_stdout_func(s): | |
205 | # print or save the string 's' |
|
205 | # print or save the string 's' | |
206 | write_to_stdout(s) |
|
206 | write_to_stdout(s) | |
207 | def my_stdin_func(): |
|
207 | def my_stdin_func(): | |
208 | # If input is available, return it as a string. |
|
208 | # If input is available, return it as a string. | |
209 | if input_available(): |
|
209 | if input_available(): | |
210 | return get_input() |
|
210 | return get_input() | |
211 | # If no input available, return None after a short delay to |
|
211 | # If no input available, return None after a short delay to | |
212 | # keep from blocking. |
|
212 | # keep from blocking. | |
213 | else: |
|
213 | else: | |
214 | time.sleep(0.01) |
|
214 | time.sleep(0.01) | |
215 | return None |
|
215 | return None | |
216 |
|
216 | |||
217 | with ShellCommandController('python -i') as scc: |
|
217 | with ShellCommandController('python -i') as scc: | |
218 | scc.run(my_stdout_func, my_stdin_func) |
|
218 | scc.run(my_stdout_func, my_stdin_func) | |
219 | """ |
|
219 | """ | |
220 |
|
220 | |||
221 | def __init__(self, cmd, mergeout = True): |
|
221 | def __init__(self, cmd, mergeout = True): | |
222 | """Initializes the shell command controller. |
|
222 | """Initializes the shell command controller. | |
223 |
|
223 | |||
224 | The cmd is the program to execute, and mergeout is |
|
224 | The cmd is the program to execute, and mergeout is | |
225 | whether to blend stdout and stderr into one output |
|
225 | whether to blend stdout and stderr into one output | |
226 | in stdout. Merging them together in this fashion more |
|
226 | in stdout. Merging them together in this fashion more | |
227 | reliably keeps stdout and stderr in the correct order |
|
227 | reliably keeps stdout and stderr in the correct order | |
228 | especially for interactive shell usage. |
|
228 | especially for interactive shell usage. | |
229 | """ |
|
229 | """ | |
230 | self.cmd = cmd |
|
230 | self.cmd = cmd | |
231 | self.mergeout = mergeout |
|
231 | self.mergeout = mergeout | |
232 |
|
232 | |||
233 | def __enter__(self): |
|
233 | def __enter__(self): | |
234 | cmd = self.cmd |
|
234 | cmd = self.cmd | |
235 | mergeout = self.mergeout |
|
235 | mergeout = self.mergeout | |
236 |
|
236 | |||
237 | self.hstdout, self.hstdin, self.hstderr = None, None, None |
|
237 | self.hstdout, self.hstdin, self.hstderr = None, None, None | |
238 | self.piProcInfo = None |
|
238 | self.piProcInfo = None | |
239 | try: |
|
239 | try: | |
240 | p_hstdout, c_hstdout, p_hstderr, \ |
|
240 | p_hstdout, c_hstdout, p_hstderr, \ | |
241 | c_hstderr, p_hstdin, c_hstdin = [None]*6 |
|
241 | c_hstderr, p_hstdin, c_hstdin = [None]*6 | |
242 |
|
242 | |||
243 | # SECURITY_ATTRIBUTES with inherit handle set to True |
|
243 | # SECURITY_ATTRIBUTES with inherit handle set to True | |
244 | saAttr = SECURITY_ATTRIBUTES() |
|
244 | saAttr = SECURITY_ATTRIBUTES() | |
245 | saAttr.nLength = ctypes.sizeof(saAttr) |
|
245 | saAttr.nLength = ctypes.sizeof(saAttr) | |
246 | saAttr.bInheritHandle = True |
|
246 | saAttr.bInheritHandle = True | |
247 | saAttr.lpSecurityDescriptor = None |
|
247 | saAttr.lpSecurityDescriptor = None | |
248 |
|
248 | |||
249 | def create_pipe(uninherit): |
|
249 | def create_pipe(uninherit): | |
250 | """Creates a Windows pipe, which consists of two handles. |
|
250 | """Creates a Windows pipe, which consists of two handles. | |
251 |
|
251 | |||
252 | The 'uninherit' parameter controls which handle is not |
|
252 | The 'uninherit' parameter controls which handle is not | |
253 | inherited by the child process. |
|
253 | inherited by the child process. | |
254 | """ |
|
254 | """ | |
255 | handles = HANDLE(), HANDLE() |
|
255 | handles = HANDLE(), HANDLE() | |
256 | if not CreatePipe(ctypes.byref(handles[0]), |
|
256 | if not CreatePipe(ctypes.byref(handles[0]), | |
257 | ctypes.byref(handles[1]), ctypes.byref(saAttr), 0): |
|
257 | ctypes.byref(handles[1]), ctypes.byref(saAttr), 0): | |
258 | raise ctypes.WinError() |
|
258 | raise ctypes.WinError() | |
259 | if not SetHandleInformation(handles[uninherit], |
|
259 | if not SetHandleInformation(handles[uninherit], | |
260 | HANDLE_FLAG_INHERIT, 0): |
|
260 | HANDLE_FLAG_INHERIT, 0): | |
261 | raise ctypes.WinError() |
|
261 | raise ctypes.WinError() | |
262 | return handles[0].value, handles[1].value |
|
262 | return handles[0].value, handles[1].value | |
263 |
|
263 | |||
264 | p_hstdout, c_hstdout = create_pipe(uninherit=0) |
|
264 | p_hstdout, c_hstdout = create_pipe(uninherit=0) | |
265 | # 'mergeout' signals that stdout and stderr should be merged. |
|
265 | # 'mergeout' signals that stdout and stderr should be merged. | |
266 | # We do that by using one pipe for both of them. |
|
266 | # We do that by using one pipe for both of them. | |
267 | if mergeout: |
|
267 | if mergeout: | |
268 | c_hstderr = HANDLE() |
|
268 | c_hstderr = HANDLE() | |
269 | if not DuplicateHandle(GetCurrentProcess(), c_hstdout, |
|
269 | if not DuplicateHandle(GetCurrentProcess(), c_hstdout, | |
270 | GetCurrentProcess(), ctypes.byref(c_hstderr), |
|
270 | GetCurrentProcess(), ctypes.byref(c_hstderr), | |
271 | 0, True, DUPLICATE_SAME_ACCESS): |
|
271 | 0, True, DUPLICATE_SAME_ACCESS): | |
272 | raise ctypes.WinError() |
|
272 | raise ctypes.WinError() | |
273 | else: |
|
273 | else: | |
274 | p_hstderr, c_hstderr = create_pipe(uninherit=0) |
|
274 | p_hstderr, c_hstderr = create_pipe(uninherit=0) | |
275 | c_hstdin, p_hstdin = create_pipe(uninherit=1) |
|
275 | c_hstdin, p_hstdin = create_pipe(uninherit=1) | |
276 |
|
276 | |||
277 | # Create the process object |
|
277 | # Create the process object | |
278 | piProcInfo = PROCESS_INFORMATION() |
|
278 | piProcInfo = PROCESS_INFORMATION() | |
279 | siStartInfo = STARTUPINFO() |
|
279 | siStartInfo = STARTUPINFO() | |
280 | siStartInfo.cb = ctypes.sizeof(siStartInfo) |
|
280 | siStartInfo.cb = ctypes.sizeof(siStartInfo) | |
281 | siStartInfo.hStdInput = c_hstdin |
|
281 | siStartInfo.hStdInput = c_hstdin | |
282 | siStartInfo.hStdOutput = c_hstdout |
|
282 | siStartInfo.hStdOutput = c_hstdout | |
283 | siStartInfo.hStdError = c_hstderr |
|
283 | siStartInfo.hStdError = c_hstderr | |
284 | siStartInfo.dwFlags = STARTF_USESTDHANDLES |
|
284 | siStartInfo.dwFlags = STARTF_USESTDHANDLES | |
285 | dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE |
|
285 | dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE | |
286 |
|
286 | |||
287 | if not CreateProcess(None, |
|
287 | if not CreateProcess(None, | |
288 | u"cmd.exe /c " + cmd, |
|
288 | u"cmd.exe /c " + cmd, | |
289 | None, None, True, dwCreationFlags, |
|
289 | None, None, True, dwCreationFlags, | |
290 | None, None, ctypes.byref(siStartInfo), |
|
290 | None, None, ctypes.byref(siStartInfo), | |
291 | ctypes.byref(piProcInfo)): |
|
291 | ctypes.byref(piProcInfo)): | |
292 | raise ctypes.WinError() |
|
292 | raise ctypes.WinError() | |
293 |
|
293 | |||
294 | # Close this process's versions of the child handles |
|
294 | # Close this process's versions of the child handles | |
295 | CloseHandle(c_hstdin) |
|
295 | CloseHandle(c_hstdin) | |
296 | c_hstdin = None |
|
296 | c_hstdin = None | |
297 | CloseHandle(c_hstdout) |
|
297 | CloseHandle(c_hstdout) | |
298 | c_hstdout = None |
|
298 | c_hstdout = None | |
299 | if c_hstderr != None: |
|
299 | if c_hstderr != None: | |
300 | CloseHandle(c_hstderr) |
|
300 | CloseHandle(c_hstderr) | |
301 | c_hstderr = None |
|
301 | c_hstderr = None | |
302 |
|
302 | |||
303 | # Transfer ownership of the parent handles to the object |
|
303 | # Transfer ownership of the parent handles to the object | |
304 | self.hstdin = p_hstdin |
|
304 | self.hstdin = p_hstdin | |
305 | p_hstdin = None |
|
305 | p_hstdin = None | |
306 | self.hstdout = p_hstdout |
|
306 | self.hstdout = p_hstdout | |
307 | p_hstdout = None |
|
307 | p_hstdout = None | |
308 | if not mergeout: |
|
308 | if not mergeout: | |
309 | self.hstderr = p_hstderr |
|
309 | self.hstderr = p_hstderr | |
310 | p_hstderr = None |
|
310 | p_hstderr = None | |
311 | self.piProcInfo = piProcInfo |
|
311 | self.piProcInfo = piProcInfo | |
312 |
|
312 | |||
313 | finally: |
|
313 | finally: | |
314 | if p_hstdin: |
|
314 | if p_hstdin: | |
315 | CloseHandle(p_hstdin) |
|
315 | CloseHandle(p_hstdin) | |
316 | if c_hstdin: |
|
316 | if c_hstdin: | |
317 | CloseHandle(c_hstdin) |
|
317 | CloseHandle(c_hstdin) | |
318 | if p_hstdout: |
|
318 | if p_hstdout: | |
319 | CloseHandle(p_hstdout) |
|
319 | CloseHandle(p_hstdout) | |
320 | if c_hstdout: |
|
320 | if c_hstdout: | |
321 | CloseHandle(c_hstdout) |
|
321 | CloseHandle(c_hstdout) | |
322 | if p_hstderr: |
|
322 | if p_hstderr: | |
323 | CloseHandle(p_hstderr) |
|
323 | CloseHandle(p_hstderr) | |
324 | if c_hstderr: |
|
324 | if c_hstderr: | |
325 | CloseHandle(c_hstderr) |
|
325 | CloseHandle(c_hstderr) | |
326 |
|
326 | |||
327 | return self |
|
327 | return self | |
328 |
|
328 | |||
329 | def _stdin_thread(self, handle, hprocess, func, stdout_func): |
|
329 | def _stdin_thread(self, handle, hprocess, func, stdout_func): | |
330 | exitCode = DWORD() |
|
330 | exitCode = DWORD() | |
331 | bytesWritten = DWORD(0) |
|
331 | bytesWritten = DWORD(0) | |
332 | while True: |
|
332 | while True: | |
333 | #print("stdin thread loop start") |
|
333 | #print("stdin thread loop start") | |
334 | # Get the input string (may be bytes or unicode) |
|
334 | # Get the input string (may be bytes or unicode) | |
335 | data = func() |
|
335 | data = func() | |
336 |
|
336 | |||
337 | # None signals to poll whether the process has exited |
|
337 | # None signals to poll whether the process has exited | |
338 | if data is None: |
|
338 | if data is None: | |
339 | #print("checking for process completion") |
|
339 | #print("checking for process completion") | |
340 | if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)): |
|
340 | if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)): | |
341 | raise ctypes.WinError() |
|
341 | raise ctypes.WinError() | |
342 | if exitCode.value != STILL_ACTIVE: |
|
342 | if exitCode.value != STILL_ACTIVE: | |
343 | return |
|
343 | return | |
344 | # TESTING: Does zero-sized writefile help? |
|
344 | # TESTING: Does zero-sized writefile help? | |
345 | if not WriteFile(handle, "", 0, |
|
345 | if not WriteFile(handle, "", 0, | |
346 | ctypes.byref(bytesWritten), None): |
|
346 | ctypes.byref(bytesWritten), None): | |
347 | raise ctypes.WinError() |
|
347 | raise ctypes.WinError() | |
348 | continue |
|
348 | continue | |
349 | #print("\nGot str %s\n" % repr(data), file=sys.stderr) |
|
349 | #print("\nGot str %s\n" % repr(data), file=sys.stderr) | |
350 |
|
350 | |||
351 | # Encode the string to the console encoding |
|
351 | # Encode the string to the console encoding | |
352 | if isinstance(data, unicode): #FIXME: Python3 |
|
352 | if isinstance(data, unicode): #FIXME: Python3 | |
353 | data = data.encode('utf_8') |
|
353 | data = data.encode('utf_8') | |
354 |
|
354 | |||
355 | # What we have now must be a string of bytes |
|
355 | # What we have now must be a string of bytes | |
356 | if not isinstance(data, str): #FIXME: Python3 |
|
356 | if not isinstance(data, str): #FIXME: Python3 | |
357 | raise RuntimeError("internal stdin function string error") |
|
357 | raise RuntimeError("internal stdin function string error") | |
358 |
|
358 | |||
359 | # An empty string signals EOF |
|
359 | # An empty string signals EOF | |
360 | if len(data) == 0: |
|
360 | if len(data) == 0: | |
361 | return |
|
361 | return | |
362 |
|
362 | |||
363 | # In a windows console, sometimes the input is echoed, |
|
363 | # In a windows console, sometimes the input is echoed, | |
364 | # but sometimes not. How do we determine when to do this? |
|
364 | # but sometimes not. How do we determine when to do this? | |
365 | stdout_func(data) |
|
365 | stdout_func(data) | |
366 | # WriteFile may not accept all the data at once. |
|
366 | # WriteFile may not accept all the data at once. | |
367 | # Loop until everything is processed |
|
367 | # Loop until everything is processed | |
368 | while len(data) != 0: |
|
368 | while len(data) != 0: | |
369 | #print("Calling writefile") |
|
369 | #print("Calling writefile") | |
370 | if not WriteFile(handle, data, len(data), |
|
370 | if not WriteFile(handle, data, len(data), | |
371 | ctypes.byref(bytesWritten), None): |
|
371 | ctypes.byref(bytesWritten), None): | |
372 | # This occurs at exit |
|
372 | # This occurs at exit | |
373 | if GetLastError() == ERROR_NO_DATA: |
|
373 | if GetLastError() == ERROR_NO_DATA: | |
374 | return |
|
374 | return | |
375 | raise ctypes.WinError() |
|
375 | raise ctypes.WinError() | |
376 | #print("Called writefile") |
|
376 | #print("Called writefile") | |
377 | data = data[bytesWritten.value:] |
|
377 | data = data[bytesWritten.value:] | |
378 |
|
378 | |||
379 | def _stdout_thread(self, handle, func): |
|
379 | def _stdout_thread(self, handle, func): | |
380 | # Allocate the output buffer |
|
380 | # Allocate the output buffer | |
381 | data = ctypes.create_string_buffer(4096) |
|
381 | data = ctypes.create_string_buffer(4096) | |
382 | while True: |
|
382 | while True: | |
383 | bytesRead = DWORD(0) |
|
383 | bytesRead = DWORD(0) | |
384 | if not ReadFile(handle, data, 4096, |
|
384 | if not ReadFile(handle, data, 4096, | |
385 | ctypes.byref(bytesRead), None): |
|
385 | ctypes.byref(bytesRead), None): | |
386 | le = GetLastError() |
|
386 | le = GetLastError() | |
387 | if le == ERROR_BROKEN_PIPE: |
|
387 | if le == ERROR_BROKEN_PIPE: | |
388 | return |
|
388 | return | |
389 | else: |
|
389 | else: | |
390 | raise ctypes.WinError() |
|
390 | raise ctypes.WinError() | |
391 | # FIXME: Python3 |
|
391 | # FIXME: Python3 | |
392 | s = data.value[0:bytesRead.value] |
|
392 | s = data.value[0:bytesRead.value] | |
393 | #print("\nv: %s" % repr(s), file=sys.stderr) |
|
393 | #print("\nv: %s" % repr(s), file=sys.stderr) | |
394 | func(s.decode('utf_8', 'replace')) |
|
394 | func(s.decode('utf_8', 'replace')) | |
395 |
|
395 | |||
396 | def run(self, stdout_func = None, stdin_func = None, stderr_func = None): |
|
396 | def run(self, stdout_func = None, stdin_func = None, stderr_func = None): | |
397 | """Runs the process, using the provided functions for I/O. |
|
397 | """Runs the process, using the provided functions for I/O. | |
398 |
|
398 | |||
399 | The function stdin_func should return strings whenever a |
|
399 | The function stdin_func should return strings whenever a | |
400 | character or characters become available. |
|
400 | character or characters become available. | |
401 | The functions stdout_func and stderr_func are called whenever |
|
401 | The functions stdout_func and stderr_func are called whenever | |
402 | something is printed to stdout or stderr, respectively. |
|
402 | something is printed to stdout or stderr, respectively. | |
403 | These functions are called from different threads (but not |
|
403 | These functions are called from different threads (but not | |
404 | concurrently, because of the GIL). |
|
404 | concurrently, because of the GIL). | |
405 | """ |
|
405 | """ | |
406 | if stdout_func == None and stdin_func == None and stderr_func == None: |
|
406 | if stdout_func == None and stdin_func == None and stderr_func == None: | |
407 | return self._run_stdio() |
|
407 | return self._run_stdio() | |
408 |
|
408 | |||
409 | if stderr_func != None and self.mergeout: |
|
409 | if stderr_func != None and self.mergeout: | |
410 | raise RuntimeError("Shell command was initiated with " |
|
410 | raise RuntimeError("Shell command was initiated with " | |
411 | "merged stdin/stdout, but a separate stderr_func " |
|
411 | "merged stdin/stdout, but a separate stderr_func " | |
412 | "was provided to the run() method") |
|
412 | "was provided to the run() method") | |
413 |
|
413 | |||
414 | # Create a thread for each input/output handle |
|
414 | # Create a thread for each input/output handle | |
415 | stdin_thread = None |
|
415 | stdin_thread = None | |
416 | threads = [] |
|
416 | threads = [] | |
417 | if stdin_func: |
|
417 | if stdin_func: | |
418 | stdin_thread = threading.Thread(target=self._stdin_thread, |
|
418 | stdin_thread = threading.Thread(target=self._stdin_thread, | |
419 | args=(self.hstdin, self.piProcInfo.hProcess, |
|
419 | args=(self.hstdin, self.piProcInfo.hProcess, | |
420 | stdin_func, stdout_func)) |
|
420 | stdin_func, stdout_func)) | |
421 | threads.append(threading.Thread(target=self._stdout_thread, |
|
421 | threads.append(threading.Thread(target=self._stdout_thread, | |
422 | args=(self.hstdout, stdout_func))) |
|
422 | args=(self.hstdout, stdout_func))) | |
423 | if not self.mergeout: |
|
423 | if not self.mergeout: | |
424 | if stderr_func == None: |
|
424 | if stderr_func == None: | |
425 | stderr_func = stdout_func |
|
425 | stderr_func = stdout_func | |
426 | threads.append(threading.Thread(target=self._stdout_thread, |
|
426 | threads.append(threading.Thread(target=self._stdout_thread, | |
427 | args=(self.hstderr, stderr_func))) |
|
427 | args=(self.hstderr, stderr_func))) | |
428 | # Start the I/O threads and the process |
|
428 | # Start the I/O threads and the process | |
429 | if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF: |
|
429 | if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF: | |
430 | raise ctypes.WinError() |
|
430 | raise ctypes.WinError() | |
431 | if stdin_thread is not None: |
|
431 | if stdin_thread is not None: | |
432 | stdin_thread.start() |
|
432 | stdin_thread.start() | |
433 | for thread in threads: |
|
433 | for thread in threads: | |
434 | thread.start() |
|
434 | thread.start() | |
435 | # Wait for the process to complete |
|
435 | # Wait for the process to complete | |
436 | if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \ |
|
436 | if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \ | |
437 | WAIT_FAILED: |
|
437 | WAIT_FAILED: | |
438 | raise ctypes.WinError() |
|
438 | raise ctypes.WinError() | |
439 | # Wait for the I/O threads to complete |
|
439 | # Wait for the I/O threads to complete | |
440 | for thread in threads: |
|
440 | for thread in threads: | |
441 | thread.join() |
|
441 | thread.join() | |
442 |
|
442 | |||
443 | # Wait for the stdin thread to complete |
|
443 | # Wait for the stdin thread to complete | |
444 | if stdin_thread is not None: |
|
444 | if stdin_thread is not None: | |
445 | stdin_thread.join() |
|
445 | stdin_thread.join() | |
446 |
|
446 | |||
447 | def _stdin_raw_nonblock(self): |
|
447 | def _stdin_raw_nonblock(self): | |
448 | """Use the raw Win32 handle of sys.stdin to do non-blocking reads""" |
|
448 | """Use the raw Win32 handle of sys.stdin to do non-blocking reads""" | |
449 | # WARNING: This is experimental, and produces inconsistent results. |
|
449 | # WARNING: This is experimental, and produces inconsistent results. | |
450 | # It's possible for the handle not to be appropriate for use |
|
450 | # It's possible for the handle not to be appropriate for use | |
451 | # with WaitForSingleObject, among other things. |
|
451 | # with WaitForSingleObject, among other things. | |
452 | handle = msvcrt.get_osfhandle(sys.stdin.fileno()) |
|
452 | handle = msvcrt.get_osfhandle(sys.stdin.fileno()) | |
453 | result = WaitForSingleObject(handle, 100) |
|
453 | result = WaitForSingleObject(handle, 100) | |
454 | if result == WAIT_FAILED: |
|
454 | if result == WAIT_FAILED: | |
455 | raise ctypes.WinError() |
|
455 | raise ctypes.WinError() | |
456 | elif result == WAIT_TIMEOUT: |
|
456 | elif result == WAIT_TIMEOUT: | |
457 | print(".", end='') |
|
457 | print(".", end='') | |
458 | return None |
|
458 | return None | |
459 | else: |
|
459 | else: | |
460 | data = ctypes.create_string_buffer(256) |
|
460 | data = ctypes.create_string_buffer(256) | |
461 | bytesRead = DWORD(0) |
|
461 | bytesRead = DWORD(0) | |
462 | print('?', end='') |
|
462 | print('?', end='') | |
463 |
|
463 | |||
464 | if not ReadFile(handle, data, 256, |
|
464 | if not ReadFile(handle, data, 256, | |
465 | ctypes.byref(bytesRead), None): |
|
465 | ctypes.byref(bytesRead), None): | |
466 | raise ctypes.WinError() |
|
466 | raise ctypes.WinError() | |
467 | # This ensures the non-blocking works with an actual console |
|
467 | # This ensures the non-blocking works with an actual console | |
468 | # Not checking the error, so the processing will still work with |
|
468 | # Not checking the error, so the processing will still work with | |
469 | # other handle types |
|
469 | # other handle types | |
470 | FlushConsoleInputBuffer(handle) |
|
470 | FlushConsoleInputBuffer(handle) | |
471 |
|
471 | |||
472 | data = data.value |
|
472 | data = data.value | |
473 | data = data.replace('\r\n', '\n') |
|
473 | data = data.replace('\r\n', '\n') | |
474 | data = data.replace('\r', '\n') |
|
474 | data = data.replace('\r', '\n') | |
475 | print(repr(data) + " ", end='') |
|
475 | print(repr(data) + " ", end='') | |
476 | return data |
|
476 | return data | |
477 |
|
477 | |||
478 | def _stdin_raw_block(self): |
|
478 | def _stdin_raw_block(self): | |
479 | """Use a blocking stdin read""" |
|
479 | """Use a blocking stdin read""" | |
480 | # The big problem with the blocking read is that it doesn't |
|
480 | # The big problem with the blocking read is that it doesn't | |
481 | # exit when it's supposed to in all contexts. An extra |
|
481 | # exit when it's supposed to in all contexts. An extra | |
482 | # key-press may be required to trigger the exit. |
|
482 | # key-press may be required to trigger the exit. | |
483 | try: |
|
483 | try: | |
484 | data = sys.stdin.read(1) |
|
484 | data = sys.stdin.read(1) | |
485 | data = data.replace('\r', '\n') |
|
485 | data = data.replace('\r', '\n') | |
486 | return data |
|
486 | return data | |
487 | except WindowsError as we: |
|
487 | except WindowsError as we: | |
488 | if we.winerror == ERROR_NO_DATA: |
|
488 | if we.winerror == ERROR_NO_DATA: | |
489 | # This error occurs when the pipe is closed |
|
489 | # This error occurs when the pipe is closed | |
490 | return None |
|
490 | return None | |
491 | else: |
|
491 | else: | |
492 | # Otherwise let the error propagate |
|
492 | # Otherwise let the error propagate | |
493 | raise we |
|
493 | raise we | |
494 |
|
494 | |||
495 | def _stdout_raw(self, s): |
|
495 | def _stdout_raw(self, s): | |
496 | """Writes the string to stdout""" |
|
496 | """Writes the string to stdout""" | |
497 | print(s, end='', file=sys.stdout) |
|
497 | print(s, end='', file=sys.stdout) | |
498 | sys.stdout.flush() |
|
498 | sys.stdout.flush() | |
499 |
|
499 | |||
500 | def _stderr_raw(self, s): |
|
500 | def _stderr_raw(self, s): | |
501 | """Writes the string to stdout""" |
|
501 | """Writes the string to stdout""" | |
502 | print(s, end='', file=sys.stderr) |
|
502 | print(s, end='', file=sys.stderr) | |
503 | sys.stderr.flush() |
|
503 | sys.stderr.flush() | |
504 |
|
504 | |||
505 | def _run_stdio(self): |
|
505 | def _run_stdio(self): | |
506 | """Runs the process using the system standard I/O. |
|
506 | """Runs the process using the system standard I/O. | |
507 |
|
507 | |||
508 | IMPORTANT: stdin needs to be asynchronous, so the Python |
|
508 | IMPORTANT: stdin needs to be asynchronous, so the Python | |
509 | sys.stdin object is not used. Instead, |
|
509 | sys.stdin object is not used. Instead, | |
510 | msvcrt.kbhit/getwch are used asynchronously. |
|
510 | msvcrt.kbhit/getwch are used asynchronously. | |
511 | """ |
|
511 | """ | |
512 | # Disable Line and Echo mode |
|
512 | # Disable Line and Echo mode | |
513 | #lpMode = DWORD() |
|
513 | #lpMode = DWORD() | |
514 | #handle = msvcrt.get_osfhandle(sys.stdin.fileno()) |
|
514 | #handle = msvcrt.get_osfhandle(sys.stdin.fileno()) | |
515 | #if GetConsoleMode(handle, ctypes.byref(lpMode)): |
|
515 | #if GetConsoleMode(handle, ctypes.byref(lpMode)): | |
516 | # set_console_mode = True |
|
516 | # set_console_mode = True | |
517 | # if not SetConsoleMode(handle, lpMode.value & |
|
517 | # if not SetConsoleMode(handle, lpMode.value & | |
518 | # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)): |
|
518 | # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)): | |
519 | # raise ctypes.WinError() |
|
519 | # raise ctypes.WinError() | |
520 |
|
520 | |||
521 | if self.mergeout: |
|
521 | if self.mergeout: | |
522 | return self.run(stdout_func = self._stdout_raw, |
|
522 | return self.run(stdout_func = self._stdout_raw, | |
523 | stdin_func = self._stdin_raw_block) |
|
523 | stdin_func = self._stdin_raw_block) | |
524 | else: |
|
524 | else: | |
525 | return self.run(stdout_func = self._stdout_raw, |
|
525 | return self.run(stdout_func = self._stdout_raw, | |
526 | stdin_func = self._stdin_raw_block, |
|
526 | stdin_func = self._stdin_raw_block, | |
527 | stderr_func = self._stderr_raw) |
|
527 | stderr_func = self._stderr_raw) | |
528 |
|
528 | |||
529 | # Restore the previous console mode |
|
529 | # Restore the previous console mode | |
530 | #if set_console_mode: |
|
530 | #if set_console_mode: | |
531 | # if not SetConsoleMode(handle, lpMode.value): |
|
531 | # if not SetConsoleMode(handle, lpMode.value): | |
532 | # raise ctypes.WinError() |
|
532 | # raise ctypes.WinError() | |
533 |
|
533 | |||
534 | def __exit__(self, exc_type, exc_value, traceback): |
|
534 | def __exit__(self, exc_type, exc_value, traceback): | |
535 | if self.hstdin: |
|
535 | if self.hstdin: | |
536 | CloseHandle(self.hstdin) |
|
536 | CloseHandle(self.hstdin) | |
537 | self.hstdin = None |
|
537 | self.hstdin = None | |
538 | if self.hstdout: |
|
538 | if self.hstdout: | |
539 | CloseHandle(self.hstdout) |
|
539 | CloseHandle(self.hstdout) | |
540 | self.hstdout = None |
|
540 | self.hstdout = None | |
541 | if self.hstderr: |
|
541 | if self.hstderr: | |
542 | CloseHandle(self.hstderr) |
|
542 | CloseHandle(self.hstderr) | |
543 | self.hstderr = None |
|
543 | self.hstderr = None | |
544 | if self.piProcInfo != None: |
|
544 | if self.piProcInfo != None: | |
545 | CloseHandle(self.piProcInfo.hProcess) |
|
545 | CloseHandle(self.piProcInfo.hProcess) | |
546 | CloseHandle(self.piProcInfo.hThread) |
|
546 | CloseHandle(self.piProcInfo.hThread) | |
547 | self.piProcInfo = None |
|
547 | self.piProcInfo = None | |
548 |
|
548 | |||
549 |
|
549 | |||
550 | def system(cmd): |
|
550 | def system(cmd): | |
551 | """Win32 version of os.system() that works with network shares. |
|
551 | """Win32 version of os.system() that works with network shares. | |
552 |
|
552 | |||
553 | Note that this implementation returns None, as meant for use in IPython. |
|
553 | Note that this implementation returns None, as meant for use in IPython. | |
554 |
|
554 | |||
555 | Parameters |
|
555 | Parameters | |
556 | ---------- |
|
556 | ---------- | |
557 | cmd : str |
|
557 | cmd : str | |
558 | A command to be executed in the system shell. |
|
558 | A command to be executed in the system shell. | |
559 |
|
559 | |||
560 | Returns |
|
560 | Returns | |
561 | ------- |
|
561 | ------- | |
562 | None : we explicitly do NOT return the subprocess status code, as this |
|
562 | None : we explicitly do NOT return the subprocess status code, as this | |
563 | utility is meant to be used extensively in IPython, where any return value |
|
563 | utility is meant to be used extensively in IPython, where any return value | |
564 | would trigger :func:`sys.displayhook` calls. |
|
564 | would trigger :func:`sys.displayhook` calls. | |
565 | """ |
|
565 | """ | |
566 | with AvoidUNCPath() as path: |
|
566 | with AvoidUNCPath() as path: | |
567 | if path is not None: |
|
567 | if path is not None: | |
568 | cmd = '"pushd %s &&"%s' % (path, cmd) |
|
568 | cmd = '"pushd %s &&"%s' % (path, cmd) | |
569 | with Win32ShellCommandController(cmd) as scc: |
|
569 | with Win32ShellCommandController(cmd) as scc: | |
570 | scc.run() |
|
570 | scc.run() | |
571 |
|
571 | |||
572 |
|
572 | |||
573 | if __name__ == "__main__": |
|
573 | if __name__ == "__main__": | |
574 | print("Test starting!") |
|
574 | print("Test starting!") | |
575 | #system("cmd") |
|
575 | #system("cmd") | |
576 | system("python -i") |
|
576 | system("python -i") | |
577 | print("Test finished!") |
|
577 | print("Test finished!") |
@@ -1,574 +1,574 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | Utilities for path handling. |
|
3 | Utilities for path handling. | |
4 | """ |
|
4 | """ | |
5 |
|
5 | |||
6 | #----------------------------------------------------------------------------- |
|
6 | #----------------------------------------------------------------------------- | |
7 | # Copyright (C) 2008-2011 The IPython Development Team |
|
7 | # Copyright (C) 2008-2011 The IPython Development Team | |
8 | # |
|
8 | # | |
9 | # Distributed under the terms of the BSD License. The full license is in |
|
9 | # Distributed under the terms of the BSD License. The full license is in | |
10 | # the file COPYING, distributed as part of this software. |
|
10 | # the file COPYING, distributed as part of this software. | |
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 |
|
12 | |||
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 | # Imports |
|
14 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 |
|
16 | |||
17 | import os |
|
17 | import os | |
18 | import sys |
|
18 | import sys | |
19 | import errno |
|
19 | import errno | |
20 | import shutil |
|
20 | import shutil | |
21 | import random |
|
21 | import random | |
22 | import tempfile |
|
22 | import tempfile | |
23 | import warnings |
|
23 | import warnings | |
24 | from hashlib import md5 |
|
24 | from hashlib import md5 | |
25 | import glob |
|
25 | import glob | |
26 |
|
26 | |||
27 | import IPython |
|
27 | import IPython | |
28 | from IPython.testing.skipdoctest import skip_doctest |
|
28 | from IPython.testing.skipdoctest import skip_doctest | |
29 | from IPython.utils.process import system |
|
29 | from IPython.utils.process import system | |
30 | from IPython.utils.importstring import import_item |
|
30 | from IPython.utils.importstring import import_item | |
31 | from IPython.utils import py3compat |
|
31 | from IPython.utils import py3compat | |
32 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
33 | # Code |
|
33 | # Code | |
34 | #----------------------------------------------------------------------------- |
|
34 | #----------------------------------------------------------------------------- | |
35 |
|
35 | |||
36 | fs_encoding = sys.getfilesystemencoding() |
|
36 | fs_encoding = sys.getfilesystemencoding() | |
37 |
|
37 | |||
38 | def _get_long_path_name(path): |
|
38 | def _get_long_path_name(path): | |
39 | """Dummy no-op.""" |
|
39 | """Dummy no-op.""" | |
40 | return path |
|
40 | return path | |
41 |
|
41 | |||
42 | def _writable_dir(path): |
|
42 | def _writable_dir(path): | |
43 | """Whether `path` is a directory, to which the user has write access.""" |
|
43 | """Whether `path` is a directory, to which the user has write access.""" | |
44 | return os.path.isdir(path) and os.access(path, os.W_OK) |
|
44 | return os.path.isdir(path) and os.access(path, os.W_OK) | |
45 |
|
45 | |||
46 | if sys.platform == 'win32': |
|
46 | if sys.platform == 'win32': | |
47 | @skip_doctest |
|
47 | @skip_doctest | |
48 | def _get_long_path_name(path): |
|
48 | def _get_long_path_name(path): | |
49 | """Get a long path name (expand ~) on Windows using ctypes. |
|
49 | """Get a long path name (expand ~) on Windows using ctypes. | |
50 |
|
50 | |||
51 | Examples |
|
51 | Examples | |
52 | -------- |
|
52 | -------- | |
53 |
|
53 | |||
54 | >>> get_long_path_name('c:\\docume~1') |
|
54 | >>> get_long_path_name('c:\\docume~1') | |
55 | u'c:\\\\Documents and Settings' |
|
55 | u'c:\\\\Documents and Settings' | |
56 |
|
56 | |||
57 | """ |
|
57 | """ | |
58 | try: |
|
58 | try: | |
59 | import ctypes |
|
59 | import ctypes | |
60 | except ImportError: |
|
60 | except ImportError: | |
61 | raise ImportError('you need to have ctypes installed for this to work') |
|
61 | raise ImportError('you need to have ctypes installed for this to work') | |
62 | _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW |
|
62 | _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW | |
63 | _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, |
|
63 | _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, | |
64 | ctypes.c_uint ] |
|
64 | ctypes.c_uint ] | |
65 |
|
65 | |||
66 | buf = ctypes.create_unicode_buffer(260) |
|
66 | buf = ctypes.create_unicode_buffer(260) | |
67 | rv = _GetLongPathName(path, buf, 260) |
|
67 | rv = _GetLongPathName(path, buf, 260) | |
68 | if rv == 0 or rv > 260: |
|
68 | if rv == 0 or rv > 260: | |
69 | return path |
|
69 | return path | |
70 | else: |
|
70 | else: | |
71 | return buf.value |
|
71 | return buf.value | |
72 |
|
72 | |||
73 |
|
73 | |||
74 | def get_long_path_name(path): |
|
74 | def get_long_path_name(path): | |
75 | """Expand a path into its long form. |
|
75 | """Expand a path into its long form. | |
76 |
|
76 | |||
77 | On Windows this expands any ~ in the paths. On other platforms, it is |
|
77 | On Windows this expands any ~ in the paths. On other platforms, it is | |
78 | a null operation. |
|
78 | a null operation. | |
79 | """ |
|
79 | """ | |
80 | return _get_long_path_name(path) |
|
80 | return _get_long_path_name(path) | |
81 |
|
81 | |||
82 |
|
82 | |||
83 | def unquote_filename(name, win32=(sys.platform=='win32')): |
|
83 | def unquote_filename(name, win32=(sys.platform=='win32')): | |
84 | """ On Windows, remove leading and trailing quotes from filenames. |
|
84 | """ On Windows, remove leading and trailing quotes from filenames. | |
85 | """ |
|
85 | """ | |
86 | if win32: |
|
86 | if win32: | |
87 | if name.startswith(("'", '"')) and name.endswith(("'", '"')): |
|
87 | if name.startswith(("'", '"')) and name.endswith(("'", '"')): | |
88 | name = name[1:-1] |
|
88 | name = name[1:-1] | |
89 | return name |
|
89 | return name | |
90 |
|
90 | |||
91 | def compress_user(path): |
|
91 | def compress_user(path): | |
92 | """Reverse of :func:`os.path.expanduser` |
|
92 | """Reverse of :func:`os.path.expanduser` | |
93 | """ |
|
93 | """ | |
94 | home = os.path.expanduser('~') |
|
94 | home = os.path.expanduser('~') | |
95 | if path.startswith(home): |
|
95 | if path.startswith(home): | |
96 | path = "~" + path[len(home):] |
|
96 | path = "~" + path[len(home):] | |
97 | return path |
|
97 | return path | |
98 |
|
98 | |||
99 | def get_py_filename(name, force_win32=None): |
|
99 | def get_py_filename(name, force_win32=None): | |
100 | """Return a valid python filename in the current directory. |
|
100 | """Return a valid python filename in the current directory. | |
101 |
|
101 | |||
102 | If the given name is not a file, it adds '.py' and searches again. |
|
102 | If the given name is not a file, it adds '.py' and searches again. | |
103 | Raises IOError with an informative message if the file isn't found. |
|
103 | Raises IOError with an informative message if the file isn't found. | |
104 |
|
104 | |||
105 | On Windows, apply Windows semantics to the filename. In particular, remove |
|
105 | On Windows, apply Windows semantics to the filename. In particular, remove | |
106 | any quoting that has been applied to it. This option can be forced for |
|
106 | any quoting that has been applied to it. This option can be forced for | |
107 | testing purposes. |
|
107 | testing purposes. | |
108 | """ |
|
108 | """ | |
109 |
|
109 | |||
110 | name = os.path.expanduser(name) |
|
110 | name = os.path.expanduser(name) | |
111 | if force_win32 is None: |
|
111 | if force_win32 is None: | |
112 | win32 = (sys.platform == 'win32') |
|
112 | win32 = (sys.platform == 'win32') | |
113 | else: |
|
113 | else: | |
114 | win32 = force_win32 |
|
114 | win32 = force_win32 | |
115 | name = unquote_filename(name, win32=win32) |
|
115 | name = unquote_filename(name, win32=win32) | |
116 | if not os.path.isfile(name) and not name.endswith('.py'): |
|
116 | if not os.path.isfile(name) and not name.endswith('.py'): | |
117 | name += '.py' |
|
117 | name += '.py' | |
118 | if os.path.isfile(name): |
|
118 | if os.path.isfile(name): | |
119 | return name |
|
119 | return name | |
120 | else: |
|
120 | else: | |
121 | raise IOError('File `%r` not found.' % name) |
|
121 | raise IOError('File `%r` not found.' % name) | |
122 |
|
122 | |||
123 |
|
123 | |||
124 | def filefind(filename, path_dirs=None): |
|
124 | def filefind(filename, path_dirs=None): | |
125 | """Find a file by looking through a sequence of paths. |
|
125 | """Find a file by looking through a sequence of paths. | |
126 |
|
126 | |||
127 | This iterates through a sequence of paths looking for a file and returns |
|
127 | This iterates through a sequence of paths looking for a file and returns | |
128 | the full, absolute path of the first occurence of the file. If no set of |
|
128 | the full, absolute path of the first occurence of the file. If no set of | |
129 | path dirs is given, the filename is tested as is, after running through |
|
129 | path dirs is given, the filename is tested as is, after running through | |
130 | :func:`expandvars` and :func:`expanduser`. Thus a simple call:: |
|
130 | :func:`expandvars` and :func:`expanduser`. Thus a simple call:: | |
131 |
|
131 | |||
132 | filefind('myfile.txt') |
|
132 | filefind('myfile.txt') | |
133 |
|
133 | |||
134 | will find the file in the current working dir, but:: |
|
134 | will find the file in the current working dir, but:: | |
135 |
|
135 | |||
136 | filefind('~/myfile.txt') |
|
136 | filefind('~/myfile.txt') | |
137 |
|
137 | |||
138 | Will find the file in the users home directory. This function does not |
|
138 | Will find the file in the users home directory. This function does not | |
139 | automatically try any paths, such as the cwd or the user's home directory. |
|
139 | automatically try any paths, such as the cwd or the user's home directory. | |
140 |
|
140 | |||
141 | Parameters |
|
141 | Parameters | |
142 | ---------- |
|
142 | ---------- | |
143 | filename : str |
|
143 | filename : str | |
144 | The filename to look for. |
|
144 | The filename to look for. | |
145 | path_dirs : str, None or sequence of str |
|
145 | path_dirs : str, None or sequence of str | |
146 | The sequence of paths to look for the file in. If None, the filename |
|
146 | The sequence of paths to look for the file in. If None, the filename | |
147 | need to be absolute or be in the cwd. If a string, the string is |
|
147 | need to be absolute or be in the cwd. If a string, the string is | |
148 | put into a sequence and the searched. If a sequence, walk through |
|
148 | put into a sequence and the searched. If a sequence, walk through | |
149 | each element and join with ``filename``, calling :func:`expandvars` |
|
149 | each element and join with ``filename``, calling :func:`expandvars` | |
150 | and :func:`expanduser` before testing for existence. |
|
150 | and :func:`expanduser` before testing for existence. | |
151 |
|
151 | |||
152 | Returns |
|
152 | Returns | |
153 | ------- |
|
153 | ------- | |
154 | Raises :exc:`IOError` or returns absolute path to file. |
|
154 | Raises :exc:`IOError` or returns absolute path to file. | |
155 | """ |
|
155 | """ | |
156 |
|
156 | |||
157 | # If paths are quoted, abspath gets confused, strip them... |
|
157 | # If paths are quoted, abspath gets confused, strip them... | |
158 | filename = filename.strip('"').strip("'") |
|
158 | filename = filename.strip('"').strip("'") | |
159 | # If the input is an absolute path, just check it exists |
|
159 | # If the input is an absolute path, just check it exists | |
160 | if os.path.isabs(filename) and os.path.isfile(filename): |
|
160 | if os.path.isabs(filename) and os.path.isfile(filename): | |
161 | return filename |
|
161 | return filename | |
162 |
|
162 | |||
163 | if path_dirs is None: |
|
163 | if path_dirs is None: | |
164 | path_dirs = ("",) |
|
164 | path_dirs = ("",) | |
165 | elif isinstance(path_dirs, py3compat.string_types): |
|
165 | elif isinstance(path_dirs, py3compat.string_types): | |
166 | path_dirs = (path_dirs,) |
|
166 | path_dirs = (path_dirs,) | |
167 |
|
167 | |||
168 | for path in path_dirs: |
|
168 | for path in path_dirs: | |
169 |
if path == '.': path = |
|
169 | if path == '.': path = py3compat.getcwd() | |
170 | testname = expand_path(os.path.join(path, filename)) |
|
170 | testname = expand_path(os.path.join(path, filename)) | |
171 | if os.path.isfile(testname): |
|
171 | if os.path.isfile(testname): | |
172 | return os.path.abspath(testname) |
|
172 | return os.path.abspath(testname) | |
173 |
|
173 | |||
174 | raise IOError("File %r does not exist in any of the search paths: %r" % |
|
174 | raise IOError("File %r does not exist in any of the search paths: %r" % | |
175 | (filename, path_dirs) ) |
|
175 | (filename, path_dirs) ) | |
176 |
|
176 | |||
177 |
|
177 | |||
178 | class HomeDirError(Exception): |
|
178 | class HomeDirError(Exception): | |
179 | pass |
|
179 | pass | |
180 |
|
180 | |||
181 |
|
181 | |||
182 | def get_home_dir(require_writable=False): |
|
182 | def get_home_dir(require_writable=False): | |
183 | """Return the 'home' directory, as a unicode string. |
|
183 | """Return the 'home' directory, as a unicode string. | |
184 |
|
184 | |||
185 | Uses os.path.expanduser('~'), and checks for writability. |
|
185 | Uses os.path.expanduser('~'), and checks for writability. | |
186 |
|
186 | |||
187 | See stdlib docs for how this is determined. |
|
187 | See stdlib docs for how this is determined. | |
188 | $HOME is first priority on *ALL* platforms. |
|
188 | $HOME is first priority on *ALL* platforms. | |
189 |
|
189 | |||
190 | Parameters |
|
190 | Parameters | |
191 | ---------- |
|
191 | ---------- | |
192 |
|
192 | |||
193 | require_writable : bool [default: False] |
|
193 | require_writable : bool [default: False] | |
194 | if True: |
|
194 | if True: | |
195 | guarantees the return value is a writable directory, otherwise |
|
195 | guarantees the return value is a writable directory, otherwise | |
196 | raises HomeDirError |
|
196 | raises HomeDirError | |
197 | if False: |
|
197 | if False: | |
198 | The path is resolved, but it is not guaranteed to exist or be writable. |
|
198 | The path is resolved, but it is not guaranteed to exist or be writable. | |
199 | """ |
|
199 | """ | |
200 |
|
200 | |||
201 | homedir = os.path.expanduser('~') |
|
201 | homedir = os.path.expanduser('~') | |
202 | # Next line will make things work even when /home/ is a symlink to |
|
202 | # Next line will make things work even when /home/ is a symlink to | |
203 | # /usr/home as it is on FreeBSD, for example |
|
203 | # /usr/home as it is on FreeBSD, for example | |
204 | homedir = os.path.realpath(homedir) |
|
204 | homedir = os.path.realpath(homedir) | |
205 |
|
205 | |||
206 | if not _writable_dir(homedir) and os.name == 'nt': |
|
206 | if not _writable_dir(homedir) and os.name == 'nt': | |
207 | # expanduser failed, use the registry to get the 'My Documents' folder. |
|
207 | # expanduser failed, use the registry to get the 'My Documents' folder. | |
208 | try: |
|
208 | try: | |
209 | try: |
|
209 | try: | |
210 | import winreg as wreg # Py 3 |
|
210 | import winreg as wreg # Py 3 | |
211 | except ImportError: |
|
211 | except ImportError: | |
212 | import _winreg as wreg # Py 2 |
|
212 | import _winreg as wreg # Py 2 | |
213 | key = wreg.OpenKey( |
|
213 | key = wreg.OpenKey( | |
214 | wreg.HKEY_CURRENT_USER, |
|
214 | wreg.HKEY_CURRENT_USER, | |
215 | "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" |
|
215 | "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" | |
216 | ) |
|
216 | ) | |
217 | homedir = wreg.QueryValueEx(key,'Personal')[0] |
|
217 | homedir = wreg.QueryValueEx(key,'Personal')[0] | |
218 | key.Close() |
|
218 | key.Close() | |
219 | except: |
|
219 | except: | |
220 | pass |
|
220 | pass | |
221 |
|
221 | |||
222 | if (not require_writable) or _writable_dir(homedir): |
|
222 | if (not require_writable) or _writable_dir(homedir): | |
223 | return py3compat.cast_unicode(homedir, fs_encoding) |
|
223 | return py3compat.cast_unicode(homedir, fs_encoding) | |
224 | else: |
|
224 | else: | |
225 | raise HomeDirError('%s is not a writable dir, ' |
|
225 | raise HomeDirError('%s is not a writable dir, ' | |
226 | 'set $HOME environment variable to override' % homedir) |
|
226 | 'set $HOME environment variable to override' % homedir) | |
227 |
|
227 | |||
228 | def get_xdg_dir(): |
|
228 | def get_xdg_dir(): | |
229 | """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. |
|
229 | """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. | |
230 |
|
230 | |||
231 | This is only for non-OS X posix (Linux,Unix,etc.) systems. |
|
231 | This is only for non-OS X posix (Linux,Unix,etc.) systems. | |
232 | """ |
|
232 | """ | |
233 |
|
233 | |||
234 | env = os.environ |
|
234 | env = os.environ | |
235 |
|
235 | |||
236 | if os.name == 'posix' and sys.platform != 'darwin': |
|
236 | if os.name == 'posix' and sys.platform != 'darwin': | |
237 | # Linux, Unix, AIX, etc. |
|
237 | # Linux, Unix, AIX, etc. | |
238 | # use ~/.config if empty OR not set |
|
238 | # use ~/.config if empty OR not set | |
239 | xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') |
|
239 | xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') | |
240 | if xdg and _writable_dir(xdg): |
|
240 | if xdg and _writable_dir(xdg): | |
241 | return py3compat.cast_unicode(xdg, fs_encoding) |
|
241 | return py3compat.cast_unicode(xdg, fs_encoding) | |
242 |
|
242 | |||
243 | return None |
|
243 | return None | |
244 |
|
244 | |||
245 |
|
245 | |||
246 | def get_xdg_cache_dir(): |
|
246 | def get_xdg_cache_dir(): | |
247 | """Return the XDG_CACHE_HOME, if it is defined and exists, else None. |
|
247 | """Return the XDG_CACHE_HOME, if it is defined and exists, else None. | |
248 |
|
248 | |||
249 | This is only for non-OS X posix (Linux,Unix,etc.) systems. |
|
249 | This is only for non-OS X posix (Linux,Unix,etc.) systems. | |
250 | """ |
|
250 | """ | |
251 |
|
251 | |||
252 | env = os.environ |
|
252 | env = os.environ | |
253 |
|
253 | |||
254 | if os.name == 'posix' and sys.platform != 'darwin': |
|
254 | if os.name == 'posix' and sys.platform != 'darwin': | |
255 | # Linux, Unix, AIX, etc. |
|
255 | # Linux, Unix, AIX, etc. | |
256 | # use ~/.cache if empty OR not set |
|
256 | # use ~/.cache if empty OR not set | |
257 | xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache') |
|
257 | xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache') | |
258 | if xdg and _writable_dir(xdg): |
|
258 | if xdg and _writable_dir(xdg): | |
259 | return py3compat.cast_unicode(xdg, fs_encoding) |
|
259 | return py3compat.cast_unicode(xdg, fs_encoding) | |
260 |
|
260 | |||
261 | return None |
|
261 | return None | |
262 |
|
262 | |||
263 |
|
263 | |||
264 | def get_ipython_dir(): |
|
264 | def get_ipython_dir(): | |
265 | """Get the IPython directory for this platform and user. |
|
265 | """Get the IPython directory for this platform and user. | |
266 |
|
266 | |||
267 | This uses the logic in `get_home_dir` to find the home directory |
|
267 | This uses the logic in `get_home_dir` to find the home directory | |
268 | and then adds .ipython to the end of the path. |
|
268 | and then adds .ipython to the end of the path. | |
269 | """ |
|
269 | """ | |
270 |
|
270 | |||
271 | env = os.environ |
|
271 | env = os.environ | |
272 | pjoin = os.path.join |
|
272 | pjoin = os.path.join | |
273 |
|
273 | |||
274 |
|
274 | |||
275 | ipdir_def = '.ipython' |
|
275 | ipdir_def = '.ipython' | |
276 | xdg_def = 'ipython' |
|
276 | xdg_def = 'ipython' | |
277 |
|
277 | |||
278 | home_dir = get_home_dir() |
|
278 | home_dir = get_home_dir() | |
279 | xdg_dir = get_xdg_dir() |
|
279 | xdg_dir = get_xdg_dir() | |
280 |
|
280 | |||
281 | # import pdb; pdb.set_trace() # dbg |
|
281 | # import pdb; pdb.set_trace() # dbg | |
282 | if 'IPYTHON_DIR' in env: |
|
282 | if 'IPYTHON_DIR' in env: | |
283 | warnings.warn('The environment variable IPYTHON_DIR is deprecated. ' |
|
283 | warnings.warn('The environment variable IPYTHON_DIR is deprecated. ' | |
284 | 'Please use IPYTHONDIR instead.') |
|
284 | 'Please use IPYTHONDIR instead.') | |
285 | ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None)) |
|
285 | ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None)) | |
286 | if ipdir is None: |
|
286 | if ipdir is None: | |
287 | # not set explicitly, use XDG_CONFIG_HOME or HOME |
|
287 | # not set explicitly, use XDG_CONFIG_HOME or HOME | |
288 | home_ipdir = pjoin(home_dir, ipdir_def) |
|
288 | home_ipdir = pjoin(home_dir, ipdir_def) | |
289 | if xdg_dir: |
|
289 | if xdg_dir: | |
290 | # use XDG, as long as the user isn't already |
|
290 | # use XDG, as long as the user isn't already | |
291 | # using $HOME/.ipython and *not* XDG/ipython |
|
291 | # using $HOME/.ipython and *not* XDG/ipython | |
292 |
|
292 | |||
293 | xdg_ipdir = pjoin(xdg_dir, xdg_def) |
|
293 | xdg_ipdir = pjoin(xdg_dir, xdg_def) | |
294 |
|
294 | |||
295 | if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir): |
|
295 | if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir): | |
296 | ipdir = xdg_ipdir |
|
296 | ipdir = xdg_ipdir | |
297 |
|
297 | |||
298 | if ipdir is None: |
|
298 | if ipdir is None: | |
299 | # not using XDG |
|
299 | # not using XDG | |
300 | ipdir = home_ipdir |
|
300 | ipdir = home_ipdir | |
301 |
|
301 | |||
302 | ipdir = os.path.normpath(os.path.expanduser(ipdir)) |
|
302 | ipdir = os.path.normpath(os.path.expanduser(ipdir)) | |
303 |
|
303 | |||
304 | if os.path.exists(ipdir) and not _writable_dir(ipdir): |
|
304 | if os.path.exists(ipdir) and not _writable_dir(ipdir): | |
305 | # ipdir exists, but is not writable |
|
305 | # ipdir exists, but is not writable | |
306 | warnings.warn("IPython dir '%s' is not a writable location," |
|
306 | warnings.warn("IPython dir '%s' is not a writable location," | |
307 | " using a temp directory."%ipdir) |
|
307 | " using a temp directory."%ipdir) | |
308 | ipdir = tempfile.mkdtemp() |
|
308 | ipdir = tempfile.mkdtemp() | |
309 | elif not os.path.exists(ipdir): |
|
309 | elif not os.path.exists(ipdir): | |
310 | parent = os.path.dirname(ipdir) |
|
310 | parent = os.path.dirname(ipdir) | |
311 | if not _writable_dir(parent): |
|
311 | if not _writable_dir(parent): | |
312 | # ipdir does not exist and parent isn't writable |
|
312 | # ipdir does not exist and parent isn't writable | |
313 | warnings.warn("IPython parent '%s' is not a writable location," |
|
313 | warnings.warn("IPython parent '%s' is not a writable location," | |
314 | " using a temp directory."%parent) |
|
314 | " using a temp directory."%parent) | |
315 | ipdir = tempfile.mkdtemp() |
|
315 | ipdir = tempfile.mkdtemp() | |
316 |
|
316 | |||
317 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
317 | return py3compat.cast_unicode(ipdir, fs_encoding) | |
318 |
|
318 | |||
319 |
|
319 | |||
320 | def get_ipython_cache_dir(): |
|
320 | def get_ipython_cache_dir(): | |
321 | """Get the cache directory it is created if it does not exist.""" |
|
321 | """Get the cache directory it is created if it does not exist.""" | |
322 | xdgdir = get_xdg_cache_dir() |
|
322 | xdgdir = get_xdg_cache_dir() | |
323 | if xdgdir is None: |
|
323 | if xdgdir is None: | |
324 | return get_ipython_dir() |
|
324 | return get_ipython_dir() | |
325 | ipdir = os.path.join(xdgdir, "ipython") |
|
325 | ipdir = os.path.join(xdgdir, "ipython") | |
326 | if not os.path.exists(ipdir) and _writable_dir(xdgdir): |
|
326 | if not os.path.exists(ipdir) and _writable_dir(xdgdir): | |
327 | os.makedirs(ipdir) |
|
327 | os.makedirs(ipdir) | |
328 | elif not _writable_dir(xdgdir): |
|
328 | elif not _writable_dir(xdgdir): | |
329 | return get_ipython_dir() |
|
329 | return get_ipython_dir() | |
330 |
|
330 | |||
331 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
331 | return py3compat.cast_unicode(ipdir, fs_encoding) | |
332 |
|
332 | |||
333 |
|
333 | |||
334 | def get_ipython_package_dir(): |
|
334 | def get_ipython_package_dir(): | |
335 | """Get the base directory where IPython itself is installed.""" |
|
335 | """Get the base directory where IPython itself is installed.""" | |
336 | ipdir = os.path.dirname(IPython.__file__) |
|
336 | ipdir = os.path.dirname(IPython.__file__) | |
337 | return py3compat.cast_unicode(ipdir, fs_encoding) |
|
337 | return py3compat.cast_unicode(ipdir, fs_encoding) | |
338 |
|
338 | |||
339 |
|
339 | |||
340 | def get_ipython_module_path(module_str): |
|
340 | def get_ipython_module_path(module_str): | |
341 | """Find the path to an IPython module in this version of IPython. |
|
341 | """Find the path to an IPython module in this version of IPython. | |
342 |
|
342 | |||
343 | This will always find the version of the module that is in this importable |
|
343 | This will always find the version of the module that is in this importable | |
344 | IPython package. This will always return the path to the ``.py`` |
|
344 | IPython package. This will always return the path to the ``.py`` | |
345 | version of the module. |
|
345 | version of the module. | |
346 | """ |
|
346 | """ | |
347 | if module_str == 'IPython': |
|
347 | if module_str == 'IPython': | |
348 | return os.path.join(get_ipython_package_dir(), '__init__.py') |
|
348 | return os.path.join(get_ipython_package_dir(), '__init__.py') | |
349 | mod = import_item(module_str) |
|
349 | mod = import_item(module_str) | |
350 | the_path = mod.__file__.replace('.pyc', '.py') |
|
350 | the_path = mod.__file__.replace('.pyc', '.py') | |
351 | the_path = the_path.replace('.pyo', '.py') |
|
351 | the_path = the_path.replace('.pyo', '.py') | |
352 | return py3compat.cast_unicode(the_path, fs_encoding) |
|
352 | return py3compat.cast_unicode(the_path, fs_encoding) | |
353 |
|
353 | |||
354 | def locate_profile(profile='default'): |
|
354 | def locate_profile(profile='default'): | |
355 | """Find the path to the folder associated with a given profile. |
|
355 | """Find the path to the folder associated with a given profile. | |
356 |
|
356 | |||
357 | I.e. find $IPYTHONDIR/profile_whatever. |
|
357 | I.e. find $IPYTHONDIR/profile_whatever. | |
358 | """ |
|
358 | """ | |
359 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
359 | from IPython.core.profiledir import ProfileDir, ProfileDirError | |
360 | try: |
|
360 | try: | |
361 | pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) |
|
361 | pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) | |
362 | except ProfileDirError: |
|
362 | except ProfileDirError: | |
363 | # IOError makes more sense when people are expecting a path |
|
363 | # IOError makes more sense when people are expecting a path | |
364 | raise IOError("Couldn't find profile %r" % profile) |
|
364 | raise IOError("Couldn't find profile %r" % profile) | |
365 | return pd.location |
|
365 | return pd.location | |
366 |
|
366 | |||
367 | def expand_path(s): |
|
367 | def expand_path(s): | |
368 | """Expand $VARS and ~names in a string, like a shell |
|
368 | """Expand $VARS and ~names in a string, like a shell | |
369 |
|
369 | |||
370 | :Examples: |
|
370 | :Examples: | |
371 |
|
371 | |||
372 | In [2]: os.environ['FOO']='test' |
|
372 | In [2]: os.environ['FOO']='test' | |
373 |
|
373 | |||
374 | In [3]: expand_path('variable FOO is $FOO') |
|
374 | In [3]: expand_path('variable FOO is $FOO') | |
375 | Out[3]: 'variable FOO is test' |
|
375 | Out[3]: 'variable FOO is test' | |
376 | """ |
|
376 | """ | |
377 | # This is a pretty subtle hack. When expand user is given a UNC path |
|
377 | # This is a pretty subtle hack. When expand user is given a UNC path | |
378 | # on Windows (\\server\share$\%username%), os.path.expandvars, removes |
|
378 | # on Windows (\\server\share$\%username%), os.path.expandvars, removes | |
379 | # the $ to get (\\server\share\%username%). I think it considered $ |
|
379 | # the $ to get (\\server\share\%username%). I think it considered $ | |
380 | # alone an empty var. But, we need the $ to remains there (it indicates |
|
380 | # alone an empty var. But, we need the $ to remains there (it indicates | |
381 | # a hidden share). |
|
381 | # a hidden share). | |
382 | if os.name=='nt': |
|
382 | if os.name=='nt': | |
383 | s = s.replace('$\\', 'IPYTHON_TEMP') |
|
383 | s = s.replace('$\\', 'IPYTHON_TEMP') | |
384 | s = os.path.expandvars(os.path.expanduser(s)) |
|
384 | s = os.path.expandvars(os.path.expanduser(s)) | |
385 | if os.name=='nt': |
|
385 | if os.name=='nt': | |
386 | s = s.replace('IPYTHON_TEMP', '$\\') |
|
386 | s = s.replace('IPYTHON_TEMP', '$\\') | |
387 | return s |
|
387 | return s | |
388 |
|
388 | |||
389 |
|
389 | |||
390 | def unescape_glob(string): |
|
390 | def unescape_glob(string): | |
391 | """Unescape glob pattern in `string`.""" |
|
391 | """Unescape glob pattern in `string`.""" | |
392 | def unescape(s): |
|
392 | def unescape(s): | |
393 | for pattern in '*[]!?': |
|
393 | for pattern in '*[]!?': | |
394 | s = s.replace(r'\{0}'.format(pattern), pattern) |
|
394 | s = s.replace(r'\{0}'.format(pattern), pattern) | |
395 | return s |
|
395 | return s | |
396 | return '\\'.join(map(unescape, string.split('\\\\'))) |
|
396 | return '\\'.join(map(unescape, string.split('\\\\'))) | |
397 |
|
397 | |||
398 |
|
398 | |||
399 | def shellglob(args): |
|
399 | def shellglob(args): | |
400 | """ |
|
400 | """ | |
401 | Do glob expansion for each element in `args` and return a flattened list. |
|
401 | Do glob expansion for each element in `args` and return a flattened list. | |
402 |
|
402 | |||
403 | Unmatched glob pattern will remain as-is in the returned list. |
|
403 | Unmatched glob pattern will remain as-is in the returned list. | |
404 |
|
404 | |||
405 | """ |
|
405 | """ | |
406 | expanded = [] |
|
406 | expanded = [] | |
407 | # Do not unescape backslash in Windows as it is interpreted as |
|
407 | # Do not unescape backslash in Windows as it is interpreted as | |
408 | # path separator: |
|
408 | # path separator: | |
409 | unescape = unescape_glob if sys.platform != 'win32' else lambda x: x |
|
409 | unescape = unescape_glob if sys.platform != 'win32' else lambda x: x | |
410 | for a in args: |
|
410 | for a in args: | |
411 | expanded.extend(glob.glob(a) or [unescape(a)]) |
|
411 | expanded.extend(glob.glob(a) or [unescape(a)]) | |
412 | return expanded |
|
412 | return expanded | |
413 |
|
413 | |||
414 |
|
414 | |||
415 | def target_outdated(target,deps): |
|
415 | def target_outdated(target,deps): | |
416 | """Determine whether a target is out of date. |
|
416 | """Determine whether a target is out of date. | |
417 |
|
417 | |||
418 | target_outdated(target,deps) -> 1/0 |
|
418 | target_outdated(target,deps) -> 1/0 | |
419 |
|
419 | |||
420 | deps: list of filenames which MUST exist. |
|
420 | deps: list of filenames which MUST exist. | |
421 | target: single filename which may or may not exist. |
|
421 | target: single filename which may or may not exist. | |
422 |
|
422 | |||
423 | If target doesn't exist or is older than any file listed in deps, return |
|
423 | If target doesn't exist or is older than any file listed in deps, return | |
424 | true, otherwise return false. |
|
424 | true, otherwise return false. | |
425 | """ |
|
425 | """ | |
426 | try: |
|
426 | try: | |
427 | target_time = os.path.getmtime(target) |
|
427 | target_time = os.path.getmtime(target) | |
428 | except os.error: |
|
428 | except os.error: | |
429 | return 1 |
|
429 | return 1 | |
430 | for dep in deps: |
|
430 | for dep in deps: | |
431 | dep_time = os.path.getmtime(dep) |
|
431 | dep_time = os.path.getmtime(dep) | |
432 | if dep_time > target_time: |
|
432 | if dep_time > target_time: | |
433 | #print "For target",target,"Dep failed:",dep # dbg |
|
433 | #print "For target",target,"Dep failed:",dep # dbg | |
434 | #print "times (dep,tar):",dep_time,target_time # dbg |
|
434 | #print "times (dep,tar):",dep_time,target_time # dbg | |
435 | return 1 |
|
435 | return 1 | |
436 | return 0 |
|
436 | return 0 | |
437 |
|
437 | |||
438 |
|
438 | |||
439 | def target_update(target,deps,cmd): |
|
439 | def target_update(target,deps,cmd): | |
440 | """Update a target with a given command given a list of dependencies. |
|
440 | """Update a target with a given command given a list of dependencies. | |
441 |
|
441 | |||
442 | target_update(target,deps,cmd) -> runs cmd if target is outdated. |
|
442 | target_update(target,deps,cmd) -> runs cmd if target is outdated. | |
443 |
|
443 | |||
444 | This is just a wrapper around target_outdated() which calls the given |
|
444 | This is just a wrapper around target_outdated() which calls the given | |
445 | command if target is outdated.""" |
|
445 | command if target is outdated.""" | |
446 |
|
446 | |||
447 | if target_outdated(target,deps): |
|
447 | if target_outdated(target,deps): | |
448 | system(cmd) |
|
448 | system(cmd) | |
449 |
|
449 | |||
450 | def filehash(path): |
|
450 | def filehash(path): | |
451 | """Make an MD5 hash of a file, ignoring any differences in line |
|
451 | """Make an MD5 hash of a file, ignoring any differences in line | |
452 | ending characters.""" |
|
452 | ending characters.""" | |
453 | with open(path, "rU") as f: |
|
453 | with open(path, "rU") as f: | |
454 | return md5(py3compat.str_to_bytes(f.read())).hexdigest() |
|
454 | return md5(py3compat.str_to_bytes(f.read())).hexdigest() | |
455 |
|
455 | |||
456 | # If the config is unmodified from the default, we'll just delete it. |
|
456 | # If the config is unmodified from the default, we'll just delete it. | |
457 | # These are consistent for 0.10.x, thankfully. We're not going to worry about |
|
457 | # These are consistent for 0.10.x, thankfully. We're not going to worry about | |
458 | # older versions. |
|
458 | # older versions. | |
459 | old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3', |
|
459 | old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3', | |
460 | 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'} |
|
460 | 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'} | |
461 |
|
461 | |||
462 | def check_for_old_config(ipython_dir=None): |
|
462 | def check_for_old_config(ipython_dir=None): | |
463 | """Check for old config files, and present a warning if they exist. |
|
463 | """Check for old config files, and present a warning if they exist. | |
464 |
|
464 | |||
465 | A link to the docs of the new config is included in the message. |
|
465 | A link to the docs of the new config is included in the message. | |
466 |
|
466 | |||
467 | This should mitigate confusion with the transition to the new |
|
467 | This should mitigate confusion with the transition to the new | |
468 | config system in 0.11. |
|
468 | config system in 0.11. | |
469 | """ |
|
469 | """ | |
470 | if ipython_dir is None: |
|
470 | if ipython_dir is None: | |
471 | ipython_dir = get_ipython_dir() |
|
471 | ipython_dir = get_ipython_dir() | |
472 |
|
472 | |||
473 | old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py'] |
|
473 | old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py'] | |
474 | warned = False |
|
474 | warned = False | |
475 | for cfg in old_configs: |
|
475 | for cfg in old_configs: | |
476 | f = os.path.join(ipython_dir, cfg) |
|
476 | f = os.path.join(ipython_dir, cfg) | |
477 | if os.path.exists(f): |
|
477 | if os.path.exists(f): | |
478 | if filehash(f) == old_config_md5.get(cfg, ''): |
|
478 | if filehash(f) == old_config_md5.get(cfg, ''): | |
479 | os.unlink(f) |
|
479 | os.unlink(f) | |
480 | else: |
|
480 | else: | |
481 | warnings.warn("Found old IPython config file %r (modified by user)"%f) |
|
481 | warnings.warn("Found old IPython config file %r (modified by user)"%f) | |
482 | warned = True |
|
482 | warned = True | |
483 |
|
483 | |||
484 | if warned: |
|
484 | if warned: | |
485 | warnings.warn(""" |
|
485 | warnings.warn(""" | |
486 | The IPython configuration system has changed as of 0.11, and these files will |
|
486 | The IPython configuration system has changed as of 0.11, and these files will | |
487 | be ignored. See http://ipython.github.com/ipython-doc/dev/config for details |
|
487 | be ignored. See http://ipython.github.com/ipython-doc/dev/config for details | |
488 | of the new config system. |
|
488 | of the new config system. | |
489 | To start configuring IPython, do `ipython profile create`, and edit |
|
489 | To start configuring IPython, do `ipython profile create`, and edit | |
490 | `ipython_config.py` in <ipython_dir>/profile_default. |
|
490 | `ipython_config.py` in <ipython_dir>/profile_default. | |
491 | If you need to leave the old config files in place for an older version of |
|
491 | If you need to leave the old config files in place for an older version of | |
492 | IPython and want to suppress this warning message, set |
|
492 | IPython and want to suppress this warning message, set | |
493 | `c.InteractiveShellApp.ignore_old_config=True` in the new config.""") |
|
493 | `c.InteractiveShellApp.ignore_old_config=True` in the new config.""") | |
494 |
|
494 | |||
495 | def get_security_file(filename, profile='default'): |
|
495 | def get_security_file(filename, profile='default'): | |
496 | """Return the absolute path of a security file given by filename and profile |
|
496 | """Return the absolute path of a security file given by filename and profile | |
497 |
|
497 | |||
498 | This allows users and developers to find security files without |
|
498 | This allows users and developers to find security files without | |
499 | knowledge of the IPython directory structure. The search path |
|
499 | knowledge of the IPython directory structure. The search path | |
500 | will be ['.', profile.security_dir] |
|
500 | will be ['.', profile.security_dir] | |
501 |
|
501 | |||
502 | Parameters |
|
502 | Parameters | |
503 | ---------- |
|
503 | ---------- | |
504 |
|
504 | |||
505 | filename : str |
|
505 | filename : str | |
506 | The file to be found. If it is passed as an absolute path, it will |
|
506 | The file to be found. If it is passed as an absolute path, it will | |
507 | simply be returned. |
|
507 | simply be returned. | |
508 | profile : str [default: 'default'] |
|
508 | profile : str [default: 'default'] | |
509 | The name of the profile to search. Leaving this unspecified |
|
509 | The name of the profile to search. Leaving this unspecified | |
510 | The file to be found. If it is passed as an absolute path, fname will |
|
510 | The file to be found. If it is passed as an absolute path, fname will | |
511 | simply be returned. |
|
511 | simply be returned. | |
512 |
|
512 | |||
513 | Returns |
|
513 | Returns | |
514 | ------- |
|
514 | ------- | |
515 | Raises :exc:`IOError` if file not found or returns absolute path to file. |
|
515 | Raises :exc:`IOError` if file not found or returns absolute path to file. | |
516 | """ |
|
516 | """ | |
517 | # import here, because profiledir also imports from utils.path |
|
517 | # import here, because profiledir also imports from utils.path | |
518 | from IPython.core.profiledir import ProfileDir |
|
518 | from IPython.core.profiledir import ProfileDir | |
519 | try: |
|
519 | try: | |
520 | pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) |
|
520 | pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) | |
521 | except Exception: |
|
521 | except Exception: | |
522 | # will raise ProfileDirError if no such profile |
|
522 | # will raise ProfileDirError if no such profile | |
523 | raise IOError("Profile %r not found") |
|
523 | raise IOError("Profile %r not found") | |
524 | return filefind(filename, ['.', pd.security_dir]) |
|
524 | return filefind(filename, ['.', pd.security_dir]) | |
525 |
|
525 | |||
526 |
|
526 | |||
527 | ENOLINK = 1998 |
|
527 | ENOLINK = 1998 | |
528 |
|
528 | |||
529 | def link(src, dst): |
|
529 | def link(src, dst): | |
530 | """Hard links ``src`` to ``dst``, returning 0 or errno. |
|
530 | """Hard links ``src`` to ``dst``, returning 0 or errno. | |
531 |
|
531 | |||
532 | Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't |
|
532 | Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't | |
533 | supported by the operating system. |
|
533 | supported by the operating system. | |
534 | """ |
|
534 | """ | |
535 |
|
535 | |||
536 | if not hasattr(os, "link"): |
|
536 | if not hasattr(os, "link"): | |
537 | return ENOLINK |
|
537 | return ENOLINK | |
538 | link_errno = 0 |
|
538 | link_errno = 0 | |
539 | try: |
|
539 | try: | |
540 | os.link(src, dst) |
|
540 | os.link(src, dst) | |
541 | except OSError as e: |
|
541 | except OSError as e: | |
542 | link_errno = e.errno |
|
542 | link_errno = e.errno | |
543 | return link_errno |
|
543 | return link_errno | |
544 |
|
544 | |||
545 |
|
545 | |||
546 | def link_or_copy(src, dst): |
|
546 | def link_or_copy(src, dst): | |
547 | """Attempts to hardlink ``src`` to ``dst``, copying if the link fails. |
|
547 | """Attempts to hardlink ``src`` to ``dst``, copying if the link fails. | |
548 |
|
548 | |||
549 | Attempts to maintain the semantics of ``shutil.copy``. |
|
549 | Attempts to maintain the semantics of ``shutil.copy``. | |
550 |
|
550 | |||
551 | Because ``os.link`` does not overwrite files, a unique temporary file |
|
551 | Because ``os.link`` does not overwrite files, a unique temporary file | |
552 | will be used if the target already exists, then that file will be moved |
|
552 | will be used if the target already exists, then that file will be moved | |
553 | into place. |
|
553 | into place. | |
554 | """ |
|
554 | """ | |
555 |
|
555 | |||
556 | if os.path.isdir(dst): |
|
556 | if os.path.isdir(dst): | |
557 | dst = os.path.join(dst, os.path.basename(src)) |
|
557 | dst = os.path.join(dst, os.path.basename(src)) | |
558 |
|
558 | |||
559 | link_errno = link(src, dst) |
|
559 | link_errno = link(src, dst) | |
560 | if link_errno == errno.EEXIST: |
|
560 | if link_errno == errno.EEXIST: | |
561 | new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), ) |
|
561 | new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), ) | |
562 | try: |
|
562 | try: | |
563 | link_or_copy(src, new_dst) |
|
563 | link_or_copy(src, new_dst) | |
564 | except: |
|
564 | except: | |
565 | try: |
|
565 | try: | |
566 | os.remove(new_dst) |
|
566 | os.remove(new_dst) | |
567 | except OSError: |
|
567 | except OSError: | |
568 | pass |
|
568 | pass | |
569 | raise |
|
569 | raise | |
570 | os.rename(new_dst, dst) |
|
570 | os.rename(new_dst, dst) | |
571 | elif link_errno != 0: |
|
571 | elif link_errno != 0: | |
572 | # Either link isn't supported, or the filesystem doesn't support |
|
572 | # Either link isn't supported, or the filesystem doesn't support | |
573 | # linking, or 'src' and 'dst' are on different filesystems. |
|
573 | # linking, or 'src' and 'dst' are on different filesystems. | |
574 | shutil.copy(src, dst) |
|
574 | shutil.copy(src, dst) |
@@ -1,122 +1,123 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | Utilities for working with external processes. |
|
3 | Utilities for working with external processes. | |
4 | """ |
|
4 | """ | |
5 |
|
5 | |||
6 | #----------------------------------------------------------------------------- |
|
6 | #----------------------------------------------------------------------------- | |
7 | # Copyright (C) 2008-2011 The IPython Development Team |
|
7 | # Copyright (C) 2008-2011 The IPython Development Team | |
8 | # |
|
8 | # | |
9 | # Distributed under the terms of the BSD License. The full license is in |
|
9 | # Distributed under the terms of the BSD License. The full license is in | |
10 | # the file COPYING, distributed as part of this software. |
|
10 | # the file COPYING, distributed as part of this software. | |
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 |
|
12 | |||
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 | # Imports |
|
14 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | from __future__ import print_function |
|
16 | from __future__ import print_function | |
17 |
|
17 | |||
18 | # Stdlib |
|
18 | # Stdlib | |
19 | import os |
|
19 | import os | |
20 | import sys |
|
20 | import sys | |
21 | import shlex |
|
21 | import shlex | |
22 |
|
22 | |||
23 | # Our own |
|
23 | # Our own | |
24 | if sys.platform == 'win32': |
|
24 | if sys.platform == 'win32': | |
25 | from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split |
|
25 | from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split | |
26 | else: |
|
26 | else: | |
27 | from ._process_posix import _find_cmd, system, getoutput, arg_split |
|
27 | from ._process_posix import _find_cmd, system, getoutput, arg_split | |
28 |
|
28 | |||
29 |
|
29 | |||
30 | from ._process_common import getoutputerror, get_output_error_code |
|
30 | from ._process_common import getoutputerror, get_output_error_code | |
|
31 | from . import py3compat | |||
31 |
|
32 | |||
32 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
33 | # Code |
|
34 | # Code | |
34 | #----------------------------------------------------------------------------- |
|
35 | #----------------------------------------------------------------------------- | |
35 |
|
36 | |||
36 |
|
37 | |||
37 | class FindCmdError(Exception): |
|
38 | class FindCmdError(Exception): | |
38 | pass |
|
39 | pass | |
39 |
|
40 | |||
40 |
|
41 | |||
41 | def find_cmd(cmd): |
|
42 | def find_cmd(cmd): | |
42 | """Find absolute path to executable cmd in a cross platform manner. |
|
43 | """Find absolute path to executable cmd in a cross platform manner. | |
43 |
|
44 | |||
44 | This function tries to determine the full path to a command line program |
|
45 | This function tries to determine the full path to a command line program | |
45 | using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the |
|
46 | using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the | |
46 | time it will use the version that is first on the users `PATH`. |
|
47 | time it will use the version that is first on the users `PATH`. | |
47 |
|
48 | |||
48 | Warning, don't use this to find IPython command line programs as there |
|
49 | Warning, don't use this to find IPython command line programs as there | |
49 | is a risk you will find the wrong one. Instead find those using the |
|
50 | is a risk you will find the wrong one. Instead find those using the | |
50 | following code and looking for the application itself:: |
|
51 | following code and looking for the application itself:: | |
51 |
|
52 | |||
52 | from IPython.utils.path import get_ipython_module_path |
|
53 | from IPython.utils.path import get_ipython_module_path | |
53 | from IPython.utils.process import pycmd2argv |
|
54 | from IPython.utils.process import pycmd2argv | |
54 | argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp')) |
|
55 | argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp')) | |
55 |
|
56 | |||
56 | Parameters |
|
57 | Parameters | |
57 | ---------- |
|
58 | ---------- | |
58 | cmd : str |
|
59 | cmd : str | |
59 | The command line program to look for. |
|
60 | The command line program to look for. | |
60 | """ |
|
61 | """ | |
61 | try: |
|
62 | try: | |
62 | path = _find_cmd(cmd).rstrip() |
|
63 | path = _find_cmd(cmd).rstrip() | |
63 | except OSError: |
|
64 | except OSError: | |
64 | raise FindCmdError('command could not be found: %s' % cmd) |
|
65 | raise FindCmdError('command could not be found: %s' % cmd) | |
65 | # which returns empty if not found |
|
66 | # which returns empty if not found | |
66 | if path == '': |
|
67 | if path == '': | |
67 | raise FindCmdError('command could not be found: %s' % cmd) |
|
68 | raise FindCmdError('command could not be found: %s' % cmd) | |
68 | return os.path.abspath(path) |
|
69 | return os.path.abspath(path) | |
69 |
|
70 | |||
70 |
|
71 | |||
71 | def is_cmd_found(cmd): |
|
72 | def is_cmd_found(cmd): | |
72 | """Check whether executable `cmd` exists or not and return a bool.""" |
|
73 | """Check whether executable `cmd` exists or not and return a bool.""" | |
73 | try: |
|
74 | try: | |
74 | find_cmd(cmd) |
|
75 | find_cmd(cmd) | |
75 | return True |
|
76 | return True | |
76 | except FindCmdError: |
|
77 | except FindCmdError: | |
77 | return False |
|
78 | return False | |
78 |
|
79 | |||
79 |
|
80 | |||
80 | def pycmd2argv(cmd): |
|
81 | def pycmd2argv(cmd): | |
81 | r"""Take the path of a python command and return a list (argv-style). |
|
82 | r"""Take the path of a python command and return a list (argv-style). | |
82 |
|
83 | |||
83 | This only works on Python based command line programs and will find the |
|
84 | This only works on Python based command line programs and will find the | |
84 | location of the ``python`` executable using ``sys.executable`` to make |
|
85 | location of the ``python`` executable using ``sys.executable`` to make | |
85 | sure the right version is used. |
|
86 | sure the right version is used. | |
86 |
|
87 | |||
87 | For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe, |
|
88 | For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe, | |
88 | .com or .bat, and [, cmd] otherwise. |
|
89 | .com or .bat, and [, cmd] otherwise. | |
89 |
|
90 | |||
90 | Parameters |
|
91 | Parameters | |
91 | ---------- |
|
92 | ---------- | |
92 | cmd : string |
|
93 | cmd : string | |
93 | The path of the command. |
|
94 | The path of the command. | |
94 |
|
95 | |||
95 | Returns |
|
96 | Returns | |
96 | ------- |
|
97 | ------- | |
97 | argv-style list. |
|
98 | argv-style list. | |
98 | """ |
|
99 | """ | |
99 | ext = os.path.splitext(cmd)[1] |
|
100 | ext = os.path.splitext(cmd)[1] | |
100 | if ext in ['.exe', '.com', '.bat']: |
|
101 | if ext in ['.exe', '.com', '.bat']: | |
101 | return [cmd] |
|
102 | return [cmd] | |
102 | else: |
|
103 | else: | |
103 | return [sys.executable, cmd] |
|
104 | return [sys.executable, cmd] | |
104 |
|
105 | |||
105 |
|
106 | |||
106 | def abbrev_cwd(): |
|
107 | def abbrev_cwd(): | |
107 | """ Return abbreviated version of cwd, e.g. d:mydir """ |
|
108 | """ Return abbreviated version of cwd, e.g. d:mydir """ | |
108 |
cwd = |
|
109 | cwd = py3compat.getcwd().replace('\\','/') | |
109 | drivepart = '' |
|
110 | drivepart = '' | |
110 | tail = cwd |
|
111 | tail = cwd | |
111 | if sys.platform == 'win32': |
|
112 | if sys.platform == 'win32': | |
112 | if len(cwd) < 4: |
|
113 | if len(cwd) < 4: | |
113 | return cwd |
|
114 | return cwd | |
114 | drivepart,tail = os.path.splitdrive(cwd) |
|
115 | drivepart,tail = os.path.splitdrive(cwd) | |
115 |
|
116 | |||
116 |
|
117 | |||
117 | parts = tail.split('/') |
|
118 | parts = tail.split('/') | |
118 | if len(parts) > 2: |
|
119 | if len(parts) > 2: | |
119 | tail = '/'.join(parts[-2:]) |
|
120 | tail = '/'.join(parts[-2:]) | |
120 |
|
121 | |||
121 | return (drivepart + ( |
|
122 | return (drivepart + ( | |
122 | cwd == '/' and '/' or tail)) |
|
123 | cwd == '/' and '/' or tail)) |
@@ -1,239 +1,242 b'' | |||||
1 | # coding: utf-8 |
|
1 | # coding: utf-8 | |
2 | """Compatibility tricks for Python 3. Mainly to do with unicode.""" |
|
2 | """Compatibility tricks for Python 3. Mainly to do with unicode.""" | |
3 | import functools |
|
3 | import functools | |
|
4 | import os | |||
4 | import sys |
|
5 | import sys | |
5 | import re |
|
6 | import re | |
6 | import types |
|
7 | import types | |
7 |
|
8 | |||
8 | from .encoding import DEFAULT_ENCODING |
|
9 | from .encoding import DEFAULT_ENCODING | |
9 |
|
10 | |||
10 | orig_open = open |
|
11 | orig_open = open | |
11 |
|
12 | |||
12 | def no_code(x, encoding=None): |
|
13 | def no_code(x, encoding=None): | |
13 | return x |
|
14 | return x | |
14 |
|
15 | |||
15 | def decode(s, encoding=None): |
|
16 | def decode(s, encoding=None): | |
16 | encoding = encoding or DEFAULT_ENCODING |
|
17 | encoding = encoding or DEFAULT_ENCODING | |
17 | return s.decode(encoding, "replace") |
|
18 | return s.decode(encoding, "replace") | |
18 |
|
19 | |||
19 | def encode(u, encoding=None): |
|
20 | def encode(u, encoding=None): | |
20 | encoding = encoding or DEFAULT_ENCODING |
|
21 | encoding = encoding or DEFAULT_ENCODING | |
21 | return u.encode(encoding, "replace") |
|
22 | return u.encode(encoding, "replace") | |
22 |
|
23 | |||
23 |
|
24 | |||
24 | def cast_unicode(s, encoding=None): |
|
25 | def cast_unicode(s, encoding=None): | |
25 | if isinstance(s, bytes): |
|
26 | if isinstance(s, bytes): | |
26 | return decode(s, encoding) |
|
27 | return decode(s, encoding) | |
27 | return s |
|
28 | return s | |
28 |
|
29 | |||
29 | def cast_bytes(s, encoding=None): |
|
30 | def cast_bytes(s, encoding=None): | |
30 | if not isinstance(s, bytes): |
|
31 | if not isinstance(s, bytes): | |
31 | return encode(s, encoding) |
|
32 | return encode(s, encoding) | |
32 | return s |
|
33 | return s | |
33 |
|
34 | |||
34 | def _modify_str_or_docstring(str_change_func): |
|
35 | def _modify_str_or_docstring(str_change_func): | |
35 | @functools.wraps(str_change_func) |
|
36 | @functools.wraps(str_change_func) | |
36 | def wrapper(func_or_str): |
|
37 | def wrapper(func_or_str): | |
37 | if isinstance(func_or_str, string_types): |
|
38 | if isinstance(func_or_str, string_types): | |
38 | func = None |
|
39 | func = None | |
39 | doc = func_or_str |
|
40 | doc = func_or_str | |
40 | else: |
|
41 | else: | |
41 | func = func_or_str |
|
42 | func = func_or_str | |
42 | doc = func.__doc__ |
|
43 | doc = func.__doc__ | |
43 |
|
44 | |||
44 | doc = str_change_func(doc) |
|
45 | doc = str_change_func(doc) | |
45 |
|
46 | |||
46 | if func: |
|
47 | if func: | |
47 | func.__doc__ = doc |
|
48 | func.__doc__ = doc | |
48 | return func |
|
49 | return func | |
49 | return doc |
|
50 | return doc | |
50 | return wrapper |
|
51 | return wrapper | |
51 |
|
52 | |||
52 | def safe_unicode(e): |
|
53 | def safe_unicode(e): | |
53 | """unicode(e) with various fallbacks. Used for exceptions, which may not be |
|
54 | """unicode(e) with various fallbacks. Used for exceptions, which may not be | |
54 | safe to call unicode() on. |
|
55 | safe to call unicode() on. | |
55 | """ |
|
56 | """ | |
56 | try: |
|
57 | try: | |
57 | return unicode_type(e) |
|
58 | return unicode_type(e) | |
58 | except UnicodeError: |
|
59 | except UnicodeError: | |
59 | pass |
|
60 | pass | |
60 |
|
61 | |||
61 | try: |
|
62 | try: | |
62 | return str_to_unicode(str(e)) |
|
63 | return str_to_unicode(str(e)) | |
63 | except UnicodeError: |
|
64 | except UnicodeError: | |
64 | pass |
|
65 | pass | |
65 |
|
66 | |||
66 | try: |
|
67 | try: | |
67 | return str_to_unicode(repr(e)) |
|
68 | return str_to_unicode(repr(e)) | |
68 | except UnicodeError: |
|
69 | except UnicodeError: | |
69 | pass |
|
70 | pass | |
70 |
|
71 | |||
71 | return u'Unrecoverably corrupt evalue' |
|
72 | return u'Unrecoverably corrupt evalue' | |
72 |
|
73 | |||
73 | if sys.version_info[0] >= 3: |
|
74 | if sys.version_info[0] >= 3: | |
74 | PY3 = True |
|
75 | PY3 = True | |
75 |
|
76 | |||
76 | input = input |
|
77 | input = input | |
77 | builtin_mod_name = "builtins" |
|
78 | builtin_mod_name = "builtins" | |
78 | import builtins as builtin_mod |
|
79 | import builtins as builtin_mod | |
79 |
|
80 | |||
80 | str_to_unicode = no_code |
|
81 | str_to_unicode = no_code | |
81 | unicode_to_str = no_code |
|
82 | unicode_to_str = no_code | |
82 | str_to_bytes = encode |
|
83 | str_to_bytes = encode | |
83 | bytes_to_str = decode |
|
84 | bytes_to_str = decode | |
84 | cast_bytes_py2 = no_code |
|
85 | cast_bytes_py2 = no_code | |
85 |
|
86 | |||
86 | string_types = (str,) |
|
87 | string_types = (str,) | |
87 | unicode_type = str |
|
88 | unicode_type = str | |
88 |
|
89 | |||
89 | def isidentifier(s, dotted=False): |
|
90 | def isidentifier(s, dotted=False): | |
90 | if dotted: |
|
91 | if dotted: | |
91 | return all(isidentifier(a) for a in s.split(".")) |
|
92 | return all(isidentifier(a) for a in s.split(".")) | |
92 | return s.isidentifier() |
|
93 | return s.isidentifier() | |
93 |
|
94 | |||
94 | open = orig_open |
|
95 | open = orig_open | |
95 | xrange = range |
|
96 | xrange = range | |
96 | def iteritems(d): return iter(d.items()) |
|
97 | def iteritems(d): return iter(d.items()) | |
97 | def itervalues(d): return iter(d.values()) |
|
98 | def itervalues(d): return iter(d.values()) | |
|
99 | getcwd = os.getcwd | |||
98 |
|
100 | |||
99 | MethodType = types.MethodType |
|
101 | MethodType = types.MethodType | |
100 |
|
102 | |||
101 | def execfile(fname, glob, loc=None): |
|
103 | def execfile(fname, glob, loc=None): | |
102 | loc = loc if (loc is not None) else glob |
|
104 | loc = loc if (loc is not None) else glob | |
103 | with open(fname, 'rb') as f: |
|
105 | with open(fname, 'rb') as f: | |
104 | exec(compile(f.read(), fname, 'exec'), glob, loc) |
|
106 | exec(compile(f.read(), fname, 'exec'), glob, loc) | |
105 |
|
107 | |||
106 | # Refactor print statements in doctests. |
|
108 | # Refactor print statements in doctests. | |
107 | _print_statement_re = re.compile(r"\bprint (?P<expr>.*)$", re.MULTILINE) |
|
109 | _print_statement_re = re.compile(r"\bprint (?P<expr>.*)$", re.MULTILINE) | |
108 | def _print_statement_sub(match): |
|
110 | def _print_statement_sub(match): | |
109 | expr = match.groups('expr') |
|
111 | expr = match.groups('expr') | |
110 | return "print(%s)" % expr |
|
112 | return "print(%s)" % expr | |
111 |
|
113 | |||
112 | @_modify_str_or_docstring |
|
114 | @_modify_str_or_docstring | |
113 | def doctest_refactor_print(doc): |
|
115 | def doctest_refactor_print(doc): | |
114 | """Refactor 'print x' statements in a doctest to print(x) style. 2to3 |
|
116 | """Refactor 'print x' statements in a doctest to print(x) style. 2to3 | |
115 | unfortunately doesn't pick up on our doctests. |
|
117 | unfortunately doesn't pick up on our doctests. | |
116 |
|
118 | |||
117 | Can accept a string or a function, so it can be used as a decorator.""" |
|
119 | Can accept a string or a function, so it can be used as a decorator.""" | |
118 | return _print_statement_re.sub(_print_statement_sub, doc) |
|
120 | return _print_statement_re.sub(_print_statement_sub, doc) | |
119 |
|
121 | |||
120 | # Abstract u'abc' syntax: |
|
122 | # Abstract u'abc' syntax: | |
121 | @_modify_str_or_docstring |
|
123 | @_modify_str_or_docstring | |
122 | def u_format(s): |
|
124 | def u_format(s): | |
123 | """"{u}'abc'" --> "'abc'" (Python 3) |
|
125 | """"{u}'abc'" --> "'abc'" (Python 3) | |
124 |
|
126 | |||
125 | Accepts a string or a function, so it can be used as a decorator.""" |
|
127 | Accepts a string or a function, so it can be used as a decorator.""" | |
126 | return s.format(u='') |
|
128 | return s.format(u='') | |
127 |
|
129 | |||
128 | else: |
|
130 | else: | |
129 | PY3 = False |
|
131 | PY3 = False | |
130 |
|
132 | |||
131 | input = raw_input |
|
133 | input = raw_input | |
132 | builtin_mod_name = "__builtin__" |
|
134 | builtin_mod_name = "__builtin__" | |
133 | import __builtin__ as builtin_mod |
|
135 | import __builtin__ as builtin_mod | |
134 |
|
136 | |||
135 | str_to_unicode = decode |
|
137 | str_to_unicode = decode | |
136 | unicode_to_str = encode |
|
138 | unicode_to_str = encode | |
137 | str_to_bytes = no_code |
|
139 | str_to_bytes = no_code | |
138 | bytes_to_str = no_code |
|
140 | bytes_to_str = no_code | |
139 | cast_bytes_py2 = cast_bytes |
|
141 | cast_bytes_py2 = cast_bytes | |
140 |
|
142 | |||
141 | string_types = (str, unicode) |
|
143 | string_types = (str, unicode) | |
142 | unicode_type = unicode |
|
144 | unicode_type = unicode | |
143 |
|
145 | |||
144 | import re |
|
146 | import re | |
145 | _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") |
|
147 | _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") | |
146 | def isidentifier(s, dotted=False): |
|
148 | def isidentifier(s, dotted=False): | |
147 | if dotted: |
|
149 | if dotted: | |
148 | return all(isidentifier(a) for a in s.split(".")) |
|
150 | return all(isidentifier(a) for a in s.split(".")) | |
149 | return bool(_name_re.match(s)) |
|
151 | return bool(_name_re.match(s)) | |
150 |
|
152 | |||
151 | class open(object): |
|
153 | class open(object): | |
152 | """Wrapper providing key part of Python 3 open() interface.""" |
|
154 | """Wrapper providing key part of Python 3 open() interface.""" | |
153 | def __init__(self, fname, mode="r", encoding="utf-8"): |
|
155 | def __init__(self, fname, mode="r", encoding="utf-8"): | |
154 | self.f = orig_open(fname, mode) |
|
156 | self.f = orig_open(fname, mode) | |
155 | self.enc = encoding |
|
157 | self.enc = encoding | |
156 |
|
158 | |||
157 | def write(self, s): |
|
159 | def write(self, s): | |
158 | return self.f.write(s.encode(self.enc)) |
|
160 | return self.f.write(s.encode(self.enc)) | |
159 |
|
161 | |||
160 | def read(self, size=-1): |
|
162 | def read(self, size=-1): | |
161 | return self.f.read(size).decode(self.enc) |
|
163 | return self.f.read(size).decode(self.enc) | |
162 |
|
164 | |||
163 | def close(self): |
|
165 | def close(self): | |
164 | return self.f.close() |
|
166 | return self.f.close() | |
165 |
|
167 | |||
166 | def __enter__(self): |
|
168 | def __enter__(self): | |
167 | return self |
|
169 | return self | |
168 |
|
170 | |||
169 | def __exit__(self, etype, value, traceback): |
|
171 | def __exit__(self, etype, value, traceback): | |
170 | self.f.close() |
|
172 | self.f.close() | |
171 |
|
173 | |||
172 | xrange = xrange |
|
174 | xrange = xrange | |
173 | def iteritems(d): return d.iteritems() |
|
175 | def iteritems(d): return d.iteritems() | |
174 | def itervalues(d): return d.itervalues() |
|
176 | def itervalues(d): return d.itervalues() | |
|
177 | getcwd = os.getcwdu | |||
175 |
|
178 | |||
176 | def MethodType(func, instance): |
|
179 | def MethodType(func, instance): | |
177 | return types.MethodType(func, instance, type(instance)) |
|
180 | return types.MethodType(func, instance, type(instance)) | |
178 |
|
181 | |||
179 | # don't override system execfile on 2.x: |
|
182 | # don't override system execfile on 2.x: | |
180 | execfile = execfile |
|
183 | execfile = execfile | |
181 |
|
184 | |||
182 | def doctest_refactor_print(func_or_str): |
|
185 | def doctest_refactor_print(func_or_str): | |
183 | return func_or_str |
|
186 | return func_or_str | |
184 |
|
187 | |||
185 |
|
188 | |||
186 | # Abstract u'abc' syntax: |
|
189 | # Abstract u'abc' syntax: | |
187 | @_modify_str_or_docstring |
|
190 | @_modify_str_or_docstring | |
188 | def u_format(s): |
|
191 | def u_format(s): | |
189 | """"{u}'abc'" --> "u'abc'" (Python 2) |
|
192 | """"{u}'abc'" --> "u'abc'" (Python 2) | |
190 |
|
193 | |||
191 | Accepts a string or a function, so it can be used as a decorator.""" |
|
194 | Accepts a string or a function, so it can be used as a decorator.""" | |
192 | return s.format(u='u') |
|
195 | return s.format(u='u') | |
193 |
|
196 | |||
194 | if sys.platform == 'win32': |
|
197 | if sys.platform == 'win32': | |
195 | def execfile(fname, glob=None, loc=None): |
|
198 | def execfile(fname, glob=None, loc=None): | |
196 | loc = loc if (loc is not None) else glob |
|
199 | loc = loc if (loc is not None) else glob | |
197 | # The rstrip() is necessary b/c trailing whitespace in files will |
|
200 | # The rstrip() is necessary b/c trailing whitespace in files will | |
198 | # cause an IndentationError in Python 2.6 (this was fixed in 2.7, |
|
201 | # cause an IndentationError in Python 2.6 (this was fixed in 2.7, | |
199 | # but we still support 2.6). See issue 1027. |
|
202 | # but we still support 2.6). See issue 1027. | |
200 | scripttext = builtin_mod.open(fname).read().rstrip() + '\n' |
|
203 | scripttext = builtin_mod.open(fname).read().rstrip() + '\n' | |
201 | # compile converts unicode filename to str assuming |
|
204 | # compile converts unicode filename to str assuming | |
202 | # ascii. Let's do the conversion before calling compile |
|
205 | # ascii. Let's do the conversion before calling compile | |
203 | if isinstance(fname, unicode): |
|
206 | if isinstance(fname, unicode): | |
204 | filename = unicode_to_str(fname) |
|
207 | filename = unicode_to_str(fname) | |
205 | else: |
|
208 | else: | |
206 | filename = fname |
|
209 | filename = fname | |
207 | exec(compile(scripttext, filename, 'exec'), glob, loc) |
|
210 | exec(compile(scripttext, filename, 'exec'), glob, loc) | |
208 | else: |
|
211 | else: | |
209 | def execfile(fname, *where): |
|
212 | def execfile(fname, *where): | |
210 | if isinstance(fname, unicode): |
|
213 | if isinstance(fname, unicode): | |
211 | filename = fname.encode(sys.getfilesystemencoding()) |
|
214 | filename = fname.encode(sys.getfilesystemencoding()) | |
212 | else: |
|
215 | else: | |
213 | filename = fname |
|
216 | filename = fname | |
214 | builtin_mod.execfile(filename, *where) |
|
217 | builtin_mod.execfile(filename, *where) | |
215 |
|
218 | |||
216 | # Parts below taken from six: |
|
219 | # Parts below taken from six: | |
217 | # Copyright (c) 2010-2013 Benjamin Peterson |
|
220 | # Copyright (c) 2010-2013 Benjamin Peterson | |
218 | # |
|
221 | # | |
219 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
|
222 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
220 | # of this software and associated documentation files (the "Software"), to deal |
|
223 | # of this software and associated documentation files (the "Software"), to deal | |
221 | # in the Software without restriction, including without limitation the rights |
|
224 | # in the Software without restriction, including without limitation the rights | |
222 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
225 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
223 | # copies of the Software, and to permit persons to whom the Software is |
|
226 | # copies of the Software, and to permit persons to whom the Software is | |
224 | # furnished to do so, subject to the following conditions: |
|
227 | # furnished to do so, subject to the following conditions: | |
225 | # |
|
228 | # | |
226 | # The above copyright notice and this permission notice shall be included in all |
|
229 | # The above copyright notice and this permission notice shall be included in all | |
227 | # copies or substantial portions of the Software. |
|
230 | # copies or substantial portions of the Software. | |
228 | # |
|
231 | # | |
229 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
232 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
230 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
233 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
231 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
234 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
232 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
235 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
233 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
236 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
234 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
237 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
235 | # SOFTWARE. |
|
238 | # SOFTWARE. | |
236 |
|
239 | |||
237 | def with_metaclass(meta, *bases): |
|
240 | def with_metaclass(meta, *bases): | |
238 | """Create a base class with a metaclass.""" |
|
241 | """Create a base class with a metaclass.""" | |
239 | return meta("NewBase", bases, {}) |
|
242 | return meta("NewBase", bases, {}) |
@@ -1,162 +1,164 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | Utilities for working with terminals. |
|
3 | Utilities for working with terminals. | |
4 |
|
4 | |||
5 | Authors: |
|
5 | Authors: | |
6 |
|
6 | |||
7 | * Brian E. Granger |
|
7 | * Brian E. Granger | |
8 | * Fernando Perez |
|
8 | * Fernando Perez | |
9 | * Alexander Belchenko (e-mail: bialix AT ukr.net) |
|
9 | * Alexander Belchenko (e-mail: bialix AT ukr.net) | |
10 | """ |
|
10 | """ | |
11 |
|
11 | |||
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 | # Copyright (C) 2008-2011 The IPython Development Team |
|
13 | # Copyright (C) 2008-2011 The IPython Development Team | |
14 | # |
|
14 | # | |
15 | # Distributed under the terms of the BSD License. The full license is in |
|
15 | # Distributed under the terms of the BSD License. The full license is in | |
16 | # the file COPYING, distributed as part of this software. |
|
16 | # the file COPYING, distributed as part of this software. | |
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 |
|
18 | |||
19 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
20 | # Imports |
|
20 | # Imports | |
21 | #----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
22 |
|
22 | |||
23 | import os |
|
23 | import os | |
24 | import struct |
|
24 | import struct | |
25 | import sys |
|
25 | import sys | |
26 | import warnings |
|
26 | import warnings | |
27 |
|
27 | |||
|
28 | from . import py3compat | |||
|
29 | ||||
28 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
29 | # Code |
|
31 | # Code | |
30 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
31 |
|
33 | |||
32 | # This variable is part of the expected API of the module: |
|
34 | # This variable is part of the expected API of the module: | |
33 | ignore_termtitle = True |
|
35 | ignore_termtitle = True | |
34 |
|
36 | |||
35 |
|
37 | |||
36 | def _term_clear(): |
|
38 | def _term_clear(): | |
37 | pass |
|
39 | pass | |
38 |
|
40 | |||
39 |
|
41 | |||
40 | if os.name == 'posix': |
|
42 | if os.name == 'posix': | |
41 | def _term_clear(): |
|
43 | def _term_clear(): | |
42 | os.system('clear') |
|
44 | os.system('clear') | |
43 |
|
45 | |||
44 |
|
46 | |||
45 | if sys.platform == 'win32': |
|
47 | if sys.platform == 'win32': | |
46 | def _term_clear(): |
|
48 | def _term_clear(): | |
47 | os.system('cls') |
|
49 | os.system('cls') | |
48 |
|
50 | |||
49 |
|
51 | |||
50 | def term_clear(): |
|
52 | def term_clear(): | |
51 | _term_clear() |
|
53 | _term_clear() | |
52 |
|
54 | |||
53 |
|
55 | |||
54 | def toggle_set_term_title(val): |
|
56 | def toggle_set_term_title(val): | |
55 | """Control whether set_term_title is active or not. |
|
57 | """Control whether set_term_title is active or not. | |
56 |
|
58 | |||
57 | set_term_title() allows writing to the console titlebar. In embedded |
|
59 | set_term_title() allows writing to the console titlebar. In embedded | |
58 | widgets this can cause problems, so this call can be used to toggle it on |
|
60 | widgets this can cause problems, so this call can be used to toggle it on | |
59 | or off as needed. |
|
61 | or off as needed. | |
60 |
|
62 | |||
61 | The default state of the module is for the function to be disabled. |
|
63 | The default state of the module is for the function to be disabled. | |
62 |
|
64 | |||
63 | Parameters |
|
65 | Parameters | |
64 | ---------- |
|
66 | ---------- | |
65 | val : bool |
|
67 | val : bool | |
66 | If True, set_term_title() actually writes to the terminal (using the |
|
68 | If True, set_term_title() actually writes to the terminal (using the | |
67 | appropriate platform-specific module). If False, it is a no-op. |
|
69 | appropriate platform-specific module). If False, it is a no-op. | |
68 | """ |
|
70 | """ | |
69 | global ignore_termtitle |
|
71 | global ignore_termtitle | |
70 | ignore_termtitle = not(val) |
|
72 | ignore_termtitle = not(val) | |
71 |
|
73 | |||
72 |
|
74 | |||
73 | def _set_term_title(*args,**kw): |
|
75 | def _set_term_title(*args,**kw): | |
74 | """Dummy no-op.""" |
|
76 | """Dummy no-op.""" | |
75 | pass |
|
77 | pass | |
76 |
|
78 | |||
77 |
|
79 | |||
78 | def _set_term_title_xterm(title): |
|
80 | def _set_term_title_xterm(title): | |
79 | """ Change virtual terminal title in xterm-workalikes """ |
|
81 | """ Change virtual terminal title in xterm-workalikes """ | |
80 | sys.stdout.write('\033]0;%s\007' % title) |
|
82 | sys.stdout.write('\033]0;%s\007' % title) | |
81 |
|
83 | |||
82 | if os.name == 'posix': |
|
84 | if os.name == 'posix': | |
83 | TERM = os.environ.get('TERM','') |
|
85 | TERM = os.environ.get('TERM','') | |
84 | if TERM.startswith('xterm'): |
|
86 | if TERM.startswith('xterm'): | |
85 | _set_term_title = _set_term_title_xterm |
|
87 | _set_term_title = _set_term_title_xterm | |
86 |
|
88 | |||
87 |
|
89 | |||
88 | if sys.platform == 'win32': |
|
90 | if sys.platform == 'win32': | |
89 | try: |
|
91 | try: | |
90 | import ctypes |
|
92 | import ctypes | |
91 |
|
93 | |||
92 | SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW |
|
94 | SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW | |
93 | SetConsoleTitleW.argtypes = [ctypes.c_wchar_p] |
|
95 | SetConsoleTitleW.argtypes = [ctypes.c_wchar_p] | |
94 |
|
96 | |||
95 | def _set_term_title(title): |
|
97 | def _set_term_title(title): | |
96 | """Set terminal title using ctypes to access the Win32 APIs.""" |
|
98 | """Set terminal title using ctypes to access the Win32 APIs.""" | |
97 | SetConsoleTitleW(title) |
|
99 | SetConsoleTitleW(title) | |
98 | except ImportError: |
|
100 | except ImportError: | |
99 | def _set_term_title(title): |
|
101 | def _set_term_title(title): | |
100 | """Set terminal title using the 'title' command.""" |
|
102 | """Set terminal title using the 'title' command.""" | |
101 | global ignore_termtitle |
|
103 | global ignore_termtitle | |
102 |
|
104 | |||
103 | try: |
|
105 | try: | |
104 | # Cannot be on network share when issuing system commands |
|
106 | # Cannot be on network share when issuing system commands | |
105 |
curr = |
|
107 | curr = py3compat.getcwd() | |
106 | os.chdir("C:") |
|
108 | os.chdir("C:") | |
107 | ret = os.system("title " + title) |
|
109 | ret = os.system("title " + title) | |
108 | finally: |
|
110 | finally: | |
109 | os.chdir(curr) |
|
111 | os.chdir(curr) | |
110 | if ret: |
|
112 | if ret: | |
111 | # non-zero return code signals error, don't try again |
|
113 | # non-zero return code signals error, don't try again | |
112 | ignore_termtitle = True |
|
114 | ignore_termtitle = True | |
113 |
|
115 | |||
114 |
|
116 | |||
115 | def set_term_title(title): |
|
117 | def set_term_title(title): | |
116 | """Set terminal title using the necessary platform-dependent calls.""" |
|
118 | """Set terminal title using the necessary platform-dependent calls.""" | |
117 | if ignore_termtitle: |
|
119 | if ignore_termtitle: | |
118 | return |
|
120 | return | |
119 | _set_term_title(title) |
|
121 | _set_term_title(title) | |
120 |
|
122 | |||
121 |
|
123 | |||
122 | def freeze_term_title(): |
|
124 | def freeze_term_title(): | |
123 | warnings.warn("This function is deprecated, use toggle_set_term_title()") |
|
125 | warnings.warn("This function is deprecated, use toggle_set_term_title()") | |
124 | global ignore_termtitle |
|
126 | global ignore_termtitle | |
125 | ignore_termtitle = True |
|
127 | ignore_termtitle = True | |
126 |
|
128 | |||
127 |
|
129 | |||
128 | def get_terminal_size(defaultx=80, defaulty=25): |
|
130 | def get_terminal_size(defaultx=80, defaulty=25): | |
129 | return defaultx, defaulty |
|
131 | return defaultx, defaulty | |
130 |
|
132 | |||
131 |
|
133 | |||
132 | if sys.platform == 'win32': |
|
134 | if sys.platform == 'win32': | |
133 | def get_terminal_size(defaultx=80, defaulty=25): |
|
135 | def get_terminal_size(defaultx=80, defaulty=25): | |
134 | """Return size of current terminal console. |
|
136 | """Return size of current terminal console. | |
135 |
|
137 | |||
136 | This function try to determine actual size of current working |
|
138 | This function try to determine actual size of current working | |
137 | console window and return tuple (sizex, sizey) if success, |
|
139 | console window and return tuple (sizex, sizey) if success, | |
138 | or default size (defaultx, defaulty) otherwise. |
|
140 | or default size (defaultx, defaulty) otherwise. | |
139 |
|
141 | |||
140 | Dependencies: ctypes should be installed. |
|
142 | Dependencies: ctypes should be installed. | |
141 |
|
143 | |||
142 | Author: Alexander Belchenko (e-mail: bialix AT ukr.net) |
|
144 | Author: Alexander Belchenko (e-mail: bialix AT ukr.net) | |
143 | """ |
|
145 | """ | |
144 | try: |
|
146 | try: | |
145 | import ctypes |
|
147 | import ctypes | |
146 | except ImportError: |
|
148 | except ImportError: | |
147 | return defaultx, defaulty |
|
149 | return defaultx, defaulty | |
148 |
|
150 | |||
149 | h = ctypes.windll.kernel32.GetStdHandle(-11) |
|
151 | h = ctypes.windll.kernel32.GetStdHandle(-11) | |
150 | csbi = ctypes.create_string_buffer(22) |
|
152 | csbi = ctypes.create_string_buffer(22) | |
151 | res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) |
|
153 | res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) | |
152 |
|
154 | |||
153 | if res: |
|
155 | if res: | |
154 | (bufx, bufy, curx, cury, wattr, |
|
156 | (bufx, bufy, curx, cury, wattr, | |
155 | left, top, right, bottom, maxx, maxy) = struct.unpack( |
|
157 | left, top, right, bottom, maxx, maxy) = struct.unpack( | |
156 | "hhhhHhhhhhh", csbi.raw) |
|
158 | "hhhhHhhhhhh", csbi.raw) | |
157 | sizex = right - left + 1 |
|
159 | sizex = right - left + 1 | |
158 | sizey = bottom - top + 1 |
|
160 | sizey = bottom - top + 1 | |
159 | return (sizex, sizey) |
|
161 | return (sizex, sizey) | |
160 | else: |
|
162 | else: | |
161 | return (defaultx, defaulty) |
|
163 | return (defaultx, defaulty) | |
162 |
|
164 |
@@ -1,643 +1,643 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """Tests for IPython.utils.path.py""" |
|
2 | """Tests for IPython.utils.path.py""" | |
3 |
|
3 | |||
4 | #----------------------------------------------------------------------------- |
|
4 | #----------------------------------------------------------------------------- | |
5 | # Copyright (C) 2008-2011 The IPython Development Team |
|
5 | # Copyright (C) 2008-2011 The IPython Development Team | |
6 | # |
|
6 | # | |
7 | # Distributed under the terms of the BSD License. The full license is in |
|
7 | # Distributed under the terms of the BSD License. The full license is in | |
8 | # the file COPYING, distributed as part of this software. |
|
8 | # the file COPYING, distributed as part of this software. | |
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 |
|
10 | |||
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 | # Imports |
|
12 | # Imports | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | from __future__ import with_statement |
|
15 | from __future__ import with_statement | |
16 |
|
16 | |||
17 | import os |
|
17 | import os | |
18 | import shutil |
|
18 | import shutil | |
19 | import sys |
|
19 | import sys | |
20 | import tempfile |
|
20 | import tempfile | |
21 | from contextlib import contextmanager |
|
21 | from contextlib import contextmanager | |
22 |
|
22 | |||
23 | from os.path import join, abspath, split |
|
23 | from os.path import join, abspath, split | |
24 |
|
24 | |||
25 | import nose.tools as nt |
|
25 | import nose.tools as nt | |
26 |
|
26 | |||
27 | from nose import with_setup |
|
27 | from nose import with_setup | |
28 |
|
28 | |||
29 | import IPython |
|
29 | import IPython | |
30 | from IPython.testing import decorators as dec |
|
30 | from IPython.testing import decorators as dec | |
31 | from IPython.testing.decorators import (skip_if_not_win32, skip_win32, |
|
31 | from IPython.testing.decorators import (skip_if_not_win32, skip_win32, | |
32 | onlyif_unicode_paths,) |
|
32 | onlyif_unicode_paths,) | |
33 | from IPython.testing.tools import make_tempfile, AssertPrints |
|
33 | from IPython.testing.tools import make_tempfile, AssertPrints | |
34 | from IPython.utils import path |
|
34 | from IPython.utils import path | |
35 | from IPython.utils import py3compat |
|
35 | from IPython.utils import py3compat | |
36 | from IPython.utils.tempdir import TemporaryDirectory |
|
36 | from IPython.utils.tempdir import TemporaryDirectory | |
37 |
|
37 | |||
38 | # Platform-dependent imports |
|
38 | # Platform-dependent imports | |
39 | try: |
|
39 | try: | |
40 | import winreg as wreg # Py 3 |
|
40 | import winreg as wreg # Py 3 | |
41 | except ImportError: |
|
41 | except ImportError: | |
42 | try: |
|
42 | try: | |
43 | import _winreg as wreg # Py 2 |
|
43 | import _winreg as wreg # Py 2 | |
44 | except ImportError: |
|
44 | except ImportError: | |
45 | #Fake _winreg module on none windows platforms |
|
45 | #Fake _winreg module on none windows platforms | |
46 | import types |
|
46 | import types | |
47 | wr_name = "winreg" if py3compat.PY3 else "_winreg" |
|
47 | wr_name = "winreg" if py3compat.PY3 else "_winreg" | |
48 | sys.modules[wr_name] = types.ModuleType(wr_name) |
|
48 | sys.modules[wr_name] = types.ModuleType(wr_name) | |
49 | try: |
|
49 | try: | |
50 | import winreg as wreg |
|
50 | import winreg as wreg | |
51 | except ImportError: |
|
51 | except ImportError: | |
52 | import _winreg as wreg |
|
52 | import _winreg as wreg | |
53 | #Add entries that needs to be stubbed by the testing code |
|
53 | #Add entries that needs to be stubbed by the testing code | |
54 | (wreg.OpenKey, wreg.QueryValueEx,) = (None, None) |
|
54 | (wreg.OpenKey, wreg.QueryValueEx,) = (None, None) | |
55 |
|
55 | |||
56 | try: |
|
56 | try: | |
57 | reload |
|
57 | reload | |
58 | except NameError: # Python 3 |
|
58 | except NameError: # Python 3 | |
59 | from imp import reload |
|
59 | from imp import reload | |
60 |
|
60 | |||
61 | #----------------------------------------------------------------------------- |
|
61 | #----------------------------------------------------------------------------- | |
62 | # Globals |
|
62 | # Globals | |
63 | #----------------------------------------------------------------------------- |
|
63 | #----------------------------------------------------------------------------- | |
64 | env = os.environ |
|
64 | env = os.environ | |
65 | TEST_FILE_PATH = split(abspath(__file__))[0] |
|
65 | TEST_FILE_PATH = split(abspath(__file__))[0] | |
66 | TMP_TEST_DIR = tempfile.mkdtemp() |
|
66 | TMP_TEST_DIR = tempfile.mkdtemp() | |
67 | HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir") |
|
67 | HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir") | |
68 | XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir") |
|
68 | XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir") | |
69 | XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir") |
|
69 | XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir") | |
70 | IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython') |
|
70 | IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython') | |
71 | # |
|
71 | # | |
72 | # Setup/teardown functions/decorators |
|
72 | # Setup/teardown functions/decorators | |
73 | # |
|
73 | # | |
74 |
|
74 | |||
75 | def setup(): |
|
75 | def setup(): | |
76 | """Setup testenvironment for the module: |
|
76 | """Setup testenvironment for the module: | |
77 |
|
77 | |||
78 | - Adds dummy home dir tree |
|
78 | - Adds dummy home dir tree | |
79 | """ |
|
79 | """ | |
80 | # Do not mask exceptions here. In particular, catching WindowsError is a |
|
80 | # Do not mask exceptions here. In particular, catching WindowsError is a | |
81 | # problem because that exception is only defined on Windows... |
|
81 | # problem because that exception is only defined on Windows... | |
82 | os.makedirs(IP_TEST_DIR) |
|
82 | os.makedirs(IP_TEST_DIR) | |
83 | os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython')) |
|
83 | os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython')) | |
84 | os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython')) |
|
84 | os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython')) | |
85 |
|
85 | |||
86 |
|
86 | |||
87 | def teardown(): |
|
87 | def teardown(): | |
88 | """Teardown testenvironment for the module: |
|
88 | """Teardown testenvironment for the module: | |
89 |
|
89 | |||
90 | - Remove dummy home dir tree |
|
90 | - Remove dummy home dir tree | |
91 | """ |
|
91 | """ | |
92 | # Note: we remove the parent test dir, which is the root of all test |
|
92 | # Note: we remove the parent test dir, which is the root of all test | |
93 | # subdirs we may have created. Use shutil instead of os.removedirs, so |
|
93 | # subdirs we may have created. Use shutil instead of os.removedirs, so | |
94 | # that non-empty directories are all recursively removed. |
|
94 | # that non-empty directories are all recursively removed. | |
95 | shutil.rmtree(TMP_TEST_DIR) |
|
95 | shutil.rmtree(TMP_TEST_DIR) | |
96 |
|
96 | |||
97 |
|
97 | |||
98 | def setup_environment(): |
|
98 | def setup_environment(): | |
99 | """Setup testenvironment for some functions that are tested |
|
99 | """Setup testenvironment for some functions that are tested | |
100 | in this module. In particular this functions stores attributes |
|
100 | in this module. In particular this functions stores attributes | |
101 | and other things that we need to stub in some test functions. |
|
101 | and other things that we need to stub in some test functions. | |
102 | This needs to be done on a function level and not module level because |
|
102 | This needs to be done on a function level and not module level because | |
103 | each testfunction needs a pristine environment. |
|
103 | each testfunction needs a pristine environment. | |
104 | """ |
|
104 | """ | |
105 | global oldstuff, platformstuff |
|
105 | global oldstuff, platformstuff | |
106 | oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd()) |
|
106 | oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd()) | |
107 |
|
107 | |||
108 | if os.name == 'nt': |
|
108 | if os.name == 'nt': | |
109 | platformstuff = (wreg.OpenKey, wreg.QueryValueEx,) |
|
109 | platformstuff = (wreg.OpenKey, wreg.QueryValueEx,) | |
110 |
|
110 | |||
111 |
|
111 | |||
112 | def teardown_environment(): |
|
112 | def teardown_environment(): | |
113 | """Restore things that were remembered by the setup_environment function |
|
113 | """Restore things that were remembered by the setup_environment function | |
114 | """ |
|
114 | """ | |
115 | (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff |
|
115 | (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff | |
116 | os.chdir(old_wd) |
|
116 | os.chdir(old_wd) | |
117 | reload(path) |
|
117 | reload(path) | |
118 |
|
118 | |||
119 | for key in list(env): |
|
119 | for key in list(env): | |
120 | if key not in oldenv: |
|
120 | if key not in oldenv: | |
121 | del env[key] |
|
121 | del env[key] | |
122 | env.update(oldenv) |
|
122 | env.update(oldenv) | |
123 | if hasattr(sys, 'frozen'): |
|
123 | if hasattr(sys, 'frozen'): | |
124 | del sys.frozen |
|
124 | del sys.frozen | |
125 | if os.name == 'nt': |
|
125 | if os.name == 'nt': | |
126 | (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff |
|
126 | (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff | |
127 |
|
127 | |||
128 | # Build decorator that uses the setup_environment/setup_environment |
|
128 | # Build decorator that uses the setup_environment/setup_environment | |
129 | with_environment = with_setup(setup_environment, teardown_environment) |
|
129 | with_environment = with_setup(setup_environment, teardown_environment) | |
130 |
|
130 | |||
131 | @skip_if_not_win32 |
|
131 | @skip_if_not_win32 | |
132 | @with_environment |
|
132 | @with_environment | |
133 | def test_get_home_dir_1(): |
|
133 | def test_get_home_dir_1(): | |
134 | """Testcase for py2exe logic, un-compressed lib |
|
134 | """Testcase for py2exe logic, un-compressed lib | |
135 | """ |
|
135 | """ | |
136 | unfrozen = path.get_home_dir() |
|
136 | unfrozen = path.get_home_dir() | |
137 | sys.frozen = True |
|
137 | sys.frozen = True | |
138 |
|
138 | |||
139 | #fake filename for IPython.__init__ |
|
139 | #fake filename for IPython.__init__ | |
140 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) |
|
140 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) | |
141 |
|
141 | |||
142 | home_dir = path.get_home_dir() |
|
142 | home_dir = path.get_home_dir() | |
143 | nt.assert_equal(home_dir, unfrozen) |
|
143 | nt.assert_equal(home_dir, unfrozen) | |
144 |
|
144 | |||
145 |
|
145 | |||
146 | @skip_if_not_win32 |
|
146 | @skip_if_not_win32 | |
147 | @with_environment |
|
147 | @with_environment | |
148 | def test_get_home_dir_2(): |
|
148 | def test_get_home_dir_2(): | |
149 | """Testcase for py2exe logic, compressed lib |
|
149 | """Testcase for py2exe logic, compressed lib | |
150 | """ |
|
150 | """ | |
151 | unfrozen = path.get_home_dir() |
|
151 | unfrozen = path.get_home_dir() | |
152 | sys.frozen = True |
|
152 | sys.frozen = True | |
153 | #fake filename for IPython.__init__ |
|
153 | #fake filename for IPython.__init__ | |
154 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower() |
|
154 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower() | |
155 |
|
155 | |||
156 | home_dir = path.get_home_dir(True) |
|
156 | home_dir = path.get_home_dir(True) | |
157 | nt.assert_equal(home_dir, unfrozen) |
|
157 | nt.assert_equal(home_dir, unfrozen) | |
158 |
|
158 | |||
159 |
|
159 | |||
160 | @with_environment |
|
160 | @with_environment | |
161 | def test_get_home_dir_3(): |
|
161 | def test_get_home_dir_3(): | |
162 | """get_home_dir() uses $HOME if set""" |
|
162 | """get_home_dir() uses $HOME if set""" | |
163 | env["HOME"] = HOME_TEST_DIR |
|
163 | env["HOME"] = HOME_TEST_DIR | |
164 | home_dir = path.get_home_dir(True) |
|
164 | home_dir = path.get_home_dir(True) | |
165 | # get_home_dir expands symlinks |
|
165 | # get_home_dir expands symlinks | |
166 | nt.assert_equal(home_dir, os.path.realpath(env["HOME"])) |
|
166 | nt.assert_equal(home_dir, os.path.realpath(env["HOME"])) | |
167 |
|
167 | |||
168 |
|
168 | |||
169 | @with_environment |
|
169 | @with_environment | |
170 | def test_get_home_dir_4(): |
|
170 | def test_get_home_dir_4(): | |
171 | """get_home_dir() still works if $HOME is not set""" |
|
171 | """get_home_dir() still works if $HOME is not set""" | |
172 |
|
172 | |||
173 | if 'HOME' in env: del env['HOME'] |
|
173 | if 'HOME' in env: del env['HOME'] | |
174 | # this should still succeed, but we don't care what the answer is |
|
174 | # this should still succeed, but we don't care what the answer is | |
175 | home = path.get_home_dir(False) |
|
175 | home = path.get_home_dir(False) | |
176 |
|
176 | |||
177 | @with_environment |
|
177 | @with_environment | |
178 | def test_get_home_dir_5(): |
|
178 | def test_get_home_dir_5(): | |
179 | """raise HomeDirError if $HOME is specified, but not a writable dir""" |
|
179 | """raise HomeDirError if $HOME is specified, but not a writable dir""" | |
180 | env['HOME'] = abspath(HOME_TEST_DIR+'garbage') |
|
180 | env['HOME'] = abspath(HOME_TEST_DIR+'garbage') | |
181 | # set os.name = posix, to prevent My Documents fallback on Windows |
|
181 | # set os.name = posix, to prevent My Documents fallback on Windows | |
182 | os.name = 'posix' |
|
182 | os.name = 'posix' | |
183 | nt.assert_raises(path.HomeDirError, path.get_home_dir, True) |
|
183 | nt.assert_raises(path.HomeDirError, path.get_home_dir, True) | |
184 |
|
184 | |||
185 |
|
185 | |||
186 | # Should we stub wreg fully so we can run the test on all platforms? |
|
186 | # Should we stub wreg fully so we can run the test on all platforms? | |
187 | @skip_if_not_win32 |
|
187 | @skip_if_not_win32 | |
188 | @with_environment |
|
188 | @with_environment | |
189 | def test_get_home_dir_8(): |
|
189 | def test_get_home_dir_8(): | |
190 | """Using registry hack for 'My Documents', os=='nt' |
|
190 | """Using registry hack for 'My Documents', os=='nt' | |
191 |
|
191 | |||
192 | HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing. |
|
192 | HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing. | |
193 | """ |
|
193 | """ | |
194 | os.name = 'nt' |
|
194 | os.name = 'nt' | |
195 | # Remove from stub environment all keys that may be set |
|
195 | # Remove from stub environment all keys that may be set | |
196 | for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']: |
|
196 | for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']: | |
197 | env.pop(key, None) |
|
197 | env.pop(key, None) | |
198 |
|
198 | |||
199 | #Stub windows registry functions |
|
199 | #Stub windows registry functions | |
200 | def OpenKey(x, y): |
|
200 | def OpenKey(x, y): | |
201 | class key: |
|
201 | class key: | |
202 | def Close(self): |
|
202 | def Close(self): | |
203 | pass |
|
203 | pass | |
204 | return key() |
|
204 | return key() | |
205 | def QueryValueEx(x, y): |
|
205 | def QueryValueEx(x, y): | |
206 | return [abspath(HOME_TEST_DIR)] |
|
206 | return [abspath(HOME_TEST_DIR)] | |
207 |
|
207 | |||
208 | wreg.OpenKey = OpenKey |
|
208 | wreg.OpenKey = OpenKey | |
209 | wreg.QueryValueEx = QueryValueEx |
|
209 | wreg.QueryValueEx = QueryValueEx | |
210 |
|
210 | |||
211 | home_dir = path.get_home_dir() |
|
211 | home_dir = path.get_home_dir() | |
212 | nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) |
|
212 | nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) | |
213 |
|
213 | |||
214 |
|
214 | |||
215 | @with_environment |
|
215 | @with_environment | |
216 | def test_get_ipython_dir_1(): |
|
216 | def test_get_ipython_dir_1(): | |
217 | """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions.""" |
|
217 | """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions.""" | |
218 | env_ipdir = os.path.join("someplace", ".ipython") |
|
218 | env_ipdir = os.path.join("someplace", ".ipython") | |
219 | path._writable_dir = lambda path: True |
|
219 | path._writable_dir = lambda path: True | |
220 | env['IPYTHONDIR'] = env_ipdir |
|
220 | env['IPYTHONDIR'] = env_ipdir | |
221 | ipdir = path.get_ipython_dir() |
|
221 | ipdir = path.get_ipython_dir() | |
222 | nt.assert_equal(ipdir, env_ipdir) |
|
222 | nt.assert_equal(ipdir, env_ipdir) | |
223 |
|
223 | |||
224 |
|
224 | |||
225 | @with_environment |
|
225 | @with_environment | |
226 | def test_get_ipython_dir_2(): |
|
226 | def test_get_ipython_dir_2(): | |
227 | """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions.""" |
|
227 | """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions.""" | |
228 | path.get_home_dir = lambda : "someplace" |
|
228 | path.get_home_dir = lambda : "someplace" | |
229 | path.get_xdg_dir = lambda : None |
|
229 | path.get_xdg_dir = lambda : None | |
230 | path._writable_dir = lambda path: True |
|
230 | path._writable_dir = lambda path: True | |
231 | os.name = "posix" |
|
231 | os.name = "posix" | |
232 | env.pop('IPYTHON_DIR', None) |
|
232 | env.pop('IPYTHON_DIR', None) | |
233 | env.pop('IPYTHONDIR', None) |
|
233 | env.pop('IPYTHONDIR', None) | |
234 | env.pop('XDG_CONFIG_HOME', None) |
|
234 | env.pop('XDG_CONFIG_HOME', None) | |
235 | ipdir = path.get_ipython_dir() |
|
235 | ipdir = path.get_ipython_dir() | |
236 | nt.assert_equal(ipdir, os.path.join("someplace", ".ipython")) |
|
236 | nt.assert_equal(ipdir, os.path.join("someplace", ".ipython")) | |
237 |
|
237 | |||
238 | @with_environment |
|
238 | @with_environment | |
239 | def test_get_ipython_dir_3(): |
|
239 | def test_get_ipython_dir_3(): | |
240 | """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist.""" |
|
240 | """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist.""" | |
241 | path.get_home_dir = lambda : "someplace" |
|
241 | path.get_home_dir = lambda : "someplace" | |
242 | path._writable_dir = lambda path: True |
|
242 | path._writable_dir = lambda path: True | |
243 | os.name = "posix" |
|
243 | os.name = "posix" | |
244 | env.pop('IPYTHON_DIR', None) |
|
244 | env.pop('IPYTHON_DIR', None) | |
245 | env.pop('IPYTHONDIR', None) |
|
245 | env.pop('IPYTHONDIR', None) | |
246 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR |
|
246 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR | |
247 | ipdir = path.get_ipython_dir() |
|
247 | ipdir = path.get_ipython_dir() | |
248 | if sys.platform == "darwin": |
|
248 | if sys.platform == "darwin": | |
249 | expected = os.path.join("someplace", ".ipython") |
|
249 | expected = os.path.join("someplace", ".ipython") | |
250 | else: |
|
250 | else: | |
251 | expected = os.path.join(XDG_TEST_DIR, "ipython") |
|
251 | expected = os.path.join(XDG_TEST_DIR, "ipython") | |
252 | nt.assert_equal(ipdir, expected) |
|
252 | nt.assert_equal(ipdir, expected) | |
253 |
|
253 | |||
254 | @with_environment |
|
254 | @with_environment | |
255 | def test_get_ipython_dir_4(): |
|
255 | def test_get_ipython_dir_4(): | |
256 | """test_get_ipython_dir_4, use XDG if both exist.""" |
|
256 | """test_get_ipython_dir_4, use XDG if both exist.""" | |
257 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
257 | path.get_home_dir = lambda : HOME_TEST_DIR | |
258 | os.name = "posix" |
|
258 | os.name = "posix" | |
259 | env.pop('IPYTHON_DIR', None) |
|
259 | env.pop('IPYTHON_DIR', None) | |
260 | env.pop('IPYTHONDIR', None) |
|
260 | env.pop('IPYTHONDIR', None) | |
261 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR |
|
261 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR | |
262 | ipdir = path.get_ipython_dir() |
|
262 | ipdir = path.get_ipython_dir() | |
263 | if sys.platform == "darwin": |
|
263 | if sys.platform == "darwin": | |
264 | expected = os.path.join(HOME_TEST_DIR, ".ipython") |
|
264 | expected = os.path.join(HOME_TEST_DIR, ".ipython") | |
265 | else: |
|
265 | else: | |
266 | expected = os.path.join(XDG_TEST_DIR, "ipython") |
|
266 | expected = os.path.join(XDG_TEST_DIR, "ipython") | |
267 | nt.assert_equal(ipdir, expected) |
|
267 | nt.assert_equal(ipdir, expected) | |
268 |
|
268 | |||
269 | @with_environment |
|
269 | @with_environment | |
270 | def test_get_ipython_dir_5(): |
|
270 | def test_get_ipython_dir_5(): | |
271 | """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist.""" |
|
271 | """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist.""" | |
272 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
272 | path.get_home_dir = lambda : HOME_TEST_DIR | |
273 | os.name = "posix" |
|
273 | os.name = "posix" | |
274 | env.pop('IPYTHON_DIR', None) |
|
274 | env.pop('IPYTHON_DIR', None) | |
275 | env.pop('IPYTHONDIR', None) |
|
275 | env.pop('IPYTHONDIR', None) | |
276 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR |
|
276 | env['XDG_CONFIG_HOME'] = XDG_TEST_DIR | |
277 | os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython')) |
|
277 | os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython')) | |
278 | ipdir = path.get_ipython_dir() |
|
278 | ipdir = path.get_ipython_dir() | |
279 | nt.assert_equal(ipdir, IP_TEST_DIR) |
|
279 | nt.assert_equal(ipdir, IP_TEST_DIR) | |
280 |
|
280 | |||
281 | @with_environment |
|
281 | @with_environment | |
282 | def test_get_ipython_dir_6(): |
|
282 | def test_get_ipython_dir_6(): | |
283 | """test_get_ipython_dir_6, use XDG if defined and neither exist.""" |
|
283 | """test_get_ipython_dir_6, use XDG if defined and neither exist.""" | |
284 | xdg = os.path.join(HOME_TEST_DIR, 'somexdg') |
|
284 | xdg = os.path.join(HOME_TEST_DIR, 'somexdg') | |
285 | os.mkdir(xdg) |
|
285 | os.mkdir(xdg) | |
286 | shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython')) |
|
286 | shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython')) | |
287 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
287 | path.get_home_dir = lambda : HOME_TEST_DIR | |
288 | path.get_xdg_dir = lambda : xdg |
|
288 | path.get_xdg_dir = lambda : xdg | |
289 | os.name = "posix" |
|
289 | os.name = "posix" | |
290 | env.pop('IPYTHON_DIR', None) |
|
290 | env.pop('IPYTHON_DIR', None) | |
291 | env.pop('IPYTHONDIR', None) |
|
291 | env.pop('IPYTHONDIR', None) | |
292 | env.pop('XDG_CONFIG_HOME', None) |
|
292 | env.pop('XDG_CONFIG_HOME', None) | |
293 | xdg_ipdir = os.path.join(xdg, "ipython") |
|
293 | xdg_ipdir = os.path.join(xdg, "ipython") | |
294 | ipdir = path.get_ipython_dir() |
|
294 | ipdir = path.get_ipython_dir() | |
295 | nt.assert_equal(ipdir, xdg_ipdir) |
|
295 | nt.assert_equal(ipdir, xdg_ipdir) | |
296 |
|
296 | |||
297 | @with_environment |
|
297 | @with_environment | |
298 | def test_get_ipython_dir_7(): |
|
298 | def test_get_ipython_dir_7(): | |
299 | """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR""" |
|
299 | """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR""" | |
300 | path._writable_dir = lambda path: True |
|
300 | path._writable_dir = lambda path: True | |
301 | home_dir = os.path.normpath(os.path.expanduser('~')) |
|
301 | home_dir = os.path.normpath(os.path.expanduser('~')) | |
302 | env['IPYTHONDIR'] = os.path.join('~', 'somewhere') |
|
302 | env['IPYTHONDIR'] = os.path.join('~', 'somewhere') | |
303 | ipdir = path.get_ipython_dir() |
|
303 | ipdir = path.get_ipython_dir() | |
304 | nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere')) |
|
304 | nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere')) | |
305 |
|
305 | |||
306 | @skip_win32 |
|
306 | @skip_win32 | |
307 | @with_environment |
|
307 | @with_environment | |
308 | def test_get_ipython_dir_8(): |
|
308 | def test_get_ipython_dir_8(): | |
309 | """test_get_ipython_dir_8, test / home directory""" |
|
309 | """test_get_ipython_dir_8, test / home directory""" | |
310 | old = path._writable_dir, path.get_xdg_dir |
|
310 | old = path._writable_dir, path.get_xdg_dir | |
311 | try: |
|
311 | try: | |
312 | path._writable_dir = lambda path: bool(path) |
|
312 | path._writable_dir = lambda path: bool(path) | |
313 | path.get_xdg_dir = lambda: None |
|
313 | path.get_xdg_dir = lambda: None | |
314 | env.pop('IPYTHON_DIR', None) |
|
314 | env.pop('IPYTHON_DIR', None) | |
315 | env.pop('IPYTHONDIR', None) |
|
315 | env.pop('IPYTHONDIR', None) | |
316 | env['HOME'] = '/' |
|
316 | env['HOME'] = '/' | |
317 | nt.assert_equal(path.get_ipython_dir(), '/.ipython') |
|
317 | nt.assert_equal(path.get_ipython_dir(), '/.ipython') | |
318 | finally: |
|
318 | finally: | |
319 | path._writable_dir, path.get_xdg_dir = old |
|
319 | path._writable_dir, path.get_xdg_dir = old | |
320 |
|
320 | |||
321 | @with_environment |
|
321 | @with_environment | |
322 | def test_get_xdg_dir_0(): |
|
322 | def test_get_xdg_dir_0(): | |
323 | """test_get_xdg_dir_0, check xdg_dir""" |
|
323 | """test_get_xdg_dir_0, check xdg_dir""" | |
324 | reload(path) |
|
324 | reload(path) | |
325 | path._writable_dir = lambda path: True |
|
325 | path._writable_dir = lambda path: True | |
326 | path.get_home_dir = lambda : 'somewhere' |
|
326 | path.get_home_dir = lambda : 'somewhere' | |
327 | os.name = "posix" |
|
327 | os.name = "posix" | |
328 | sys.platform = "linux2" |
|
328 | sys.platform = "linux2" | |
329 | env.pop('IPYTHON_DIR', None) |
|
329 | env.pop('IPYTHON_DIR', None) | |
330 | env.pop('IPYTHONDIR', None) |
|
330 | env.pop('IPYTHONDIR', None) | |
331 | env.pop('XDG_CONFIG_HOME', None) |
|
331 | env.pop('XDG_CONFIG_HOME', None) | |
332 |
|
332 | |||
333 | nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config')) |
|
333 | nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config')) | |
334 |
|
334 | |||
335 |
|
335 | |||
336 | @with_environment |
|
336 | @with_environment | |
337 | def test_get_xdg_dir_1(): |
|
337 | def test_get_xdg_dir_1(): | |
338 | """test_get_xdg_dir_1, check nonexistant xdg_dir""" |
|
338 | """test_get_xdg_dir_1, check nonexistant xdg_dir""" | |
339 | reload(path) |
|
339 | reload(path) | |
340 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
340 | path.get_home_dir = lambda : HOME_TEST_DIR | |
341 | os.name = "posix" |
|
341 | os.name = "posix" | |
342 | sys.platform = "linux2" |
|
342 | sys.platform = "linux2" | |
343 | env.pop('IPYTHON_DIR', None) |
|
343 | env.pop('IPYTHON_DIR', None) | |
344 | env.pop('IPYTHONDIR', None) |
|
344 | env.pop('IPYTHONDIR', None) | |
345 | env.pop('XDG_CONFIG_HOME', None) |
|
345 | env.pop('XDG_CONFIG_HOME', None) | |
346 | nt.assert_equal(path.get_xdg_dir(), None) |
|
346 | nt.assert_equal(path.get_xdg_dir(), None) | |
347 |
|
347 | |||
348 | @with_environment |
|
348 | @with_environment | |
349 | def test_get_xdg_dir_2(): |
|
349 | def test_get_xdg_dir_2(): | |
350 | """test_get_xdg_dir_2, check xdg_dir default to ~/.config""" |
|
350 | """test_get_xdg_dir_2, check xdg_dir default to ~/.config""" | |
351 | reload(path) |
|
351 | reload(path) | |
352 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
352 | path.get_home_dir = lambda : HOME_TEST_DIR | |
353 | os.name = "posix" |
|
353 | os.name = "posix" | |
354 | sys.platform = "linux2" |
|
354 | sys.platform = "linux2" | |
355 | env.pop('IPYTHON_DIR', None) |
|
355 | env.pop('IPYTHON_DIR', None) | |
356 | env.pop('IPYTHONDIR', None) |
|
356 | env.pop('IPYTHONDIR', None) | |
357 | env.pop('XDG_CONFIG_HOME', None) |
|
357 | env.pop('XDG_CONFIG_HOME', None) | |
358 | cfgdir=os.path.join(path.get_home_dir(), '.config') |
|
358 | cfgdir=os.path.join(path.get_home_dir(), '.config') | |
359 | if not os.path.exists(cfgdir): |
|
359 | if not os.path.exists(cfgdir): | |
360 | os.makedirs(cfgdir) |
|
360 | os.makedirs(cfgdir) | |
361 |
|
361 | |||
362 | nt.assert_equal(path.get_xdg_dir(), cfgdir) |
|
362 | nt.assert_equal(path.get_xdg_dir(), cfgdir) | |
363 |
|
363 | |||
364 | @with_environment |
|
364 | @with_environment | |
365 | def test_get_xdg_dir_3(): |
|
365 | def test_get_xdg_dir_3(): | |
366 | """test_get_xdg_dir_3, check xdg_dir not used on OS X""" |
|
366 | """test_get_xdg_dir_3, check xdg_dir not used on OS X""" | |
367 | reload(path) |
|
367 | reload(path) | |
368 | path.get_home_dir = lambda : HOME_TEST_DIR |
|
368 | path.get_home_dir = lambda : HOME_TEST_DIR | |
369 | os.name = "posix" |
|
369 | os.name = "posix" | |
370 | sys.platform = "darwin" |
|
370 | sys.platform = "darwin" | |
371 | env.pop('IPYTHON_DIR', None) |
|
371 | env.pop('IPYTHON_DIR', None) | |
372 | env.pop('IPYTHONDIR', None) |
|
372 | env.pop('IPYTHONDIR', None) | |
373 | env.pop('XDG_CONFIG_HOME', None) |
|
373 | env.pop('XDG_CONFIG_HOME', None) | |
374 | cfgdir=os.path.join(path.get_home_dir(), '.config') |
|
374 | cfgdir=os.path.join(path.get_home_dir(), '.config') | |
375 | if not os.path.exists(cfgdir): |
|
375 | if not os.path.exists(cfgdir): | |
376 | os.makedirs(cfgdir) |
|
376 | os.makedirs(cfgdir) | |
377 |
|
377 | |||
378 | nt.assert_equal(path.get_xdg_dir(), None) |
|
378 | nt.assert_equal(path.get_xdg_dir(), None) | |
379 |
|
379 | |||
380 | def test_filefind(): |
|
380 | def test_filefind(): | |
381 | """Various tests for filefind""" |
|
381 | """Various tests for filefind""" | |
382 | f = tempfile.NamedTemporaryFile() |
|
382 | f = tempfile.NamedTemporaryFile() | |
383 | # print 'fname:',f.name |
|
383 | # print 'fname:',f.name | |
384 | alt_dirs = path.get_ipython_dir() |
|
384 | alt_dirs = path.get_ipython_dir() | |
385 | t = path.filefind(f.name, alt_dirs) |
|
385 | t = path.filefind(f.name, alt_dirs) | |
386 | # print 'found:',t |
|
386 | # print 'found:',t | |
387 |
|
387 | |||
388 | @with_environment |
|
388 | @with_environment | |
389 | def test_get_ipython_cache_dir(): |
|
389 | def test_get_ipython_cache_dir(): | |
390 | os.environ["HOME"] = HOME_TEST_DIR |
|
390 | os.environ["HOME"] = HOME_TEST_DIR | |
391 | if os.name == 'posix' and sys.platform != 'darwin': |
|
391 | if os.name == 'posix' and sys.platform != 'darwin': | |
392 | # test default |
|
392 | # test default | |
393 | os.makedirs(os.path.join(HOME_TEST_DIR, ".cache")) |
|
393 | os.makedirs(os.path.join(HOME_TEST_DIR, ".cache")) | |
394 | os.environ.pop("XDG_CACHE_HOME", None) |
|
394 | os.environ.pop("XDG_CACHE_HOME", None) | |
395 | ipdir = path.get_ipython_cache_dir() |
|
395 | ipdir = path.get_ipython_cache_dir() | |
396 | nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"), |
|
396 | nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"), | |
397 | ipdir) |
|
397 | ipdir) | |
398 | nt.assert_true(os.path.isdir(ipdir)) |
|
398 | nt.assert_true(os.path.isdir(ipdir)) | |
399 |
|
399 | |||
400 | # test env override |
|
400 | # test env override | |
401 | os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR |
|
401 | os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR | |
402 | ipdir = path.get_ipython_cache_dir() |
|
402 | ipdir = path.get_ipython_cache_dir() | |
403 | nt.assert_true(os.path.isdir(ipdir)) |
|
403 | nt.assert_true(os.path.isdir(ipdir)) | |
404 | nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython")) |
|
404 | nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython")) | |
405 | else: |
|
405 | else: | |
406 | nt.assert_equal(path.get_ipython_cache_dir(), |
|
406 | nt.assert_equal(path.get_ipython_cache_dir(), | |
407 | path.get_ipython_dir()) |
|
407 | path.get_ipython_dir()) | |
408 |
|
408 | |||
409 | def test_get_ipython_package_dir(): |
|
409 | def test_get_ipython_package_dir(): | |
410 | ipdir = path.get_ipython_package_dir() |
|
410 | ipdir = path.get_ipython_package_dir() | |
411 | nt.assert_true(os.path.isdir(ipdir)) |
|
411 | nt.assert_true(os.path.isdir(ipdir)) | |
412 |
|
412 | |||
413 |
|
413 | |||
414 | def test_get_ipython_module_path(): |
|
414 | def test_get_ipython_module_path(): | |
415 | ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp') |
|
415 | ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp') | |
416 | nt.assert_true(os.path.isfile(ipapp_path)) |
|
416 | nt.assert_true(os.path.isfile(ipapp_path)) | |
417 |
|
417 | |||
418 |
|
418 | |||
419 | @dec.skip_if_not_win32 |
|
419 | @dec.skip_if_not_win32 | |
420 | def test_get_long_path_name_win32(): |
|
420 | def test_get_long_path_name_win32(): | |
421 | with TemporaryDirectory() as tmpdir: |
|
421 | with TemporaryDirectory() as tmpdir: | |
422 |
|
422 | |||
423 | # Make a long path. |
|
423 | # Make a long path. | |
424 | long_path = os.path.join(tmpdir, u'this is my long path name') |
|
424 | long_path = os.path.join(tmpdir, u'this is my long path name') | |
425 | os.makedirs(long_path) |
|
425 | os.makedirs(long_path) | |
426 |
|
426 | |||
427 | # Test to see if the short path evaluates correctly. |
|
427 | # Test to see if the short path evaluates correctly. | |
428 | short_path = os.path.join(tmpdir, u'THISIS~1') |
|
428 | short_path = os.path.join(tmpdir, u'THISIS~1') | |
429 | evaluated_path = path.get_long_path_name(short_path) |
|
429 | evaluated_path = path.get_long_path_name(short_path) | |
430 | nt.assert_equal(evaluated_path.lower(), long_path.lower()) |
|
430 | nt.assert_equal(evaluated_path.lower(), long_path.lower()) | |
431 |
|
431 | |||
432 |
|
432 | |||
433 | @dec.skip_win32 |
|
433 | @dec.skip_win32 | |
434 | def test_get_long_path_name(): |
|
434 | def test_get_long_path_name(): | |
435 | p = path.get_long_path_name('/usr/local') |
|
435 | p = path.get_long_path_name('/usr/local') | |
436 | nt.assert_equal(p,'/usr/local') |
|
436 | nt.assert_equal(p,'/usr/local') | |
437 |
|
437 | |||
438 | @dec.skip_win32 # can't create not-user-writable dir on win |
|
438 | @dec.skip_win32 # can't create not-user-writable dir on win | |
439 | @with_environment |
|
439 | @with_environment | |
440 | def test_not_writable_ipdir(): |
|
440 | def test_not_writable_ipdir(): | |
441 | tmpdir = tempfile.mkdtemp() |
|
441 | tmpdir = tempfile.mkdtemp() | |
442 | os.name = "posix" |
|
442 | os.name = "posix" | |
443 | env.pop('IPYTHON_DIR', None) |
|
443 | env.pop('IPYTHON_DIR', None) | |
444 | env.pop('IPYTHONDIR', None) |
|
444 | env.pop('IPYTHONDIR', None) | |
445 | env.pop('XDG_CONFIG_HOME', None) |
|
445 | env.pop('XDG_CONFIG_HOME', None) | |
446 | env['HOME'] = tmpdir |
|
446 | env['HOME'] = tmpdir | |
447 | ipdir = os.path.join(tmpdir, '.ipython') |
|
447 | ipdir = os.path.join(tmpdir, '.ipython') | |
448 | os.mkdir(ipdir) |
|
448 | os.mkdir(ipdir) | |
449 | os.chmod(ipdir, 600) |
|
449 | os.chmod(ipdir, 600) | |
450 | with AssertPrints('is not a writable location', channel='stderr'): |
|
450 | with AssertPrints('is not a writable location', channel='stderr'): | |
451 | ipdir = path.get_ipython_dir() |
|
451 | ipdir = path.get_ipython_dir() | |
452 | env.pop('IPYTHON_DIR', None) |
|
452 | env.pop('IPYTHON_DIR', None) | |
453 |
|
453 | |||
454 | def test_unquote_filename(): |
|
454 | def test_unquote_filename(): | |
455 | for win32 in (True, False): |
|
455 | for win32 in (True, False): | |
456 | nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py') |
|
456 | nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py') | |
457 | nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py') |
|
457 | nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py') | |
458 | nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py') |
|
458 | nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py') | |
459 | nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py') |
|
459 | nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py') | |
460 | nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py') |
|
460 | nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py') | |
461 | nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py') |
|
461 | nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py') | |
462 | nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"') |
|
462 | nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"') | |
463 | nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"') |
|
463 | nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"') | |
464 | nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'") |
|
464 | nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'") | |
465 | nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'") |
|
465 | nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'") | |
466 |
|
466 | |||
467 | @with_environment |
|
467 | @with_environment | |
468 | def test_get_py_filename(): |
|
468 | def test_get_py_filename(): | |
469 | os.chdir(TMP_TEST_DIR) |
|
469 | os.chdir(TMP_TEST_DIR) | |
470 | for win32 in (True, False): |
|
470 | for win32 in (True, False): | |
471 | with make_tempfile('foo.py'): |
|
471 | with make_tempfile('foo.py'): | |
472 | nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py') |
|
472 | nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py') | |
473 | nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py') |
|
473 | nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py') | |
474 | with make_tempfile('foo'): |
|
474 | with make_tempfile('foo'): | |
475 | nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo') |
|
475 | nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo') | |
476 | nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) |
|
476 | nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) | |
477 | nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32) |
|
477 | nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32) | |
478 | nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) |
|
478 | nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) | |
479 | true_fn = 'foo with spaces.py' |
|
479 | true_fn = 'foo with spaces.py' | |
480 | with make_tempfile(true_fn): |
|
480 | with make_tempfile(true_fn): | |
481 | nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn) |
|
481 | nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn) | |
482 | nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn) |
|
482 | nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn) | |
483 | if win32: |
|
483 | if win32: | |
484 | nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn) |
|
484 | nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn) | |
485 | nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn) |
|
485 | nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn) | |
486 | else: |
|
486 | else: | |
487 | nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False) |
|
487 | nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False) | |
488 | nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False) |
|
488 | nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False) | |
489 |
|
489 | |||
490 | @onlyif_unicode_paths |
|
490 | @onlyif_unicode_paths | |
491 | def test_unicode_in_filename(): |
|
491 | def test_unicode_in_filename(): | |
492 | """When a file doesn't exist, the exception raised should be safe to call |
|
492 | """When a file doesn't exist, the exception raised should be safe to call | |
493 | str() on - i.e. in Python 2 it must only have ASCII characters. |
|
493 | str() on - i.e. in Python 2 it must only have ASCII characters. | |
494 |
|
494 | |||
495 | https://github.com/ipython/ipython/issues/875 |
|
495 | https://github.com/ipython/ipython/issues/875 | |
496 | """ |
|
496 | """ | |
497 | try: |
|
497 | try: | |
498 | # these calls should not throw unicode encode exceptions |
|
498 | # these calls should not throw unicode encode exceptions | |
499 | path.get_py_filename(u'fooéè.py', force_win32=False) |
|
499 | path.get_py_filename(u'fooéè.py', force_win32=False) | |
500 | except IOError as ex: |
|
500 | except IOError as ex: | |
501 | str(ex) |
|
501 | str(ex) | |
502 |
|
502 | |||
503 |
|
503 | |||
504 | class TestShellGlob(object): |
|
504 | class TestShellGlob(object): | |
505 |
|
505 | |||
506 | @classmethod |
|
506 | @classmethod | |
507 | def setUpClass(cls): |
|
507 | def setUpClass(cls): | |
508 | cls.filenames_start_with_a = ['a0', 'a1', 'a2'] |
|
508 | cls.filenames_start_with_a = ['a0', 'a1', 'a2'] | |
509 | cls.filenames_end_with_b = ['0b', '1b', '2b'] |
|
509 | cls.filenames_end_with_b = ['0b', '1b', '2b'] | |
510 | cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b |
|
510 | cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b | |
511 | cls.tempdir = TemporaryDirectory() |
|
511 | cls.tempdir = TemporaryDirectory() | |
512 | td = cls.tempdir.name |
|
512 | td = cls.tempdir.name | |
513 |
|
513 | |||
514 | with cls.in_tempdir(): |
|
514 | with cls.in_tempdir(): | |
515 | # Create empty files |
|
515 | # Create empty files | |
516 | for fname in cls.filenames: |
|
516 | for fname in cls.filenames: | |
517 | open(os.path.join(td, fname), 'w').close() |
|
517 | open(os.path.join(td, fname), 'w').close() | |
518 |
|
518 | |||
519 | @classmethod |
|
519 | @classmethod | |
520 | def tearDownClass(cls): |
|
520 | def tearDownClass(cls): | |
521 | cls.tempdir.cleanup() |
|
521 | cls.tempdir.cleanup() | |
522 |
|
522 | |||
523 | @classmethod |
|
523 | @classmethod | |
524 | @contextmanager |
|
524 | @contextmanager | |
525 | def in_tempdir(cls): |
|
525 | def in_tempdir(cls): | |
526 |
save = |
|
526 | save = py3compat.getcwd() | |
527 | try: |
|
527 | try: | |
528 | os.chdir(cls.tempdir.name) |
|
528 | os.chdir(cls.tempdir.name) | |
529 | yield |
|
529 | yield | |
530 | finally: |
|
530 | finally: | |
531 | os.chdir(save) |
|
531 | os.chdir(save) | |
532 |
|
532 | |||
533 | def check_match(self, patterns, matches): |
|
533 | def check_match(self, patterns, matches): | |
534 | with self.in_tempdir(): |
|
534 | with self.in_tempdir(): | |
535 | # glob returns unordered list. that's why sorted is required. |
|
535 | # glob returns unordered list. that's why sorted is required. | |
536 | nt.assert_equals(sorted(path.shellglob(patterns)), |
|
536 | nt.assert_equals(sorted(path.shellglob(patterns)), | |
537 | sorted(matches)) |
|
537 | sorted(matches)) | |
538 |
|
538 | |||
539 | def common_cases(self): |
|
539 | def common_cases(self): | |
540 | return [ |
|
540 | return [ | |
541 | (['*'], self.filenames), |
|
541 | (['*'], self.filenames), | |
542 | (['a*'], self.filenames_start_with_a), |
|
542 | (['a*'], self.filenames_start_with_a), | |
543 | (['*c'], ['*c']), |
|
543 | (['*c'], ['*c']), | |
544 | (['*', 'a*', '*b', '*c'], self.filenames |
|
544 | (['*', 'a*', '*b', '*c'], self.filenames | |
545 | + self.filenames_start_with_a |
|
545 | + self.filenames_start_with_a | |
546 | + self.filenames_end_with_b |
|
546 | + self.filenames_end_with_b | |
547 | + ['*c']), |
|
547 | + ['*c']), | |
548 | (['a[012]'], self.filenames_start_with_a), |
|
548 | (['a[012]'], self.filenames_start_with_a), | |
549 | ] |
|
549 | ] | |
550 |
|
550 | |||
551 | @skip_win32 |
|
551 | @skip_win32 | |
552 | def test_match_posix(self): |
|
552 | def test_match_posix(self): | |
553 | for (patterns, matches) in self.common_cases() + [ |
|
553 | for (patterns, matches) in self.common_cases() + [ | |
554 | ([r'\*'], ['*']), |
|
554 | ([r'\*'], ['*']), | |
555 | ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a), |
|
555 | ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a), | |
556 | ([r'a\[012]'], ['a[012]']), |
|
556 | ([r'a\[012]'], ['a[012]']), | |
557 | ]: |
|
557 | ]: | |
558 | yield (self.check_match, patterns, matches) |
|
558 | yield (self.check_match, patterns, matches) | |
559 |
|
559 | |||
560 | @skip_if_not_win32 |
|
560 | @skip_if_not_win32 | |
561 | def test_match_windows(self): |
|
561 | def test_match_windows(self): | |
562 | for (patterns, matches) in self.common_cases() + [ |
|
562 | for (patterns, matches) in self.common_cases() + [ | |
563 | # In windows, backslash is interpreted as path |
|
563 | # In windows, backslash is interpreted as path | |
564 | # separator. Therefore, you can't escape glob |
|
564 | # separator. Therefore, you can't escape glob | |
565 | # using it. |
|
565 | # using it. | |
566 | ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a), |
|
566 | ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a), | |
567 | ([r'a\[012]'], [r'a\[012]']), |
|
567 | ([r'a\[012]'], [r'a\[012]']), | |
568 | ]: |
|
568 | ]: | |
569 | yield (self.check_match, patterns, matches) |
|
569 | yield (self.check_match, patterns, matches) | |
570 |
|
570 | |||
571 |
|
571 | |||
572 | def test_unescape_glob(): |
|
572 | def test_unescape_glob(): | |
573 | nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?') |
|
573 | nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?') | |
574 | nt.assert_equals(path.unescape_glob(r'\\*'), r'\*') |
|
574 | nt.assert_equals(path.unescape_glob(r'\\*'), r'\*') | |
575 | nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*') |
|
575 | nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*') | |
576 | nt.assert_equals(path.unescape_glob(r'\\a'), r'\a') |
|
576 | nt.assert_equals(path.unescape_glob(r'\\a'), r'\a') | |
577 | nt.assert_equals(path.unescape_glob(r'\a'), r'\a') |
|
577 | nt.assert_equals(path.unescape_glob(r'\a'), r'\a') | |
578 |
|
578 | |||
579 |
|
579 | |||
580 | class TestLinkOrCopy(object): |
|
580 | class TestLinkOrCopy(object): | |
581 | def setUp(self): |
|
581 | def setUp(self): | |
582 | self.tempdir = TemporaryDirectory() |
|
582 | self.tempdir = TemporaryDirectory() | |
583 | self.src = self.dst("src") |
|
583 | self.src = self.dst("src") | |
584 | with open(self.src, "w") as f: |
|
584 | with open(self.src, "w") as f: | |
585 | f.write("Hello, world!") |
|
585 | f.write("Hello, world!") | |
586 |
|
586 | |||
587 | def tearDown(self): |
|
587 | def tearDown(self): | |
588 | self.tempdir.cleanup() |
|
588 | self.tempdir.cleanup() | |
589 |
|
589 | |||
590 | def dst(self, *args): |
|
590 | def dst(self, *args): | |
591 | return os.path.join(self.tempdir.name, *args) |
|
591 | return os.path.join(self.tempdir.name, *args) | |
592 |
|
592 | |||
593 | def assert_inode_not_equal(self, a, b): |
|
593 | def assert_inode_not_equal(self, a, b): | |
594 | nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino, |
|
594 | nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino, | |
595 | "%r and %r do reference the same indoes" %(a, b)) |
|
595 | "%r and %r do reference the same indoes" %(a, b)) | |
596 |
|
596 | |||
597 | def assert_inode_equal(self, a, b): |
|
597 | def assert_inode_equal(self, a, b): | |
598 | nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino, |
|
598 | nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino, | |
599 | "%r and %r do not reference the same indoes" %(a, b)) |
|
599 | "%r and %r do not reference the same indoes" %(a, b)) | |
600 |
|
600 | |||
601 | def assert_content_equal(self, a, b): |
|
601 | def assert_content_equal(self, a, b): | |
602 | with open(a) as a_f: |
|
602 | with open(a) as a_f: | |
603 | with open(b) as b_f: |
|
603 | with open(b) as b_f: | |
604 | nt.assert_equals(a_f.read(), b_f.read()) |
|
604 | nt.assert_equals(a_f.read(), b_f.read()) | |
605 |
|
605 | |||
606 | @skip_win32 |
|
606 | @skip_win32 | |
607 | def test_link_successful(self): |
|
607 | def test_link_successful(self): | |
608 | dst = self.dst("target") |
|
608 | dst = self.dst("target") | |
609 | path.link_or_copy(self.src, dst) |
|
609 | path.link_or_copy(self.src, dst) | |
610 | self.assert_inode_equal(self.src, dst) |
|
610 | self.assert_inode_equal(self.src, dst) | |
611 |
|
611 | |||
612 | @skip_win32 |
|
612 | @skip_win32 | |
613 | def test_link_into_dir(self): |
|
613 | def test_link_into_dir(self): | |
614 | dst = self.dst("some_dir") |
|
614 | dst = self.dst("some_dir") | |
615 | os.mkdir(dst) |
|
615 | os.mkdir(dst) | |
616 | path.link_or_copy(self.src, dst) |
|
616 | path.link_or_copy(self.src, dst) | |
617 | expected_dst = self.dst("some_dir", os.path.basename(self.src)) |
|
617 | expected_dst = self.dst("some_dir", os.path.basename(self.src)) | |
618 | self.assert_inode_equal(self.src, expected_dst) |
|
618 | self.assert_inode_equal(self.src, expected_dst) | |
619 |
|
619 | |||
620 | @skip_win32 |
|
620 | @skip_win32 | |
621 | def test_target_exists(self): |
|
621 | def test_target_exists(self): | |
622 | dst = self.dst("target") |
|
622 | dst = self.dst("target") | |
623 | open(dst, "w").close() |
|
623 | open(dst, "w").close() | |
624 | path.link_or_copy(self.src, dst) |
|
624 | path.link_or_copy(self.src, dst) | |
625 | self.assert_inode_equal(self.src, dst) |
|
625 | self.assert_inode_equal(self.src, dst) | |
626 |
|
626 | |||
627 | @skip_win32 |
|
627 | @skip_win32 | |
628 | def test_no_link(self): |
|
628 | def test_no_link(self): | |
629 | real_link = os.link |
|
629 | real_link = os.link | |
630 | try: |
|
630 | try: | |
631 | del os.link |
|
631 | del os.link | |
632 | dst = self.dst("target") |
|
632 | dst = self.dst("target") | |
633 | path.link_or_copy(self.src, dst) |
|
633 | path.link_or_copy(self.src, dst) | |
634 | self.assert_content_equal(self.src, dst) |
|
634 | self.assert_content_equal(self.src, dst) | |
635 | self.assert_inode_not_equal(self.src, dst) |
|
635 | self.assert_inode_not_equal(self.src, dst) | |
636 | finally: |
|
636 | finally: | |
637 | os.link = real_link |
|
637 | os.link = real_link | |
638 |
|
638 | |||
639 | @skip_if_not_win32 |
|
639 | @skip_if_not_win32 | |
640 | def test_windows(self): |
|
640 | def test_windows(self): | |
641 | dst = self.dst("target") |
|
641 | dst = self.dst("target") | |
642 | path.link_or_copy(self.src, dst) |
|
642 | path.link_or_copy(self.src, dst) | |
643 | self.assert_content_equal(self.src, dst) |
|
643 | self.assert_content_equal(self.src, dst) |
General Comments 0
You need to be logged in to leave comments.
Login now