##// END OF EJS Templates
Check for pids when listing nbserver processes
Thomas Kluyver -
Show More
@@ -1,927 +1,938 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server."""
2 """A tornado based IPython notebook server."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import errno
10 import errno
11 import io
11 import io
12 import json
12 import json
13 import logging
13 import logging
14 import os
14 import os
15 import random
15 import random
16 import re
16 import re
17 import select
17 import select
18 import signal
18 import signal
19 import socket
19 import socket
20 import sys
20 import sys
21 import threading
21 import threading
22 import time
22 import time
23 import webbrowser
23 import webbrowser
24
24
25
25
26 # check for pyzmq 2.1.11
26 # check for pyzmq 2.1.11
27 from IPython.utils.zmqrelated import check_for_zmq
27 from IPython.utils.zmqrelated import check_for_zmq
28 check_for_zmq('2.1.11', 'IPython.html')
28 check_for_zmq('2.1.11', 'IPython.html')
29
29
30 from jinja2 import Environment, FileSystemLoader
30 from jinja2 import Environment, FileSystemLoader
31
31
32 # Install the pyzmq ioloop. This has to be done before anything else from
32 # Install the pyzmq ioloop. This has to be done before anything else from
33 # tornado is imported.
33 # tornado is imported.
34 from zmq.eventloop import ioloop
34 from zmq.eventloop import ioloop
35 ioloop.install()
35 ioloop.install()
36
36
37 # check for tornado 3.1.0
37 # check for tornado 3.1.0
38 msg = "The IPython Notebook requires tornado >= 3.1.0"
38 msg = "The IPython Notebook requires tornado >= 3.1.0"
39 try:
39 try:
40 import tornado
40 import tornado
41 except ImportError:
41 except ImportError:
42 raise ImportError(msg)
42 raise ImportError(msg)
43 try:
43 try:
44 version_info = tornado.version_info
44 version_info = tornado.version_info
45 except AttributeError:
45 except AttributeError:
46 raise ImportError(msg + ", but you have < 1.1.0")
46 raise ImportError(msg + ", but you have < 1.1.0")
47 if version_info < (3,1,0):
47 if version_info < (3,1,0):
48 raise ImportError(msg + ", but you have %s" % tornado.version)
48 raise ImportError(msg + ", but you have %s" % tornado.version)
49
49
50 from tornado import httpserver
50 from tornado import httpserver
51 from tornado import web
51 from tornado import web
52 from tornado.log import LogFormatter
52 from tornado.log import LogFormatter
53
53
54 from IPython.html import DEFAULT_STATIC_FILES_PATH
54 from IPython.html import DEFAULT_STATIC_FILES_PATH
55 from .base.handlers import Template404
55 from .base.handlers import Template404
56 from .log import log_request
56 from .log import log_request
57 from .services.kernels.kernelmanager import MappingKernelManager
57 from .services.kernels.kernelmanager import MappingKernelManager
58 from .services.notebooks.nbmanager import NotebookManager
58 from .services.notebooks.nbmanager import NotebookManager
59 from .services.notebooks.filenbmanager import FileNotebookManager
59 from .services.notebooks.filenbmanager import FileNotebookManager
60 from .services.clusters.clustermanager import ClusterManager
60 from .services.clusters.clustermanager import ClusterManager
61 from .services.sessions.sessionmanager import SessionManager
61 from .services.sessions.sessionmanager import SessionManager
62
62
63 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
63 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
64
64
65 from IPython.config import Config
65 from IPython.config import Config
66 from IPython.config.application import catch_config_error, boolean_flag
66 from IPython.config.application import catch_config_error, boolean_flag
67 from IPython.core.application import (
67 from IPython.core.application import (
68 BaseIPythonApplication, base_flags, base_aliases,
68 BaseIPythonApplication, base_flags, base_aliases,
69 )
69 )
70 from IPython.core.profiledir import ProfileDir
70 from IPython.core.profiledir import ProfileDir
71 from IPython.kernel import KernelManager
71 from IPython.kernel import KernelManager
72 from IPython.kernel.kernelspec import KernelSpecManager
72 from IPython.kernel.kernelspec import KernelSpecManager
73 from IPython.kernel.zmq.session import default_secure, Session
73 from IPython.kernel.zmq.session import default_secure, Session
74 from IPython.nbformat.sign import NotebookNotary
74 from IPython.nbformat.sign import NotebookNotary
75 from IPython.utils.importstring import import_item
75 from IPython.utils.importstring import import_item
76 from IPython.utils import submodule
76 from IPython.utils import submodule
77 from IPython.utils.process import check_pid
77 from IPython.utils.traitlets import (
78 from IPython.utils.traitlets import (
78 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
79 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
79 DottedObjectName, TraitError,
80 DottedObjectName, TraitError,
80 )
81 )
81 from IPython.utils import py3compat
82 from IPython.utils import py3compat
82 from IPython.utils.path import filefind, get_ipython_dir
83 from IPython.utils.path import filefind, get_ipython_dir
83
84
84 from .utils import url_path_join
85 from .utils import url_path_join
85
86
86 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
87 # Module globals
88 # Module globals
88 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
89
90
90 _examples = """
91 _examples = """
91 ipython notebook # start the notebook
92 ipython notebook # start the notebook
92 ipython notebook --profile=sympy # use the sympy profile
93 ipython notebook --profile=sympy # use the sympy profile
93 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
94 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
94 """
95 """
95
96
96 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
97 # Helper functions
98 # Helper functions
98 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
99
100
100 def random_ports(port, n):
101 def random_ports(port, n):
101 """Generate a list of n random ports near the given port.
102 """Generate a list of n random ports near the given port.
102
103
103 The first 5 ports will be sequential, and the remaining n-5 will be
104 The first 5 ports will be sequential, and the remaining n-5 will be
104 randomly selected in the range [port-2*n, port+2*n].
105 randomly selected in the range [port-2*n, port+2*n].
105 """
106 """
106 for i in range(min(5, n)):
107 for i in range(min(5, n)):
107 yield port + i
108 yield port + i
108 for i in range(n-5):
109 for i in range(n-5):
109 yield max(1, port + random.randint(-2*n, 2*n))
110 yield max(1, port + random.randint(-2*n, 2*n))
110
111
111 def load_handlers(name):
112 def load_handlers(name):
112 """Load the (URL pattern, handler) tuples for each component."""
113 """Load the (URL pattern, handler) tuples for each component."""
113 name = 'IPython.html.' + name
114 name = 'IPython.html.' + name
114 mod = __import__(name, fromlist=['default_handlers'])
115 mod = __import__(name, fromlist=['default_handlers'])
115 return mod.default_handlers
116 return mod.default_handlers
116
117
117 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
118 # The Tornado web application
119 # The Tornado web application
119 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
120
121
121 class NotebookWebApplication(web.Application):
122 class NotebookWebApplication(web.Application):
122
123
123 def __init__(self, ipython_app, kernel_manager, notebook_manager,
124 def __init__(self, ipython_app, kernel_manager, notebook_manager,
124 cluster_manager, session_manager, kernel_spec_manager, log,
125 cluster_manager, session_manager, kernel_spec_manager, log,
125 base_url, settings_overrides, jinja_env_options):
126 base_url, settings_overrides, jinja_env_options):
126
127
127 settings = self.init_settings(
128 settings = self.init_settings(
128 ipython_app, kernel_manager, notebook_manager, cluster_manager,
129 ipython_app, kernel_manager, notebook_manager, cluster_manager,
129 session_manager, kernel_spec_manager, log, base_url,
130 session_manager, kernel_spec_manager, log, base_url,
130 settings_overrides, jinja_env_options)
131 settings_overrides, jinja_env_options)
131 handlers = self.init_handlers(settings)
132 handlers = self.init_handlers(settings)
132
133
133 super(NotebookWebApplication, self).__init__(handlers, **settings)
134 super(NotebookWebApplication, self).__init__(handlers, **settings)
134
135
135 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
136 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
136 cluster_manager, session_manager, kernel_spec_manager,
137 cluster_manager, session_manager, kernel_spec_manager,
137 log, base_url, settings_overrides,
138 log, base_url, settings_overrides,
138 jinja_env_options=None):
139 jinja_env_options=None):
139 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
140 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
140 # base_url will always be unicode, which will in turn
141 # base_url will always be unicode, which will in turn
141 # make the patterns unicode, and ultimately result in unicode
142 # make the patterns unicode, and ultimately result in unicode
142 # keys in kwargs to handler._execute(**kwargs) in tornado.
143 # keys in kwargs to handler._execute(**kwargs) in tornado.
143 # This enforces that base_url be ascii in that situation.
144 # This enforces that base_url be ascii in that situation.
144 #
145 #
145 # Note that the URLs these patterns check against are escaped,
146 # Note that the URLs these patterns check against are escaped,
146 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
147 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
147 base_url = py3compat.unicode_to_str(base_url, 'ascii')
148 base_url = py3compat.unicode_to_str(base_url, 'ascii')
148 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
149 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
149 jenv_opt = jinja_env_options if jinja_env_options else {}
150 jenv_opt = jinja_env_options if jinja_env_options else {}
150 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
151 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
151 settings = dict(
152 settings = dict(
152 # basics
153 # basics
153 log_function=log_request,
154 log_function=log_request,
154 base_url=base_url,
155 base_url=base_url,
155 template_path=template_path,
156 template_path=template_path,
156 static_path=ipython_app.static_file_path,
157 static_path=ipython_app.static_file_path,
157 static_handler_class = FileFindHandler,
158 static_handler_class = FileFindHandler,
158 static_url_prefix = url_path_join(base_url,'/static/'),
159 static_url_prefix = url_path_join(base_url,'/static/'),
159
160
160 # authentication
161 # authentication
161 cookie_secret=ipython_app.cookie_secret,
162 cookie_secret=ipython_app.cookie_secret,
162 login_url=url_path_join(base_url,'/login'),
163 login_url=url_path_join(base_url,'/login'),
163 password=ipython_app.password,
164 password=ipython_app.password,
164
165
165 # managers
166 # managers
166 kernel_manager=kernel_manager,
167 kernel_manager=kernel_manager,
167 notebook_manager=notebook_manager,
168 notebook_manager=notebook_manager,
168 cluster_manager=cluster_manager,
169 cluster_manager=cluster_manager,
169 session_manager=session_manager,
170 session_manager=session_manager,
170 kernel_spec_manager=kernel_spec_manager,
171 kernel_spec_manager=kernel_spec_manager,
171
172
172 # IPython stuff
173 # IPython stuff
173 nbextensions_path = ipython_app.nbextensions_path,
174 nbextensions_path = ipython_app.nbextensions_path,
174 mathjax_url=ipython_app.mathjax_url,
175 mathjax_url=ipython_app.mathjax_url,
175 config=ipython_app.config,
176 config=ipython_app.config,
176 jinja2_env=env,
177 jinja2_env=env,
177 )
178 )
178
179
179 # allow custom overrides for the tornado web app.
180 # allow custom overrides for the tornado web app.
180 settings.update(settings_overrides)
181 settings.update(settings_overrides)
181 return settings
182 return settings
182
183
183 def init_handlers(self, settings):
184 def init_handlers(self, settings):
184 # Load the (URL pattern, handler) tuples for each component.
185 # Load the (URL pattern, handler) tuples for each component.
185 handlers = []
186 handlers = []
186 handlers.extend(load_handlers('base.handlers'))
187 handlers.extend(load_handlers('base.handlers'))
187 handlers.extend(load_handlers('tree.handlers'))
188 handlers.extend(load_handlers('tree.handlers'))
188 handlers.extend(load_handlers('auth.login'))
189 handlers.extend(load_handlers('auth.login'))
189 handlers.extend(load_handlers('auth.logout'))
190 handlers.extend(load_handlers('auth.logout'))
190 handlers.extend(load_handlers('notebook.handlers'))
191 handlers.extend(load_handlers('notebook.handlers'))
191 handlers.extend(load_handlers('nbconvert.handlers'))
192 handlers.extend(load_handlers('nbconvert.handlers'))
192 handlers.extend(load_handlers('kernelspecs.handlers'))
193 handlers.extend(load_handlers('kernelspecs.handlers'))
193 handlers.extend(load_handlers('services.kernels.handlers'))
194 handlers.extend(load_handlers('services.kernels.handlers'))
194 handlers.extend(load_handlers('services.notebooks.handlers'))
195 handlers.extend(load_handlers('services.notebooks.handlers'))
195 handlers.extend(load_handlers('services.clusters.handlers'))
196 handlers.extend(load_handlers('services.clusters.handlers'))
196 handlers.extend(load_handlers('services.sessions.handlers'))
197 handlers.extend(load_handlers('services.sessions.handlers'))
197 handlers.extend(load_handlers('services.nbconvert.handlers'))
198 handlers.extend(load_handlers('services.nbconvert.handlers'))
198 handlers.extend(load_handlers('services.kernelspecs.handlers'))
199 handlers.extend(load_handlers('services.kernelspecs.handlers'))
199 # FIXME: /files/ should be handled by the Contents service when it exists
200 # FIXME: /files/ should be handled by the Contents service when it exists
200 nbm = settings['notebook_manager']
201 nbm = settings['notebook_manager']
201 if hasattr(nbm, 'notebook_dir'):
202 if hasattr(nbm, 'notebook_dir'):
202 handlers.extend([
203 handlers.extend([
203 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
204 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
204 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
205 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
205 ])
206 ])
206 # prepend base_url onto the patterns that we match
207 # prepend base_url onto the patterns that we match
207 new_handlers = []
208 new_handlers = []
208 for handler in handlers:
209 for handler in handlers:
209 pattern = url_path_join(settings['base_url'], handler[0])
210 pattern = url_path_join(settings['base_url'], handler[0])
210 new_handler = tuple([pattern] + list(handler[1:]))
211 new_handler = tuple([pattern] + list(handler[1:]))
211 new_handlers.append(new_handler)
212 new_handlers.append(new_handler)
212 # add 404 on the end, which will catch everything that falls through
213 # add 404 on the end, which will catch everything that falls through
213 new_handlers.append((r'(.*)', Template404))
214 new_handlers.append((r'(.*)', Template404))
214 return new_handlers
215 return new_handlers
215
216
216
217
217 class NbserverListApp(BaseIPythonApplication):
218 class NbserverListApp(BaseIPythonApplication):
218
219
219 description="List currently running notebook servers in this profile."
220 description="List currently running notebook servers in this profile."
220
221
221 flags = dict(
222 flags = dict(
222 json=({'NbserverListApp': {'json': True}},
223 json=({'NbserverListApp': {'json': True}},
223 "Produce machine-readable JSON output."),
224 "Produce machine-readable JSON output."),
224 )
225 )
225
226
226 json = Bool(False, config=True,
227 json = Bool(False, config=True,
227 help="If True, each line of output will be a JSON object with the "
228 help="If True, each line of output will be a JSON object with the "
228 "details from the server info file.")
229 "details from the server info file.")
229
230
230 def start(self):
231 def start(self):
231 if not self.json:
232 if not self.json:
232 print("Currently running servers:")
233 print("Currently running servers:")
233 for serverinfo in list_running_servers(self.profile):
234 for serverinfo in list_running_servers(self.profile):
234 if self.json:
235 if self.json:
235 print(json.dumps(serverinfo))
236 print(json.dumps(serverinfo))
236 else:
237 else:
237 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
238 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
238
239
239 #-----------------------------------------------------------------------------
240 #-----------------------------------------------------------------------------
240 # Aliases and Flags
241 # Aliases and Flags
241 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
242
243
243 flags = dict(base_flags)
244 flags = dict(base_flags)
244 flags['no-browser']=(
245 flags['no-browser']=(
245 {'NotebookApp' : {'open_browser' : False}},
246 {'NotebookApp' : {'open_browser' : False}},
246 "Don't open the notebook in a browser after startup."
247 "Don't open the notebook in a browser after startup."
247 )
248 )
248 flags['pylab']=(
249 flags['pylab']=(
249 {'NotebookApp' : {'pylab' : 'warn'}},
250 {'NotebookApp' : {'pylab' : 'warn'}},
250 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
251 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
251 )
252 )
252 flags['no-mathjax']=(
253 flags['no-mathjax']=(
253 {'NotebookApp' : {'enable_mathjax' : False}},
254 {'NotebookApp' : {'enable_mathjax' : False}},
254 """Disable MathJax
255 """Disable MathJax
255
256
256 MathJax is the javascript library IPython uses to render math/LaTeX. It is
257 MathJax is the javascript library IPython uses to render math/LaTeX. It is
257 very large, so you may want to disable it if you have a slow internet
258 very large, so you may want to disable it if you have a slow internet
258 connection, or for offline use of the notebook.
259 connection, or for offline use of the notebook.
259
260
260 When disabled, equations etc. will appear as their untransformed TeX source.
261 When disabled, equations etc. will appear as their untransformed TeX source.
261 """
262 """
262 )
263 )
263
264
264 # Add notebook manager flags
265 # Add notebook manager flags
265 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
266 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
266 'Auto-save a .py script everytime the .ipynb notebook is saved',
267 'Auto-save a .py script everytime the .ipynb notebook is saved',
267 'Do not auto-save .py scripts for every notebook'))
268 'Do not auto-save .py scripts for every notebook'))
268
269
269 aliases = dict(base_aliases)
270 aliases = dict(base_aliases)
270
271
271 aliases.update({
272 aliases.update({
272 'ip': 'NotebookApp.ip',
273 'ip': 'NotebookApp.ip',
273 'port': 'NotebookApp.port',
274 'port': 'NotebookApp.port',
274 'port-retries': 'NotebookApp.port_retries',
275 'port-retries': 'NotebookApp.port_retries',
275 'transport': 'KernelManager.transport',
276 'transport': 'KernelManager.transport',
276 'keyfile': 'NotebookApp.keyfile',
277 'keyfile': 'NotebookApp.keyfile',
277 'certfile': 'NotebookApp.certfile',
278 'certfile': 'NotebookApp.certfile',
278 'notebook-dir': 'NotebookApp.notebook_dir',
279 'notebook-dir': 'NotebookApp.notebook_dir',
279 'browser': 'NotebookApp.browser',
280 'browser': 'NotebookApp.browser',
280 'pylab': 'NotebookApp.pylab',
281 'pylab': 'NotebookApp.pylab',
281 })
282 })
282
283
283 #-----------------------------------------------------------------------------
284 #-----------------------------------------------------------------------------
284 # NotebookApp
285 # NotebookApp
285 #-----------------------------------------------------------------------------
286 #-----------------------------------------------------------------------------
286
287
287 class NotebookApp(BaseIPythonApplication):
288 class NotebookApp(BaseIPythonApplication):
288
289
289 name = 'ipython-notebook'
290 name = 'ipython-notebook'
290
291
291 description = """
292 description = """
292 The IPython HTML Notebook.
293 The IPython HTML Notebook.
293
294
294 This launches a Tornado based HTML Notebook Server that serves up an
295 This launches a Tornado based HTML Notebook Server that serves up an
295 HTML5/Javascript Notebook client.
296 HTML5/Javascript Notebook client.
296 """
297 """
297 examples = _examples
298 examples = _examples
298 aliases = aliases
299 aliases = aliases
299 flags = flags
300 flags = flags
300
301
301 classes = [
302 classes = [
302 KernelManager, ProfileDir, Session, MappingKernelManager,
303 KernelManager, ProfileDir, Session, MappingKernelManager,
303 NotebookManager, FileNotebookManager, NotebookNotary,
304 NotebookManager, FileNotebookManager, NotebookNotary,
304 ]
305 ]
305 flags = Dict(flags)
306 flags = Dict(flags)
306 aliases = Dict(aliases)
307 aliases = Dict(aliases)
307
308
308 subcommands = dict(
309 subcommands = dict(
309 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
310 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
310 )
311 )
311
312
312 kernel_argv = List(Unicode)
313 kernel_argv = List(Unicode)
313
314
314 _log_formatter_cls = LogFormatter
315 _log_formatter_cls = LogFormatter
315
316
316 def _log_level_default(self):
317 def _log_level_default(self):
317 return logging.INFO
318 return logging.INFO
318
319
319 def _log_datefmt_default(self):
320 def _log_datefmt_default(self):
320 """Exclude date from default date format"""
321 """Exclude date from default date format"""
321 return "%H:%M:%S"
322 return "%H:%M:%S"
322
323
323 def _log_format_default(self):
324 def _log_format_default(self):
324 """override default log format to include time"""
325 """override default log format to include time"""
325 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
326 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
326
327
327 # create requested profiles by default, if they don't exist:
328 # create requested profiles by default, if they don't exist:
328 auto_create = Bool(True)
329 auto_create = Bool(True)
329
330
330 # file to be opened in the notebook server
331 # file to be opened in the notebook server
331 file_to_run = Unicode('', config=True)
332 file_to_run = Unicode('', config=True)
332 def _file_to_run_changed(self, name, old, new):
333 def _file_to_run_changed(self, name, old, new):
333 path, base = os.path.split(new)
334 path, base = os.path.split(new)
334 if path:
335 if path:
335 self.file_to_run = base
336 self.file_to_run = base
336 self.notebook_dir = path
337 self.notebook_dir = path
337
338
338 # Network related information
339 # Network related information
339
340
340 allow_origin = Unicode('', config=True,
341 allow_origin = Unicode('', config=True,
341 help="""Set the Access-Control-Allow-Origin header
342 help="""Set the Access-Control-Allow-Origin header
342
343
343 Use '*' to allow any origin to access your server.
344 Use '*' to allow any origin to access your server.
344
345
345 Takes precedence over allow_origin_pat.
346 Takes precedence over allow_origin_pat.
346 """
347 """
347 )
348 )
348
349
349 allow_origin_pat = Unicode('', config=True,
350 allow_origin_pat = Unicode('', config=True,
350 help="""Use a regular expression for the Access-Control-Allow-Origin header
351 help="""Use a regular expression for the Access-Control-Allow-Origin header
351
352
352 Requests from an origin matching the expression will get replies with:
353 Requests from an origin matching the expression will get replies with:
353
354
354 Access-Control-Allow-Origin: origin
355 Access-Control-Allow-Origin: origin
355
356
356 where `origin` is the origin of the request.
357 where `origin` is the origin of the request.
357
358
358 Ignored if allow_origin is set.
359 Ignored if allow_origin is set.
359 """
360 """
360 )
361 )
361
362
362 allow_credentials = Bool(False, config=True,
363 allow_credentials = Bool(False, config=True,
363 help="Set the Access-Control-Allow-Credentials: true header"
364 help="Set the Access-Control-Allow-Credentials: true header"
364 )
365 )
365
366
366 ip = Unicode('localhost', config=True,
367 ip = Unicode('localhost', config=True,
367 help="The IP address the notebook server will listen on."
368 help="The IP address the notebook server will listen on."
368 )
369 )
369
370
370 def _ip_changed(self, name, old, new):
371 def _ip_changed(self, name, old, new):
371 if new == u'*': self.ip = u''
372 if new == u'*': self.ip = u''
372
373
373 port = Integer(8888, config=True,
374 port = Integer(8888, config=True,
374 help="The port the notebook server will listen on."
375 help="The port the notebook server will listen on."
375 )
376 )
376 port_retries = Integer(50, config=True,
377 port_retries = Integer(50, config=True,
377 help="The number of additional ports to try if the specified port is not available."
378 help="The number of additional ports to try if the specified port is not available."
378 )
379 )
379
380
380 certfile = Unicode(u'', config=True,
381 certfile = Unicode(u'', config=True,
381 help="""The full path to an SSL/TLS certificate file."""
382 help="""The full path to an SSL/TLS certificate file."""
382 )
383 )
383
384
384 keyfile = Unicode(u'', config=True,
385 keyfile = Unicode(u'', config=True,
385 help="""The full path to a private key file for usage with SSL/TLS."""
386 help="""The full path to a private key file for usage with SSL/TLS."""
386 )
387 )
387
388
388 cookie_secret_file = Unicode(config=True,
389 cookie_secret_file = Unicode(config=True,
389 help="""The file where the cookie secret is stored."""
390 help="""The file where the cookie secret is stored."""
390 )
391 )
391 def _cookie_secret_file_default(self):
392 def _cookie_secret_file_default(self):
392 if self.profile_dir is None:
393 if self.profile_dir is None:
393 return ''
394 return ''
394 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
395 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
395
396
396 cookie_secret = Bytes(b'', config=True,
397 cookie_secret = Bytes(b'', config=True,
397 help="""The random bytes used to secure cookies.
398 help="""The random bytes used to secure cookies.
398 By default this is a new random number every time you start the Notebook.
399 By default this is a new random number every time you start the Notebook.
399 Set it to a value in a config file to enable logins to persist across server sessions.
400 Set it to a value in a config file to enable logins to persist across server sessions.
400
401
401 Note: Cookie secrets should be kept private, do not share config files with
402 Note: Cookie secrets should be kept private, do not share config files with
402 cookie_secret stored in plaintext (you can read the value from a file).
403 cookie_secret stored in plaintext (you can read the value from a file).
403 """
404 """
404 )
405 )
405 def _cookie_secret_default(self):
406 def _cookie_secret_default(self):
406 if os.path.exists(self.cookie_secret_file):
407 if os.path.exists(self.cookie_secret_file):
407 with io.open(self.cookie_secret_file, 'rb') as f:
408 with io.open(self.cookie_secret_file, 'rb') as f:
408 return f.read()
409 return f.read()
409 else:
410 else:
410 secret = base64.encodestring(os.urandom(1024))
411 secret = base64.encodestring(os.urandom(1024))
411 self._write_cookie_secret_file(secret)
412 self._write_cookie_secret_file(secret)
412 return secret
413 return secret
413
414
414 def _write_cookie_secret_file(self, secret):
415 def _write_cookie_secret_file(self, secret):
415 """write my secret to my secret_file"""
416 """write my secret to my secret_file"""
416 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
417 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
417 with io.open(self.cookie_secret_file, 'wb') as f:
418 with io.open(self.cookie_secret_file, 'wb') as f:
418 f.write(secret)
419 f.write(secret)
419 try:
420 try:
420 os.chmod(self.cookie_secret_file, 0o600)
421 os.chmod(self.cookie_secret_file, 0o600)
421 except OSError:
422 except OSError:
422 self.log.warn(
423 self.log.warn(
423 "Could not set permissions on %s",
424 "Could not set permissions on %s",
424 self.cookie_secret_file
425 self.cookie_secret_file
425 )
426 )
426
427
427 password = Unicode(u'', config=True,
428 password = Unicode(u'', config=True,
428 help="""Hashed password to use for web authentication.
429 help="""Hashed password to use for web authentication.
429
430
430 To generate, type in a python/IPython shell:
431 To generate, type in a python/IPython shell:
431
432
432 from IPython.lib import passwd; passwd()
433 from IPython.lib import passwd; passwd()
433
434
434 The string should be of the form type:salt:hashed-password.
435 The string should be of the form type:salt:hashed-password.
435 """
436 """
436 )
437 )
437
438
438 open_browser = Bool(True, config=True,
439 open_browser = Bool(True, config=True,
439 help="""Whether to open in a browser after starting.
440 help="""Whether to open in a browser after starting.
440 The specific browser used is platform dependent and
441 The specific browser used is platform dependent and
441 determined by the python standard library `webbrowser`
442 determined by the python standard library `webbrowser`
442 module, unless it is overridden using the --browser
443 module, unless it is overridden using the --browser
443 (NotebookApp.browser) configuration option.
444 (NotebookApp.browser) configuration option.
444 """)
445 """)
445
446
446 browser = Unicode(u'', config=True,
447 browser = Unicode(u'', config=True,
447 help="""Specify what command to use to invoke a web
448 help="""Specify what command to use to invoke a web
448 browser when opening the notebook. If not specified, the
449 browser when opening the notebook. If not specified, the
449 default browser will be determined by the `webbrowser`
450 default browser will be determined by the `webbrowser`
450 standard library module, which allows setting of the
451 standard library module, which allows setting of the
451 BROWSER environment variable to override it.
452 BROWSER environment variable to override it.
452 """)
453 """)
453
454
454 webapp_settings = Dict(config=True,
455 webapp_settings = Dict(config=True,
455 help="Supply overrides for the tornado.web.Application that the "
456 help="Supply overrides for the tornado.web.Application that the "
456 "IPython notebook uses.")
457 "IPython notebook uses.")
457
458
458 jinja_environment_options = Dict(config=True,
459 jinja_environment_options = Dict(config=True,
459 help="Supply extra arguments that will be passed to Jinja environment.")
460 help="Supply extra arguments that will be passed to Jinja environment.")
460
461
461
462
462 enable_mathjax = Bool(True, config=True,
463 enable_mathjax = Bool(True, config=True,
463 help="""Whether to enable MathJax for typesetting math/TeX
464 help="""Whether to enable MathJax for typesetting math/TeX
464
465
465 MathJax is the javascript library IPython uses to render math/LaTeX. It is
466 MathJax is the javascript library IPython uses to render math/LaTeX. It is
466 very large, so you may want to disable it if you have a slow internet
467 very large, so you may want to disable it if you have a slow internet
467 connection, or for offline use of the notebook.
468 connection, or for offline use of the notebook.
468
469
469 When disabled, equations etc. will appear as their untransformed TeX source.
470 When disabled, equations etc. will appear as their untransformed TeX source.
470 """
471 """
471 )
472 )
472 def _enable_mathjax_changed(self, name, old, new):
473 def _enable_mathjax_changed(self, name, old, new):
473 """set mathjax url to empty if mathjax is disabled"""
474 """set mathjax url to empty if mathjax is disabled"""
474 if not new:
475 if not new:
475 self.mathjax_url = u''
476 self.mathjax_url = u''
476
477
477 base_url = Unicode('/', config=True,
478 base_url = Unicode('/', config=True,
478 help='''The base URL for the notebook server.
479 help='''The base URL for the notebook server.
479
480
480 Leading and trailing slashes can be omitted,
481 Leading and trailing slashes can be omitted,
481 and will automatically be added.
482 and will automatically be added.
482 ''')
483 ''')
483 def _base_url_changed(self, name, old, new):
484 def _base_url_changed(self, name, old, new):
484 if not new.startswith('/'):
485 if not new.startswith('/'):
485 self.base_url = '/'+new
486 self.base_url = '/'+new
486 elif not new.endswith('/'):
487 elif not new.endswith('/'):
487 self.base_url = new+'/'
488 self.base_url = new+'/'
488
489
489 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
490 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
490 def _base_project_url_changed(self, name, old, new):
491 def _base_project_url_changed(self, name, old, new):
491 self.log.warn("base_project_url is deprecated, use base_url")
492 self.log.warn("base_project_url is deprecated, use base_url")
492 self.base_url = new
493 self.base_url = new
493
494
494 extra_static_paths = List(Unicode, config=True,
495 extra_static_paths = List(Unicode, config=True,
495 help="""Extra paths to search for serving static files.
496 help="""Extra paths to search for serving static files.
496
497
497 This allows adding javascript/css to be available from the notebook server machine,
498 This allows adding javascript/css to be available from the notebook server machine,
498 or overriding individual files in the IPython"""
499 or overriding individual files in the IPython"""
499 )
500 )
500 def _extra_static_paths_default(self):
501 def _extra_static_paths_default(self):
501 return [os.path.join(self.profile_dir.location, 'static')]
502 return [os.path.join(self.profile_dir.location, 'static')]
502
503
503 @property
504 @property
504 def static_file_path(self):
505 def static_file_path(self):
505 """return extra paths + the default location"""
506 """return extra paths + the default location"""
506 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
507 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
507
508
508 nbextensions_path = List(Unicode, config=True,
509 nbextensions_path = List(Unicode, config=True,
509 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
510 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
510 )
511 )
511 def _nbextensions_path_default(self):
512 def _nbextensions_path_default(self):
512 return [os.path.join(get_ipython_dir(), 'nbextensions')]
513 return [os.path.join(get_ipython_dir(), 'nbextensions')]
513
514
514 mathjax_url = Unicode("", config=True,
515 mathjax_url = Unicode("", config=True,
515 help="""The url for MathJax.js."""
516 help="""The url for MathJax.js."""
516 )
517 )
517 def _mathjax_url_default(self):
518 def _mathjax_url_default(self):
518 if not self.enable_mathjax:
519 if not self.enable_mathjax:
519 return u''
520 return u''
520 static_url_prefix = self.webapp_settings.get("static_url_prefix",
521 static_url_prefix = self.webapp_settings.get("static_url_prefix",
521 url_path_join(self.base_url, "static")
522 url_path_join(self.base_url, "static")
522 )
523 )
523
524
524 # try local mathjax, either in nbextensions/mathjax or static/mathjax
525 # try local mathjax, either in nbextensions/mathjax or static/mathjax
525 for (url_prefix, search_path) in [
526 for (url_prefix, search_path) in [
526 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
527 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
527 (static_url_prefix, self.static_file_path),
528 (static_url_prefix, self.static_file_path),
528 ]:
529 ]:
529 self.log.debug("searching for local mathjax in %s", search_path)
530 self.log.debug("searching for local mathjax in %s", search_path)
530 try:
531 try:
531 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
532 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
532 except IOError:
533 except IOError:
533 continue
534 continue
534 else:
535 else:
535 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
536 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
536 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
537 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
537 return url
538 return url
538
539
539 # no local mathjax, serve from CDN
540 # no local mathjax, serve from CDN
540 if self.certfile:
541 if self.certfile:
541 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
542 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
542 host = u"https://c328740.ssl.cf1.rackcdn.com"
543 host = u"https://c328740.ssl.cf1.rackcdn.com"
543 else:
544 else:
544 host = u"http://cdn.mathjax.org"
545 host = u"http://cdn.mathjax.org"
545
546
546 url = host + u"/mathjax/latest/MathJax.js"
547 url = host + u"/mathjax/latest/MathJax.js"
547 self.log.info("Using MathJax from CDN: %s", url)
548 self.log.info("Using MathJax from CDN: %s", url)
548 return url
549 return url
549
550
550 def _mathjax_url_changed(self, name, old, new):
551 def _mathjax_url_changed(self, name, old, new):
551 if new and not self.enable_mathjax:
552 if new and not self.enable_mathjax:
552 # enable_mathjax=False overrides mathjax_url
553 # enable_mathjax=False overrides mathjax_url
553 self.mathjax_url = u''
554 self.mathjax_url = u''
554 else:
555 else:
555 self.log.info("Using MathJax: %s", new)
556 self.log.info("Using MathJax: %s", new)
556
557
557 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
558 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
558 config=True,
559 config=True,
559 help='The notebook manager class to use.'
560 help='The notebook manager class to use.'
560 )
561 )
561 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
562 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
562 config=True,
563 config=True,
563 help='The kernel manager class to use.'
564 help='The kernel manager class to use.'
564 )
565 )
565 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
566 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
566 config=True,
567 config=True,
567 help='The session manager class to use.'
568 help='The session manager class to use.'
568 )
569 )
569 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
570 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
570 config=True,
571 config=True,
571 help='The cluster manager class to use.'
572 help='The cluster manager class to use.'
572 )
573 )
573
574
574 kernel_spec_manager = Instance(KernelSpecManager)
575 kernel_spec_manager = Instance(KernelSpecManager)
575
576
576 def _kernel_spec_manager_default(self):
577 def _kernel_spec_manager_default(self):
577 return KernelSpecManager(ipython_dir=self.ipython_dir)
578 return KernelSpecManager(ipython_dir=self.ipython_dir)
578
579
579 trust_xheaders = Bool(False, config=True,
580 trust_xheaders = Bool(False, config=True,
580 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
581 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
581 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
582 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
582 )
583 )
583
584
584 info_file = Unicode()
585 info_file = Unicode()
585
586
586 def _info_file_default(self):
587 def _info_file_default(self):
587 info_file = "nbserver-%s.json"%os.getpid()
588 info_file = "nbserver-%s.json"%os.getpid()
588 return os.path.join(self.profile_dir.security_dir, info_file)
589 return os.path.join(self.profile_dir.security_dir, info_file)
589
590
590 notebook_dir = Unicode(py3compat.getcwd(), config=True,
591 notebook_dir = Unicode(py3compat.getcwd(), config=True,
591 help="The directory to use for notebooks and kernels."
592 help="The directory to use for notebooks and kernels."
592 )
593 )
593
594
594 pylab = Unicode('disabled', config=True,
595 pylab = Unicode('disabled', config=True,
595 help="""
596 help="""
596 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
597 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
597 """
598 """
598 )
599 )
599 def _pylab_changed(self, name, old, new):
600 def _pylab_changed(self, name, old, new):
600 """when --pylab is specified, display a warning and exit"""
601 """when --pylab is specified, display a warning and exit"""
601 if new != 'warn':
602 if new != 'warn':
602 backend = ' %s' % new
603 backend = ' %s' % new
603 else:
604 else:
604 backend = ''
605 backend = ''
605 self.log.error("Support for specifying --pylab on the command line has been removed.")
606 self.log.error("Support for specifying --pylab on the command line has been removed.")
606 self.log.error(
607 self.log.error(
607 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
608 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
608 )
609 )
609 self.exit(1)
610 self.exit(1)
610
611
611 def _notebook_dir_changed(self, name, old, new):
612 def _notebook_dir_changed(self, name, old, new):
612 """Do a bit of validation of the notebook dir."""
613 """Do a bit of validation of the notebook dir."""
613 if not os.path.isabs(new):
614 if not os.path.isabs(new):
614 # If we receive a non-absolute path, make it absolute.
615 # If we receive a non-absolute path, make it absolute.
615 self.notebook_dir = os.path.abspath(new)
616 self.notebook_dir = os.path.abspath(new)
616 return
617 return
617 if not os.path.isdir(new):
618 if not os.path.isdir(new):
618 raise TraitError("No such notebook dir: %r" % new)
619 raise TraitError("No such notebook dir: %r" % new)
619
620
620 # setting App.notebook_dir implies setting notebook and kernel dirs as well
621 # setting App.notebook_dir implies setting notebook and kernel dirs as well
621 self.config.FileNotebookManager.notebook_dir = new
622 self.config.FileNotebookManager.notebook_dir = new
622 self.config.MappingKernelManager.root_dir = new
623 self.config.MappingKernelManager.root_dir = new
623
624
624
625
625 def parse_command_line(self, argv=None):
626 def parse_command_line(self, argv=None):
626 super(NotebookApp, self).parse_command_line(argv)
627 super(NotebookApp, self).parse_command_line(argv)
627
628
628 if self.extra_args:
629 if self.extra_args:
629 arg0 = self.extra_args[0]
630 arg0 = self.extra_args[0]
630 f = os.path.abspath(arg0)
631 f = os.path.abspath(arg0)
631 self.argv.remove(arg0)
632 self.argv.remove(arg0)
632 if not os.path.exists(f):
633 if not os.path.exists(f):
633 self.log.critical("No such file or directory: %s", f)
634 self.log.critical("No such file or directory: %s", f)
634 self.exit(1)
635 self.exit(1)
635
636
636 # Use config here, to ensure that it takes higher priority than
637 # Use config here, to ensure that it takes higher priority than
637 # anything that comes from the profile.
638 # anything that comes from the profile.
638 c = Config()
639 c = Config()
639 if os.path.isdir(f):
640 if os.path.isdir(f):
640 c.NotebookApp.notebook_dir = f
641 c.NotebookApp.notebook_dir = f
641 elif os.path.isfile(f):
642 elif os.path.isfile(f):
642 c.NotebookApp.file_to_run = f
643 c.NotebookApp.file_to_run = f
643 self.update_config(c)
644 self.update_config(c)
644
645
645 def init_kernel_argv(self):
646 def init_kernel_argv(self):
646 """construct the kernel arguments"""
647 """construct the kernel arguments"""
647 # Kernel should get *absolute* path to profile directory
648 # Kernel should get *absolute* path to profile directory
648 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
649 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
649
650
650 def init_configurables(self):
651 def init_configurables(self):
651 # force Session default to be secure
652 # force Session default to be secure
652 default_secure(self.config)
653 default_secure(self.config)
653 kls = import_item(self.kernel_manager_class)
654 kls = import_item(self.kernel_manager_class)
654 self.kernel_manager = kls(
655 self.kernel_manager = kls(
655 parent=self, log=self.log, kernel_argv=self.kernel_argv,
656 parent=self, log=self.log, kernel_argv=self.kernel_argv,
656 connection_dir = self.profile_dir.security_dir,
657 connection_dir = self.profile_dir.security_dir,
657 )
658 )
658 kls = import_item(self.notebook_manager_class)
659 kls = import_item(self.notebook_manager_class)
659 self.notebook_manager = kls(parent=self, log=self.log)
660 self.notebook_manager = kls(parent=self, log=self.log)
660 kls = import_item(self.session_manager_class)
661 kls = import_item(self.session_manager_class)
661 self.session_manager = kls(parent=self, log=self.log)
662 self.session_manager = kls(parent=self, log=self.log)
662 kls = import_item(self.cluster_manager_class)
663 kls = import_item(self.cluster_manager_class)
663 self.cluster_manager = kls(parent=self, log=self.log)
664 self.cluster_manager = kls(parent=self, log=self.log)
664 self.cluster_manager.update_profiles()
665 self.cluster_manager.update_profiles()
665
666
666 def init_logging(self):
667 def init_logging(self):
667 # This prevents double log messages because tornado use a root logger that
668 # This prevents double log messages because tornado use a root logger that
668 # self.log is a child of. The logging module dipatches log messages to a log
669 # self.log is a child of. The logging module dipatches log messages to a log
669 # and all of its ancenstors until propagate is set to False.
670 # and all of its ancenstors until propagate is set to False.
670 self.log.propagate = False
671 self.log.propagate = False
671
672
672 # hook up tornado 3's loggers to our app handlers
673 # hook up tornado 3's loggers to our app handlers
673 logger = logging.getLogger('tornado')
674 logger = logging.getLogger('tornado')
674 logger.propagate = True
675 logger.propagate = True
675 logger.parent = self.log
676 logger.parent = self.log
676 logger.setLevel(self.log.level)
677 logger.setLevel(self.log.level)
677
678
678 def init_webapp(self):
679 def init_webapp(self):
679 """initialize tornado webapp and httpserver"""
680 """initialize tornado webapp and httpserver"""
680 self.webapp_settings['allow_origin'] = self.allow_origin
681 self.webapp_settings['allow_origin'] = self.allow_origin
681 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
682 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
682 self.webapp_settings['allow_credentials'] = self.allow_credentials
683 self.webapp_settings['allow_credentials'] = self.allow_credentials
683
684
684 self.web_app = NotebookWebApplication(
685 self.web_app = NotebookWebApplication(
685 self, self.kernel_manager, self.notebook_manager,
686 self, self.kernel_manager, self.notebook_manager,
686 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
687 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
687 self.log, self.base_url, self.webapp_settings,
688 self.log, self.base_url, self.webapp_settings,
688 self.jinja_environment_options
689 self.jinja_environment_options
689 )
690 )
690 if self.certfile:
691 if self.certfile:
691 ssl_options = dict(certfile=self.certfile)
692 ssl_options = dict(certfile=self.certfile)
692 if self.keyfile:
693 if self.keyfile:
693 ssl_options['keyfile'] = self.keyfile
694 ssl_options['keyfile'] = self.keyfile
694 else:
695 else:
695 ssl_options = None
696 ssl_options = None
696 self.web_app.password = self.password
697 self.web_app.password = self.password
697 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
698 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
698 xheaders=self.trust_xheaders)
699 xheaders=self.trust_xheaders)
699 if not self.ip:
700 if not self.ip:
700 warning = "WARNING: The notebook server is listening on all IP addresses"
701 warning = "WARNING: The notebook server is listening on all IP addresses"
701 if ssl_options is None:
702 if ssl_options is None:
702 self.log.critical(warning + " and not using encryption. This "
703 self.log.critical(warning + " and not using encryption. This "
703 "is not recommended.")
704 "is not recommended.")
704 if not self.password:
705 if not self.password:
705 self.log.critical(warning + " and not using authentication. "
706 self.log.critical(warning + " and not using authentication. "
706 "This is highly insecure and not recommended.")
707 "This is highly insecure and not recommended.")
707 success = None
708 success = None
708 for port in random_ports(self.port, self.port_retries+1):
709 for port in random_ports(self.port, self.port_retries+1):
709 try:
710 try:
710 self.http_server.listen(port, self.ip)
711 self.http_server.listen(port, self.ip)
711 except socket.error as e:
712 except socket.error as e:
712 if e.errno == errno.EADDRINUSE:
713 if e.errno == errno.EADDRINUSE:
713 self.log.info('The port %i is already in use, trying another random port.' % port)
714 self.log.info('The port %i is already in use, trying another random port.' % port)
714 continue
715 continue
715 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
716 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
716 self.log.warn("Permission to listen on port %i denied" % port)
717 self.log.warn("Permission to listen on port %i denied" % port)
717 continue
718 continue
718 else:
719 else:
719 raise
720 raise
720 else:
721 else:
721 self.port = port
722 self.port = port
722 success = True
723 success = True
723 break
724 break
724 if not success:
725 if not success:
725 self.log.critical('ERROR: the notebook server could not be started because '
726 self.log.critical('ERROR: the notebook server could not be started because '
726 'no available port could be found.')
727 'no available port could be found.')
727 self.exit(1)
728 self.exit(1)
728
729
729 @property
730 @property
730 def display_url(self):
731 def display_url(self):
731 ip = self.ip if self.ip else '[all ip addresses on your system]'
732 ip = self.ip if self.ip else '[all ip addresses on your system]'
732 return self._url(ip)
733 return self._url(ip)
733
734
734 @property
735 @property
735 def connection_url(self):
736 def connection_url(self):
736 ip = self.ip if self.ip else 'localhost'
737 ip = self.ip if self.ip else 'localhost'
737 return self._url(ip)
738 return self._url(ip)
738
739
739 def _url(self, ip):
740 def _url(self, ip):
740 proto = 'https' if self.certfile else 'http'
741 proto = 'https' if self.certfile else 'http'
741 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
742 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
742
743
743 def init_signal(self):
744 def init_signal(self):
744 if not sys.platform.startswith('win'):
745 if not sys.platform.startswith('win'):
745 signal.signal(signal.SIGINT, self._handle_sigint)
746 signal.signal(signal.SIGINT, self._handle_sigint)
746 signal.signal(signal.SIGTERM, self._signal_stop)
747 signal.signal(signal.SIGTERM, self._signal_stop)
747 if hasattr(signal, 'SIGUSR1'):
748 if hasattr(signal, 'SIGUSR1'):
748 # Windows doesn't support SIGUSR1
749 # Windows doesn't support SIGUSR1
749 signal.signal(signal.SIGUSR1, self._signal_info)
750 signal.signal(signal.SIGUSR1, self._signal_info)
750 if hasattr(signal, 'SIGINFO'):
751 if hasattr(signal, 'SIGINFO'):
751 # only on BSD-based systems
752 # only on BSD-based systems
752 signal.signal(signal.SIGINFO, self._signal_info)
753 signal.signal(signal.SIGINFO, self._signal_info)
753
754
754 def _handle_sigint(self, sig, frame):
755 def _handle_sigint(self, sig, frame):
755 """SIGINT handler spawns confirmation dialog"""
756 """SIGINT handler spawns confirmation dialog"""
756 # register more forceful signal handler for ^C^C case
757 # register more forceful signal handler for ^C^C case
757 signal.signal(signal.SIGINT, self._signal_stop)
758 signal.signal(signal.SIGINT, self._signal_stop)
758 # request confirmation dialog in bg thread, to avoid
759 # request confirmation dialog in bg thread, to avoid
759 # blocking the App
760 # blocking the App
760 thread = threading.Thread(target=self._confirm_exit)
761 thread = threading.Thread(target=self._confirm_exit)
761 thread.daemon = True
762 thread.daemon = True
762 thread.start()
763 thread.start()
763
764
764 def _restore_sigint_handler(self):
765 def _restore_sigint_handler(self):
765 """callback for restoring original SIGINT handler"""
766 """callback for restoring original SIGINT handler"""
766 signal.signal(signal.SIGINT, self._handle_sigint)
767 signal.signal(signal.SIGINT, self._handle_sigint)
767
768
768 def _confirm_exit(self):
769 def _confirm_exit(self):
769 """confirm shutdown on ^C
770 """confirm shutdown on ^C
770
771
771 A second ^C, or answering 'y' within 5s will cause shutdown,
772 A second ^C, or answering 'y' within 5s will cause shutdown,
772 otherwise original SIGINT handler will be restored.
773 otherwise original SIGINT handler will be restored.
773
774
774 This doesn't work on Windows.
775 This doesn't work on Windows.
775 """
776 """
776 info = self.log.info
777 info = self.log.info
777 info('interrupted')
778 info('interrupted')
778 print(self.notebook_info())
779 print(self.notebook_info())
779 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
780 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
780 sys.stdout.flush()
781 sys.stdout.flush()
781 r,w,x = select.select([sys.stdin], [], [], 5)
782 r,w,x = select.select([sys.stdin], [], [], 5)
782 if r:
783 if r:
783 line = sys.stdin.readline()
784 line = sys.stdin.readline()
784 if line.lower().startswith('y') and 'n' not in line.lower():
785 if line.lower().startswith('y') and 'n' not in line.lower():
785 self.log.critical("Shutdown confirmed")
786 self.log.critical("Shutdown confirmed")
786 ioloop.IOLoop.instance().stop()
787 ioloop.IOLoop.instance().stop()
787 return
788 return
788 else:
789 else:
789 print("No answer for 5s:", end=' ')
790 print("No answer for 5s:", end=' ')
790 print("resuming operation...")
791 print("resuming operation...")
791 # no answer, or answer is no:
792 # no answer, or answer is no:
792 # set it back to original SIGINT handler
793 # set it back to original SIGINT handler
793 # use IOLoop.add_callback because signal.signal must be called
794 # use IOLoop.add_callback because signal.signal must be called
794 # from main thread
795 # from main thread
795 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
796 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
796
797
797 def _signal_stop(self, sig, frame):
798 def _signal_stop(self, sig, frame):
798 self.log.critical("received signal %s, stopping", sig)
799 self.log.critical("received signal %s, stopping", sig)
799 ioloop.IOLoop.instance().stop()
800 ioloop.IOLoop.instance().stop()
800
801
801 def _signal_info(self, sig, frame):
802 def _signal_info(self, sig, frame):
802 print(self.notebook_info())
803 print(self.notebook_info())
803
804
804 def init_components(self):
805 def init_components(self):
805 """Check the components submodule, and warn if it's unclean"""
806 """Check the components submodule, and warn if it's unclean"""
806 status = submodule.check_submodule_status()
807 status = submodule.check_submodule_status()
807 if status == 'missing':
808 if status == 'missing':
808 self.log.warn("components submodule missing, running `git submodule update`")
809 self.log.warn("components submodule missing, running `git submodule update`")
809 submodule.update_submodules(submodule.ipython_parent())
810 submodule.update_submodules(submodule.ipython_parent())
810 elif status == 'unclean':
811 elif status == 'unclean':
811 self.log.warn("components submodule unclean, you may see 404s on static/components")
812 self.log.warn("components submodule unclean, you may see 404s on static/components")
812 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
813 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
813
814
814 @catch_config_error
815 @catch_config_error
815 def initialize(self, argv=None):
816 def initialize(self, argv=None):
816 super(NotebookApp, self).initialize(argv)
817 super(NotebookApp, self).initialize(argv)
817 self.init_logging()
818 self.init_logging()
818 self.init_kernel_argv()
819 self.init_kernel_argv()
819 self.init_configurables()
820 self.init_configurables()
820 self.init_components()
821 self.init_components()
821 self.init_webapp()
822 self.init_webapp()
822 self.init_signal()
823 self.init_signal()
823
824
824 def cleanup_kernels(self):
825 def cleanup_kernels(self):
825 """Shutdown all kernels.
826 """Shutdown all kernels.
826
827
827 The kernels will shutdown themselves when this process no longer exists,
828 The kernels will shutdown themselves when this process no longer exists,
828 but explicit shutdown allows the KernelManagers to cleanup the connection files.
829 but explicit shutdown allows the KernelManagers to cleanup the connection files.
829 """
830 """
830 self.log.info('Shutting down kernels')
831 self.log.info('Shutting down kernels')
831 self.kernel_manager.shutdown_all()
832 self.kernel_manager.shutdown_all()
832
833
833 def notebook_info(self):
834 def notebook_info(self):
834 "Return the current working directory and the server url information"
835 "Return the current working directory and the server url information"
835 info = self.notebook_manager.info_string() + "\n"
836 info = self.notebook_manager.info_string() + "\n"
836 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
837 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
837 return info + "The IPython Notebook is running at: %s" % self.display_url
838 return info + "The IPython Notebook is running at: %s" % self.display_url
838
839
839 def server_info(self):
840 def server_info(self):
840 """Return a JSONable dict of information about this server."""
841 """Return a JSONable dict of information about this server."""
841 return {'url': self.connection_url,
842 return {'url': self.connection_url,
842 'hostname': self.ip if self.ip else 'localhost',
843 'hostname': self.ip if self.ip else 'localhost',
843 'port': self.port,
844 'port': self.port,
844 'secure': bool(self.certfile),
845 'secure': bool(self.certfile),
845 'base_url': self.base_url,
846 'base_url': self.base_url,
846 'notebook_dir': os.path.abspath(self.notebook_dir),
847 'notebook_dir': os.path.abspath(self.notebook_dir),
848 'pid': os.getpid()
847 }
849 }
848
850
849 def write_server_info_file(self):
851 def write_server_info_file(self):
850 """Write the result of server_info() to the JSON file info_file."""
852 """Write the result of server_info() to the JSON file info_file."""
851 with open(self.info_file, 'w') as f:
853 with open(self.info_file, 'w') as f:
852 json.dump(self.server_info(), f, indent=2)
854 json.dump(self.server_info(), f, indent=2)
853
855
854 def remove_server_info_file(self):
856 def remove_server_info_file(self):
855 """Remove the nbserver-<pid>.json file created for this server.
857 """Remove the nbserver-<pid>.json file created for this server.
856
858
857 Ignores the error raised when the file has already been removed.
859 Ignores the error raised when the file has already been removed.
858 """
860 """
859 try:
861 try:
860 os.unlink(self.info_file)
862 os.unlink(self.info_file)
861 except OSError as e:
863 except OSError as e:
862 if e.errno != errno.ENOENT:
864 if e.errno != errno.ENOENT:
863 raise
865 raise
864
866
865 def start(self):
867 def start(self):
866 """ Start the IPython Notebook server app, after initialization
868 """ Start the IPython Notebook server app, after initialization
867
869
868 This method takes no arguments so all configuration and initialization
870 This method takes no arguments so all configuration and initialization
869 must be done prior to calling this method."""
871 must be done prior to calling this method."""
870 if self.subapp is not None:
872 if self.subapp is not None:
871 return self.subapp.start()
873 return self.subapp.start()
872
874
873 info = self.log.info
875 info = self.log.info
874 for line in self.notebook_info().split("\n"):
876 for line in self.notebook_info().split("\n"):
875 info(line)
877 info(line)
876 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
878 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
877
879
878 self.write_server_info_file()
880 self.write_server_info_file()
879
881
880 if self.open_browser or self.file_to_run:
882 if self.open_browser or self.file_to_run:
881 try:
883 try:
882 browser = webbrowser.get(self.browser or None)
884 browser = webbrowser.get(self.browser or None)
883 except webbrowser.Error as e:
885 except webbrowser.Error as e:
884 self.log.warn('No web browser found: %s.' % e)
886 self.log.warn('No web browser found: %s.' % e)
885 browser = None
887 browser = None
886
888
887 if self.file_to_run:
889 if self.file_to_run:
888 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
890 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
889 if not os.path.exists(fullpath):
891 if not os.path.exists(fullpath):
890 self.log.critical("%s does not exist" % fullpath)
892 self.log.critical("%s does not exist" % fullpath)
891 self.exit(1)
893 self.exit(1)
892
894
893 uri = url_path_join('notebooks', self.file_to_run)
895 uri = url_path_join('notebooks', self.file_to_run)
894 else:
896 else:
895 uri = 'tree'
897 uri = 'tree'
896 if browser:
898 if browser:
897 b = lambda : browser.open(url_path_join(self.connection_url, uri),
899 b = lambda : browser.open(url_path_join(self.connection_url, uri),
898 new=2)
900 new=2)
899 threading.Thread(target=b).start()
901 threading.Thread(target=b).start()
900 try:
902 try:
901 ioloop.IOLoop.instance().start()
903 ioloop.IOLoop.instance().start()
902 except KeyboardInterrupt:
904 except KeyboardInterrupt:
903 info("Interrupted...")
905 info("Interrupted...")
904 finally:
906 finally:
905 self.cleanup_kernels()
907 self.cleanup_kernels()
906 self.remove_server_info_file()
908 self.remove_server_info_file()
907
909
908
910
909 def list_running_servers(profile='default'):
911 def list_running_servers(profile='default'):
910 """Iterate over the server info files of running notebook servers.
912 """Iterate over the server info files of running notebook servers.
911
913
912 Given a profile name, find nbserver-* files in the security directory of
914 Given a profile name, find nbserver-* files in the security directory of
913 that profile, and yield dicts of their information, each one pertaining to
915 that profile, and yield dicts of their information, each one pertaining to
914 a currently running notebook server instance.
916 a currently running notebook server instance.
915 """
917 """
916 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
918 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
917 for file in os.listdir(pd.security_dir):
919 for file in os.listdir(pd.security_dir):
918 if file.startswith('nbserver-'):
920 if file.startswith('nbserver-'):
919 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
921 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
920 yield json.load(f)
922 info = json.load(f)
921
923
924 # Simple check whether that process is really still running
925 if check_pid(info['pid']):
926 yield info
927 else:
928 # If the process has died, try to delete its info file
929 try:
930 os.unlink(file)
931 except OSError:
932 pass # TODO: This should warn or log or something
922 #-----------------------------------------------------------------------------
933 #-----------------------------------------------------------------------------
923 # Main entry point
934 # Main entry point
924 #-----------------------------------------------------------------------------
935 #-----------------------------------------------------------------------------
925
936
926 launch_new_instance = NotebookApp.launch_instance
937 launch_new_instance = NotebookApp.launch_instance
927
938
@@ -1,213 +1,225 b''
1 """Posix-specific implementation of process utilities.
1 """Posix-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import errno
20 import os
19 import subprocess as sp
21 import subprocess as sp
20 import sys
22 import sys
21
23
22 from IPython.external import pexpect
24 from IPython.external import pexpect
23
25
24 # Our own
26 # Our own
25 from ._process_common import getoutput, arg_split
27 from ._process_common import getoutput, arg_split
26 from IPython.utils import py3compat
28 from IPython.utils import py3compat
27 from IPython.utils.encoding import DEFAULT_ENCODING
29 from IPython.utils.encoding import DEFAULT_ENCODING
28
30
29 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
30 # Function definitions
32 # Function definitions
31 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
32
34
33 def _find_cmd(cmd):
35 def _find_cmd(cmd):
34 """Find the full path to a command using which."""
36 """Find the full path to a command using which."""
35
37
36 path = sp.Popen(['/usr/bin/env', 'which', cmd],
38 path = sp.Popen(['/usr/bin/env', 'which', cmd],
37 stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
39 stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
38 return py3compat.bytes_to_str(path)
40 return py3compat.bytes_to_str(path)
39
41
40
42
41 class ProcessHandler(object):
43 class ProcessHandler(object):
42 """Execute subprocesses under the control of pexpect.
44 """Execute subprocesses under the control of pexpect.
43 """
45 """
44 # Timeout in seconds to wait on each reading of the subprocess' output.
46 # Timeout in seconds to wait on each reading of the subprocess' output.
45 # This should not be set too low to avoid cpu overusage from our side,
47 # This should not be set too low to avoid cpu overusage from our side,
46 # since we read in a loop whose period is controlled by this timeout.
48 # since we read in a loop whose period is controlled by this timeout.
47 read_timeout = 0.05
49 read_timeout = 0.05
48
50
49 # Timeout to give a process if we receive SIGINT, between sending the
51 # Timeout to give a process if we receive SIGINT, between sending the
50 # SIGINT to the process and forcefully terminating it.
52 # SIGINT to the process and forcefully terminating it.
51 terminate_timeout = 0.2
53 terminate_timeout = 0.2
52
54
53 # File object where stdout and stderr of the subprocess will be written
55 # File object where stdout and stderr of the subprocess will be written
54 logfile = None
56 logfile = None
55
57
56 # Shell to call for subprocesses to execute
58 # Shell to call for subprocesses to execute
57 _sh = None
59 _sh = None
58
60
59 @property
61 @property
60 def sh(self):
62 def sh(self):
61 if self._sh is None:
63 if self._sh is None:
62 self._sh = pexpect.which('sh')
64 self._sh = pexpect.which('sh')
63 if self._sh is None:
65 if self._sh is None:
64 raise OSError('"sh" shell not found')
66 raise OSError('"sh" shell not found')
65
67
66 return self._sh
68 return self._sh
67
69
68 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
70 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
69 """Arguments are used for pexpect calls."""
71 """Arguments are used for pexpect calls."""
70 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
72 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
71 None else read_timeout)
73 None else read_timeout)
72 self.terminate_timeout = (ProcessHandler.terminate_timeout if
74 self.terminate_timeout = (ProcessHandler.terminate_timeout if
73 terminate_timeout is None else
75 terminate_timeout is None else
74 terminate_timeout)
76 terminate_timeout)
75 self.logfile = sys.stdout if logfile is None else logfile
77 self.logfile = sys.stdout if logfile is None else logfile
76
78
77 def getoutput(self, cmd):
79 def getoutput(self, cmd):
78 """Run a command and return its stdout/stderr as a string.
80 """Run a command and return its stdout/stderr as a string.
79
81
80 Parameters
82 Parameters
81 ----------
83 ----------
82 cmd : str
84 cmd : str
83 A command to be executed in the system shell.
85 A command to be executed in the system shell.
84
86
85 Returns
87 Returns
86 -------
88 -------
87 output : str
89 output : str
88 A string containing the combination of stdout and stderr from the
90 A string containing the combination of stdout and stderr from the
89 subprocess, in whatever order the subprocess originally wrote to its
91 subprocess, in whatever order the subprocess originally wrote to its
90 file descriptors (so the order of the information in this string is the
92 file descriptors (so the order of the information in this string is the
91 correct order as would be seen if running the command in a terminal).
93 correct order as would be seen if running the command in a terminal).
92 """
94 """
93 try:
95 try:
94 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
96 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
95 except KeyboardInterrupt:
97 except KeyboardInterrupt:
96 print('^C', file=sys.stderr, end='')
98 print('^C', file=sys.stderr, end='')
97
99
98 def getoutput_pexpect(self, cmd):
100 def getoutput_pexpect(self, cmd):
99 """Run a command and return its stdout/stderr as a string.
101 """Run a command and return its stdout/stderr as a string.
100
102
101 Parameters
103 Parameters
102 ----------
104 ----------
103 cmd : str
105 cmd : str
104 A command to be executed in the system shell.
106 A command to be executed in the system shell.
105
107
106 Returns
108 Returns
107 -------
109 -------
108 output : str
110 output : str
109 A string containing the combination of stdout and stderr from the
111 A string containing the combination of stdout and stderr from the
110 subprocess, in whatever order the subprocess originally wrote to its
112 subprocess, in whatever order the subprocess originally wrote to its
111 file descriptors (so the order of the information in this string is the
113 file descriptors (so the order of the information in this string is the
112 correct order as would be seen if running the command in a terminal).
114 correct order as would be seen if running the command in a terminal).
113 """
115 """
114 try:
116 try:
115 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
117 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
116 except KeyboardInterrupt:
118 except KeyboardInterrupt:
117 print('^C', file=sys.stderr, end='')
119 print('^C', file=sys.stderr, end='')
118
120
119 def system(self, cmd):
121 def system(self, cmd):
120 """Execute a command in a subshell.
122 """Execute a command in a subshell.
121
123
122 Parameters
124 Parameters
123 ----------
125 ----------
124 cmd : str
126 cmd : str
125 A command to be executed in the system shell.
127 A command to be executed in the system shell.
126
128
127 Returns
129 Returns
128 -------
130 -------
129 int : child's exitstatus
131 int : child's exitstatus
130 """
132 """
131 # Get likely encoding for the output.
133 # Get likely encoding for the output.
132 enc = DEFAULT_ENCODING
134 enc = DEFAULT_ENCODING
133
135
134 # Patterns to match on the output, for pexpect. We read input and
136 # Patterns to match on the output, for pexpect. We read input and
135 # allow either a short timeout or EOF
137 # allow either a short timeout or EOF
136 patterns = [pexpect.TIMEOUT, pexpect.EOF]
138 patterns = [pexpect.TIMEOUT, pexpect.EOF]
137 # the index of the EOF pattern in the list.
139 # the index of the EOF pattern in the list.
138 # even though we know it's 1, this call means we don't have to worry if
140 # even though we know it's 1, this call means we don't have to worry if
139 # we change the above list, and forget to change this value:
141 # we change the above list, and forget to change this value:
140 EOF_index = patterns.index(pexpect.EOF)
142 EOF_index = patterns.index(pexpect.EOF)
141 # The size of the output stored so far in the process output buffer.
143 # The size of the output stored so far in the process output buffer.
142 # Since pexpect only appends to this buffer, each time we print we
144 # Since pexpect only appends to this buffer, each time we print we
143 # record how far we've printed, so that next time we only print *new*
145 # record how far we've printed, so that next time we only print *new*
144 # content from the buffer.
146 # content from the buffer.
145 out_size = 0
147 out_size = 0
146 try:
148 try:
147 # Since we're not really searching the buffer for text patterns, we
149 # Since we're not really searching the buffer for text patterns, we
148 # can set pexpect's search window to be tiny and it won't matter.
150 # can set pexpect's search window to be tiny and it won't matter.
149 # We only search for the 'patterns' timeout or EOF, which aren't in
151 # We only search for the 'patterns' timeout or EOF, which aren't in
150 # the text itself.
152 # the text itself.
151 #child = pexpect.spawn(pcmd, searchwindowsize=1)
153 #child = pexpect.spawn(pcmd, searchwindowsize=1)
152 if hasattr(pexpect, 'spawnb'):
154 if hasattr(pexpect, 'spawnb'):
153 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
155 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
154 else:
156 else:
155 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
157 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
156 flush = sys.stdout.flush
158 flush = sys.stdout.flush
157 while True:
159 while True:
158 # res is the index of the pattern that caused the match, so we
160 # res is the index of the pattern that caused the match, so we
159 # know whether we've finished (if we matched EOF) or not
161 # know whether we've finished (if we matched EOF) or not
160 res_idx = child.expect_list(patterns, self.read_timeout)
162 res_idx = child.expect_list(patterns, self.read_timeout)
161 print(child.before[out_size:].decode(enc, 'replace'), end='')
163 print(child.before[out_size:].decode(enc, 'replace'), end='')
162 flush()
164 flush()
163 if res_idx==EOF_index:
165 if res_idx==EOF_index:
164 break
166 break
165 # Update the pointer to what we've already printed
167 # Update the pointer to what we've already printed
166 out_size = len(child.before)
168 out_size = len(child.before)
167 except KeyboardInterrupt:
169 except KeyboardInterrupt:
168 # We need to send ^C to the process. The ascii code for '^C' is 3
170 # We need to send ^C to the process. The ascii code for '^C' is 3
169 # (the character is known as ETX for 'End of Text', see
171 # (the character is known as ETX for 'End of Text', see
170 # curses.ascii.ETX).
172 # curses.ascii.ETX).
171 child.sendline(chr(3))
173 child.sendline(chr(3))
172 # Read and print any more output the program might produce on its
174 # Read and print any more output the program might produce on its
173 # way out.
175 # way out.
174 try:
176 try:
175 out_size = len(child.before)
177 out_size = len(child.before)
176 child.expect_list(patterns, self.terminate_timeout)
178 child.expect_list(patterns, self.terminate_timeout)
177 print(child.before[out_size:].decode(enc, 'replace'), end='')
179 print(child.before[out_size:].decode(enc, 'replace'), end='')
178 sys.stdout.flush()
180 sys.stdout.flush()
179 except KeyboardInterrupt:
181 except KeyboardInterrupt:
180 # Impatient users tend to type it multiple times
182 # Impatient users tend to type it multiple times
181 pass
183 pass
182 finally:
184 finally:
183 # Ensure the subprocess really is terminated
185 # Ensure the subprocess really is terminated
184 child.terminate(force=True)
186 child.terminate(force=True)
185 # add isalive check, to ensure exitstatus is set:
187 # add isalive check, to ensure exitstatus is set:
186 child.isalive()
188 child.isalive()
187
189
188 # We follow the subprocess pattern, returning either the exit status
190 # We follow the subprocess pattern, returning either the exit status
189 # as a positive number, or the terminating signal as a negative
191 # as a positive number, or the terminating signal as a negative
190 # number.
192 # number.
191 # on Linux, sh returns 128+n for signals terminating child processes on Linux
193 # on Linux, sh returns 128+n for signals terminating child processes on Linux
192 # on BSD (OS X), the signal code is set instead
194 # on BSD (OS X), the signal code is set instead
193 if child.exitstatus is None:
195 if child.exitstatus is None:
194 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
196 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
195 if child.signalstatus is None:
197 if child.signalstatus is None:
196 # this condition may never occur,
198 # this condition may never occur,
197 # but let's be certain we always return an integer.
199 # but let's be certain we always return an integer.
198 return 0
200 return 0
199 return -child.signalstatus
201 return -child.signalstatus
200 if child.exitstatus > 128:
202 if child.exitstatus > 128:
201 return -(child.exitstatus - 128)
203 return -(child.exitstatus - 128)
202 return child.exitstatus
204 return child.exitstatus
203
205
204
206
205 # Make system() with a functional interface for outside use. Note that we use
207 # Make system() with a functional interface for outside use. Note that we use
206 # getoutput() from the _common utils, which is built on top of popen(). Using
208 # getoutput() from the _common utils, which is built on top of popen(). Using
207 # pexpect to get subprocess output produces difficult to parse output, since
209 # pexpect to get subprocess output produces difficult to parse output, since
208 # programs think they are talking to a tty and produce highly formatted output
210 # programs think they are talking to a tty and produce highly formatted output
209 # (ls is a good example) that makes them hard.
211 # (ls is a good example) that makes them hard.
210 system = ProcessHandler().system
212 system = ProcessHandler().system
211
213
212
214 def check_pid(pid):
213
215 try:
216 os.kill(pid, 0)
217 except OSError as err:
218 if err.errno == errno.ESRCH:
219 return False
220 elif err.errno == errno.EPERM:
221 # Don't have permission to signal the process - probably means it exists
222 return True
223 raise
224 else:
225 return True
@@ -1,187 +1,192 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 = py3compat.getcwd()
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 or list
115 cmd : str or list
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 or list
141 cmd : str or list
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
188
189 def check_pid(pid):
190 # OpenProcess returns 0 if no such process (of ours) exists
191 # positive int otherwise
192 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
@@ -1,123 +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
21
22 # Our own
22 # Our own
23 if sys.platform == 'win32':
23 if sys.platform == 'win32':
24 from ._process_win32 import _find_cmd, system, getoutput, arg_split
24 from ._process_win32 import _find_cmd, system, getoutput, arg_split, check_pid
25 elif sys.platform == 'cli':
25 elif sys.platform == 'cli':
26 from ._process_cli import _find_cmd, system, getoutput, arg_split
26 from ._process_cli import _find_cmd, system, getoutput, arg_split
27 else:
27 else:
28 from ._process_posix import _find_cmd, system, getoutput, arg_split
28 from ._process_posix import _find_cmd, system, getoutput, arg_split, check_pid
29
29
30 from ._process_common import getoutputerror, get_output_error_code, process_handler
30 from ._process_common import getoutputerror, get_output_error_code, process_handler
31 from . import py3compat
31 from . import py3compat
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Code
34 # Code
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37
37
38 class FindCmdError(Exception):
38 class FindCmdError(Exception):
39 pass
39 pass
40
40
41
41
42 def find_cmd(cmd):
42 def find_cmd(cmd):
43 """Find absolute path to executable cmd in a cross platform manner.
43 """Find absolute path to executable cmd in a cross platform manner.
44
44
45 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
46 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
47 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`.
48
48
49 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
50 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
51 following code and looking for the application itself::
51 following code and looking for the application itself::
52
52
53 from IPython.utils.path import get_ipython_module_path
53 from IPython.utils.path import get_ipython_module_path
54 from IPython.utils.process import pycmd2argv
54 from IPython.utils.process import pycmd2argv
55 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
56
56
57 Parameters
57 Parameters
58 ----------
58 ----------
59 cmd : str
59 cmd : str
60 The command line program to look for.
60 The command line program to look for.
61 """
61 """
62 try:
62 try:
63 path = _find_cmd(cmd).rstrip()
63 path = _find_cmd(cmd).rstrip()
64 except OSError:
64 except OSError:
65 raise FindCmdError('command could not be found: %s' % cmd)
65 raise FindCmdError('command could not be found: %s' % cmd)
66 # which returns empty if not found
66 # which returns empty if not found
67 if path == '':
67 if path == '':
68 raise FindCmdError('command could not be found: %s' % cmd)
68 raise FindCmdError('command could not be found: %s' % cmd)
69 return os.path.abspath(path)
69 return os.path.abspath(path)
70
70
71
71
72 def is_cmd_found(cmd):
72 def is_cmd_found(cmd):
73 """Check whether executable `cmd` exists or not and return a bool."""
73 """Check whether executable `cmd` exists or not and return a bool."""
74 try:
74 try:
75 find_cmd(cmd)
75 find_cmd(cmd)
76 return True
76 return True
77 except FindCmdError:
77 except FindCmdError:
78 return False
78 return False
79
79
80
80
81 def pycmd2argv(cmd):
81 def pycmd2argv(cmd):
82 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).
83
83
84 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
85 location of the ``python`` executable using ``sys.executable`` to make
85 location of the ``python`` executable using ``sys.executable`` to make
86 sure the right version is used.
86 sure the right version is used.
87
87
88 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,
89 .com or .bat, and [, cmd] otherwise.
89 .com or .bat, and [, cmd] otherwise.
90
90
91 Parameters
91 Parameters
92 ----------
92 ----------
93 cmd : string
93 cmd : string
94 The path of the command.
94 The path of the command.
95
95
96 Returns
96 Returns
97 -------
97 -------
98 argv-style list.
98 argv-style list.
99 """
99 """
100 ext = os.path.splitext(cmd)[1]
100 ext = os.path.splitext(cmd)[1]
101 if ext in ['.exe', '.com', '.bat']:
101 if ext in ['.exe', '.com', '.bat']:
102 return [cmd]
102 return [cmd]
103 else:
103 else:
104 return [sys.executable, cmd]
104 return [sys.executable, cmd]
105
105
106
106
107 def abbrev_cwd():
107 def abbrev_cwd():
108 """ Return abbreviated version of cwd, e.g. d:mydir """
108 """ Return abbreviated version of cwd, e.g. d:mydir """
109 cwd = py3compat.getcwd().replace('\\','/')
109 cwd = py3compat.getcwd().replace('\\','/')
110 drivepart = ''
110 drivepart = ''
111 tail = cwd
111 tail = cwd
112 if sys.platform == 'win32':
112 if sys.platform == 'win32':
113 if len(cwd) < 4:
113 if len(cwd) < 4:
114 return cwd
114 return cwd
115 drivepart,tail = os.path.splitdrive(cwd)
115 drivepart,tail = os.path.splitdrive(cwd)
116
116
117
117
118 parts = tail.split('/')
118 parts = tail.split('/')
119 if len(parts) > 2:
119 if len(parts) > 2:
120 tail = '/'.join(parts[-2:])
120 tail = '/'.join(parts[-2:])
121
121
122 return (drivepart + (
122 return (drivepart + (
123 cwd == '/' and '/' or tail))
123 cwd == '/' and '/' or tail))
General Comments 0
You need to be logged in to leave comments. Login now