##// END OF EJS Templates
Make nbconvert a little less chatty....
Matthias Bussonnier -
Show More
@@ -1,321 +1,321 b''
1 """This module defines TemplateExporter, a highly configurable converter
1 """This module defines TemplateExporter, a highly configurable converter
2 that uses Jinja2 to export notebook files into different formats.
2 that uses Jinja2 to export notebook files into different formats.
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 from __future__ import print_function, absolute_import
17 from __future__ import print_function, absolute_import
18
18
19 # Stdlib imports
19 # Stdlib imports
20 import os
20 import os
21
21
22 # other libs/dependencies are imported at runtime
22 # other libs/dependencies are imported at runtime
23 # to move ImportErrors to runtime when the requirement is actually needed
23 # to move ImportErrors to runtime when the requirement is actually needed
24
24
25 # IPython imports
25 # IPython imports
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils import py3compat, text
28 from IPython.utils import py3compat, text
29
29
30 from IPython.nbconvert import filters
30 from IPython.nbconvert import filters
31 from .exporter import Exporter
31 from .exporter import Exporter
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Globals and constants
34 # Globals and constants
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 #Jinja2 extensions to load.
37 #Jinja2 extensions to load.
38 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
38 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
39
39
40 default_filters = {
40 default_filters = {
41 'indent': text.indent,
41 'indent': text.indent,
42 'markdown2html': filters.markdown2html,
42 'markdown2html': filters.markdown2html,
43 'ansi2html': filters.ansi2html,
43 'ansi2html': filters.ansi2html,
44 'filter_data_type': filters.DataTypeFilter,
44 'filter_data_type': filters.DataTypeFilter,
45 'get_lines': filters.get_lines,
45 'get_lines': filters.get_lines,
46 'highlight2html': filters.Highlight2HTML,
46 'highlight2html': filters.Highlight2HTML,
47 'highlight2latex': filters.Highlight2Latex,
47 'highlight2latex': filters.Highlight2Latex,
48 'ipython2python': filters.ipython2python,
48 'ipython2python': filters.ipython2python,
49 'posix_path': filters.posix_path,
49 'posix_path': filters.posix_path,
50 'markdown2latex': filters.markdown2latex,
50 'markdown2latex': filters.markdown2latex,
51 'markdown2rst': filters.markdown2rst,
51 'markdown2rst': filters.markdown2rst,
52 'comment_lines': filters.comment_lines,
52 'comment_lines': filters.comment_lines,
53 'strip_ansi': filters.strip_ansi,
53 'strip_ansi': filters.strip_ansi,
54 'strip_dollars': filters.strip_dollars,
54 'strip_dollars': filters.strip_dollars,
55 'strip_files_prefix': filters.strip_files_prefix,
55 'strip_files_prefix': filters.strip_files_prefix,
56 'html2text' : filters.html2text,
56 'html2text' : filters.html2text,
57 'add_anchor': filters.add_anchor,
57 'add_anchor': filters.add_anchor,
58 'ansi2latex': filters.ansi2latex,
58 'ansi2latex': filters.ansi2latex,
59 'wrap_text': filters.wrap_text,
59 'wrap_text': filters.wrap_text,
60 'escape_latex': filters.escape_latex,
60 'escape_latex': filters.escape_latex,
61 'citation2latex': filters.citation2latex,
61 'citation2latex': filters.citation2latex,
62 'path2url': filters.path2url,
62 'path2url': filters.path2url,
63 'add_prompts': filters.add_prompts,
63 'add_prompts': filters.add_prompts,
64 'ascii_only': filters.ascii_only,
64 'ascii_only': filters.ascii_only,
65 'prevent_list_blocks': filters.prevent_list_blocks,
65 'prevent_list_blocks': filters.prevent_list_blocks,
66 }
66 }
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Class
69 # Class
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71
71
72 class TemplateExporter(Exporter):
72 class TemplateExporter(Exporter):
73 """
73 """
74 Exports notebooks into other file formats. Uses Jinja 2 templating engine
74 Exports notebooks into other file formats. Uses Jinja 2 templating engine
75 to output new formats. Inherit from this class if you are creating a new
75 to output new formats. Inherit from this class if you are creating a new
76 template type along with new filters/preprocessors. If the filters/
76 template type along with new filters/preprocessors. If the filters/
77 preprocessors provided by default suffice, there is no need to inherit from
77 preprocessors provided by default suffice, there is no need to inherit from
78 this class. Instead, override the template_file and file_extension
78 this class. Instead, override the template_file and file_extension
79 traits via a config file.
79 traits via a config file.
80
80
81 {filters}
81 {filters}
82 """
82 """
83
83
84 # finish the docstring
84 # finish the docstring
85 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
86
86
87
87
88 template_file = Unicode(u'default',
88 template_file = Unicode(u'default',
89 config=True,
89 config=True,
90 help="Name of the template file to use")
90 help="Name of the template file to use")
91 def _template_file_changed(self, name, old, new):
91 def _template_file_changed(self, name, old, new):
92 if new == 'default':
92 if new == 'default':
93 self.template_file = self.default_template
93 self.template_file = self.default_template
94 else:
94 else:
95 self.template_file = new
95 self.template_file = new
96 self.template = None
96 self.template = None
97 self._load_template()
97 self._load_template()
98
98
99 default_template = Unicode(u'')
99 default_template = Unicode(u'')
100 template = Any()
100 template = Any()
101 environment = Any()
101 environment = Any()
102
102
103 template_path = List(['.'], config=True)
103 template_path = List(['.'], config=True)
104 def _template_path_changed(self, name, old, new):
104 def _template_path_changed(self, name, old, new):
105 self._load_template()
105 self._load_template()
106
106
107 default_template_path = Unicode(
107 default_template_path = Unicode(
108 os.path.join("..", "templates"),
108 os.path.join("..", "templates"),
109 help="Path where the template files are located.")
109 help="Path where the template files are located.")
110
110
111 template_skeleton_path = Unicode(
111 template_skeleton_path = Unicode(
112 os.path.join("..", "templates", "skeleton"),
112 os.path.join("..", "templates", "skeleton"),
113 help="Path where the template skeleton files are located.")
113 help="Path where the template skeleton files are located.")
114
114
115 #Jinja block definitions
115 #Jinja block definitions
116 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_start = Unicode("", config=True)
117 jinja_comment_block_end = Unicode("", config=True)
117 jinja_comment_block_end = Unicode("", config=True)
118 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_start = Unicode("", config=True)
119 jinja_variable_block_end = Unicode("", config=True)
119 jinja_variable_block_end = Unicode("", config=True)
120 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_start = Unicode("", config=True)
121 jinja_logic_block_end = Unicode("", config=True)
121 jinja_logic_block_end = Unicode("", config=True)
122
122
123 #Extension that the template files use.
123 #Extension that the template files use.
124 template_extension = Unicode(".tpl", config=True)
124 template_extension = Unicode(".tpl", config=True)
125
125
126 filters = Dict(config=True,
126 filters = Dict(config=True,
127 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 help="""Dictionary of filters, by name and namespace, to add to the Jinja
128 environment.""")
128 environment.""")
129
129
130 raw_mimetypes = List(config=True,
130 raw_mimetypes = List(config=True,
131 help="""formats of raw cells to be included in this Exporter's output."""
131 help="""formats of raw cells to be included in this Exporter's output."""
132 )
132 )
133 def _raw_mimetypes_default(self):
133 def _raw_mimetypes_default(self):
134 return [self.output_mimetype, '']
134 return [self.output_mimetype, '']
135
135
136
136
137 def __init__(self, config=None, extra_loaders=None, **kw):
137 def __init__(self, config=None, extra_loaders=None, **kw):
138 """
138 """
139 Public constructor
139 Public constructor
140
140
141 Parameters
141 Parameters
142 ----------
142 ----------
143 config : config
143 config : config
144 User configuration instance.
144 User configuration instance.
145 extra_loaders : list[of Jinja Loaders]
145 extra_loaders : list[of Jinja Loaders]
146 ordered list of Jinja loader to find templates. Will be tried in order
146 ordered list of Jinja loader to find templates. Will be tried in order
147 before the default FileSystem ones.
147 before the default FileSystem ones.
148 template : str (optional, kw arg)
148 template : str (optional, kw arg)
149 Template to use when exporting.
149 Template to use when exporting.
150 """
150 """
151 super(TemplateExporter, self).__init__(config=config, **kw)
151 super(TemplateExporter, self).__init__(config=config, **kw)
152
152
153 #Init
153 #Init
154 self._init_template()
154 self._init_template()
155 self._init_environment(extra_loaders=extra_loaders)
155 self._init_environment(extra_loaders=extra_loaders)
156 self._init_filters()
156 self._init_filters()
157
157
158
158
159 def _load_template(self):
159 def _load_template(self):
160 """Load the Jinja template object from the template file
160 """Load the Jinja template object from the template file
161
161
162 This is a no-op if the template attribute is already defined,
162 This is a no-op if the template attribute is already defined,
163 or the Jinja environment is not setup yet.
163 or the Jinja environment is not setup yet.
164
164
165 This is triggered by various trait changes that would change the template.
165 This is triggered by various trait changes that would change the template.
166 """
166 """
167 from jinja2 import TemplateNotFound
167 from jinja2 import TemplateNotFound
168
168
169 if self.template is not None:
169 if self.template is not None:
170 return
170 return
171 # called too early, do nothing
171 # called too early, do nothing
172 if self.environment is None:
172 if self.environment is None:
173 return
173 return
174 # Try different template names during conversion. First try to load the
174 # Try different template names during conversion. First try to load the
175 # template by name with extension added, then try loading the template
175 # template by name with extension added, then try loading the template
176 # as if the name is explicitly specified, then try the name as a
176 # as if the name is explicitly specified, then try the name as a
177 # 'flavor', and lastly just try to load the template by module name.
177 # 'flavor', and lastly just try to load the template by module name.
178 try_names = []
178 try_names = []
179 if self.template_file:
179 if self.template_file:
180 try_names.extend([
180 try_names.extend([
181 self.template_file + self.template_extension,
181 self.template_file + self.template_extension,
182 self.template_file,
182 self.template_file,
183 ])
183 ])
184 for try_name in try_names:
184 for try_name in try_names:
185 self.log.debug("Attempting to load template %s", try_name)
185 self.log.debug("Attempting to load template %s", try_name)
186 try:
186 try:
187 self.template = self.environment.get_template(try_name)
187 self.template = self.environment.get_template(try_name)
188 except (TemplateNotFound, IOError):
188 except (TemplateNotFound, IOError):
189 pass
189 pass
190 except Exception as e:
190 except Exception as e:
191 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
191 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
192 else:
192 else:
193 self.log.info("Loaded template %s", try_name)
193 self.log.debug("Loaded template %s", try_name)
194 break
194 break
195
195
196 def from_notebook_node(self, nb, resources=None, **kw):
196 def from_notebook_node(self, nb, resources=None, **kw):
197 """
197 """
198 Convert a notebook from a notebook node instance.
198 Convert a notebook from a notebook node instance.
199
199
200 Parameters
200 Parameters
201 ----------
201 ----------
202 nb : :class:`~IPython.nbformat.NotebookNode`
202 nb : :class:`~IPython.nbformat.NotebookNode`
203 Notebook node
203 Notebook node
204 resources : dict
204 resources : dict
205 Additional resources that can be accessed read/write by
205 Additional resources that can be accessed read/write by
206 preprocessors and filters.
206 preprocessors and filters.
207 """
207 """
208 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
208 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
209 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
209 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
210
210
211 self._load_template()
211 self._load_template()
212
212
213 if self.template is not None:
213 if self.template is not None:
214 output = self.template.render(nb=nb_copy, resources=resources)
214 output = self.template.render(nb=nb_copy, resources=resources)
215 else:
215 else:
216 raise IOError('template file "%s" could not be found' % self.template_file)
216 raise IOError('template file "%s" could not be found' % self.template_file)
217 return output, resources
217 return output, resources
218
218
219
219
220 def register_filter(self, name, jinja_filter):
220 def register_filter(self, name, jinja_filter):
221 """
221 """
222 Register a filter.
222 Register a filter.
223 A filter is a function that accepts and acts on one string.
223 A filter is a function that accepts and acts on one string.
224 The filters are accesible within the Jinja templating engine.
224 The filters are accesible within the Jinja templating engine.
225
225
226 Parameters
226 Parameters
227 ----------
227 ----------
228 name : str
228 name : str
229 name to give the filter in the Jinja engine
229 name to give the filter in the Jinja engine
230 filter : filter
230 filter : filter
231 """
231 """
232 if jinja_filter is None:
232 if jinja_filter is None:
233 raise TypeError('filter')
233 raise TypeError('filter')
234 isclass = isinstance(jinja_filter, type)
234 isclass = isinstance(jinja_filter, type)
235 constructed = not isclass
235 constructed = not isclass
236
236
237 #Handle filter's registration based on it's type
237 #Handle filter's registration based on it's type
238 if constructed and isinstance(jinja_filter, py3compat.string_types):
238 if constructed and isinstance(jinja_filter, py3compat.string_types):
239 #filter is a string, import the namespace and recursively call
239 #filter is a string, import the namespace and recursively call
240 #this register_filter method
240 #this register_filter method
241 filter_cls = import_item(jinja_filter)
241 filter_cls = import_item(jinja_filter)
242 return self.register_filter(name, filter_cls)
242 return self.register_filter(name, filter_cls)
243
243
244 if constructed and hasattr(jinja_filter, '__call__'):
244 if constructed and hasattr(jinja_filter, '__call__'):
245 #filter is a function, no need to construct it.
245 #filter is a function, no need to construct it.
246 self.environment.filters[name] = jinja_filter
246 self.environment.filters[name] = jinja_filter
247 return jinja_filter
247 return jinja_filter
248
248
249 elif isclass and isinstance(jinja_filter, MetaHasTraits):
249 elif isclass and isinstance(jinja_filter, MetaHasTraits):
250 #filter is configurable. Make sure to pass in new default for
250 #filter is configurable. Make sure to pass in new default for
251 #the enabled flag if one was specified.
251 #the enabled flag if one was specified.
252 filter_instance = jinja_filter(parent=self)
252 filter_instance = jinja_filter(parent=self)
253 self.register_filter(name, filter_instance )
253 self.register_filter(name, filter_instance )
254
254
255 elif isclass:
255 elif isclass:
256 #filter is not configurable, construct it
256 #filter is not configurable, construct it
257 filter_instance = jinja_filter()
257 filter_instance = jinja_filter()
258 self.register_filter(name, filter_instance)
258 self.register_filter(name, filter_instance)
259
259
260 else:
260 else:
261 #filter is an instance of something without a __call__
261 #filter is an instance of something without a __call__
262 #attribute.
262 #attribute.
263 raise TypeError('filter')
263 raise TypeError('filter')
264
264
265
265
266 def _init_template(self):
266 def _init_template(self):
267 """
267 """
268 Make sure a template name is specified. If one isn't specified, try to
268 Make sure a template name is specified. If one isn't specified, try to
269 build one from the information we know.
269 build one from the information we know.
270 """
270 """
271 self._template_file_changed('template_file', self.template_file, self.template_file)
271 self._template_file_changed('template_file', self.template_file, self.template_file)
272
272
273
273
274 def _init_environment(self, extra_loaders=None):
274 def _init_environment(self, extra_loaders=None):
275 """
275 """
276 Create the Jinja templating environment.
276 Create the Jinja templating environment.
277 """
277 """
278 from jinja2 import Environment, ChoiceLoader, FileSystemLoader
278 from jinja2 import Environment, ChoiceLoader, FileSystemLoader
279 here = os.path.dirname(os.path.realpath(__file__))
279 here = os.path.dirname(os.path.realpath(__file__))
280 loaders = []
280 loaders = []
281 if extra_loaders:
281 if extra_loaders:
282 loaders.extend(extra_loaders)
282 loaders.extend(extra_loaders)
283
283
284 paths = self.template_path
284 paths = self.template_path
285 paths.extend([os.path.join(here, self.default_template_path),
285 paths.extend([os.path.join(here, self.default_template_path),
286 os.path.join(here, self.template_skeleton_path)])
286 os.path.join(here, self.template_skeleton_path)])
287 loaders.append(FileSystemLoader(paths))
287 loaders.append(FileSystemLoader(paths))
288
288
289 self.environment = Environment(
289 self.environment = Environment(
290 loader= ChoiceLoader(loaders),
290 loader= ChoiceLoader(loaders),
291 extensions=JINJA_EXTENSIONS
291 extensions=JINJA_EXTENSIONS
292 )
292 )
293
293
294 #Set special Jinja2 syntax that will not conflict with latex.
294 #Set special Jinja2 syntax that will not conflict with latex.
295 if self.jinja_logic_block_start:
295 if self.jinja_logic_block_start:
296 self.environment.block_start_string = self.jinja_logic_block_start
296 self.environment.block_start_string = self.jinja_logic_block_start
297 if self.jinja_logic_block_end:
297 if self.jinja_logic_block_end:
298 self.environment.block_end_string = self.jinja_logic_block_end
298 self.environment.block_end_string = self.jinja_logic_block_end
299 if self.jinja_variable_block_start:
299 if self.jinja_variable_block_start:
300 self.environment.variable_start_string = self.jinja_variable_block_start
300 self.environment.variable_start_string = self.jinja_variable_block_start
301 if self.jinja_variable_block_end:
301 if self.jinja_variable_block_end:
302 self.environment.variable_end_string = self.jinja_variable_block_end
302 self.environment.variable_end_string = self.jinja_variable_block_end
303 if self.jinja_comment_block_start:
303 if self.jinja_comment_block_start:
304 self.environment.comment_start_string = self.jinja_comment_block_start
304 self.environment.comment_start_string = self.jinja_comment_block_start
305 if self.jinja_comment_block_end:
305 if self.jinja_comment_block_end:
306 self.environment.comment_end_string = self.jinja_comment_block_end
306 self.environment.comment_end_string = self.jinja_comment_block_end
307
307
308
308
309 def _init_filters(self):
309 def _init_filters(self):
310 """
310 """
311 Register all of the filters required for the exporter.
311 Register all of the filters required for the exporter.
312 """
312 """
313
313
314 #Add default filters to the Jinja2 environment
314 #Add default filters to the Jinja2 environment
315 for key, value in default_filters.items():
315 for key, value in default_filters.items():
316 self.register_filter(key, value)
316 self.register_filter(key, value)
317
317
318 #Load user filters. Overwrite existing filters if need be.
318 #Load user filters. Overwrite existing filters if need be.
319 if self.filters:
319 if self.filters:
320 for key, user_filter in self.filters.items():
320 for key, user_filter in self.filters.items():
321 self.register_filter(key, user_filter)
321 self.register_filter(key, user_filter)
@@ -1,341 +1,340 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """NbConvert is a utility for conversion of .ipynb files.
2 """NbConvert is a utility for conversion of .ipynb files.
3
3
4 Command-line interface for the NbConvert conversion utility.
4 Command-line interface for the NbConvert conversion utility.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 import logging
12 import logging
13 import sys
13 import sys
14 import os
14 import os
15 import glob
15 import glob
16
16
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
18 from IPython.core.profiledir import ProfileDir
18 from IPython.core.profiledir import ProfileDir
19 from IPython.config import catch_config_error, Configurable
19 from IPython.config import catch_config_error, Configurable
20 from IPython.utils.traitlets import (
20 from IPython.utils.traitlets import (
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum, Bool,
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum, Bool,
22 )
22 )
23 from IPython.utils.importstring import import_item
23 from IPython.utils.importstring import import_item
24
24
25 from .exporters.export import get_export_names, exporter_map
25 from .exporters.export import get_export_names, exporter_map
26 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
26 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
27 from .utils.base import NbConvertBase
27 from .utils.base import NbConvertBase
28 from .utils.exceptions import ConversionException
28 from .utils.exceptions import ConversionException
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 #Classes and functions
31 #Classes and functions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 class DottedOrNone(DottedObjectName):
34 class DottedOrNone(DottedObjectName):
35 """
35 """
36 A string holding a valid dotted object name in Python, such as A.b3._c
36 A string holding a valid dotted object name in Python, such as A.b3._c
37 Also allows for None type."""
37 Also allows for None type."""
38
38
39 default_value = u''
39 default_value = u''
40
40
41 def validate(self, obj, value):
41 def validate(self, obj, value):
42 if value is not None and len(value) > 0:
42 if value is not None and len(value) > 0:
43 return super(DottedOrNone, self).validate(obj, value)
43 return super(DottedOrNone, self).validate(obj, value)
44 else:
44 else:
45 return value
45 return value
46
46
47 nbconvert_aliases = {}
47 nbconvert_aliases = {}
48 nbconvert_aliases.update(base_aliases)
48 nbconvert_aliases.update(base_aliases)
49 nbconvert_aliases.update({
49 nbconvert_aliases.update({
50 'to' : 'NbConvertApp.export_format',
50 'to' : 'NbConvertApp.export_format',
51 'template' : 'TemplateExporter.template_file',
51 'template' : 'TemplateExporter.template_file',
52 'writer' : 'NbConvertApp.writer_class',
52 'writer' : 'NbConvertApp.writer_class',
53 'post': 'NbConvertApp.postprocessor_class',
53 'post': 'NbConvertApp.postprocessor_class',
54 'output': 'NbConvertApp.output_base',
54 'output': 'NbConvertApp.output_base',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
56 'nbformat': 'NotebookExporter.nbformat_version',
56 'nbformat': 'NotebookExporter.nbformat_version',
57 })
57 })
58
58
59 nbconvert_flags = {}
59 nbconvert_flags = {}
60 nbconvert_flags.update(base_flags)
60 nbconvert_flags.update(base_flags)
61 nbconvert_flags.update({
61 nbconvert_flags.update({
62 'execute' : (
62 'execute' : (
63 {'ExecutePreprocessor' : {'enabled' : True}},
63 {'ExecutePreprocessor' : {'enabled' : True}},
64 "Execute the notebook prior to export."
64 "Execute the notebook prior to export."
65 ),
65 ),
66 'stdout' : (
66 'stdout' : (
67 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
67 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
68 "Write notebook output to stdout instead of files."
68 "Write notebook output to stdout instead of files."
69 ),
69 ),
70 'inplace' : (
70 'inplace' : (
71 {
71 {
72 'NbConvertApp' : {'use_output_suffix' : False},
72 'NbConvertApp' : {'use_output_suffix' : False},
73 'FilesWriter': {'build_directory': ''}
73 'FilesWriter': {'build_directory': ''}
74 },
74 },
75 """Run nbconvert in place, overwriting the existing notebook (only
75 """Run nbconvert in place, overwriting the existing notebook (only
76 relevant when converting to notebook format)"""
76 relevant when converting to notebook format)"""
77 )
77 )
78 })
78 })
79
79
80
80
81 class NbConvertApp(BaseIPythonApplication):
81 class NbConvertApp(BaseIPythonApplication):
82 """Application used to convert from notebook file type (``*.ipynb``)"""
82 """Application used to convert from notebook file type (``*.ipynb``)"""
83
83
84 name = 'ipython-nbconvert'
84 name = 'ipython-nbconvert'
85 aliases = nbconvert_aliases
85 aliases = nbconvert_aliases
86 flags = nbconvert_flags
86 flags = nbconvert_flags
87
87
88 def _log_level_default(self):
88 def _log_level_default(self):
89 return logging.INFO
89 return logging.INFO
90
90
91 def _classes_default(self):
91 def _classes_default(self):
92 classes = [NbConvertBase, ProfileDir]
92 classes = [NbConvertBase, ProfileDir]
93 for pkg in (exporters, preprocessors, writers, postprocessors):
93 for pkg in (exporters, preprocessors, writers, postprocessors):
94 for name in dir(pkg):
94 for name in dir(pkg):
95 cls = getattr(pkg, name)
95 cls = getattr(pkg, name)
96 if isinstance(cls, type) and issubclass(cls, Configurable):
96 if isinstance(cls, type) and issubclass(cls, Configurable):
97 classes.append(cls)
97 classes.append(cls)
98
98
99 return classes
99 return classes
100
100
101 description = Unicode(
101 description = Unicode(
102 u"""This application is used to convert notebook files (*.ipynb)
102 u"""This application is used to convert notebook files (*.ipynb)
103 to various other formats.
103 to various other formats.
104
104
105 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
105 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
106
106
107 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
107 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
108 can only be used when converting one notebook at a time.
108 can only be used when converting one notebook at a time.
109 ''')
109 ''')
110
110
111 use_output_suffix = Bool(
111 use_output_suffix = Bool(
112 True,
112 True,
113 config=True,
113 config=True,
114 help="""Whether to apply a suffix prior to the extension (only relevant
114 help="""Whether to apply a suffix prior to the extension (only relevant
115 when converting to notebook format). The suffix is determined by
115 when converting to notebook format). The suffix is determined by
116 the exporter, and is usually '.nbconvert'.""")
116 the exporter, and is usually '.nbconvert'.""")
117
117
118 examples = Unicode(u"""
118 examples = Unicode(u"""
119 The simplest way to use nbconvert is
119 The simplest way to use nbconvert is
120
120
121 > ipython nbconvert mynotebook.ipynb
121 > ipython nbconvert mynotebook.ipynb
122
122
123 which will convert mynotebook.ipynb to the default format (probably HTML).
123 which will convert mynotebook.ipynb to the default format (probably HTML).
124
124
125 You can specify the export format with `--to`.
125 You can specify the export format with `--to`.
126 Options include {0}
126 Options include {0}
127
127
128 > ipython nbconvert --to latex mynotebook.ipynb
128 > ipython nbconvert --to latex mynotebook.ipynb
129
129
130 Both HTML and LaTeX support multiple output templates. LaTeX includes
130 Both HTML and LaTeX support multiple output templates. LaTeX includes
131 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You
131 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You
132 can specify the flavor of the format used.
132 can specify the flavor of the format used.
133
133
134 > ipython nbconvert --to html --template basic mynotebook.ipynb
134 > ipython nbconvert --to html --template basic mynotebook.ipynb
135
135
136 You can also pipe the output to stdout, rather than a file
136 You can also pipe the output to stdout, rather than a file
137
137
138 > ipython nbconvert mynotebook.ipynb --stdout
138 > ipython nbconvert mynotebook.ipynb --stdout
139
139
140 PDF is generated via latex
140 PDF is generated via latex
141
141
142 > ipython nbconvert mynotebook.ipynb --to pdf
142 > ipython nbconvert mynotebook.ipynb --to pdf
143
143
144 You can get (and serve) a Reveal.js-powered slideshow
144 You can get (and serve) a Reveal.js-powered slideshow
145
145
146 > ipython nbconvert myslides.ipynb --to slides --post serve
146 > ipython nbconvert myslides.ipynb --to slides --post serve
147
147
148 Multiple notebooks can be given at the command line in a couple of
148 Multiple notebooks can be given at the command line in a couple of
149 different ways:
149 different ways:
150
150
151 > ipython nbconvert notebook*.ipynb
151 > ipython nbconvert notebook*.ipynb
152 > ipython nbconvert notebook1.ipynb notebook2.ipynb
152 > ipython nbconvert notebook1.ipynb notebook2.ipynb
153
153
154 or you can specify the notebooks list in a config file, containing::
154 or you can specify the notebooks list in a config file, containing::
155
155
156 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
156 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
157
157
158 > ipython nbconvert --config mycfg.py
158 > ipython nbconvert --config mycfg.py
159 """.format(get_export_names()))
159 """.format(get_export_names()))
160
160
161 # Writer specific variables
161 # Writer specific variables
162 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
162 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
163 help="""Instance of the writer class used to write the
163 help="""Instance of the writer class used to write the
164 results of the conversion.""")
164 results of the conversion.""")
165 writer_class = DottedObjectName('FilesWriter', config=True,
165 writer_class = DottedObjectName('FilesWriter', config=True,
166 help="""Writer class used to write the
166 help="""Writer class used to write the
167 results of the conversion""")
167 results of the conversion""")
168 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
168 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
169 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
169 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
170 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
170 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
171 writer_factory = Type()
171 writer_factory = Type()
172
172
173 def _writer_class_changed(self, name, old, new):
173 def _writer_class_changed(self, name, old, new):
174 if new.lower() in self.writer_aliases:
174 if new.lower() in self.writer_aliases:
175 new = self.writer_aliases[new.lower()]
175 new = self.writer_aliases[new.lower()]
176 self.writer_factory = import_item(new)
176 self.writer_factory = import_item(new)
177
177
178 # Post-processor specific variables
178 # Post-processor specific variables
179 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
179 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
180 help="""Instance of the PostProcessor class used to write the
180 help="""Instance of the PostProcessor class used to write the
181 results of the conversion.""")
181 results of the conversion.""")
182
182
183 postprocessor_class = DottedOrNone(config=True,
183 postprocessor_class = DottedOrNone(config=True,
184 help="""PostProcessor class used to write the
184 help="""PostProcessor class used to write the
185 results of the conversion""")
185 results of the conversion""")
186 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
186 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
187 postprocessor_factory = Type()
187 postprocessor_factory = Type()
188
188
189 def _postprocessor_class_changed(self, name, old, new):
189 def _postprocessor_class_changed(self, name, old, new):
190 if new.lower() in self.postprocessor_aliases:
190 if new.lower() in self.postprocessor_aliases:
191 new = self.postprocessor_aliases[new.lower()]
191 new = self.postprocessor_aliases[new.lower()]
192 if new:
192 if new:
193 self.postprocessor_factory = import_item(new)
193 self.postprocessor_factory = import_item(new)
194
194
195
195
196 # Other configurable variables
196 # Other configurable variables
197 export_format = CaselessStrEnum(get_export_names(),
197 export_format = CaselessStrEnum(get_export_names(),
198 default_value="html",
198 default_value="html",
199 config=True,
199 config=True,
200 help="""The export format to be used."""
200 help="""The export format to be used."""
201 )
201 )
202
202
203 notebooks = List([], config=True, help="""List of notebooks to convert.
203 notebooks = List([], config=True, help="""List of notebooks to convert.
204 Wildcards are supported.
204 Wildcards are supported.
205 Filenames passed positionally will be added to the list.
205 Filenames passed positionally will be added to the list.
206 """)
206 """)
207
207
208 @catch_config_error
208 @catch_config_error
209 def initialize(self, argv=None):
209 def initialize(self, argv=None):
210 self.init_syspath()
210 self.init_syspath()
211 super(NbConvertApp, self).initialize(argv)
211 super(NbConvertApp, self).initialize(argv)
212 self.init_notebooks()
212 self.init_notebooks()
213 self.init_writer()
213 self.init_writer()
214 self.init_postprocessor()
214 self.init_postprocessor()
215
215
216
216
217
217
218 def init_syspath(self):
218 def init_syspath(self):
219 """
219 """
220 Add the cwd to the sys.path ($PYTHONPATH)
220 Add the cwd to the sys.path ($PYTHONPATH)
221 """
221 """
222 sys.path.insert(0, os.getcwd())
222 sys.path.insert(0, os.getcwd())
223
223
224
224
225 def init_notebooks(self):
225 def init_notebooks(self):
226 """Construct the list of notebooks.
226 """Construct the list of notebooks.
227 If notebooks are passed on the command-line,
227 If notebooks are passed on the command-line,
228 they override notebooks specified in config files.
228 they override notebooks specified in config files.
229 Glob each notebook to replace notebook patterns with filenames.
229 Glob each notebook to replace notebook patterns with filenames.
230 """
230 """
231
231
232 # Specifying notebooks on the command-line overrides (rather than adds)
232 # Specifying notebooks on the command-line overrides (rather than adds)
233 # the notebook list
233 # the notebook list
234 if self.extra_args:
234 if self.extra_args:
235 patterns = self.extra_args
235 patterns = self.extra_args
236 else:
236 else:
237 patterns = self.notebooks
237 patterns = self.notebooks
238
238
239 # Use glob to replace all the notebook patterns with filenames.
239 # Use glob to replace all the notebook patterns with filenames.
240 filenames = []
240 filenames = []
241 for pattern in patterns:
241 for pattern in patterns:
242
242
243 # Use glob to find matching filenames. Allow the user to convert
243 # Use glob to find matching filenames. Allow the user to convert
244 # notebooks without having to type the extension.
244 # notebooks without having to type the extension.
245 globbed_files = glob.glob(pattern)
245 globbed_files = glob.glob(pattern)
246 globbed_files.extend(glob.glob(pattern + '.ipynb'))
246 globbed_files.extend(glob.glob(pattern + '.ipynb'))
247 if not globbed_files:
247 if not globbed_files:
248 self.log.warn("pattern %r matched no files", pattern)
248 self.log.warn("pattern %r matched no files", pattern)
249
249
250 for filename in globbed_files:
250 for filename in globbed_files:
251 if not filename in filenames:
251 if not filename in filenames:
252 filenames.append(filename)
252 filenames.append(filename)
253 self.notebooks = filenames
253 self.notebooks = filenames
254
254
255 def init_writer(self):
255 def init_writer(self):
256 """
256 """
257 Initialize the writer (which is stateless)
257 Initialize the writer (which is stateless)
258 """
258 """
259 self._writer_class_changed(None, self.writer_class, self.writer_class)
259 self._writer_class_changed(None, self.writer_class, self.writer_class)
260 self.writer = self.writer_factory(parent=self)
260 self.writer = self.writer_factory(parent=self)
261 if hasattr(self.writer, 'build_directory') and self.writer.build_directory != '':
261 if hasattr(self.writer, 'build_directory') and self.writer.build_directory != '':
262 self.use_output_suffix = False
262 self.use_output_suffix = False
263
263
264 def init_postprocessor(self):
264 def init_postprocessor(self):
265 """
265 """
266 Initialize the postprocessor (which is stateless)
266 Initialize the postprocessor (which is stateless)
267 """
267 """
268 self._postprocessor_class_changed(None, self.postprocessor_class,
268 self._postprocessor_class_changed(None, self.postprocessor_class,
269 self.postprocessor_class)
269 self.postprocessor_class)
270 if self.postprocessor_factory:
270 if self.postprocessor_factory:
271 self.postprocessor = self.postprocessor_factory(parent=self)
271 self.postprocessor = self.postprocessor_factory(parent=self)
272
272
273 def start(self):
273 def start(self):
274 """
274 """
275 Ran after initialization completed
275 Ran after initialization completed
276 """
276 """
277 super(NbConvertApp, self).start()
277 super(NbConvertApp, self).start()
278 self.convert_notebooks()
278 self.convert_notebooks()
279
279
280 def convert_notebooks(self):
280 def convert_notebooks(self):
281 """
281 """
282 Convert the notebooks in the self.notebook traitlet
282 Convert the notebooks in the self.notebook traitlet
283 """
283 """
284 # Export each notebook
284 # Export each notebook
285 conversion_success = 0
285 conversion_success = 0
286
286
287 if self.output_base != '' and len(self.notebooks) > 1:
287 if self.output_base != '' and len(self.notebooks) > 1:
288 self.log.error(
288 self.log.error(
289 """UsageError: --output flag or `NbConvertApp.output_base` config option
289 """UsageError: --output flag or `NbConvertApp.output_base` config option
290 cannot be used when converting multiple notebooks.
290 cannot be used when converting multiple notebooks.
291 """)
291 """)
292 self.exit(1)
292 self.exit(1)
293
293
294 exporter = exporter_map[self.export_format](config=self.config)
294 exporter = exporter_map[self.export_format](config=self.config)
295
295
296 for notebook_filename in self.notebooks:
296 for notebook_filename in self.notebooks:
297 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
297 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
298
298
299 # Get a unique key for the notebook and set it in the resources object.
299 # Get a unique key for the notebook and set it in the resources object.
300 basename = os.path.basename(notebook_filename)
300 basename = os.path.basename(notebook_filename)
301 notebook_name = basename[:basename.rfind('.')]
301 notebook_name = basename[:basename.rfind('.')]
302 if self.output_base:
302 if self.output_base:
303 # strip duplicate extension from output_base, to avoid Basname.ext.ext
303 # strip duplicate extension from output_base, to avoid Basname.ext.ext
304 if getattr(exporter, 'file_extension', False):
304 if getattr(exporter, 'file_extension', False):
305 base, ext = os.path.splitext(self.output_base)
305 base, ext = os.path.splitext(self.output_base)
306 if ext == exporter.file_extension:
306 if ext == exporter.file_extension:
307 self.output_base = base
307 self.output_base = base
308 notebook_name = self.output_base
308 notebook_name = self.output_base
309 resources = {}
309 resources = {}
310 resources['profile_dir'] = self.profile_dir.location
310 resources['profile_dir'] = self.profile_dir.location
311 resources['unique_key'] = notebook_name
311 resources['unique_key'] = notebook_name
312 resources['output_files_dir'] = '%s_files' % notebook_name
312 resources['output_files_dir'] = '%s_files' % notebook_name
313 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
314
313
315 # Try to export
314 # Try to export
316 try:
315 try:
317 output, resources = exporter.from_filename(notebook_filename, resources=resources)
316 output, resources = exporter.from_filename(notebook_filename, resources=resources)
318 except ConversionException as e:
317 except ConversionException as e:
319 self.log.error("Error while converting '%s'", notebook_filename,
318 self.log.error("Error while converting '%s'", notebook_filename,
320 exc_info=True)
319 exc_info=True)
321 self.exit(1)
320 self.exit(1)
322 else:
321 else:
323 if self.use_output_suffix and 'output_suffix' in resources and not self.output_base:
322 if self.use_output_suffix and 'output_suffix' in resources and not self.output_base:
324 notebook_name += resources['output_suffix']
323 notebook_name += resources['output_suffix']
325 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
324 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
326
325
327 #Post-process if post processor has been defined.
326 #Post-process if post processor has been defined.
328 if hasattr(self, 'postprocessor') and self.postprocessor:
327 if hasattr(self, 'postprocessor') and self.postprocessor:
329 self.postprocessor(write_results)
328 self.postprocessor(write_results)
330 conversion_success += 1
329 conversion_success += 1
331
330
332 # If nothing was converted successfully, help the user.
331 # If nothing was converted successfully, help the user.
333 if conversion_success == 0:
332 if conversion_success == 0:
334 self.print_help()
333 self.print_help()
335 sys.exit(-1)
334 sys.exit(-1)
336
335
337 #-----------------------------------------------------------------------------
336 #-----------------------------------------------------------------------------
338 # Main entry point
337 # Main entry point
339 #-----------------------------------------------------------------------------
338 #-----------------------------------------------------------------------------
340
339
341 launch_new_instance = NbConvertApp.launch_instance
340 launch_new_instance = NbConvertApp.launch_instance
@@ -1,131 +1,135 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(
28 relpath = Unicode(
29 "", config=True,
29 "", config=True,
30 help="""When copying files that the notebook depends on, copy them in
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
31 relation to this path, such that the destination filename will be
32 os.path.relpath(filename, relpath). If FilesWriter is operating on a
32 os.path.relpath(filename, relpath). If FilesWriter is operating on a
33 notebook that already exists elsewhere on disk, then the default will be
33 notebook that already exists elsewhere on disk, then the default will be
34 the directory containing that notebook.""")
34 the directory containing that notebook.""")
35
35
36
36
37 # Make sure that the output directory exists.
37 # Make sure that the output directory exists.
38 def _build_directory_changed(self, name, old, new):
38 def _build_directory_changed(self, name, old, new):
39 if new:
39 if new:
40 ensure_dir_exists(new)
40 ensure_dir_exists(new)
41
41
42
42
43 def __init__(self, **kw):
43 def __init__(self, **kw):
44 super(FilesWriter, self).__init__(**kw)
44 super(FilesWriter, self).__init__(**kw)
45 self._build_directory_changed('build_directory', self.build_directory,
45 self._build_directory_changed('build_directory', self.build_directory,
46 self.build_directory)
46 self.build_directory)
47
47
48 def _makedir(self, path):
48 def _makedir(self, path):
49 """Make a directory if it doesn't already exist"""
49 """Make a directory if it doesn't already exist"""
50 if path:
50 if path:
51 self.log.info("Making directory %s", path)
51 self.log.info("Making directory %s", path)
52 ensure_dir_exists(path)
52 ensure_dir_exists(path)
53
53
54 def write(self, output, resources, notebook_name=None, **kw):
54 def write(self, output, resources, notebook_name=None, **kw):
55 """
55 """
56 Consume and write Jinja output to the file system. Output directory
56 Consume and write Jinja output to the file system. Output directory
57 is set via the 'build_directory' variable of this instance (a
57 is set via the 'build_directory' variable of this instance (a
58 configurable).
58 configurable).
59
59
60 See base for more...
60 See base for more...
61 """
61 """
62
62
63 # Verify that a notebook name is provided.
63 # Verify that a notebook name is provided.
64 if notebook_name is None:
64 if notebook_name is None:
65 raise TypeError('notebook_name')
65 raise TypeError('notebook_name')
66
66
67 # Pull the extension and subdir from the resources dict.
67 # Pull the extension and subdir from the resources dict.
68 output_extension = resources.get('output_extension', None)
68 output_extension = resources.get('output_extension', None)
69
69
70 # Get the relative path for copying files
70 # Get the relative path for copying files
71 if self.relpath == '':
71 if self.relpath == '':
72 relpath = resources.get('metadata', {}).get('path', '')
72 relpath = resources.get('metadata', {}).get('path', '')
73 else:
73 else:
74 relpath = self.relpath
74 relpath = self.relpath
75
75
76 # Write all of the extracted resources to the destination directory.
76 # Write all of the extracted resources to the destination directory.
77 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
77 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
78 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
78 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
79 for filename, data in resources.get('outputs', {}).items():
79
80 items = resources.get('outputs', {}).items()
81 if items:
82 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
83 for filename, data in items:
80
84
81 # Determine where to write the file to
85 # Determine where to write the file to
82 dest = os.path.join(self.build_directory, filename)
86 dest = os.path.join(self.build_directory, filename)
83 path = os.path.dirname(dest)
87 path = os.path.dirname(dest)
84 self._makedir(path)
88 self._makedir(path)
85
89
86 # Write file
90 # Write file
87 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
91 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
88 with io.open(dest, 'wb') as f:
92 with io.open(dest, 'wb') as f:
89 f.write(data)
93 f.write(data)
90
94
91 # Copy referenced files to output directory
95 # Copy referenced files to output directory
92 if self.build_directory:
96 if self.build_directory:
93 for filename in self.files:
97 for filename in self.files:
94
98
95 # Copy files that match search pattern
99 # Copy files that match search pattern
96 for matching_filename in glob.glob(filename):
100 for matching_filename in glob.glob(filename):
97
101
98 # compute the relative path for the filename
102 # compute the relative path for the filename
99 if relpath != '':
103 if relpath != '':
100 dest_filename = os.path.relpath(matching_filename, relpath)
104 dest_filename = os.path.relpath(matching_filename, relpath)
101 else:
105 else:
102 dest_filename = matching_filename
106 dest_filename = matching_filename
103
107
104 # Make sure folder exists.
108 # Make sure folder exists.
105 dest = os.path.join(self.build_directory, dest_filename)
109 dest = os.path.join(self.build_directory, dest_filename)
106 path = os.path.dirname(dest)
110 path = os.path.dirname(dest)
107 self._makedir(path)
111 self._makedir(path)
108
112
109 # Copy if destination is different.
113 # Copy if destination is different.
110 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
114 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
111 self.log.info("Linking %s -> %s", matching_filename, dest)
115 self.log.info("Linking %s -> %s", matching_filename, dest)
112 link_or_copy(matching_filename, dest)
116 link_or_copy(matching_filename, dest)
113
117
114 # Determine where to write conversion results.
118 # Determine where to write conversion results.
115 if output_extension is not None:
119 if output_extension is not None:
116 dest = notebook_name + output_extension
120 dest = notebook_name + output_extension
117 else:
121 else:
118 dest = notebook_name
122 dest = notebook_name
119 if self.build_directory:
123 if self.build_directory:
120 dest = os.path.join(self.build_directory, dest)
124 dest = os.path.join(self.build_directory, dest)
121
125
122 # Write conversion results.
126 # Write conversion results.
123 self.log.info("Writing %i bytes to %s", len(output), dest)
127 self.log.info("Writing %i bytes to %s", len(output), dest)
124 if isinstance(output, unicode_type):
128 if isinstance(output, unicode_type):
125 with io.open(dest, 'w', encoding='utf-8') as f:
129 with io.open(dest, 'w', encoding='utf-8') as f:
126 f.write(output)
130 f.write(output)
127 else:
131 else:
128 with io.open(dest, 'wb') as f:
132 with io.open(dest, 'wb') as f:
129 f.write(output)
133 f.write(output)
130
134
131 return dest
135 return dest
General Comments 0
You need to be logged in to leave comments. Login now