Show More
@@ -0,0 +1,101 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 | 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 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 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