##// END OF EJS Templates
Add relpath option to FilesWriter
Jessica B. Hamrick -
Show More
@@ -1,111 +1,123 b''
1 """Contains writer for writing nbconvert output to filesystem."""
1 """Contains writer for writing nbconvert output to filesystem."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import io
6 import io
7 import os
7 import os
8 import glob
8 import glob
9
9
10 from IPython.utils.traitlets import Unicode
10 from IPython.utils.traitlets import Unicode
11 from IPython.utils.path import link_or_copy, ensure_dir_exists
11 from IPython.utils.path import link_or_copy, ensure_dir_exists
12 from IPython.utils.py3compat import unicode_type
12 from IPython.utils.py3compat import unicode_type
13
13
14 from .base import WriterBase
14 from .base import WriterBase
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Classes
17 # Classes
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 class FilesWriter(WriterBase):
20 class FilesWriter(WriterBase):
21 """Consumes nbconvert output and produces files."""
21 """Consumes nbconvert output and produces files."""
22
22
23
23
24 build_directory = Unicode("", config=True,
24 build_directory = Unicode("", config=True,
25 help="""Directory to write output to. Leave blank
25 help="""Directory to write output to. Leave blank
26 to output to the current directory""")
26 to output to the current directory""")
27
27
28 relpath = Unicode(
29 "", config=True,
30 help="""When copying files that the notebook depends on, copy them in
31 relation to this path, such that the destination filename will be
32 os.path.relpath(filename, relpath).""")
33
28
34
29 # Make sure that the output directory exists.
35 # Make sure that the output directory exists.
30 def _build_directory_changed(self, name, old, new):
36 def _build_directory_changed(self, name, old, new):
31 if new:
37 if new:
32 ensure_dir_exists(new)
38 ensure_dir_exists(new)
33
39
34
40
35 def __init__(self, **kw):
41 def __init__(self, **kw):
36 super(FilesWriter, self).__init__(**kw)
42 super(FilesWriter, self).__init__(**kw)
37 self._build_directory_changed('build_directory', self.build_directory,
43 self._build_directory_changed('build_directory', self.build_directory,
38 self.build_directory)
44 self.build_directory)
39
45
40 def _makedir(self, path):
46 def _makedir(self, path):
41 """Make a directory if it doesn't already exist"""
47 """Make a directory if it doesn't already exist"""
42 if path:
48 if path:
43 self.log.info("Making directory %s", path)
49 self.log.info("Making directory %s", path)
44 ensure_dir_exists(path)
50 ensure_dir_exists(path)
45
51
46 def write(self, output, resources, notebook_name=None, **kw):
52 def write(self, output, resources, notebook_name=None, **kw):
47 """
53 """
48 Consume and write Jinja output to the file system. Output directory
54 Consume and write Jinja output to the file system. Output directory
49 is set via the 'build_directory' variable of this instance (a
55 is set via the 'build_directory' variable of this instance (a
50 configurable).
56 configurable).
51
57
52 See base for more...
58 See base for more...
53 """
59 """
54
60
55 # Verify that a notebook name is provided.
61 # Verify that a notebook name is provided.
56 if notebook_name is None:
62 if notebook_name is None:
57 raise TypeError('notebook_name')
63 raise TypeError('notebook_name')
58
64
59 # Pull the extension and subdir from the resources dict.
65 # Pull the extension and subdir from the resources dict.
60 output_extension = resources.get('output_extension', None)
66 output_extension = resources.get('output_extension', None)
61
67
62 # Write all of the extracted resources to the destination directory.
68 # Write all of the extracted resources to the destination directory.
63 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
69 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
64 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
70 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
65 for filename, data in resources.get('outputs', {}).items():
71 for filename, data in resources.get('outputs', {}).items():
66
72
67 # Determine where to write the file to
73 # Determine where to write the file to
68 dest = os.path.join(self.build_directory, filename)
74 dest = os.path.join(self.build_directory, filename)
69 path = os.path.dirname(dest)
75 path = os.path.dirname(dest)
70 self._makedir(path)
76 self._makedir(path)
71
77
72 # Write file
78 # Write file
73 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
79 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
74 with io.open(dest, 'wb') as f:
80 with io.open(dest, 'wb') as f:
75 f.write(data)
81 f.write(data)
76
82
77 # Copy referenced files to output directory
83 # Copy referenced files to output directory
78 if self.build_directory:
84 if self.build_directory:
79 for filename in self.files:
85 for filename in self.files:
80
86
81 # Copy files that match search pattern
87 # Copy files that match search pattern
82 for matching_filename in glob.glob(filename):
88 for matching_filename in glob.glob(filename):
83
89
90 # compute the relative path for the filename
91 if self.relpath:
92 dest_filename = os.path.relpath(matching_filename, self.relpath)
93 else:
94 dest_filename = matching_filename
95
84 # Make sure folder exists.
96 # Make sure folder exists.
85 dest = os.path.join(self.build_directory, matching_filename)
97 dest = os.path.join(self.build_directory, dest_filename)
86 path = os.path.dirname(dest)
98 path = os.path.dirname(dest)
87 self._makedir(path)
99 self._makedir(path)
88
100
89 # Copy if destination is different.
101 # Copy if destination is different.
90 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
102 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
91 self.log.info("Linking %s -> %s", matching_filename, dest)
103 self.log.info("Linking %s -> %s", matching_filename, dest)
92 link_or_copy(matching_filename, dest)
104 link_or_copy(matching_filename, dest)
93
105
94 # Determine where to write conversion results.
106 # Determine where to write conversion results.
95 if output_extension is not None:
107 if output_extension is not None:
96 dest = notebook_name + output_extension
108 dest = notebook_name + output_extension
97 else:
109 else:
98 dest = notebook_name
110 dest = notebook_name
99 if self.build_directory:
111 if self.build_directory:
100 dest = os.path.join(self.build_directory, dest)
112 dest = os.path.join(self.build_directory, dest)
101
113
102 # Write conversion results.
114 # Write conversion results.
103 self.log.info("Writing %i bytes to %s", len(output), dest)
115 self.log.info("Writing %i bytes to %s", len(output), dest)
104 if isinstance(output, unicode_type):
116 if isinstance(output, unicode_type):
105 with io.open(dest, 'w', encoding='utf-8') as f:
117 with io.open(dest, 'w', encoding='utf-8') as f:
106 f.write(output)
118 f.write(output)
107 else:
119 else:
108 with io.open(dest, 'wb') as f:
120 with io.open(dest, 'wb') as f:
109 f.write(output)
121 f.write(output)
110
122
111 return dest
123 return dest
@@ -1,203 +1,238 b''
1 """
1 """
2 Module with tests for files
2 Module with tests for files
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import sys
17 import sys
18 import os
18 import os
19
19
20 from ...tests.base import TestsBase
20 from ...tests.base import TestsBase
21 from ..files import FilesWriter
21 from ..files import FilesWriter
22 from IPython.utils.py3compat import PY3
22 from IPython.utils.py3compat import PY3
23
23
24 if PY3:
24 if PY3:
25 from io import StringIO
25 from io import StringIO
26 else:
26 else:
27 from StringIO import StringIO
27 from StringIO import StringIO
28
28
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Class
31 # Class
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 class Testfiles(TestsBase):
34 class Testfiles(TestsBase):
35 """Contains test functions for files.py"""
35 """Contains test functions for files.py"""
36
36
37 def test_basic_output(self):
37 def test_basic_output(self):
38 """Is FilesWriter basic output correct?"""
38 """Is FilesWriter basic output correct?"""
39
39
40 # Work in a temporary directory.
40 # Work in a temporary directory.
41 with self.create_temp_cwd():
41 with self.create_temp_cwd():
42
42
43 # Create the resoruces dictionary
43 # Create the resoruces dictionary
44 res = {}
44 res = {}
45
45
46 # Create files writer, test output
46 # Create files writer, test output
47 writer = FilesWriter()
47 writer = FilesWriter()
48 writer.write(u'y', res, notebook_name="z")
48 writer.write(u'y', res, notebook_name="z")
49
49
50 # Check the output of the file
50 # Check the output of the file
51 with open('z', 'r') as f:
51 with open('z', 'r') as f:
52 output = f.read()
52 output = f.read()
53 self.assertEqual(output, u'y')
53 self.assertEqual(output, u'y')
54
54
55 def test_ext(self):
55 def test_ext(self):
56 """Does the FilesWriter add the correct extension to the output?"""
56 """Does the FilesWriter add the correct extension to the output?"""
57
57
58 # Work in a temporary directory.
58 # Work in a temporary directory.
59 with self.create_temp_cwd():
59 with self.create_temp_cwd():
60
60
61 # Create the resoruces dictionary
61 # Create the resoruces dictionary
62 res = {'output_extension': '.txt'}
62 res = {'output_extension': '.txt'}
63
63
64 # Create files writer, test output
64 # Create files writer, test output
65 writer = FilesWriter()
65 writer = FilesWriter()
66 writer.write(u'y', res, notebook_name="z")
66 writer.write(u'y', res, notebook_name="z")
67
67
68 # Check the output of the file
68 # Check the output of the file
69 assert os.path.isfile('z.txt')
69 assert os.path.isfile('z.txt')
70 with open('z.txt', 'r') as f:
70 with open('z.txt', 'r') as f:
71 output = f.read()
71 output = f.read()
72 self.assertEqual(output, u'y')
72 self.assertEqual(output, u'y')
73
73
74
74
75 def test_extract(self):
75 def test_extract(self):
76 """Can FilesWriter write extracted figures correctly?"""
76 """Can FilesWriter write extracted figures correctly?"""
77
77
78 # Work in a temporary directory.
78 # Work in a temporary directory.
79 with self.create_temp_cwd():
79 with self.create_temp_cwd():
80
80
81 # Create the resoruces dictionary
81 # Create the resoruces dictionary
82 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
82 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
83
83
84 # Create files writer, test output
84 # Create files writer, test output
85 writer = FilesWriter()
85 writer = FilesWriter()
86 writer.write(u'y', res, notebook_name="z")
86 writer.write(u'y', res, notebook_name="z")
87
87
88 # Check the output of the file
88 # Check the output of the file
89 with open('z', 'r') as f:
89 with open('z', 'r') as f:
90 output = f.read()
90 output = f.read()
91 self.assertEqual(output, u'y')
91 self.assertEqual(output, u'y')
92
92
93 # Check the output of the extracted file
93 # Check the output of the extracted file
94 extracted_file_dest = os.path.join('z_files', 'a')
94 extracted_file_dest = os.path.join('z_files', 'a')
95 assert os.path.isfile(extracted_file_dest)
95 assert os.path.isfile(extracted_file_dest)
96 with open(extracted_file_dest, 'r') as f:
96 with open(extracted_file_dest, 'r') as f:
97 output = f.read()
97 output = f.read()
98 self.assertEqual(output, 'b')
98 self.assertEqual(output, 'b')
99
99
100
100
101 def test_builddir(self):
101 def test_builddir(self):
102 """Can FilesWriter write to a build dir correctly?"""
102 """Can FilesWriter write to a build dir correctly?"""
103
103
104 # Work in a temporary directory.
104 # Work in a temporary directory.
105 with self.create_temp_cwd():
105 with self.create_temp_cwd():
106
106
107 # Create the resoruces dictionary
107 # Create the resoruces dictionary
108 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
108 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
109
109
110 # Create files writer, test output
110 # Create files writer, test output
111 writer = FilesWriter()
111 writer = FilesWriter()
112 writer.build_directory = u'build'
112 writer.build_directory = u'build'
113 writer.write(u'y', res, notebook_name="z")
113 writer.write(u'y', res, notebook_name="z")
114
114
115 # Check the output of the file
115 # Check the output of the file
116 assert os.path.isdir(writer.build_directory)
116 assert os.path.isdir(writer.build_directory)
117 dest = os.path.join(writer.build_directory, 'z')
117 dest = os.path.join(writer.build_directory, 'z')
118 with open(dest, 'r') as f:
118 with open(dest, 'r') as f:
119 output = f.read()
119 output = f.read()
120 self.assertEqual(output, u'y')
120 self.assertEqual(output, u'y')
121
121
122 # Check the output of the extracted file
122 # Check the output of the extracted file
123 extracted_file_dest = os.path.join(writer.build_directory, 'z_files', 'a')
123 extracted_file_dest = os.path.join(writer.build_directory, 'z_files', 'a')
124 assert os.path.isfile(extracted_file_dest)
124 assert os.path.isfile(extracted_file_dest)
125 with open(extracted_file_dest, 'r') as f:
125 with open(extracted_file_dest, 'r') as f:
126 output = f.read()
126 output = f.read()
127 self.assertEqual(output, 'b')
127 self.assertEqual(output, 'b')
128
128
129
129
130 def test_links(self):
130 def test_links(self):
131 """Can the FilesWriter handle linked files correctly?"""
131 """Can the FilesWriter handle linked files correctly?"""
132
132
133 # Work in a temporary directory.
133 # Work in a temporary directory.
134 with self.create_temp_cwd():
134 with self.create_temp_cwd():
135
135
136 # Create test file
136 # Create test file
137 os.mkdir('sub')
137 os.mkdir('sub')
138 with open(os.path.join('sub', 'c'), 'w') as f:
138 with open(os.path.join('sub', 'c'), 'w') as f:
139 f.write('d')
139 f.write('d')
140
140
141 # Create the resoruces dictionary
141 # Create the resoruces dictionary
142 res = {}
142 res = {}
143
143
144 # Create files writer, test output
144 # Create files writer, test output
145 writer = FilesWriter()
145 writer = FilesWriter()
146 writer.files = [os.path.join('sub', 'c')]
146 writer.files = [os.path.join('sub', 'c')]
147 writer.build_directory = u'build'
147 writer.build_directory = u'build'
148 writer.write(u'y', res, notebook_name="z")
148 writer.write(u'y', res, notebook_name="z")
149
149
150 # Check the output of the file
150 # Check the output of the file
151 assert os.path.isdir(writer.build_directory)
151 assert os.path.isdir(writer.build_directory)
152 dest = os.path.join(writer.build_directory, 'z')
152 dest = os.path.join(writer.build_directory, 'z')
153 with open(dest, 'r') as f:
153 with open(dest, 'r') as f:
154 output = f.read()
154 output = f.read()
155 self.assertEqual(output, u'y')
155 self.assertEqual(output, u'y')
156
156
157 # Check to make sure the linked file was copied
157 # Check to make sure the linked file was copied
158 path = os.path.join(writer.build_directory, 'sub')
158 path = os.path.join(writer.build_directory, 'sub')
159 assert os.path.isdir(path)
159 assert os.path.isdir(path)
160 dest = os.path.join(path, 'c')
160 dest = os.path.join(path, 'c')
161 assert os.path.isfile(dest)
161 assert os.path.isfile(dest)
162 with open(dest, 'r') as f:
162 with open(dest, 'r') as f:
163 output = f.read()
163 output = f.read()
164 self.assertEqual(output, 'd')
164 self.assertEqual(output, 'd')
165
165
166 def test_glob(self):
166 def test_glob(self):
167 """Can the FilesWriter handle globbed files correctly?"""
167 """Can the FilesWriter handle globbed files correctly?"""
168
168
169 # Work in a temporary directory.
169 # Work in a temporary directory.
170 with self.create_temp_cwd():
170 with self.create_temp_cwd():
171
171
172 # Create test files
172 # Create test files
173 os.mkdir('sub')
173 os.mkdir('sub')
174 with open(os.path.join('sub', 'c'), 'w') as f:
174 with open(os.path.join('sub', 'c'), 'w') as f:
175 f.write('e')
175 f.write('e')
176 with open(os.path.join('sub', 'd'), 'w') as f:
176 with open(os.path.join('sub', 'd'), 'w') as f:
177 f.write('e')
177 f.write('e')
178
178
179 # Create the resoruces dictionary
179 # Create the resoruces dictionary
180 res = {}
180 res = {}
181
181
182 # Create files writer, test output
182 # Create files writer, test output
183 writer = FilesWriter()
183 writer = FilesWriter()
184 writer.files = ['sub/*']
184 writer.files = ['sub/*']
185 writer.build_directory = u'build'
185 writer.build_directory = u'build'
186 writer.write(u'y', res, notebook_name="z")
186 writer.write(u'y', res, notebook_name="z")
187
187
188 # Check the output of the file
188 # Check the output of the file
189 assert os.path.isdir(writer.build_directory)
189 assert os.path.isdir(writer.build_directory)
190 dest = os.path.join(writer.build_directory, 'z')
190 dest = os.path.join(writer.build_directory, 'z')
191 with open(dest, 'r') as f:
191 with open(dest, 'r') as f:
192 output = f.read()
192 output = f.read()
193 self.assertEqual(output, u'y')
193 self.assertEqual(output, u'y')
194
194
195 # Check to make sure the globbed files were copied
195 # Check to make sure the globbed files were copied
196 path = os.path.join(writer.build_directory, 'sub')
196 path = os.path.join(writer.build_directory, 'sub')
197 assert os.path.isdir(path)
197 assert os.path.isdir(path)
198 for filename in ['c', 'd']:
198 for filename in ['c', 'd']:
199 dest = os.path.join(path, filename)
199 dest = os.path.join(path, filename)
200 assert os.path.isfile(dest)
200 assert os.path.isfile(dest)
201 with open(dest, 'r') as f:
201 with open(dest, 'r') as f:
202 output = f.read()
202 output = f.read()
203 self.assertEqual(output, 'e')
203 self.assertEqual(output, 'e')
204
205 def test_relpath(self):
206 """Can the FilesWriter handle relative paths for linked files correctly?"""
207
208 # Work in a temporary directory.
209 with self.create_temp_cwd():
210
211 # Create test file
212 os.mkdir('sub')
213 with open(os.path.join('sub', 'c'), 'w') as f:
214 f.write('d')
215
216 # Create the resoruces dictionary
217 res = {}
218
219 # Create files writer, test output
220 writer = FilesWriter()
221 writer.files = [os.path.join('sub', 'c')]
222 writer.build_directory = u'build'
223 writer.relpath = 'sub'
224 writer.write(u'y', res, notebook_name="z")
225
226 # Check the output of the file
227 assert os.path.isdir(writer.build_directory)
228 dest = os.path.join(writer.build_directory, 'z')
229 with open(dest, 'r') as f:
230 output = f.read()
231 self.assertEqual(output, u'y')
232
233 # Check to make sure the linked file was copied
234 dest = os.path.join(writer.build_directory, 'c')
235 assert os.path.isfile(dest)
236 with open(dest, 'r') as f:
237 output = f.read()
238 self.assertEqual(output, 'd')
General Comments 0
You need to be logged in to leave comments. Login now