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