##// END OF EJS Templates
Merge pull request #4778 from minrk/install-nbextensions...
Thomas Kluyver -
r15344:e4f2e7fb merge
parent child Browse files
Show More
@@ -0,0 +1,268 b''
1 # coding: utf-8
2 """Utilities for installing Javascript extensions for the notebook"""
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2014 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 from __future__ import print_function
12
13 import os
14 import shutil
15 import tarfile
16 import zipfile
17 from os.path import basename, join as pjoin
18
19 # Deferred imports
20 try:
21 from urllib.parse import urlparse # Py3
22 from urllib.request import urlretrieve
23 except ImportError:
24 from urlparse import urlparse
25 from urllib import urlretrieve
26
27 from IPython.utils.path import get_ipython_dir
28 from IPython.utils.py3compat import string_types, cast_unicode_py2
29 from IPython.utils.tempdir import TemporaryDirectory
30
31
32 def _should_copy(src, dest, verbose=1):
33 """should a file be copied?"""
34 if not os.path.exists(dest):
35 return True
36 if os.stat(dest).st_mtime < os.stat(src).st_mtime:
37 if verbose >= 2:
38 print("%s is out of date" % dest)
39 return True
40 if verbose >= 2:
41 print("%s is up to date" % dest)
42 return False
43
44
45 def _maybe_copy(src, dest, verbose=1):
46 """copy a file if it needs updating"""
47 if _should_copy(src, dest, verbose):
48 if verbose >= 1:
49 print("copying %s -> %s" % (src, dest))
50 shutil.copy2(src, dest)
51
52
53 def _safe_is_tarfile(path):
54 """safe version of is_tarfile, return False on IOError"""
55 try:
56 return tarfile.is_tarfile(path)
57 except IOError:
58 return False
59
60
61 def check_nbextension(files, ipython_dir=None):
62 """Check whether nbextension files have been installed
63
64 files should be a list of relative paths within nbextensions.
65
66 Returns True if all files are found, False if any are missing.
67 """
68 ipython_dir = ipython_dir or get_ipython_dir()
69 nbext = pjoin(ipython_dir, u'nbextensions')
70 # make sure nbextensions dir exists
71 if not os.path.exists(nbext):
72 return False
73
74 if isinstance(files, string_types):
75 # one file given, turn it into a list
76 files = [files]
77
78 return all(os.path.exists(pjoin(nbext, f)) for f in files)
79
80
81 def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, verbose=1):
82 """Install a Javascript extension for the notebook
83
84 Stages files and/or directories into IPYTHONDIR/nbextensions.
85 By default, this compares modification time, and only stages files that need updating.
86 If `overwrite` is specified, matching files are purged before proceeding.
87
88 Parameters
89 ----------
90
91 files : list(paths or URLs)
92 One or more paths or URLs to existing files directories to install.
93 These will be installed with their base name, so '/path/to/foo'
94 will install to 'nbextensions/foo'.
95 Archives (zip or tarballs) will be extracted into the nbextensions directory.
96 overwrite : bool [default: False]
97 If True, always install the files, regardless of what may already be installed.
98 symlink : bool [default: False]
99 If True, create a symlink in nbextensions, rather than copying files.
100 Not allowed with URLs or archives.
101 ipython_dir : str [optional]
102 The path to an IPython directory, if the default value is not desired.
103 get_ipython_dir() is used by default.
104 verbose : int [default: 1]
105 Set verbosity level. The default is 1, where file actions are printed.
106 set verbose=2 for more output, or verbose=0 for silence.
107 """
108
109 ipython_dir = ipython_dir or get_ipython_dir()
110 nbext = pjoin(ipython_dir, u'nbextensions')
111 # make sure nbextensions dir exists
112 if not os.path.exists(nbext):
113 os.makedirs(nbext)
114
115 if isinstance(files, string_types):
116 # one file given, turn it into a list
117 files = [files]
118
119 for path in map(cast_unicode_py2, files):
120
121 if path.startswith(('https://', 'http://')):
122 if symlink:
123 raise ValueError("Cannot symlink from URLs")
124 # Given a URL, download it
125 with TemporaryDirectory() as td:
126 filename = urlparse(path).path.split('/')[-1]
127 local_path = os.path.join(td, filename)
128 if verbose >= 1:
129 print("downloading %s to %s" % (path, local_path))
130 urlretrieve(path, local_path)
131 # now install from the local copy
132 install_nbextension(local_path, overwrite, symlink, ipython_dir, verbose)
133 continue
134
135 # handle archives
136 archive = None
137 if path.endswith('.zip'):
138 archive = zipfile.ZipFile(path)
139 elif _safe_is_tarfile(path):
140 archive = tarfile.open(path)
141
142 if archive:
143 if symlink:
144 raise ValueError("Cannot symlink from archives")
145 if verbose >= 1:
146 print("extracting %s to %s" % (path, nbext))
147 archive.extractall(nbext)
148 archive.close()
149 continue
150
151 dest = pjoin(nbext, basename(path))
152 if overwrite and os.path.exists(dest):
153 if verbose >= 1:
154 print("removing %s" % dest)
155 if os.path.isdir(dest):
156 shutil.rmtree(dest)
157 else:
158 os.remove(dest)
159
160 if symlink:
161 path = os.path.abspath(path)
162 if not os.path.exists(dest):
163 if verbose >= 1:
164 print("symlink %s -> %s" % (dest, path))
165 os.symlink(path, dest)
166 continue
167
168 if os.path.isdir(path):
169 strip_prefix_len = len(path) - len(basename(path))
170 for parent, dirs, files in os.walk(path):
171 dest_dir = pjoin(nbext, parent[strip_prefix_len:])
172 if not os.path.exists(dest_dir):
173 if verbose >= 2:
174 print("making directory %s" % dest_dir)
175 os.makedirs(dest_dir)
176 for file in files:
177 src = pjoin(parent, file)
178 # print("%r, %r" % (dest_dir, file))
179 dest = pjoin(dest_dir, file)
180 _maybe_copy(src, dest, verbose)
181 else:
182 src = path
183 _maybe_copy(src, dest, verbose)
184
185 #----------------------------------------------------------------------
186 # install nbextension app
187 #----------------------------------------------------------------------
188
189 from IPython.utils.traitlets import Bool, Enum
190 from IPython.core.application import BaseIPythonApplication
191
192 flags = {
193 "overwrite" : ({
194 "NBExtensionApp" : {
195 "overwrite" : True,
196 }}, "Force overwrite of existing files"
197 ),
198 "debug" : ({
199 "NBExtensionApp" : {
200 "verbose" : 2,
201 }}, "Extra output"
202 ),
203 "quiet" : ({
204 "NBExtensionApp" : {
205 "verbose" : 0,
206 }}, "Minimal output"
207 ),
208 "symlink" : ({
209 "NBExtensionApp" : {
210 "symlink" : True,
211 }}, "Create symlinks instead of copying files"
212 ),
213 }
214 flags['s'] = flags['symlink']
215
216 aliases = {
217 "ipython-dir" : "NBExtensionApp.ipython_dir"
218 }
219
220 class NBExtensionApp(BaseIPythonApplication):
221 """Entry point for installing notebook extensions"""
222
223 description = """Install IPython notebook extensions
224
225 Usage
226
227 ipython install-nbextension file [more files, folders, archives or urls]
228
229 This copies files and/or folders into the IPython nbextensions directory.
230 If a URL is given, it will be downloaded.
231 If an archive is given, it will be extracted into nbextensions.
232 If the requested files are already up to date, no action is taken
233 unless --overwrite is specified.
234 """
235
236 examples = """
237 ipython install-nbextension /path/to/d3.js /path/to/myextension
238 """
239 aliases = aliases
240 flags = flags
241
242 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
243 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
244 verbose = Enum((0,1,2), default_value=1, config=True,
245 help="Verbosity level"
246 )
247
248 def install_extensions(self):
249 install_nbextension(self.extra_args,
250 overwrite=self.overwrite,
251 symlink=self.symlink,
252 verbose=self.verbose,
253 ipython_dir=self.ipython_dir,
254 )
255
256 def start(self):
257 if not self.extra_args:
258 nbext = pjoin(self.ipython_dir, u'nbextensions')
259 print("Notebook extensions in %s:" % nbext)
260 for ext in os.listdir(nbext):
261 print(u" %s" % ext)
262 else:
263 self.install_extensions()
264
265
266 if __name__ == '__main__':
267 NBExtensionApp.launch_instance()
268 No newline at end of file
@@ -0,0 +1,272 b''
1 # coding: utf-8
2 """Test installation of notebook extensions"""
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2014 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 import glob
15 import os
16 import re
17 import tarfile
18 import zipfile
19 from io import BytesIO
20 from os.path import basename, join as pjoin
21 from unittest import TestCase
22
23 import IPython.testing.tools as tt
24 import IPython.testing.decorators as dec
25 from IPython.utils import py3compat
26 from IPython.utils.tempdir import TemporaryDirectory
27 from IPython.html import nbextensions
28 from IPython.html.nbextensions import install_nbextension, check_nbextension
29
30 #-----------------------------------------------------------------------------
31 # Test functions
32 #-----------------------------------------------------------------------------
33
34 def touch(file, mtime=None):
35 """ensure a file exists, and set its modification time
36
37 returns the modification time of the file
38 """
39 open(file, 'a').close()
40 # set explicit mtime
41 if mtime:
42 atime = os.stat(file).st_atime
43 os.utime(file, (atime, mtime))
44 return os.stat(file).st_mtime
45
46
47 class TestInstallNBExtension(TestCase):
48
49 def tempdir(self):
50 td = TemporaryDirectory()
51 self.tempdirs.append(td)
52 return py3compat.cast_unicode(td.name)
53
54 def setUp(self):
55 self.tempdirs = []
56 src = self.src = self.tempdir()
57 self.files = files = [
58 pjoin(u'Ζ’ile'),
59 pjoin(u'βˆ‚ir', u'Ζ’ile1'),
60 pjoin(u'βˆ‚ir', u'βˆ‚ir2', u'Ζ’ile2'),
61 ]
62 for file in files:
63 fullpath = os.path.join(self.src, file)
64 parent = os.path.dirname(fullpath)
65 if not os.path.exists(parent):
66 os.makedirs(parent)
67 touch(fullpath)
68
69 self.ipdir = self.tempdir()
70 self.save_get_ipython_dir = nbextensions.get_ipython_dir
71 nbextensions.get_ipython_dir = lambda : self.ipdir
72
73 def tearDown(self):
74 for td in self.tempdirs:
75 td.cleanup()
76 nbextensions.get_ipython_dir = self.save_get_ipython_dir
77
78 def assert_path_exists(self, path):
79 if not os.path.exists(path):
80 do_exist = os.listdir(os.path.dirname(path))
81 self.fail(u"%s should exist (found %s)" % (path, do_exist))
82
83 def assert_not_path_exists(self, path):
84 if os.path.exists(path):
85 self.fail(u"%s should not exist" % path)
86
87 def assert_installed(self, relative_path, ipdir=None):
88 self.assert_path_exists(
89 pjoin(ipdir or self.ipdir, u'nbextensions', relative_path)
90 )
91
92 def assert_not_installed(self, relative_path, ipdir=None):
93 self.assert_not_path_exists(
94 pjoin(ipdir or self.ipdir, u'nbextensions', relative_path)
95 )
96
97 def test_create_ipython_dir(self):
98 """install_nbextension when ipython_dir doesn't exist"""
99 with TemporaryDirectory() as td:
100 ipdir = pjoin(td, u'ipython')
101 install_nbextension(self.src, ipython_dir=ipdir)
102 self.assert_path_exists(ipdir)
103 for file in self.files:
104 self.assert_installed(
105 pjoin(basename(self.src), file),
106 ipdir
107 )
108
109 def test_create_nbextensions(self):
110 with TemporaryDirectory() as ipdir:
111 install_nbextension(self.src, ipython_dir=ipdir)
112 self.assert_installed(
113 pjoin(basename(self.src), u'Ζ’ile'),
114 ipdir
115 )
116
117 def test_single_file(self):
118 file = self.files[0]
119 install_nbextension(pjoin(self.src, file))
120 self.assert_installed(file)
121
122 def test_single_dir(self):
123 d = u'βˆ‚ir'
124 install_nbextension(pjoin(self.src, d))
125 self.assert_installed(self.files[-1])
126
127 def test_install_nbextension(self):
128 install_nbextension(glob.glob(pjoin(self.src, '*')))
129 for file in self.files:
130 self.assert_installed(file)
131
132 def test_overwrite_file(self):
133 with TemporaryDirectory() as d:
134 fname = u'Ζ’.js'
135 src = pjoin(d, fname)
136 with open(src, 'w') as f:
137 f.write('first')
138 mtime = touch(src)
139 dest = pjoin(self.ipdir, u'nbextensions', fname)
140 install_nbextension(src)
141 with open(src, 'w') as f:
142 f.write('overwrite')
143 mtime = touch(src, mtime - 100)
144 install_nbextension(src, overwrite=True)
145 with open(dest) as f:
146 self.assertEqual(f.read(), 'overwrite')
147
148 def test_overwrite_dir(self):
149 with TemporaryDirectory() as src:
150 # src = py3compat.cast_unicode_py2(src)
151 base = basename(src)
152 fname = u'Ζ’.js'
153 touch(pjoin(src, fname))
154 install_nbextension(src)
155 self.assert_installed(pjoin(base, fname))
156 os.remove(pjoin(src, fname))
157 fname2 = u'βˆ‚.js'
158 touch(pjoin(src, fname2))
159 install_nbextension(src, overwrite=True)
160 self.assert_installed(pjoin(base, fname2))
161 self.assert_not_installed(pjoin(base, fname))
162
163 def test_update_file(self):
164 with TemporaryDirectory() as d:
165 fname = u'Ζ’.js'
166 src = pjoin(d, fname)
167 with open(src, 'w') as f:
168 f.write('first')
169 mtime = touch(src)
170 install_nbextension(src)
171 self.assert_installed(fname)
172 dest = pjoin(self.ipdir, u'nbextensions', fname)
173 old_mtime = os.stat(dest).st_mtime
174 with open(src, 'w') as f:
175 f.write('overwrite')
176 touch(src, mtime + 10)
177 install_nbextension(src)
178 with open(dest) as f:
179 self.assertEqual(f.read(), 'overwrite')
180
181 def test_skip_old_file(self):
182 with TemporaryDirectory() as d:
183 fname = u'Ζ’.js'
184 src = pjoin(d, fname)
185 mtime = touch(src)
186 install_nbextension(src)
187 self.assert_installed(fname)
188 dest = pjoin(self.ipdir, u'nbextensions', fname)
189 old_mtime = os.stat(dest).st_mtime
190
191 mtime = touch(src, mtime - 100)
192 install_nbextension(src)
193 new_mtime = os.stat(dest).st_mtime
194 self.assertEqual(new_mtime, old_mtime)
195
196 def test_quiet(self):
197 with tt.AssertNotPrints(re.compile(r'.+')):
198 install_nbextension(self.src, verbose=0)
199
200 def test_install_zip(self):
201 path = pjoin(self.src, "myjsext.zip")
202 with zipfile.ZipFile(path, 'w') as f:
203 f.writestr("a.js", b"b();")
204 f.writestr("foo/a.js", b"foo();")
205 install_nbextension(path)
206 self.assert_installed("a.js")
207 self.assert_installed(pjoin("foo", "a.js"))
208
209 def test_install_tar(self):
210 def _add_file(f, fname, buf):
211 info = tarfile.TarInfo(fname)
212 info.size = len(buf)
213 f.addfile(info, BytesIO(buf))
214
215 for i,ext in enumerate((".tar.gz", ".tgz", ".tar.bz2")):
216 path = pjoin(self.src, "myjsext" + ext)
217 with tarfile.open(path, 'w') as f:
218 _add_file(f, "b%i.js" % i, b"b();")
219 _add_file(f, "foo/b%i.js" % i, b"foo();")
220 install_nbextension(path)
221 self.assert_installed("b%i.js" % i)
222 self.assert_installed(pjoin("foo", "b%i.js" % i))
223
224 def test_install_url(self):
225 def fake_urlretrieve(url, dest):
226 touch(dest)
227 save_urlretrieve = nbextensions.urlretrieve
228 nbextensions.urlretrieve = fake_urlretrieve
229 try:
230 install_nbextension("http://example.com/path/to/foo.js")
231 self.assert_installed("foo.js")
232 install_nbextension("https://example.com/path/to/another/bar.js")
233 self.assert_installed("bar.js")
234 finally:
235 nbextensions.urlretrieve = save_urlretrieve
236
237 def test_check_nbextension(self):
238 with TemporaryDirectory() as d:
239 f = u'Ζ’.js'
240 src = pjoin(d, f)
241 touch(src)
242 install_nbextension(src)
243
244 assert check_nbextension(f, self.ipdir)
245 assert check_nbextension([f], self.ipdir)
246 assert not check_nbextension([f, pjoin('dne', f)], self.ipdir)
247
248 @dec.skip_win32
249 def test_install_symlink(self):
250 with TemporaryDirectory() as d:
251 f = u'Ζ’.js'
252 src = pjoin(d, f)
253 touch(src)
254 install_nbextension(src, symlink=True)
255 dest = pjoin(self.ipdir, u'nbextensions', f)
256 assert os.path.islink(dest)
257 link = os.readlink(dest)
258 self.assertEqual(link, src)
259
260 def test_install_symlink_bad(self):
261 with self.assertRaises(ValueError):
262 install_nbextension("http://example.com/foo.js", symlink=True)
263
264 with TemporaryDirectory() as d:
265 zf = u'Ζ’.zip'
266 zsrc = pjoin(d, zf)
267 with zipfile.ZipFile(zsrc, 'w') as z:
268 z.writestr("a.js", b"b();")
269
270 with self.assertRaises(ValueError):
271 install_nbextension(zsrc, symlink=True)
272
@@ -1,7 +1,9 b''
1 """The IPython HTML Notebook"""
1 """The IPython HTML Notebook"""
2
2
3 import os
3 import os
4 # Packagers: modify this line if you store the notebook static files elsewhere
4 # Packagers: modify this line if you store the notebook static files elsewhere
5 DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static")
5 DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static")
6
6
7 del os
7 del os
8
9 from .nbextensions import install_nbextension No newline at end of file
@@ -1,547 +1,575 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Utilities
9 // Utilities
10 //============================================================================
10 //============================================================================
11 IPython.namespace('IPython.utils');
11 IPython.namespace('IPython.utils');
12
12
13 IPython.utils = (function (IPython) {
13 IPython.utils = (function (IPython) {
14 "use strict";
14 "use strict";
15
16 IPython.load_extensions = function () {
17 // load one or more IPython notebook extensions with requirejs
18
19 var extensions = [];
20 var extension_names = arguments;
21 for (var i = 0; i < extension_names.length; i++) {
22 extensions.push("nbextensions/" + arguments[i]);
23 }
24
25 require(extensions,
26 function () {
27 for (var i = 0; i < arguments.length; i++) {
28 var ext = arguments[i];
29 var ext_name = extension_names[i];
30 // success callback
31 console.log("Loaded extension: " + ext_name);
32 if (ext && ext.load_ipython_extension !== undefined) {
33 ext.load_ipython_extension();
34 }
35 }
36 },
37 function (err) {
38 // failure callback
39 console.log("Failed to load extension(s):", err.requireModules, err);
40 }
41 );
42 };
15
43
16 //============================================================================
44 //============================================================================
17 // Cross-browser RegEx Split
45 // Cross-browser RegEx Split
18 //============================================================================
46 //============================================================================
19
47
20 // This code has been MODIFIED from the code licensed below to not replace the
48 // This code has been MODIFIED from the code licensed below to not replace the
21 // default browser split. The license is reproduced here.
49 // default browser split. The license is reproduced here.
22
50
23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
51 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
24 /*!
52 /*!
25 * Cross-Browser Split 1.1.1
53 * Cross-Browser Split 1.1.1
26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
54 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
27 * Available under the MIT License
55 * Available under the MIT License
28 * ECMAScript compliant, uniform cross-browser split method
56 * ECMAScript compliant, uniform cross-browser split method
29 */
57 */
30
58
31 /**
59 /**
32 * Splits a string into an array of strings using a regex or string
60 * Splits a string into an array of strings using a regex or string
33 * separator. Matches of the separator are not included in the result array.
61 * separator. Matches of the separator are not included in the result array.
34 * However, if `separator` is a regex that contains capturing groups,
62 * However, if `separator` is a regex that contains capturing groups,
35 * backreferences are spliced into the result each time `separator` is
63 * backreferences are spliced into the result each time `separator` is
36 * matched. Fixes browser bugs compared to the native
64 * matched. Fixes browser bugs compared to the native
37 * `String.prototype.split` and can be used reliably cross-browser.
65 * `String.prototype.split` and can be used reliably cross-browser.
38 * @param {String} str String to split.
66 * @param {String} str String to split.
39 * @param {RegExp|String} separator Regex or string to use for separating
67 * @param {RegExp|String} separator Regex or string to use for separating
40 * the string.
68 * the string.
41 * @param {Number} [limit] Maximum number of items to include in the result
69 * @param {Number} [limit] Maximum number of items to include in the result
42 * array.
70 * array.
43 * @returns {Array} Array of substrings.
71 * @returns {Array} Array of substrings.
44 * @example
72 * @example
45 *
73 *
46 * // Basic use
74 * // Basic use
47 * regex_split('a b c d', ' ');
75 * regex_split('a b c d', ' ');
48 * // -> ['a', 'b', 'c', 'd']
76 * // -> ['a', 'b', 'c', 'd']
49 *
77 *
50 * // With limit
78 * // With limit
51 * regex_split('a b c d', ' ', 2);
79 * regex_split('a b c d', ' ', 2);
52 * // -> ['a', 'b']
80 * // -> ['a', 'b']
53 *
81 *
54 * // Backreferences in result array
82 * // Backreferences in result array
55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
83 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
84 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
57 */
85 */
58 var regex_split = function (str, separator, limit) {
86 var regex_split = function (str, separator, limit) {
59 // If `separator` is not a regex, use `split`
87 // If `separator` is not a regex, use `split`
60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
88 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
61 return split.call(str, separator, limit);
89 return split.call(str, separator, limit);
62 }
90 }
63 var output = [],
91 var output = [],
64 flags = (separator.ignoreCase ? "i" : "") +
92 flags = (separator.ignoreCase ? "i" : "") +
65 (separator.multiline ? "m" : "") +
93 (separator.multiline ? "m" : "") +
66 (separator.extended ? "x" : "") + // Proposed for ES6
94 (separator.extended ? "x" : "") + // Proposed for ES6
67 (separator.sticky ? "y" : ""), // Firefox 3+
95 (separator.sticky ? "y" : ""), // Firefox 3+
68 lastLastIndex = 0,
96 lastLastIndex = 0,
69 // Make `global` and avoid `lastIndex` issues by working with a copy
97 // Make `global` and avoid `lastIndex` issues by working with a copy
70 separator = new RegExp(separator.source, flags + "g"),
98 separator = new RegExp(separator.source, flags + "g"),
71 separator2, match, lastIndex, lastLength;
99 separator2, match, lastIndex, lastLength;
72 str += ""; // Type-convert
100 str += ""; // Type-convert
73
101
74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
102 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
75 if (!compliantExecNpcg) {
103 if (!compliantExecNpcg) {
76 // Doesn't need flags gy, but they don't hurt
104 // Doesn't need flags gy, but they don't hurt
77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
105 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
78 }
106 }
79 /* Values for `limit`, per the spec:
107 /* Values for `limit`, per the spec:
80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
108 * If undefined: 4294967295 // Math.pow(2, 32) - 1
81 * If 0, Infinity, or NaN: 0
109 * If 0, Infinity, or NaN: 0
82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
110 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
111 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
84 * If other: Type-convert, then use the above rules
112 * If other: Type-convert, then use the above rules
85 */
113 */
86 limit = typeof(limit) === "undefined" ?
114 limit = typeof(limit) === "undefined" ?
87 -1 >>> 0 : // Math.pow(2, 32) - 1
115 -1 >>> 0 : // Math.pow(2, 32) - 1
88 limit >>> 0; // ToUint32(limit)
116 limit >>> 0; // ToUint32(limit)
89 while (match = separator.exec(str)) {
117 while (match = separator.exec(str)) {
90 // `separator.lastIndex` is not reliable cross-browser
118 // `separator.lastIndex` is not reliable cross-browser
91 lastIndex = match.index + match[0].length;
119 lastIndex = match.index + match[0].length;
92 if (lastIndex > lastLastIndex) {
120 if (lastIndex > lastLastIndex) {
93 output.push(str.slice(lastLastIndex, match.index));
121 output.push(str.slice(lastLastIndex, match.index));
94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
122 // Fix browsers whose `exec` methods don't consistently return `undefined` for
95 // nonparticipating capturing groups
123 // nonparticipating capturing groups
96 if (!compliantExecNpcg && match.length > 1) {
124 if (!compliantExecNpcg && match.length > 1) {
97 match[0].replace(separator2, function () {
125 match[0].replace(separator2, function () {
98 for (var i = 1; i < arguments.length - 2; i++) {
126 for (var i = 1; i < arguments.length - 2; i++) {
99 if (typeof(arguments[i]) === "undefined") {
127 if (typeof(arguments[i]) === "undefined") {
100 match[i] = undefined;
128 match[i] = undefined;
101 }
129 }
102 }
130 }
103 });
131 });
104 }
132 }
105 if (match.length > 1 && match.index < str.length) {
133 if (match.length > 1 && match.index < str.length) {
106 Array.prototype.push.apply(output, match.slice(1));
134 Array.prototype.push.apply(output, match.slice(1));
107 }
135 }
108 lastLength = match[0].length;
136 lastLength = match[0].length;
109 lastLastIndex = lastIndex;
137 lastLastIndex = lastIndex;
110 if (output.length >= limit) {
138 if (output.length >= limit) {
111 break;
139 break;
112 }
140 }
113 }
141 }
114 if (separator.lastIndex === match.index) {
142 if (separator.lastIndex === match.index) {
115 separator.lastIndex++; // Avoid an infinite loop
143 separator.lastIndex++; // Avoid an infinite loop
116 }
144 }
117 }
145 }
118 if (lastLastIndex === str.length) {
146 if (lastLastIndex === str.length) {
119 if (lastLength || !separator.test("")) {
147 if (lastLength || !separator.test("")) {
120 output.push("");
148 output.push("");
121 }
149 }
122 } else {
150 } else {
123 output.push(str.slice(lastLastIndex));
151 output.push(str.slice(lastLastIndex));
124 }
152 }
125 return output.length > limit ? output.slice(0, limit) : output;
153 return output.length > limit ? output.slice(0, limit) : output;
126 };
154 };
127
155
128 //============================================================================
156 //============================================================================
129 // End contributed Cross-browser RegEx Split
157 // End contributed Cross-browser RegEx Split
130 //============================================================================
158 //============================================================================
131
159
132
160
133 var uuid = function () {
161 var uuid = function () {
134 // http://www.ietf.org/rfc/rfc4122.txt
162 // http://www.ietf.org/rfc/rfc4122.txt
135 var s = [];
163 var s = [];
136 var hexDigits = "0123456789ABCDEF";
164 var hexDigits = "0123456789ABCDEF";
137 for (var i = 0; i < 32; i++) {
165 for (var i = 0; i < 32; i++) {
138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
166 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
139 }
167 }
140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
168 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
141 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
169 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
142
170
143 var uuid = s.join("");
171 var uuid = s.join("");
144 return uuid;
172 return uuid;
145 };
173 };
146
174
147
175
148 //Fix raw text to parse correctly in crazy XML
176 //Fix raw text to parse correctly in crazy XML
149 function xmlencode(string) {
177 function xmlencode(string) {
150 return string.replace(/\&/g,'&'+'amp;')
178 return string.replace(/\&/g,'&'+'amp;')
151 .replace(/</g,'&'+'lt;')
179 .replace(/</g,'&'+'lt;')
152 .replace(/>/g,'&'+'gt;')
180 .replace(/>/g,'&'+'gt;')
153 .replace(/\'/g,'&'+'apos;')
181 .replace(/\'/g,'&'+'apos;')
154 .replace(/\"/g,'&'+'quot;')
182 .replace(/\"/g,'&'+'quot;')
155 .replace(/`/g,'&'+'#96;');
183 .replace(/`/g,'&'+'#96;');
156 }
184 }
157
185
158
186
159 //Map from terminal commands to CSS classes
187 //Map from terminal commands to CSS classes
160 var ansi_colormap = {
188 var ansi_colormap = {
161 "01":"ansibold",
189 "01":"ansibold",
162
190
163 "30":"ansiblack",
191 "30":"ansiblack",
164 "31":"ansired",
192 "31":"ansired",
165 "32":"ansigreen",
193 "32":"ansigreen",
166 "33":"ansiyellow",
194 "33":"ansiyellow",
167 "34":"ansiblue",
195 "34":"ansiblue",
168 "35":"ansipurple",
196 "35":"ansipurple",
169 "36":"ansicyan",
197 "36":"ansicyan",
170 "37":"ansigray",
198 "37":"ansigray",
171
199
172 "40":"ansibgblack",
200 "40":"ansibgblack",
173 "41":"ansibgred",
201 "41":"ansibgred",
174 "42":"ansibggreen",
202 "42":"ansibggreen",
175 "43":"ansibgyellow",
203 "43":"ansibgyellow",
176 "44":"ansibgblue",
204 "44":"ansibgblue",
177 "45":"ansibgpurple",
205 "45":"ansibgpurple",
178 "46":"ansibgcyan",
206 "46":"ansibgcyan",
179 "47":"ansibggray"
207 "47":"ansibggray"
180 };
208 };
181
209
182 function _process_numbers(attrs, numbers) {
210 function _process_numbers(attrs, numbers) {
183 // process ansi escapes
211 // process ansi escapes
184 var n = numbers.shift();
212 var n = numbers.shift();
185 if (ansi_colormap[n]) {
213 if (ansi_colormap[n]) {
186 if ( ! attrs["class"] ) {
214 if ( ! attrs["class"] ) {
187 attrs["class"] = ansi_colormap[n];
215 attrs["class"] = ansi_colormap[n];
188 } else {
216 } else {
189 attrs["class"] += " " + ansi_colormap[n];
217 attrs["class"] += " " + ansi_colormap[n];
190 }
218 }
191 } else if (n == "38" || n == "48") {
219 } else if (n == "38" || n == "48") {
192 // VT100 256 color or 24 bit RGB
220 // VT100 256 color or 24 bit RGB
193 if (numbers.length < 2) {
221 if (numbers.length < 2) {
194 console.log("Not enough fields for VT100 color", numbers);
222 console.log("Not enough fields for VT100 color", numbers);
195 return;
223 return;
196 }
224 }
197
225
198 var index_or_rgb = numbers.shift();
226 var index_or_rgb = numbers.shift();
199 var r,g,b;
227 var r,g,b;
200 if (index_or_rgb == "5") {
228 if (index_or_rgb == "5") {
201 // 256 color
229 // 256 color
202 var idx = parseInt(numbers.shift());
230 var idx = parseInt(numbers.shift());
203 if (idx < 16) {
231 if (idx < 16) {
204 // indexed ANSI
232 // indexed ANSI
205 // ignore bright / non-bright distinction
233 // ignore bright / non-bright distinction
206 idx = idx % 8;
234 idx = idx % 8;
207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
235 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
208 if ( ! attrs["class"] ) {
236 if ( ! attrs["class"] ) {
209 attrs["class"] = ansiclass;
237 attrs["class"] = ansiclass;
210 } else {
238 } else {
211 attrs["class"] += " " + ansiclass;
239 attrs["class"] += " " + ansiclass;
212 }
240 }
213 return;
241 return;
214 } else if (idx < 232) {
242 } else if (idx < 232) {
215 // 216 color 6x6x6 RGB
243 // 216 color 6x6x6 RGB
216 idx = idx - 16;
244 idx = idx - 16;
217 b = idx % 6;
245 b = idx % 6;
218 g = Math.floor(idx / 6) % 6;
246 g = Math.floor(idx / 6) % 6;
219 r = Math.floor(idx / 36) % 6;
247 r = Math.floor(idx / 36) % 6;
220 // convert to rgb
248 // convert to rgb
221 r = (r * 51);
249 r = (r * 51);
222 g = (g * 51);
250 g = (g * 51);
223 b = (b * 51);
251 b = (b * 51);
224 } else {
252 } else {
225 // grayscale
253 // grayscale
226 idx = idx - 231;
254 idx = idx - 231;
227 // it's 1-24 and should *not* include black or white,
255 // it's 1-24 and should *not* include black or white,
228 // so a 26 point scale
256 // so a 26 point scale
229 r = g = b = Math.floor(idx * 256 / 26);
257 r = g = b = Math.floor(idx * 256 / 26);
230 }
258 }
231 } else if (index_or_rgb == "2") {
259 } else if (index_or_rgb == "2") {
232 // Simple 24 bit RGB
260 // Simple 24 bit RGB
233 if (numbers.length > 3) {
261 if (numbers.length > 3) {
234 console.log("Not enough fields for RGB", numbers);
262 console.log("Not enough fields for RGB", numbers);
235 return;
263 return;
236 }
264 }
237 r = numbers.shift();
265 r = numbers.shift();
238 g = numbers.shift();
266 g = numbers.shift();
239 b = numbers.shift();
267 b = numbers.shift();
240 } else {
268 } else {
241 console.log("unrecognized control", numbers);
269 console.log("unrecognized control", numbers);
242 return;
270 return;
243 }
271 }
244 if (r !== undefined) {
272 if (r !== undefined) {
245 // apply the rgb color
273 // apply the rgb color
246 var line;
274 var line;
247 if (n == "38") {
275 if (n == "38") {
248 line = "color: ";
276 line = "color: ";
249 } else {
277 } else {
250 line = "background-color: ";
278 line = "background-color: ";
251 }
279 }
252 line = line + "rgb(" + r + "," + g + "," + b + ");"
280 line = line + "rgb(" + r + "," + g + "," + b + ");"
253 if ( !attrs["style"] ) {
281 if ( !attrs["style"] ) {
254 attrs["style"] = line;
282 attrs["style"] = line;
255 } else {
283 } else {
256 attrs["style"] += " " + line;
284 attrs["style"] += " " + line;
257 }
285 }
258 }
286 }
259 }
287 }
260 }
288 }
261
289
262 function ansispan(str) {
290 function ansispan(str) {
263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
291 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
264 // regular ansi escapes (using the table above)
292 // regular ansi escapes (using the table above)
265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
293 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
266 if (!pattern) {
294 if (!pattern) {
267 // [(01|22|39|)m close spans
295 // [(01|22|39|)m close spans
268 return "</span>";
296 return "</span>";
269 }
297 }
270 // consume sequence of color escapes
298 // consume sequence of color escapes
271 var numbers = pattern.match(/\d+/g);
299 var numbers = pattern.match(/\d+/g);
272 var attrs = {};
300 var attrs = {};
273 while (numbers.length > 0) {
301 while (numbers.length > 0) {
274 _process_numbers(attrs, numbers);
302 _process_numbers(attrs, numbers);
275 }
303 }
276
304
277 var span = "<span ";
305 var span = "<span ";
278 for (var attr in attrs) {
306 for (var attr in attrs) {
279 var value = attrs[attr];
307 var value = attrs[attr];
280 span = span + " " + attr + '="' + attrs[attr] + '"';
308 span = span + " " + attr + '="' + attrs[attr] + '"';
281 }
309 }
282 return span + ">";
310 return span + ">";
283 });
311 });
284 };
312 };
285
313
286 // Transform ANSI color escape codes into HTML <span> tags with css
314 // Transform ANSI color escape codes into HTML <span> tags with css
287 // classes listed in the above ansi_colormap object. The actual color used
315 // classes listed in the above ansi_colormap object. The actual color used
288 // are set in the css file.
316 // are set in the css file.
289 function fixConsole(txt) {
317 function fixConsole(txt) {
290 txt = xmlencode(txt);
318 txt = xmlencode(txt);
291 var re = /\033\[([\dA-Fa-f;]*?)m/;
319 var re = /\033\[([\dA-Fa-f;]*?)m/;
292 var opened = false;
320 var opened = false;
293 var cmds = [];
321 var cmds = [];
294 var opener = "";
322 var opener = "";
295 var closer = "";
323 var closer = "";
296
324
297 // Strip all ANSI codes that are not color related. Matches
325 // Strip all ANSI codes that are not color related. Matches
298 // all ANSI codes that do not end with "m".
326 // all ANSI codes that do not end with "m".
299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
327 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
300 txt = txt.replace(ignored_re, "");
328 txt = txt.replace(ignored_re, "");
301
329
302 // color ansi codes
330 // color ansi codes
303 txt = ansispan(txt);
331 txt = ansispan(txt);
304 return txt;
332 return txt;
305 }
333 }
306
334
307 // Remove chunks that should be overridden by the effect of
335 // Remove chunks that should be overridden by the effect of
308 // carriage return characters
336 // carriage return characters
309 function fixCarriageReturn(txt) {
337 function fixCarriageReturn(txt) {
310 var tmp = txt;
338 var tmp = txt;
311 do {
339 do {
312 txt = tmp;
340 txt = tmp;
313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
341 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
342 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
315 } while (tmp.length < txt.length);
343 } while (tmp.length < txt.length);
316 return txt;
344 return txt;
317 }
345 }
318
346
319 // Locate any URLs and convert them to a anchor tag
347 // Locate any URLs and convert them to a anchor tag
320 function autoLinkUrls(txt) {
348 function autoLinkUrls(txt) {
321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
349 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
350 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
323 }
351 }
324
352
325 // some keycodes that seem to be platform/browser independent
353 // some keycodes that seem to be platform/browser independent
326 var keycodes = {
354 var keycodes = {
327 BACKSPACE: 8,
355 BACKSPACE: 8,
328 TAB : 9,
356 TAB : 9,
329 ENTER : 13,
357 ENTER : 13,
330 SHIFT : 16,
358 SHIFT : 16,
331 CTRL : 17,
359 CTRL : 17,
332 CONTROL : 17,
360 CONTROL : 17,
333 ALT : 18,
361 ALT : 18,
334 CAPS_LOCK: 20,
362 CAPS_LOCK: 20,
335 ESC : 27,
363 ESC : 27,
336 SPACE : 32,
364 SPACE : 32,
337 PGUP : 33,
365 PGUP : 33,
338 PGDOWN : 34,
366 PGDOWN : 34,
339 END : 35,
367 END : 35,
340 HOME : 36,
368 HOME : 36,
341 LEFT_ARROW: 37,
369 LEFT_ARROW: 37,
342 LEFTARROW: 37,
370 LEFTARROW: 37,
343 LEFT : 37,
371 LEFT : 37,
344 UP_ARROW : 38,
372 UP_ARROW : 38,
345 UPARROW : 38,
373 UPARROW : 38,
346 UP : 38,
374 UP : 38,
347 RIGHT_ARROW:39,
375 RIGHT_ARROW:39,
348 RIGHTARROW:39,
376 RIGHTARROW:39,
349 RIGHT : 39,
377 RIGHT : 39,
350 DOWN_ARROW: 40,
378 DOWN_ARROW: 40,
351 DOWNARROW: 40,
379 DOWNARROW: 40,
352 DOWN : 40,
380 DOWN : 40,
353 I : 73,
381 I : 73,
354 M : 77,
382 M : 77,
355 // all three of these keys may be COMMAND on OS X:
383 // all three of these keys may be COMMAND on OS X:
356 LEFT_SUPER : 91,
384 LEFT_SUPER : 91,
357 RIGHT_SUPER : 92,
385 RIGHT_SUPER : 92,
358 COMMAND : 93,
386 COMMAND : 93,
359 };
387 };
360
388
361 // trigger a key press event
389 // trigger a key press event
362 var press = function (key) {
390 var press = function (key) {
363 var key_press = $.Event('keydown', {which: key});
391 var key_press = $.Event('keydown', {which: key});
364 $(document).trigger(key_press);
392 $(document).trigger(key_press);
365 }
393 }
366
394
367 var press_up = function() { press(keycodes.UP); };
395 var press_up = function() { press(keycodes.UP); };
368 var press_down = function() { press(keycodes.DOWN); };
396 var press_down = function() { press(keycodes.DOWN); };
369
397
370 var press_ctrl_enter = function() {
398 var press_ctrl_enter = function() {
371 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
399 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
372 };
400 };
373
401
374 var press_shift_enter = function() {
402 var press_shift_enter = function() {
375 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
403 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
376 };
404 };
377
405
378 // trigger the ctrl-m shortcut followed by one of our keys
406 // trigger the ctrl-m shortcut followed by one of our keys
379 var press_ghetto = function(key) {
407 var press_ghetto = function(key) {
380 $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true}));
408 $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true}));
381 press(key);
409 press(key);
382 };
410 };
383
411
384
412
385 var points_to_pixels = function (points) {
413 var points_to_pixels = function (points) {
386 // A reasonably good way of converting between points and pixels.
414 // A reasonably good way of converting between points and pixels.
387 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
415 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
388 $(body).append(test);
416 $(body).append(test);
389 var pixel_per_point = test.width()/10000;
417 var pixel_per_point = test.width()/10000;
390 test.remove();
418 test.remove();
391 return Math.floor(points*pixel_per_point);
419 return Math.floor(points*pixel_per_point);
392 };
420 };
393
421
394 var always_new = function (constructor) {
422 var always_new = function (constructor) {
395 // wrapper around contructor to avoid requiring `var a = new constructor()`
423 // wrapper around contructor to avoid requiring `var a = new constructor()`
396 // useful for passing constructors as callbacks,
424 // useful for passing constructors as callbacks,
397 // not for programmer laziness.
425 // not for programmer laziness.
398 // from http://programmers.stackexchange.com/questions/118798
426 // from http://programmers.stackexchange.com/questions/118798
399 return function () {
427 return function () {
400 var obj = Object.create(constructor.prototype);
428 var obj = Object.create(constructor.prototype);
401 constructor.apply(obj, arguments);
429 constructor.apply(obj, arguments);
402 return obj;
430 return obj;
403 };
431 };
404 };
432 };
405
433
406
434
407 var url_path_join = function () {
435 var url_path_join = function () {
408 // join a sequence of url components with '/'
436 // join a sequence of url components with '/'
409 var url = '';
437 var url = '';
410 for (var i = 0; i < arguments.length; i++) {
438 for (var i = 0; i < arguments.length; i++) {
411 if (arguments[i] === '') {
439 if (arguments[i] === '') {
412 continue;
440 continue;
413 }
441 }
414 if (url.length > 0 && url[url.length-1] != '/') {
442 if (url.length > 0 && url[url.length-1] != '/') {
415 url = url + '/' + arguments[i];
443 url = url + '/' + arguments[i];
416 } else {
444 } else {
417 url = url + arguments[i];
445 url = url + arguments[i];
418 }
446 }
419 }
447 }
420 url = url.replace(/\/\/+/, '/');
448 url = url.replace(/\/\/+/, '/');
421 return url;
449 return url;
422 };
450 };
423
451
424 var parse_url = function (url) {
452 var parse_url = function (url) {
425 // an `a` element with an href allows attr-access to the parsed segments of a URL
453 // an `a` element with an href allows attr-access to the parsed segments of a URL
426 // a = parse_url("http://localhost:8888/path/name#hash")
454 // a = parse_url("http://localhost:8888/path/name#hash")
427 // a.protocol = "http:"
455 // a.protocol = "http:"
428 // a.host = "localhost:8888"
456 // a.host = "localhost:8888"
429 // a.hostname = "localhost"
457 // a.hostname = "localhost"
430 // a.port = 8888
458 // a.port = 8888
431 // a.pathname = "/path/name"
459 // a.pathname = "/path/name"
432 // a.hash = "#hash"
460 // a.hash = "#hash"
433 var a = document.createElement("a");
461 var a = document.createElement("a");
434 a.href = url;
462 a.href = url;
435 return a;
463 return a;
436 };
464 };
437
465
438 var encode_uri_components = function (uri) {
466 var encode_uri_components = function (uri) {
439 // encode just the components of a multi-segment uri,
467 // encode just the components of a multi-segment uri,
440 // leaving '/' separators
468 // leaving '/' separators
441 return uri.split('/').map(encodeURIComponent).join('/');
469 return uri.split('/').map(encodeURIComponent).join('/');
442 };
470 };
443
471
444 var url_join_encode = function () {
472 var url_join_encode = function () {
445 // join a sequence of url components with '/',
473 // join a sequence of url components with '/',
446 // encoding each component with encodeURIComponent
474 // encoding each component with encodeURIComponent
447 return encode_uri_components(url_path_join.apply(null, arguments));
475 return encode_uri_components(url_path_join.apply(null, arguments));
448 };
476 };
449
477
450
478
451 var splitext = function (filename) {
479 var splitext = function (filename) {
452 // mimic Python os.path.splitext
480 // mimic Python os.path.splitext
453 // Returns ['base', '.ext']
481 // Returns ['base', '.ext']
454 var idx = filename.lastIndexOf('.');
482 var idx = filename.lastIndexOf('.');
455 if (idx > 0) {
483 if (idx > 0) {
456 return [filename.slice(0, idx), filename.slice(idx)];
484 return [filename.slice(0, idx), filename.slice(idx)];
457 } else {
485 } else {
458 return [filename, ''];
486 return [filename, ''];
459 }
487 }
460 };
488 };
461
489
462
490
463 var get_body_data = function(key) {
491 var get_body_data = function(key) {
464 // get a url-encoded item from body.data and decode it
492 // get a url-encoded item from body.data and decode it
465 // we should never have any encoded URLs anywhere else in code
493 // we should never have any encoded URLs anywhere else in code
466 // until we are building an actual request
494 // until we are building an actual request
467 return decodeURIComponent($('body').data(key));
495 return decodeURIComponent($('body').data(key));
468 };
496 };
469
497
470
498
471 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
499 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
472 var browser = (function() {
500 var browser = (function() {
473 if (typeof navigator === 'undefined') {
501 if (typeof navigator === 'undefined') {
474 // navigator undefined in node
502 // navigator undefined in node
475 return 'None';
503 return 'None';
476 }
504 }
477 var N= navigator.appName, ua= navigator.userAgent, tem;
505 var N= navigator.appName, ua= navigator.userAgent, tem;
478 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
506 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
479 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
507 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
480 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
508 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
481 return M;
509 return M;
482 })();
510 })();
483
511
484 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
512 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
485 var platform = (function () {
513 var platform = (function () {
486 if (typeof navigator === 'undefined') {
514 if (typeof navigator === 'undefined') {
487 // navigator undefined in node
515 // navigator undefined in node
488 return 'None';
516 return 'None';
489 }
517 }
490 var OSName="None";
518 var OSName="None";
491 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
519 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
492 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
520 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
493 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
521 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
494 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
522 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
495 return OSName
523 return OSName
496 })();
524 })();
497
525
498 var is_or_has = function (a, b) {
526 var is_or_has = function (a, b) {
499 // Is b a child of a or a itself?
527 // Is b a child of a or a itself?
500 return a.has(b).length !==0 || a.is(b);
528 return a.has(b).length !==0 || a.is(b);
501 }
529 }
502
530
503 var is_focused = function (e) {
531 var is_focused = function (e) {
504 // Is element e, or one of its children focused?
532 // Is element e, or one of its children focused?
505 e = $(e);
533 e = $(e);
506 var target = $(document.activeElement);
534 var target = $(document.activeElement);
507 if (target.length > 0) {
535 if (target.length > 0) {
508 if (is_or_has(e, target)) {
536 if (is_or_has(e, target)) {
509 return true;
537 return true;
510 } else {
538 } else {
511 return false;
539 return false;
512 }
540 }
513 } else {
541 } else {
514 return false;
542 return false;
515 }
543 }
516 }
544 }
517
545
518
546
519 return {
547 return {
520 regex_split : regex_split,
548 regex_split : regex_split,
521 uuid : uuid,
549 uuid : uuid,
522 fixConsole : fixConsole,
550 fixConsole : fixConsole,
523 keycodes : keycodes,
551 keycodes : keycodes,
524 press : press,
552 press : press,
525 press_up : press_up,
553 press_up : press_up,
526 press_down : press_down,
554 press_down : press_down,
527 press_ctrl_enter : press_ctrl_enter,
555 press_ctrl_enter : press_ctrl_enter,
528 press_shift_enter : press_shift_enter,
556 press_shift_enter : press_shift_enter,
529 press_ghetto : press_ghetto,
557 press_ghetto : press_ghetto,
530 fixCarriageReturn : fixCarriageReturn,
558 fixCarriageReturn : fixCarriageReturn,
531 autoLinkUrls : autoLinkUrls,
559 autoLinkUrls : autoLinkUrls,
532 points_to_pixels : points_to_pixels,
560 points_to_pixels : points_to_pixels,
533 get_body_data : get_body_data,
561 get_body_data : get_body_data,
534 parse_url : parse_url,
562 parse_url : parse_url,
535 url_path_join : url_path_join,
563 url_path_join : url_path_join,
536 url_join_encode : url_join_encode,
564 url_join_encode : url_join_encode,
537 encode_uri_components : encode_uri_components,
565 encode_uri_components : encode_uri_components,
538 splitext : splitext,
566 splitext : splitext,
539 always_new : always_new,
567 always_new : always_new,
540 browser : browser,
568 browser : browser,
541 platform: platform,
569 platform: platform,
542 is_or_has : is_or_has,
570 is_or_has : is_or_has,
543 is_focused : is_focused
571 is_focused : is_focused
544 };
572 };
545
573
546 }(IPython));
574 }(IPython));
547
575
@@ -1,391 +1,395 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27 from __future__ import print_function
27 from __future__ import print_function
28
28
29 import logging
29 import logging
30 import os
30 import os
31 import sys
31 import sys
32
32
33 from IPython.config.loader import (
33 from IPython.config.loader import (
34 Config, PyFileConfigLoader, ConfigFileNotFound
34 Config, PyFileConfigLoader, ConfigFileNotFound
35 )
35 )
36 from IPython.config.application import boolean_flag, catch_config_error, Application
36 from IPython.config.application import boolean_flag, catch_config_error, Application
37 from IPython.core import release
37 from IPython.core import release
38 from IPython.core import usage
38 from IPython.core import usage
39 from IPython.core.completer import IPCompleter
39 from IPython.core.completer import IPCompleter
40 from IPython.core.crashhandler import CrashHandler
40 from IPython.core.crashhandler import CrashHandler
41 from IPython.core.formatters import PlainTextFormatter
41 from IPython.core.formatters import PlainTextFormatter
42 from IPython.core.history import HistoryManager
42 from IPython.core.history import HistoryManager
43 from IPython.core.prompts import PromptManager
43 from IPython.core.prompts import PromptManager
44 from IPython.core.application import (
44 from IPython.core.application import (
45 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
45 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
46 )
46 )
47 from IPython.core.magics import ScriptMagics
47 from IPython.core.magics import ScriptMagics
48 from IPython.core.shellapp import (
48 from IPython.core.shellapp import (
49 InteractiveShellApp, shell_flags, shell_aliases
49 InteractiveShellApp, shell_flags, shell_aliases
50 )
50 )
51 from IPython.extensions.storemagic import StoreMagics
51 from IPython.extensions.storemagic import StoreMagics
52 from IPython.terminal.interactiveshell import TerminalInteractiveShell
52 from IPython.terminal.interactiveshell import TerminalInteractiveShell
53 from IPython.utils import warn
53 from IPython.utils import warn
54 from IPython.utils.path import get_ipython_dir, check_for_old_config
54 from IPython.utils.path import get_ipython_dir, check_for_old_config
55 from IPython.utils.traitlets import (
55 from IPython.utils.traitlets import (
56 Bool, List, Dict,
56 Bool, List, Dict,
57 )
57 )
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals, utilities and helpers
60 # Globals, utilities and helpers
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63 _examples = """
63 _examples = """
64 ipython --matplotlib # enable matplotlib integration
64 ipython --matplotlib # enable matplotlib integration
65 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
65 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
66
66
67 ipython --log-level=DEBUG # set logging to DEBUG
67 ipython --log-level=DEBUG # set logging to DEBUG
68 ipython --profile=foo # start with profile foo
68 ipython --profile=foo # start with profile foo
69
69
70 ipython qtconsole # start the qtconsole GUI application
70 ipython qtconsole # start the qtconsole GUI application
71 ipython help qtconsole # show the help for the qtconsole subcmd
71 ipython help qtconsole # show the help for the qtconsole subcmd
72
72
73 ipython console # start the terminal-based console application
73 ipython console # start the terminal-based console application
74 ipython help console # show the help for the console subcmd
74 ipython help console # show the help for the console subcmd
75
75
76 ipython notebook # start the IPython notebook
76 ipython notebook # start the IPython notebook
77 ipython help notebook # show the help for the notebook subcmd
77 ipython help notebook # show the help for the notebook subcmd
78
78
79 ipython profile create foo # create profile foo w/ default config files
79 ipython profile create foo # create profile foo w/ default config files
80 ipython help profile # show the help for the profile subcmd
80 ipython help profile # show the help for the profile subcmd
81
81
82 ipython locate # print the path to the IPython directory
82 ipython locate # print the path to the IPython directory
83 ipython locate profile foo # print the path to the directory for profile `foo`
83 ipython locate profile foo # print the path to the directory for profile `foo`
84
84
85 ipython nbconvert # convert notebooks to/from other formats
85 ipython nbconvert # convert notebooks to/from other formats
86 """
86 """
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Crash handler for this application
89 # Crash handler for this application
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 class IPAppCrashHandler(CrashHandler):
92 class IPAppCrashHandler(CrashHandler):
93 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
93 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
94
94
95 def __init__(self, app):
95 def __init__(self, app):
96 contact_name = release.author
96 contact_name = release.author
97 contact_email = release.author_email
97 contact_email = release.author_email
98 bug_tracker = 'https://github.com/ipython/ipython/issues'
98 bug_tracker = 'https://github.com/ipython/ipython/issues'
99 super(IPAppCrashHandler,self).__init__(
99 super(IPAppCrashHandler,self).__init__(
100 app, contact_name, contact_email, bug_tracker
100 app, contact_name, contact_email, bug_tracker
101 )
101 )
102
102
103 def make_report(self,traceback):
103 def make_report(self,traceback):
104 """Return a string containing a crash report."""
104 """Return a string containing a crash report."""
105
105
106 sec_sep = self.section_sep
106 sec_sep = self.section_sep
107 # Start with parent report
107 # Start with parent report
108 report = [super(IPAppCrashHandler, self).make_report(traceback)]
108 report = [super(IPAppCrashHandler, self).make_report(traceback)]
109 # Add interactive-specific info we may have
109 # Add interactive-specific info we may have
110 rpt_add = report.append
110 rpt_add = report.append
111 try:
111 try:
112 rpt_add(sec_sep+"History of session input:")
112 rpt_add(sec_sep+"History of session input:")
113 for line in self.app.shell.user_ns['_ih']:
113 for line in self.app.shell.user_ns['_ih']:
114 rpt_add(line)
114 rpt_add(line)
115 rpt_add('\n*** Last line of input (may not be in above history):\n')
115 rpt_add('\n*** Last line of input (may not be in above history):\n')
116 rpt_add(self.app.shell._last_input_line+'\n')
116 rpt_add(self.app.shell._last_input_line+'\n')
117 except:
117 except:
118 pass
118 pass
119
119
120 return ''.join(report)
120 return ''.join(report)
121
121
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123 # Aliases and Flags
123 # Aliases and Flags
124 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
125 flags = dict(base_flags)
125 flags = dict(base_flags)
126 flags.update(shell_flags)
126 flags.update(shell_flags)
127 frontend_flags = {}
127 frontend_flags = {}
128 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
128 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
129 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
129 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
130 'Turn on auto editing of files with syntax errors.',
130 'Turn on auto editing of files with syntax errors.',
131 'Turn off auto editing of files with syntax errors.'
131 'Turn off auto editing of files with syntax errors.'
132 )
132 )
133 addflag('banner', 'TerminalIPythonApp.display_banner',
133 addflag('banner', 'TerminalIPythonApp.display_banner',
134 "Display a banner upon starting IPython.",
134 "Display a banner upon starting IPython.",
135 "Don't display a banner upon starting IPython."
135 "Don't display a banner upon starting IPython."
136 )
136 )
137 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
137 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
138 """Set to confirm when you try to exit IPython with an EOF (Control-D
138 """Set to confirm when you try to exit IPython with an EOF (Control-D
139 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
139 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
140 you can force a direct exit without any confirmation.""",
140 you can force a direct exit without any confirmation.""",
141 "Don't prompt the user when exiting."
141 "Don't prompt the user when exiting."
142 )
142 )
143 addflag('term-title', 'TerminalInteractiveShell.term_title',
143 addflag('term-title', 'TerminalInteractiveShell.term_title',
144 "Enable auto setting the terminal title.",
144 "Enable auto setting the terminal title.",
145 "Disable auto setting the terminal title."
145 "Disable auto setting the terminal title."
146 )
146 )
147 classic_config = Config()
147 classic_config = Config()
148 classic_config.InteractiveShell.cache_size = 0
148 classic_config.InteractiveShell.cache_size = 0
149 classic_config.PlainTextFormatter.pprint = False
149 classic_config.PlainTextFormatter.pprint = False
150 classic_config.PromptManager.in_template = '>>> '
150 classic_config.PromptManager.in_template = '>>> '
151 classic_config.PromptManager.in2_template = '... '
151 classic_config.PromptManager.in2_template = '... '
152 classic_config.PromptManager.out_template = ''
152 classic_config.PromptManager.out_template = ''
153 classic_config.InteractiveShell.separate_in = ''
153 classic_config.InteractiveShell.separate_in = ''
154 classic_config.InteractiveShell.separate_out = ''
154 classic_config.InteractiveShell.separate_out = ''
155 classic_config.InteractiveShell.separate_out2 = ''
155 classic_config.InteractiveShell.separate_out2 = ''
156 classic_config.InteractiveShell.colors = 'NoColor'
156 classic_config.InteractiveShell.colors = 'NoColor'
157 classic_config.InteractiveShell.xmode = 'Plain'
157 classic_config.InteractiveShell.xmode = 'Plain'
158
158
159 frontend_flags['classic']=(
159 frontend_flags['classic']=(
160 classic_config,
160 classic_config,
161 "Gives IPython a similar feel to the classic Python prompt."
161 "Gives IPython a similar feel to the classic Python prompt."
162 )
162 )
163 # # log doesn't make so much sense this way anymore
163 # # log doesn't make so much sense this way anymore
164 # paa('--log','-l',
164 # paa('--log','-l',
165 # action='store_true', dest='InteractiveShell.logstart',
165 # action='store_true', dest='InteractiveShell.logstart',
166 # help="Start logging to the default log file (./ipython_log.py).")
166 # help="Start logging to the default log file (./ipython_log.py).")
167 #
167 #
168 # # quick is harder to implement
168 # # quick is harder to implement
169 frontend_flags['quick']=(
169 frontend_flags['quick']=(
170 {'TerminalIPythonApp' : {'quick' : True}},
170 {'TerminalIPythonApp' : {'quick' : True}},
171 "Enable quick startup with no config files."
171 "Enable quick startup with no config files."
172 )
172 )
173
173
174 frontend_flags['i'] = (
174 frontend_flags['i'] = (
175 {'TerminalIPythonApp' : {'force_interact' : True}},
175 {'TerminalIPythonApp' : {'force_interact' : True}},
176 """If running code from the command line, become interactive afterwards.
176 """If running code from the command line, become interactive afterwards.
177 Note: can also be given simply as '-i'."""
177 Note: can also be given simply as '-i'."""
178 )
178 )
179 flags.update(frontend_flags)
179 flags.update(frontend_flags)
180
180
181 aliases = dict(base_aliases)
181 aliases = dict(base_aliases)
182 aliases.update(shell_aliases)
182 aliases.update(shell_aliases)
183
183
184 #-----------------------------------------------------------------------------
184 #-----------------------------------------------------------------------------
185 # Main classes and functions
185 # Main classes and functions
186 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
187
187
188
188
189 class LocateIPythonApp(BaseIPythonApplication):
189 class LocateIPythonApp(BaseIPythonApplication):
190 description = """print the path to the IPython dir"""
190 description = """print the path to the IPython dir"""
191 subcommands = Dict(dict(
191 subcommands = Dict(dict(
192 profile=('IPython.core.profileapp.ProfileLocate',
192 profile=('IPython.core.profileapp.ProfileLocate',
193 "print the path to an IPython profile directory",
193 "print the path to an IPython profile directory",
194 ),
194 ),
195 ))
195 ))
196 def start(self):
196 def start(self):
197 if self.subapp is not None:
197 if self.subapp is not None:
198 return self.subapp.start()
198 return self.subapp.start()
199 else:
199 else:
200 print(self.ipython_dir)
200 print(self.ipython_dir)
201
201
202
202
203 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
203 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
204 name = u'ipython'
204 name = u'ipython'
205 description = usage.cl_usage
205 description = usage.cl_usage
206 crash_handler_class = IPAppCrashHandler
206 crash_handler_class = IPAppCrashHandler
207 examples = _examples
207 examples = _examples
208
208
209 flags = Dict(flags)
209 flags = Dict(flags)
210 aliases = Dict(aliases)
210 aliases = Dict(aliases)
211 classes = List()
211 classes = List()
212 def _classes_default(self):
212 def _classes_default(self):
213 """This has to be in a method, for TerminalIPythonApp to be available."""
213 """This has to be in a method, for TerminalIPythonApp to be available."""
214 return [
214 return [
215 InteractiveShellApp, # ShellApp comes before TerminalApp, because
215 InteractiveShellApp, # ShellApp comes before TerminalApp, because
216 self.__class__, # it will also affect subclasses (e.g. QtConsole)
216 self.__class__, # it will also affect subclasses (e.g. QtConsole)
217 TerminalInteractiveShell,
217 TerminalInteractiveShell,
218 PromptManager,
218 PromptManager,
219 HistoryManager,
219 HistoryManager,
220 ProfileDir,
220 ProfileDir,
221 PlainTextFormatter,
221 PlainTextFormatter,
222 IPCompleter,
222 IPCompleter,
223 ScriptMagics,
223 ScriptMagics,
224 StoreMagics,
224 StoreMagics,
225 ]
225 ]
226
226
227 subcommands = Dict(dict(
227 subcommands = dict(
228 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
228 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
229 """Launch the IPython Qt Console."""
229 """Launch the IPython Qt Console."""
230 ),
230 ),
231 notebook=('IPython.html.notebookapp.NotebookApp',
231 notebook=('IPython.html.notebookapp.NotebookApp',
232 """Launch the IPython HTML Notebook Server."""
232 """Launch the IPython HTML Notebook Server."""
233 ),
233 ),
234 profile = ("IPython.core.profileapp.ProfileApp",
234 profile = ("IPython.core.profileapp.ProfileApp",
235 "Create and manage IPython profiles."
235 "Create and manage IPython profiles."
236 ),
236 ),
237 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
237 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
238 "Start a kernel without an attached frontend."
238 "Start a kernel without an attached frontend."
239 ),
239 ),
240 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
240 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
241 """Launch the IPython terminal-based Console."""
241 """Launch the IPython terminal-based Console."""
242 ),
242 ),
243 locate=('IPython.terminal.ipapp.LocateIPythonApp',
243 locate=('IPython.terminal.ipapp.LocateIPythonApp',
244 LocateIPythonApp.description
244 LocateIPythonApp.description
245 ),
245 ),
246 history=('IPython.core.historyapp.HistoryApp',
246 history=('IPython.core.historyapp.HistoryApp',
247 "Manage the IPython history database."
247 "Manage the IPython history database."
248 ),
248 ),
249 nbconvert=('IPython.nbconvert.nbconvertapp.NbConvertApp',
249 nbconvert=('IPython.nbconvert.nbconvertapp.NbConvertApp',
250 "Convert notebooks to/from other formats."
250 "Convert notebooks to/from other formats."
251 ),
251 ),
252 trust=('IPython.nbformat.sign.TrustNotebookApp',
252 trust=('IPython.nbformat.sign.TrustNotebookApp',
253 "Sign notebooks to trust their potentially unsafe contents at load."
253 "Sign notebooks to trust their potentially unsafe contents at load."
254 ),
254 ),
255 ))
255 )
256 subcommands['install-nbextension'] = (
257 "IPython.html.nbextensions.NBExtensionApp",
258 "Install IPython notebook extension files"
259 )
256
260
257 # *do* autocreate requested profile, but don't create the config file.
261 # *do* autocreate requested profile, but don't create the config file.
258 auto_create=Bool(True)
262 auto_create=Bool(True)
259 # configurables
263 # configurables
260 ignore_old_config=Bool(False, config=True,
264 ignore_old_config=Bool(False, config=True,
261 help="Suppress warning messages about legacy config files"
265 help="Suppress warning messages about legacy config files"
262 )
266 )
263 quick = Bool(False, config=True,
267 quick = Bool(False, config=True,
264 help="""Start IPython quickly by skipping the loading of config files."""
268 help="""Start IPython quickly by skipping the loading of config files."""
265 )
269 )
266 def _quick_changed(self, name, old, new):
270 def _quick_changed(self, name, old, new):
267 if new:
271 if new:
268 self.load_config_file = lambda *a, **kw: None
272 self.load_config_file = lambda *a, **kw: None
269 self.ignore_old_config=True
273 self.ignore_old_config=True
270
274
271 display_banner = Bool(True, config=True,
275 display_banner = Bool(True, config=True,
272 help="Whether to display a banner upon starting IPython."
276 help="Whether to display a banner upon starting IPython."
273 )
277 )
274
278
275 # if there is code of files to run from the cmd line, don't interact
279 # if there is code of files to run from the cmd line, don't interact
276 # unless the --i flag (App.force_interact) is true.
280 # unless the --i flag (App.force_interact) is true.
277 force_interact = Bool(False, config=True,
281 force_interact = Bool(False, config=True,
278 help="""If a command or file is given via the command-line,
282 help="""If a command or file is given via the command-line,
279 e.g. 'ipython foo.py', start an interactive shell after executing the
283 e.g. 'ipython foo.py', start an interactive shell after executing the
280 file or command."""
284 file or command."""
281 )
285 )
282 def _force_interact_changed(self, name, old, new):
286 def _force_interact_changed(self, name, old, new):
283 if new:
287 if new:
284 self.interact = True
288 self.interact = True
285
289
286 def _file_to_run_changed(self, name, old, new):
290 def _file_to_run_changed(self, name, old, new):
287 if new:
291 if new:
288 self.something_to_run = True
292 self.something_to_run = True
289 if new and not self.force_interact:
293 if new and not self.force_interact:
290 self.interact = False
294 self.interact = False
291 _code_to_run_changed = _file_to_run_changed
295 _code_to_run_changed = _file_to_run_changed
292 _module_to_run_changed = _file_to_run_changed
296 _module_to_run_changed = _file_to_run_changed
293
297
294 # internal, not-configurable
298 # internal, not-configurable
295 interact=Bool(True)
299 interact=Bool(True)
296 something_to_run=Bool(False)
300 something_to_run=Bool(False)
297
301
298 def parse_command_line(self, argv=None):
302 def parse_command_line(self, argv=None):
299 """override to allow old '-pylab' flag with deprecation warning"""
303 """override to allow old '-pylab' flag with deprecation warning"""
300
304
301 argv = sys.argv[1:] if argv is None else argv
305 argv = sys.argv[1:] if argv is None else argv
302
306
303 if '-pylab' in argv:
307 if '-pylab' in argv:
304 # deprecated `-pylab` given,
308 # deprecated `-pylab` given,
305 # warn and transform into current syntax
309 # warn and transform into current syntax
306 argv = argv[:] # copy, don't clobber
310 argv = argv[:] # copy, don't clobber
307 idx = argv.index('-pylab')
311 idx = argv.index('-pylab')
308 warn.warn("`-pylab` flag has been deprecated.\n"
312 warn.warn("`-pylab` flag has been deprecated.\n"
309 " Use `--matplotlib <backend>` and import pylab manually.")
313 " Use `--matplotlib <backend>` and import pylab manually.")
310 argv[idx] = '--pylab'
314 argv[idx] = '--pylab'
311
315
312 return super(TerminalIPythonApp, self).parse_command_line(argv)
316 return super(TerminalIPythonApp, self).parse_command_line(argv)
313
317
314 @catch_config_error
318 @catch_config_error
315 def initialize(self, argv=None):
319 def initialize(self, argv=None):
316 """Do actions after construct, but before starting the app."""
320 """Do actions after construct, but before starting the app."""
317 super(TerminalIPythonApp, self).initialize(argv)
321 super(TerminalIPythonApp, self).initialize(argv)
318 if self.subapp is not None:
322 if self.subapp is not None:
319 # don't bother initializing further, starting subapp
323 # don't bother initializing further, starting subapp
320 return
324 return
321 if not self.ignore_old_config:
325 if not self.ignore_old_config:
322 check_for_old_config(self.ipython_dir)
326 check_for_old_config(self.ipython_dir)
323 # print self.extra_args
327 # print self.extra_args
324 if self.extra_args and not self.something_to_run:
328 if self.extra_args and not self.something_to_run:
325 self.file_to_run = self.extra_args[0]
329 self.file_to_run = self.extra_args[0]
326 self.init_path()
330 self.init_path()
327 # create the shell
331 # create the shell
328 self.init_shell()
332 self.init_shell()
329 # and draw the banner
333 # and draw the banner
330 self.init_banner()
334 self.init_banner()
331 # Now a variety of things that happen after the banner is printed.
335 # Now a variety of things that happen after the banner is printed.
332 self.init_gui_pylab()
336 self.init_gui_pylab()
333 self.init_extensions()
337 self.init_extensions()
334 self.init_code()
338 self.init_code()
335
339
336 def init_shell(self):
340 def init_shell(self):
337 """initialize the InteractiveShell instance"""
341 """initialize the InteractiveShell instance"""
338 # Create an InteractiveShell instance.
342 # Create an InteractiveShell instance.
339 # shell.display_banner should always be False for the terminal
343 # shell.display_banner should always be False for the terminal
340 # based app, because we call shell.show_banner() by hand below
344 # based app, because we call shell.show_banner() by hand below
341 # so the banner shows *before* all extension loading stuff.
345 # so the banner shows *before* all extension loading stuff.
342 self.shell = TerminalInteractiveShell.instance(parent=self,
346 self.shell = TerminalInteractiveShell.instance(parent=self,
343 display_banner=False, profile_dir=self.profile_dir,
347 display_banner=False, profile_dir=self.profile_dir,
344 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
348 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
345 self.shell.configurables.append(self)
349 self.shell.configurables.append(self)
346
350
347 def init_banner(self):
351 def init_banner(self):
348 """optionally display the banner"""
352 """optionally display the banner"""
349 if self.display_banner and self.interact:
353 if self.display_banner and self.interact:
350 self.shell.show_banner()
354 self.shell.show_banner()
351 # Make sure there is a space below the banner.
355 # Make sure there is a space below the banner.
352 if self.log_level <= logging.INFO: print()
356 if self.log_level <= logging.INFO: print()
353
357
354 def _pylab_changed(self, name, old, new):
358 def _pylab_changed(self, name, old, new):
355 """Replace --pylab='inline' with --pylab='auto'"""
359 """Replace --pylab='inline' with --pylab='auto'"""
356 if new == 'inline':
360 if new == 'inline':
357 warn.warn("'inline' not available as pylab backend, "
361 warn.warn("'inline' not available as pylab backend, "
358 "using 'auto' instead.")
362 "using 'auto' instead.")
359 self.pylab = 'auto'
363 self.pylab = 'auto'
360
364
361 def start(self):
365 def start(self):
362 if self.subapp is not None:
366 if self.subapp is not None:
363 return self.subapp.start()
367 return self.subapp.start()
364 # perform any prexec steps:
368 # perform any prexec steps:
365 if self.interact:
369 if self.interact:
366 self.log.debug("Starting IPython's mainloop...")
370 self.log.debug("Starting IPython's mainloop...")
367 self.shell.mainloop()
371 self.shell.mainloop()
368 else:
372 else:
369 self.log.debug("IPython not interactive...")
373 self.log.debug("IPython not interactive...")
370
374
371 def load_default_config(ipython_dir=None):
375 def load_default_config(ipython_dir=None):
372 """Load the default config file from the default ipython_dir.
376 """Load the default config file from the default ipython_dir.
373
377
374 This is useful for embedded shells.
378 This is useful for embedded shells.
375 """
379 """
376 if ipython_dir is None:
380 if ipython_dir is None:
377 ipython_dir = get_ipython_dir()
381 ipython_dir = get_ipython_dir()
378
382
379 profile_dir = os.path.join(ipython_dir, 'profile_default')
383 profile_dir = os.path.join(ipython_dir, 'profile_default')
380
384
381 config = Config()
385 config = Config()
382 for cf in Application._load_config_files("ipython_config", path=profile_dir):
386 for cf in Application._load_config_files("ipython_config", path=profile_dir):
383 config.update(cf)
387 config.update(cf)
384
388
385 return config
389 return config
386
390
387 launch_new_instance = TerminalIPythonApp.launch_instance
391 launch_new_instance = TerminalIPythonApp.launch_instance
388
392
389
393
390 if __name__ == '__main__':
394 if __name__ == '__main__':
391 launch_new_instance()
395 launch_new_instance()
@@ -1,446 +1,454 b''
1 """Generic testing tools.
1 """Generic testing tools.
2
2
3 Authors
3 Authors
4 -------
4 -------
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 """
6 """
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2009 The IPython Development Team
11 # Copyright (C) 2009 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 import os
21 import os
22 import re
22 import re
23 import sys
23 import sys
24 import tempfile
24 import tempfile
25
25
26 from contextlib import contextmanager
26 from contextlib import contextmanager
27 from io import StringIO
27 from io import StringIO
28 from subprocess import Popen, PIPE
28 from subprocess import Popen, PIPE
29
29
30 try:
30 try:
31 # These tools are used by parts of the runtime, so we make the nose
31 # These tools are used by parts of the runtime, so we make the nose
32 # dependency optional at this point. Nose is a hard dependency to run the
32 # dependency optional at this point. Nose is a hard dependency to run the
33 # test suite, but NOT to use ipython itself.
33 # test suite, but NOT to use ipython itself.
34 import nose.tools as nt
34 import nose.tools as nt
35 has_nose = True
35 has_nose = True
36 except ImportError:
36 except ImportError:
37 has_nose = False
37 has_nose = False
38
38
39 from IPython.config.loader import Config
39 from IPython.config.loader import Config
40 from IPython.utils.process import get_output_error_code
40 from IPython.utils.process import get_output_error_code
41 from IPython.utils.text import list_strings
41 from IPython.utils.text import list_strings
42 from IPython.utils.io import temp_pyfile, Tee
42 from IPython.utils.io import temp_pyfile, Tee
43 from IPython.utils import py3compat
43 from IPython.utils import py3compat
44 from IPython.utils.encoding import DEFAULT_ENCODING
44 from IPython.utils.encoding import DEFAULT_ENCODING
45
45
46 from . import decorators as dec
46 from . import decorators as dec
47 from . import skipdoctest
47 from . import skipdoctest
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Functions and classes
50 # Functions and classes
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 # The docstring for full_path doctests differently on win32 (different path
53 # The docstring for full_path doctests differently on win32 (different path
54 # separator) so just skip the doctest there. The example remains informative.
54 # separator) so just skip the doctest there. The example remains informative.
55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
56
56
57 @doctest_deco
57 @doctest_deco
58 def full_path(startPath,files):
58 def full_path(startPath,files):
59 """Make full paths for all the listed files, based on startPath.
59 """Make full paths for all the listed files, based on startPath.
60
60
61 Only the base part of startPath is kept, since this routine is typically
61 Only the base part of startPath is kept, since this routine is typically
62 used with a script's ``__file__`` variable as startPath. The base of startPath
62 used with a script's ``__file__`` variable as startPath. The base of startPath
63 is then prepended to all the listed files, forming the output list.
63 is then prepended to all the listed files, forming the output list.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 startPath : string
67 startPath : string
68 Initial path to use as the base for the results. This path is split
68 Initial path to use as the base for the results. This path is split
69 using os.path.split() and only its first component is kept.
69 using os.path.split() and only its first component is kept.
70
70
71 files : string or list
71 files : string or list
72 One or more files.
72 One or more files.
73
73
74 Examples
74 Examples
75 --------
75 --------
76
76
77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
78 ['/foo/a.txt', '/foo/b.txt']
78 ['/foo/a.txt', '/foo/b.txt']
79
79
80 >>> full_path('/foo',['a.txt','b.txt'])
80 >>> full_path('/foo',['a.txt','b.txt'])
81 ['/a.txt', '/b.txt']
81 ['/a.txt', '/b.txt']
82
82
83 If a single file is given, the output is still a list::
83 If a single file is given, the output is still a list::
84
84
85 >>> full_path('/foo','a.txt')
85 >>> full_path('/foo','a.txt')
86 ['/a.txt']
86 ['/a.txt']
87 """
87 """
88
88
89 files = list_strings(files)
89 files = list_strings(files)
90 base = os.path.split(startPath)[0]
90 base = os.path.split(startPath)[0]
91 return [ os.path.join(base,f) for f in files ]
91 return [ os.path.join(base,f) for f in files ]
92
92
93
93
94 def parse_test_output(txt):
94 def parse_test_output(txt):
95 """Parse the output of a test run and return errors, failures.
95 """Parse the output of a test run and return errors, failures.
96
96
97 Parameters
97 Parameters
98 ----------
98 ----------
99 txt : str
99 txt : str
100 Text output of a test run, assumed to contain a line of one of the
100 Text output of a test run, assumed to contain a line of one of the
101 following forms::
101 following forms::
102
102
103 'FAILED (errors=1)'
103 'FAILED (errors=1)'
104 'FAILED (failures=1)'
104 'FAILED (failures=1)'
105 'FAILED (errors=1, failures=1)'
105 'FAILED (errors=1, failures=1)'
106
106
107 Returns
107 Returns
108 -------
108 -------
109 nerr, nfail
109 nerr, nfail
110 number of errors and failures.
110 number of errors and failures.
111 """
111 """
112
112
113 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
113 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
114 if err_m:
114 if err_m:
115 nerr = int(err_m.group(1))
115 nerr = int(err_m.group(1))
116 nfail = 0
116 nfail = 0
117 return nerr, nfail
117 return nerr, nfail
118
118
119 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
119 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
120 if fail_m:
120 if fail_m:
121 nerr = 0
121 nerr = 0
122 nfail = int(fail_m.group(1))
122 nfail = int(fail_m.group(1))
123 return nerr, nfail
123 return nerr, nfail
124
124
125 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
125 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
126 re.MULTILINE)
126 re.MULTILINE)
127 if both_m:
127 if both_m:
128 nerr = int(both_m.group(1))
128 nerr = int(both_m.group(1))
129 nfail = int(both_m.group(2))
129 nfail = int(both_m.group(2))
130 return nerr, nfail
130 return nerr, nfail
131
131
132 # If the input didn't match any of these forms, assume no error/failures
132 # If the input didn't match any of these forms, assume no error/failures
133 return 0, 0
133 return 0, 0
134
134
135
135
136 # So nose doesn't think this is a test
136 # So nose doesn't think this is a test
137 parse_test_output.__test__ = False
137 parse_test_output.__test__ = False
138
138
139
139
140 def default_argv():
140 def default_argv():
141 """Return a valid default argv for creating testing instances of ipython"""
141 """Return a valid default argv for creating testing instances of ipython"""
142
142
143 return ['--quick', # so no config file is loaded
143 return ['--quick', # so no config file is loaded
144 # Other defaults to minimize side effects on stdout
144 # Other defaults to minimize side effects on stdout
145 '--colors=NoColor', '--no-term-title','--no-banner',
145 '--colors=NoColor', '--no-term-title','--no-banner',
146 '--autocall=0']
146 '--autocall=0']
147
147
148
148
149 def default_config():
149 def default_config():
150 """Return a config object with good defaults for testing."""
150 """Return a config object with good defaults for testing."""
151 config = Config()
151 config = Config()
152 config.TerminalInteractiveShell.colors = 'NoColor'
152 config.TerminalInteractiveShell.colors = 'NoColor'
153 config.TerminalTerminalInteractiveShell.term_title = False,
153 config.TerminalTerminalInteractiveShell.term_title = False,
154 config.TerminalInteractiveShell.autocall = 0
154 config.TerminalInteractiveShell.autocall = 0
155 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
155 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
156 config.HistoryManager.db_cache_size = 10000
156 config.HistoryManager.db_cache_size = 10000
157 return config
157 return config
158
158
159
159
160 def get_ipython_cmd(as_string=False):
160 def get_ipython_cmd(as_string=False):
161 """
161 """
162 Return appropriate IPython command line name. By default, this will return
162 Return appropriate IPython command line name. By default, this will return
163 a list that can be used with subprocess.Popen, for example, but passing
163 a list that can be used with subprocess.Popen, for example, but passing
164 `as_string=True` allows for returning the IPython command as a string.
164 `as_string=True` allows for returning the IPython command as a string.
165
165
166 Parameters
166 Parameters
167 ----------
167 ----------
168 as_string: bool
168 as_string: bool
169 Flag to allow to return the command as a string.
169 Flag to allow to return the command as a string.
170 """
170 """
171 ipython_cmd = [sys.executable, "-m", "IPython"]
171 ipython_cmd = [sys.executable, "-m", "IPython"]
172
172
173 if as_string:
173 if as_string:
174 ipython_cmd = " ".join(ipython_cmd)
174 ipython_cmd = " ".join(ipython_cmd)
175
175
176 return ipython_cmd
176 return ipython_cmd
177
177
178 def ipexec(fname, options=None):
178 def ipexec(fname, options=None):
179 """Utility to call 'ipython filename'.
179 """Utility to call 'ipython filename'.
180
180
181 Starts IPython with a minimal and safe configuration to make startup as fast
181 Starts IPython with a minimal and safe configuration to make startup as fast
182 as possible.
182 as possible.
183
183
184 Note that this starts IPython in a subprocess!
184 Note that this starts IPython in a subprocess!
185
185
186 Parameters
186 Parameters
187 ----------
187 ----------
188 fname : str
188 fname : str
189 Name of file to be executed (should have .py or .ipy extension).
189 Name of file to be executed (should have .py or .ipy extension).
190
190
191 options : optional, list
191 options : optional, list
192 Extra command-line flags to be passed to IPython.
192 Extra command-line flags to be passed to IPython.
193
193
194 Returns
194 Returns
195 -------
195 -------
196 (stdout, stderr) of ipython subprocess.
196 (stdout, stderr) of ipython subprocess.
197 """
197 """
198 if options is None: options = []
198 if options is None: options = []
199
199
200 # For these subprocess calls, eliminate all prompt printing so we only see
200 # For these subprocess calls, eliminate all prompt printing so we only see
201 # output from script execution
201 # output from script execution
202 prompt_opts = [ '--PromptManager.in_template=""',
202 prompt_opts = [ '--PromptManager.in_template=""',
203 '--PromptManager.in2_template=""',
203 '--PromptManager.in2_template=""',
204 '--PromptManager.out_template=""'
204 '--PromptManager.out_template=""'
205 ]
205 ]
206 cmdargs = default_argv() + prompt_opts + options
206 cmdargs = default_argv() + prompt_opts + options
207
207
208 test_dir = os.path.dirname(__file__)
208 test_dir = os.path.dirname(__file__)
209
209
210 ipython_cmd = get_ipython_cmd()
210 ipython_cmd = get_ipython_cmd()
211 # Absolute path for filename
211 # Absolute path for filename
212 full_fname = os.path.join(test_dir, fname)
212 full_fname = os.path.join(test_dir, fname)
213 full_cmd = ipython_cmd + cmdargs + [full_fname]
213 full_cmd = ipython_cmd + cmdargs + [full_fname]
214 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
214 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
215 out, err = p.communicate()
215 out, err = p.communicate()
216 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
216 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
217 # `import readline` causes 'ESC[?1034h' to be output sometimes,
217 # `import readline` causes 'ESC[?1034h' to be output sometimes,
218 # so strip that out before doing comparisons
218 # so strip that out before doing comparisons
219 if out:
219 if out:
220 out = re.sub(r'\x1b\[[^h]+h', '', out)
220 out = re.sub(r'\x1b\[[^h]+h', '', out)
221 return out, err
221 return out, err
222
222
223
223
224 def ipexec_validate(fname, expected_out, expected_err='',
224 def ipexec_validate(fname, expected_out, expected_err='',
225 options=None):
225 options=None):
226 """Utility to call 'ipython filename' and validate output/error.
226 """Utility to call 'ipython filename' and validate output/error.
227
227
228 This function raises an AssertionError if the validation fails.
228 This function raises an AssertionError if the validation fails.
229
229
230 Note that this starts IPython in a subprocess!
230 Note that this starts IPython in a subprocess!
231
231
232 Parameters
232 Parameters
233 ----------
233 ----------
234 fname : str
234 fname : str
235 Name of the file to be executed (should have .py or .ipy extension).
235 Name of the file to be executed (should have .py or .ipy extension).
236
236
237 expected_out : str
237 expected_out : str
238 Expected stdout of the process.
238 Expected stdout of the process.
239
239
240 expected_err : optional, str
240 expected_err : optional, str
241 Expected stderr of the process.
241 Expected stderr of the process.
242
242
243 options : optional, list
243 options : optional, list
244 Extra command-line flags to be passed to IPython.
244 Extra command-line flags to be passed to IPython.
245
245
246 Returns
246 Returns
247 -------
247 -------
248 None
248 None
249 """
249 """
250
250
251 import nose.tools as nt
251 import nose.tools as nt
252
252
253 out, err = ipexec(fname, options)
253 out, err = ipexec(fname, options)
254 #print 'OUT', out # dbg
254 #print 'OUT', out # dbg
255 #print 'ERR', err # dbg
255 #print 'ERR', err # dbg
256 # If there are any errors, we must check those befor stdout, as they may be
256 # If there are any errors, we must check those befor stdout, as they may be
257 # more informative than simply having an empty stdout.
257 # more informative than simply having an empty stdout.
258 if err:
258 if err:
259 if expected_err:
259 if expected_err:
260 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
260 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
261 else:
261 else:
262 raise ValueError('Running file %r produced error: %r' %
262 raise ValueError('Running file %r produced error: %r' %
263 (fname, err))
263 (fname, err))
264 # If no errors or output on stderr was expected, match stdout
264 # If no errors or output on stderr was expected, match stdout
265 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
265 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
266
266
267
267
268 class TempFileMixin(object):
268 class TempFileMixin(object):
269 """Utility class to create temporary Python/IPython files.
269 """Utility class to create temporary Python/IPython files.
270
270
271 Meant as a mixin class for test cases."""
271 Meant as a mixin class for test cases."""
272
272
273 def mktmp(self, src, ext='.py'):
273 def mktmp(self, src, ext='.py'):
274 """Make a valid python temp file."""
274 """Make a valid python temp file."""
275 fname, f = temp_pyfile(src, ext)
275 fname, f = temp_pyfile(src, ext)
276 self.tmpfile = f
276 self.tmpfile = f
277 self.fname = fname
277 self.fname = fname
278
278
279 def tearDown(self):
279 def tearDown(self):
280 if hasattr(self, 'tmpfile'):
280 if hasattr(self, 'tmpfile'):
281 # If the tmpfile wasn't made because of skipped tests, like in
281 # If the tmpfile wasn't made because of skipped tests, like in
282 # win32, there's nothing to cleanup.
282 # win32, there's nothing to cleanup.
283 self.tmpfile.close()
283 self.tmpfile.close()
284 try:
284 try:
285 os.unlink(self.fname)
285 os.unlink(self.fname)
286 except:
286 except:
287 # On Windows, even though we close the file, we still can't
287 # On Windows, even though we close the file, we still can't
288 # delete it. I have no clue why
288 # delete it. I have no clue why
289 pass
289 pass
290
290
291 pair_fail_msg = ("Testing {0}\n\n"
291 pair_fail_msg = ("Testing {0}\n\n"
292 "In:\n"
292 "In:\n"
293 " {1!r}\n"
293 " {1!r}\n"
294 "Expected:\n"
294 "Expected:\n"
295 " {2!r}\n"
295 " {2!r}\n"
296 "Got:\n"
296 "Got:\n"
297 " {3!r}\n")
297 " {3!r}\n")
298 def check_pairs(func, pairs):
298 def check_pairs(func, pairs):
299 """Utility function for the common case of checking a function with a
299 """Utility function for the common case of checking a function with a
300 sequence of input/output pairs.
300 sequence of input/output pairs.
301
301
302 Parameters
302 Parameters
303 ----------
303 ----------
304 func : callable
304 func : callable
305 The function to be tested. Should accept a single argument.
305 The function to be tested. Should accept a single argument.
306 pairs : iterable
306 pairs : iterable
307 A list of (input, expected_output) tuples.
307 A list of (input, expected_output) tuples.
308
308
309 Returns
309 Returns
310 -------
310 -------
311 None. Raises an AssertionError if any output does not match the expected
311 None. Raises an AssertionError if any output does not match the expected
312 value.
312 value.
313 """
313 """
314 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
314 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
315 for inp, expected in pairs:
315 for inp, expected in pairs:
316 out = func(inp)
316 out = func(inp)
317 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
317 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
318
318
319
319
320 if py3compat.PY3:
320 if py3compat.PY3:
321 MyStringIO = StringIO
321 MyStringIO = StringIO
322 else:
322 else:
323 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
323 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
324 # so we need a class that can handle both.
324 # so we need a class that can handle both.
325 class MyStringIO(StringIO):
325 class MyStringIO(StringIO):
326 def write(self, s):
326 def write(self, s):
327 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
327 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
328 super(MyStringIO, self).write(s)
328 super(MyStringIO, self).write(s)
329
329
330 _re_type = type(re.compile(r''))
331
330 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
332 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
331 -------
333 -------
332 {2!s}
334 {2!s}
333 -------
335 -------
334 """
336 """
335
337
336 class AssertPrints(object):
338 class AssertPrints(object):
337 """Context manager for testing that code prints certain text.
339 """Context manager for testing that code prints certain text.
338
340
339 Examples
341 Examples
340 --------
342 --------
341 >>> with AssertPrints("abc", suppress=False):
343 >>> with AssertPrints("abc", suppress=False):
342 ... print("abcd")
344 ... print("abcd")
343 ... print("def")
345 ... print("def")
344 ...
346 ...
345 abcd
347 abcd
346 def
348 def
347 """
349 """
348 def __init__(self, s, channel='stdout', suppress=True):
350 def __init__(self, s, channel='stdout', suppress=True):
349 self.s = s
351 self.s = s
350 if isinstance(self.s, py3compat.string_types):
352 if isinstance(self.s, (py3compat.string_types, _re_type)):
351 self.s = [self.s]
353 self.s = [self.s]
352 self.channel = channel
354 self.channel = channel
353 self.suppress = suppress
355 self.suppress = suppress
354
356
355 def __enter__(self):
357 def __enter__(self):
356 self.orig_stream = getattr(sys, self.channel)
358 self.orig_stream = getattr(sys, self.channel)
357 self.buffer = MyStringIO()
359 self.buffer = MyStringIO()
358 self.tee = Tee(self.buffer, channel=self.channel)
360 self.tee = Tee(self.buffer, channel=self.channel)
359 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
361 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
360
362
361 def __exit__(self, etype, value, traceback):
363 def __exit__(self, etype, value, traceback):
362 if value is not None:
364 if value is not None:
363 # If an error was raised, don't check anything else
365 # If an error was raised, don't check anything else
364 return False
366 return False
365 self.tee.flush()
367 self.tee.flush()
366 setattr(sys, self.channel, self.orig_stream)
368 setattr(sys, self.channel, self.orig_stream)
367 printed = self.buffer.getvalue()
369 printed = self.buffer.getvalue()
368 for s in self.s:
370 for s in self.s:
369 assert s in printed, notprinted_msg.format(s, self.channel, printed)
371 if isinstance(s, _re_type):
372 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
373 else:
374 assert s in printed, notprinted_msg.format(s, self.channel, printed)
370 return False
375 return False
371
376
372 printed_msg = """Found {0!r} in printed output (on {1}):
377 printed_msg = """Found {0!r} in printed output (on {1}):
373 -------
378 -------
374 {2!s}
379 {2!s}
375 -------
380 -------
376 """
381 """
377
382
378 class AssertNotPrints(AssertPrints):
383 class AssertNotPrints(AssertPrints):
379 """Context manager for checking that certain output *isn't* produced.
384 """Context manager for checking that certain output *isn't* produced.
380
385
381 Counterpart of AssertPrints"""
386 Counterpart of AssertPrints"""
382 def __exit__(self, etype, value, traceback):
387 def __exit__(self, etype, value, traceback):
383 if value is not None:
388 if value is not None:
384 # If an error was raised, don't check anything else
389 # If an error was raised, don't check anything else
385 return False
390 return False
386 self.tee.flush()
391 self.tee.flush()
387 setattr(sys, self.channel, self.orig_stream)
392 setattr(sys, self.channel, self.orig_stream)
388 printed = self.buffer.getvalue()
393 printed = self.buffer.getvalue()
389 for s in self.s:
394 for s in self.s:
390 assert s not in printed, printed_msg.format(s, self.channel, printed)
395 if isinstance(s, _re_type):
396 assert not s.search(printed), printed_msg.format(s.pattern, self.channel, printed)
397 else:
398 assert s not in printed, printed_msg.format(s, self.channel, printed)
391 return False
399 return False
392
400
393 @contextmanager
401 @contextmanager
394 def mute_warn():
402 def mute_warn():
395 from IPython.utils import warn
403 from IPython.utils import warn
396 save_warn = warn.warn
404 save_warn = warn.warn
397 warn.warn = lambda *a, **kw: None
405 warn.warn = lambda *a, **kw: None
398 try:
406 try:
399 yield
407 yield
400 finally:
408 finally:
401 warn.warn = save_warn
409 warn.warn = save_warn
402
410
403 @contextmanager
411 @contextmanager
404 def make_tempfile(name):
412 def make_tempfile(name):
405 """ Create an empty, named, temporary file for the duration of the context.
413 """ Create an empty, named, temporary file for the duration of the context.
406 """
414 """
407 f = open(name, 'w')
415 f = open(name, 'w')
408 f.close()
416 f.close()
409 try:
417 try:
410 yield
418 yield
411 finally:
419 finally:
412 os.unlink(name)
420 os.unlink(name)
413
421
414
422
415 @contextmanager
423 @contextmanager
416 def monkeypatch(obj, name, attr):
424 def monkeypatch(obj, name, attr):
417 """
425 """
418 Context manager to replace attribute named `name` in `obj` with `attr`.
426 Context manager to replace attribute named `name` in `obj` with `attr`.
419 """
427 """
420 orig = getattr(obj, name)
428 orig = getattr(obj, name)
421 setattr(obj, name, attr)
429 setattr(obj, name, attr)
422 yield
430 yield
423 setattr(obj, name, orig)
431 setattr(obj, name, orig)
424
432
425
433
426 def help_output_test(subcommand=''):
434 def help_output_test(subcommand=''):
427 """test that `ipython [subcommand] -h` works"""
435 """test that `ipython [subcommand] -h` works"""
428 cmd = get_ipython_cmd() + [subcommand, '-h']
436 cmd = get_ipython_cmd() + [subcommand, '-h']
429 out, err, rc = get_output_error_code(cmd)
437 out, err, rc = get_output_error_code(cmd)
430 nt.assert_equal(rc, 0, err)
438 nt.assert_equal(rc, 0, err)
431 nt.assert_not_in("Traceback", err)
439 nt.assert_not_in("Traceback", err)
432 nt.assert_in("Options", out)
440 nt.assert_in("Options", out)
433 nt.assert_in("--help-all", out)
441 nt.assert_in("--help-all", out)
434 return out, err
442 return out, err
435
443
436
444
437 def help_all_output_test(subcommand=''):
445 def help_all_output_test(subcommand=''):
438 """test that `ipython [subcommand] --help-all` works"""
446 """test that `ipython [subcommand] --help-all` works"""
439 cmd = get_ipython_cmd() + [subcommand, '--help-all']
447 cmd = get_ipython_cmd() + [subcommand, '--help-all']
440 out, err, rc = get_output_error_code(cmd)
448 out, err, rc = get_output_error_code(cmd)
441 nt.assert_equal(rc, 0, err)
449 nt.assert_equal(rc, 0, err)
442 nt.assert_not_in("Traceback", err)
450 nt.assert_not_in("Traceback", err)
443 nt.assert_in("Options", out)
451 nt.assert_in("Options", out)
444 nt.assert_in("Class parameters", out)
452 nt.assert_in("Class parameters", out)
445 return out, err
453 return out, err
446
454
General Comments 0
You need to be logged in to leave comments. Login now