Show More
@@ -0,0 +1,101 | |||
|
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 | from os.path import basename, join as pjoin | |
|
16 | ||
|
17 | from IPython.utils.path import get_ipython_dir | |
|
18 | from IPython.utils.py3compat import string_types, cast_unicode_py2 | |
|
19 | ||
|
20 | ||
|
21 | def _should_copy(src, dest, verbose=1): | |
|
22 | """should a file be copied?""" | |
|
23 | if not os.path.exists(dest): | |
|
24 | return True | |
|
25 | if os.stat(dest).st_mtime < os.stat(src).st_mtime: | |
|
26 | if verbose >= 2: | |
|
27 | print("%s is out of date" % dest) | |
|
28 | return True | |
|
29 | if verbose >= 2: | |
|
30 | print("%s is up to date" % dest) | |
|
31 | return False | |
|
32 | ||
|
33 | ||
|
34 | def _maybe_copy(src, dest, verbose=1): | |
|
35 | """copy a file if it needs updating""" | |
|
36 | if _should_copy(src, dest, verbose): | |
|
37 | if verbose >= 1: | |
|
38 | print("copying %s -> %s" % (src, dest)) | |
|
39 | shutil.copy2(src, dest) | |
|
40 | ||
|
41 | ||
|
42 | def install_nbextension(files, overwrite=False, ipython_dir=None, verbose=1): | |
|
43 | """Install a Javascript extension for the notebook | |
|
44 | ||
|
45 | Stages files and/or directories into IPYTHONDIR/nbextensions. | |
|
46 | By default, this comparse modification time, and only stages files that need updating. | |
|
47 | If `overwrite` is specified, matching files are purged before proceeding. | |
|
48 | ||
|
49 | Parameters | |
|
50 | ---------- | |
|
51 | ||
|
52 | files : list(paths) | |
|
53 | One or more paths to existing files or directories to install. | |
|
54 | These will be installed with their base name, so '/path/to/foo' | |
|
55 | will install to 'nbextensions/foo'. | |
|
56 | overwrite : bool [default: False] | |
|
57 | If True, always install the files, regardless of what may already be installed. | |
|
58 | ipython_dir : str [optional] | |
|
59 | The path to an IPython directory, if the default value is not desired. | |
|
60 | get_ipython_dir() is used by default. | |
|
61 | verbose : int [default: 1] | |
|
62 | Set verbosity level. The default is 1, where file actions are printed. | |
|
63 | set verbose=2 for more output, or verbose=0 for silence. | |
|
64 | """ | |
|
65 | ||
|
66 | ipython_dir = ipython_dir or get_ipython_dir() | |
|
67 | nbext = pjoin(ipython_dir, u'nbextensions') | |
|
68 | # make sure nbextensions dir exists | |
|
69 | if not os.path.exists(nbext): | |
|
70 | os.makedirs(nbext) | |
|
71 | ||
|
72 | if isinstance(files, string_types): | |
|
73 | # one file given, turn it into a list | |
|
74 | files = [files] | |
|
75 | ||
|
76 | for path in map(cast_unicode_py2, files): | |
|
77 | dest = pjoin(nbext, basename(path)) | |
|
78 | if overwrite and os.path.exists(dest): | |
|
79 | if verbose >= 1: | |
|
80 | print("removing %s" % dest) | |
|
81 | if os.path.isdir(dest): | |
|
82 | shutil.rmtree(dest) | |
|
83 | else: | |
|
84 | os.remove(dest) | |
|
85 | ||
|
86 | if os.path.isdir(path): | |
|
87 | strip_prefix_len = len(path) - len(basename(path)) | |
|
88 | for parent, dirs, files in os.walk(path): | |
|
89 | dest_dir = pjoin(nbext, parent[strip_prefix_len:]) | |
|
90 | if not os.path.exists(dest_dir): | |
|
91 | if verbose >= 2: | |
|
92 | print("making directory %s" % dest_dir) | |
|
93 | os.makedirs(dest_dir) | |
|
94 | for file in files: | |
|
95 | src = pjoin(parent, file) | |
|
96 | # print("%r, %r" % (dest_dir, file)) | |
|
97 | dest = pjoin(dest_dir, file) | |
|
98 | _maybe_copy(src, dest, verbose) | |
|
99 | else: | |
|
100 | src = path | |
|
101 | _maybe_copy(src, dest, verbose) |
@@ -0,0 +1,201 | |||
|
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 time | |
|
18 | from contextlib import contextmanager | |
|
19 | from os.path import basename, join as pjoin | |
|
20 | from unittest import TestCase | |
|
21 | ||
|
22 | import nose.tools as nt | |
|
23 | ||
|
24 | from IPython.external.decorator import decorator | |
|
25 | ||
|
26 | import IPython.testing.tools as tt | |
|
27 | import IPython.utils.path | |
|
28 | from IPython.utils import py3compat | |
|
29 | from IPython.utils.tempdir import TemporaryDirectory | |
|
30 | from IPython.html import nbextensions | |
|
31 | from IPython.html.nbextensions import install_nbextension | |
|
32 | ||
|
33 | #----------------------------------------------------------------------------- | |
|
34 | # Test functions | |
|
35 | #----------------------------------------------------------------------------- | |
|
36 | ||
|
37 | def touch(file, mtime=None): | |
|
38 | """ensure a file exists, and set its modification time | |
|
39 | ||
|
40 | returns the modification time of the file | |
|
41 | """ | |
|
42 | open(file, 'a').close() | |
|
43 | # set explicit mtime | |
|
44 | if mtime: | |
|
45 | atime = os.stat(file).st_atime | |
|
46 | os.utime(file, (atime, mtime)) | |
|
47 | return os.stat(file).st_mtime | |
|
48 | ||
|
49 | ||
|
50 | class TestInstallNBExtension(TestCase): | |
|
51 | ||
|
52 | def tempdir(self): | |
|
53 | td = TemporaryDirectory() | |
|
54 | self.tempdirs.append(td) | |
|
55 | return py3compat.cast_unicode(td.name) | |
|
56 | ||
|
57 | def setUp(self): | |
|
58 | self.tempdirs = [] | |
|
59 | src = self.src = self.tempdir() | |
|
60 | self.files = files = [ | |
|
61 | pjoin(u'ƒile'), | |
|
62 | pjoin(u'∂ir', u'ƒile1'), | |
|
63 | pjoin(u'∂ir', u'∂ir2', u'ƒile2'), | |
|
64 | ] | |
|
65 | for file in files: | |
|
66 | fullpath = os.path.join(self.src, file) | |
|
67 | parent = os.path.dirname(fullpath) | |
|
68 | if not os.path.exists(parent): | |
|
69 | os.makedirs(parent) | |
|
70 | touch(fullpath) | |
|
71 | ||
|
72 | self.ipdir = self.tempdir() | |
|
73 | self.save_get_ipython_dir = nbextensions.get_ipython_dir | |
|
74 | nbextensions.get_ipython_dir = lambda : self.ipdir | |
|
75 | ||
|
76 | def tearDown(self): | |
|
77 | for td in self.tempdirs: | |
|
78 | td.cleanup() | |
|
79 | nbextensions.get_ipython_dir = self.save_get_ipython_dir | |
|
80 | ||
|
81 | def assert_path_exists(self, path): | |
|
82 | if not os.path.exists(path): | |
|
83 | self.fail(u"%s should exist" % path) | |
|
84 | ||
|
85 | def assert_not_path_exists(self, path): | |
|
86 | if os.path.exists(path): | |
|
87 | self.fail(u"%s should not exist" % path) | |
|
88 | ||
|
89 | def assert_installed(self, relative_path, ipdir=None): | |
|
90 | self.assert_path_exists( | |
|
91 | pjoin(ipdir or self.ipdir, u'nbextensions', relative_path) | |
|
92 | ) | |
|
93 | ||
|
94 | def assert_not_installed(self, relative_path, ipdir=None): | |
|
95 | self.assert_not_path_exists( | |
|
96 | pjoin(ipdir or self.ipdir, u'nbextensions', relative_path) | |
|
97 | ) | |
|
98 | ||
|
99 | def test_create_ipython_dir(self): | |
|
100 | """install_nbextension when ipython_dir doesn't exist""" | |
|
101 | with TemporaryDirectory() as td: | |
|
102 | ipdir = pjoin(td, u'ipython') | |
|
103 | install_nbextension(self.src, ipython_dir=ipdir) | |
|
104 | self.assert_path_exists(ipdir) | |
|
105 | for file in self.files: | |
|
106 | self.assert_installed( | |
|
107 | pjoin(basename(self.src), file), | |
|
108 | ipdir | |
|
109 | ) | |
|
110 | ||
|
111 | def test_create_nbextensions(self): | |
|
112 | with TemporaryDirectory() as ipdir: | |
|
113 | install_nbextension(self.src, ipython_dir=ipdir) | |
|
114 | self.assert_installed( | |
|
115 | pjoin(basename(self.src), u'ƒile'), | |
|
116 | ipdir | |
|
117 | ) | |
|
118 | ||
|
119 | def test_single_file(self): | |
|
120 | file = self.files[0] | |
|
121 | install_nbextension(pjoin(self.src, file)) | |
|
122 | self.assert_installed(file) | |
|
123 | ||
|
124 | def test_single_dir(self): | |
|
125 | d = u'∂ir' | |
|
126 | install_nbextension(pjoin(self.src, d)) | |
|
127 | self.assert_installed(self.files[-1]) | |
|
128 | ||
|
129 | def test_install_nbextension(self): | |
|
130 | install_nbextension(glob.glob(pjoin(self.src, '*'))) | |
|
131 | for file in self.files: | |
|
132 | self.assert_installed(file) | |
|
133 | ||
|
134 | def test_overwrite_file(self): | |
|
135 | with TemporaryDirectory() as d: | |
|
136 | fname = u'ƒ.js' | |
|
137 | src = pjoin(d, fname) | |
|
138 | with open(src, 'w') as f: | |
|
139 | f.write('first') | |
|
140 | mtime = touch(src) | |
|
141 | dest = pjoin(self.ipdir, u'nbextensions', fname) | |
|
142 | install_nbextension(src) | |
|
143 | with open(src, 'w') as f: | |
|
144 | f.write('overwrite') | |
|
145 | mtime = touch(src, mtime - 100) | |
|
146 | install_nbextension(src, overwrite=True) | |
|
147 | with open(dest) as f: | |
|
148 | self.assertEqual(f.read(), 'overwrite') | |
|
149 | ||
|
150 | def test_overwrite_dir(self): | |
|
151 | with TemporaryDirectory() as src: | |
|
152 | # src = py3compat.cast_unicode_py2(src) | |
|
153 | base = basename(src) | |
|
154 | fname = u'ƒ.js' | |
|
155 | touch(pjoin(src, fname)) | |
|
156 | install_nbextension(src) | |
|
157 | self.assert_installed(pjoin(base, fname)) | |
|
158 | os.remove(pjoin(src, fname)) | |
|
159 | fname2 = u'∂.js' | |
|
160 | touch(pjoin(src, fname2)) | |
|
161 | install_nbextension(src, overwrite=True) | |
|
162 | self.assert_installed(pjoin(base, fname2)) | |
|
163 | self.assert_not_installed(pjoin(base, fname)) | |
|
164 | ||
|
165 | def test_update_file(self): | |
|
166 | with TemporaryDirectory() as d: | |
|
167 | fname = u'ƒ.js' | |
|
168 | src = pjoin(d, fname) | |
|
169 | with open(src, 'w') as f: | |
|
170 | f.write('first') | |
|
171 | mtime = touch(src) | |
|
172 | install_nbextension(src) | |
|
173 | self.assert_installed(fname) | |
|
174 | dest = pjoin(self.ipdir, u'nbextensions', fname) | |
|
175 | old_mtime = os.stat(dest).st_mtime | |
|
176 | with open(src, 'w') as f: | |
|
177 | f.write('overwrite') | |
|
178 | touch(src, mtime + 10) | |
|
179 | install_nbextension(src) | |
|
180 | with open(dest) as f: | |
|
181 | self.assertEqual(f.read(), 'overwrite') | |
|
182 | ||
|
183 | def test_skip_old_file(self): | |
|
184 | with TemporaryDirectory() as d: | |
|
185 | fname = u'ƒ.js' | |
|
186 | src = pjoin(d, fname) | |
|
187 | mtime = touch(src) | |
|
188 | install_nbextension(src) | |
|
189 | self.assert_installed(fname) | |
|
190 | dest = pjoin(self.ipdir, u'nbextensions', fname) | |
|
191 | old_mtime = os.stat(dest).st_mtime | |
|
192 | ||
|
193 | mtime = touch(src, mtime - 100) | |
|
194 | install_nbextension(src) | |
|
195 | new_mtime = os.stat(dest).st_mtime | |
|
196 | self.assertEqual(new_mtime, old_mtime) | |
|
197 | ||
|
198 | def test_quiet(self): | |
|
199 | with tt.AssertNotPrints(re.compile(r'.+')): | |
|
200 | install_nbextension(self.src, verbose=0) | |
|
201 |
General Comments 0
You need to be logged in to leave comments.
Login now