##// END OF EJS Templates
move NotebookNode to top-level...
MinRK -
Show More
@@ -0,0 +1,21 b''
1 """NotebookNode - adding attribute access to dicts"""
2
3 from IPython.utils.ipstruct import Struct
4
5 class NotebookNode(Struct):
6 """A dict-like node with attribute-access"""
7 pass
8
9 def from_dict(d):
10 """Convert dict to dict-like NotebookNode
11
12 Recursively converts any dict in the container to a NotebookNode
13 """
14 if isinstance(d, dict):
15 return NotebookNode({k:from_dict(v) for k,v in d.items()})
16 elif isinstance(d, (tuple, list)):
17 return [from_dict(i) for i in d]
18 else:
19 return d
20
21
@@ -1,174 +1,174 b''
1 """Module containing single call export functions."""
1 """Module containing single call export functions."""
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 from functools import wraps
6 from functools import wraps
7
7
8 from IPython.nbformat.v4 import NotebookNode
8 from IPython.nbformat import NotebookNode
9 from IPython.utils.decorators import undoc
9 from IPython.utils.decorators import undoc
10 from IPython.utils.py3compat import string_types
10 from IPython.utils.py3compat import string_types
11
11
12 from .exporter import Exporter
12 from .exporter import Exporter
13 from .templateexporter import TemplateExporter
13 from .templateexporter import TemplateExporter
14 from .html import HTMLExporter
14 from .html import HTMLExporter
15 from .slides import SlidesExporter
15 from .slides import SlidesExporter
16 from .latex import LatexExporter
16 from .latex import LatexExporter
17 from .pdf import PDFExporter
17 from .pdf import PDFExporter
18 from .markdown import MarkdownExporter
18 from .markdown import MarkdownExporter
19 from .python import PythonExporter
19 from .python import PythonExporter
20 from .rst import RSTExporter
20 from .rst import RSTExporter
21 from .notebook import NotebookExporter
21 from .notebook import NotebookExporter
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Classes
24 # Classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 @undoc
27 @undoc
28 def DocDecorator(f):
28 def DocDecorator(f):
29
29
30 #Set docstring of function
30 #Set docstring of function
31 f.__doc__ = f.__doc__ + """
31 f.__doc__ = f.__doc__ + """
32 nb : :class:`~{nbnode_mod}.NotebookNode`
32 nb : :class:`~IPython.nbformat.NotebookNode`
33 The notebook to export.
33 The notebook to export.
34 config : config (optional, keyword arg)
34 config : config (optional, keyword arg)
35 User configuration instance.
35 User configuration instance.
36 resources : dict (optional, keyword arg)
36 resources : dict (optional, keyword arg)
37 Resources used in the conversion process.
37 Resources used in the conversion process.
38
38
39 Returns
39 Returns
40 -------
40 -------
41 tuple- output, resources, exporter_instance
41 tuple- output, resources, exporter_instance
42 output : str
42 output : str
43 Jinja 2 output. This is the resulting converted notebook.
43 Jinja 2 output. This is the resulting converted notebook.
44 resources : dictionary
44 resources : dictionary
45 Dictionary of resources used prior to and during the conversion
45 Dictionary of resources used prior to and during the conversion
46 process.
46 process.
47 exporter_instance : Exporter
47 exporter_instance : Exporter
48 Instance of the Exporter class used to export the document. Useful
48 Instance of the Exporter class used to export the document. Useful
49 to caller because it provides a 'file_extension' property which
49 to caller because it provides a 'file_extension' property which
50 specifies what extension the output should be saved as.
50 specifies what extension the output should be saved as.
51
51
52 Notes
52 Notes
53 -----
53 -----
54 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT
54 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT
55 """.format(nbnode_mod=NotebookNode.__module__)
55 """
56
56
57 @wraps(f)
57 @wraps(f)
58 def decorator(*args, **kwargs):
58 def decorator(*args, **kwargs):
59 return f(*args, **kwargs)
59 return f(*args, **kwargs)
60
60
61 return decorator
61 return decorator
62
62
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Functions
65 # Functions
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 __all__ = [
68 __all__ = [
69 'export',
69 'export',
70 'export_html',
70 'export_html',
71 'export_custom',
71 'export_custom',
72 'export_slides',
72 'export_slides',
73 'export_latex',
73 'export_latex',
74 'export_pdf',
74 'export_pdf',
75 'export_markdown',
75 'export_markdown',
76 'export_python',
76 'export_python',
77 'export_rst',
77 'export_rst',
78 'export_by_name',
78 'export_by_name',
79 'get_export_names',
79 'get_export_names',
80 'ExporterNameError'
80 'ExporterNameError'
81 ]
81 ]
82
82
83
83
84 class ExporterNameError(NameError):
84 class ExporterNameError(NameError):
85 pass
85 pass
86
86
87 @DocDecorator
87 @DocDecorator
88 def export(exporter, nb, **kw):
88 def export(exporter, nb, **kw):
89 """
89 """
90 Export a notebook object using specific exporter class.
90 Export a notebook object using specific exporter class.
91
91
92 Parameters
92 Parameters
93 ----------
93 ----------
94 exporter : class:`~IPython.nbconvert.exporters.exporter.Exporter` class or instance
94 exporter : class:`~IPython.nbconvert.exporters.exporter.Exporter` class or instance
95 Class type or instance of the exporter that should be used. If the
95 Class type or instance of the exporter that should be used. If the
96 method initializes it's own instance of the class, it is ASSUMED that
96 method initializes it's own instance of the class, it is ASSUMED that
97 the class type provided exposes a constructor (``__init__``) with the same
97 the class type provided exposes a constructor (``__init__``) with the same
98 signature as the base Exporter class.
98 signature as the base Exporter class.
99 """
99 """
100
100
101 #Check arguments
101 #Check arguments
102 if exporter is None:
102 if exporter is None:
103 raise TypeError("Exporter is None")
103 raise TypeError("Exporter is None")
104 elif not isinstance(exporter, Exporter) and not issubclass(exporter, Exporter):
104 elif not isinstance(exporter, Exporter) and not issubclass(exporter, Exporter):
105 raise TypeError("exporter does not inherit from Exporter (base)")
105 raise TypeError("exporter does not inherit from Exporter (base)")
106 if nb is None:
106 if nb is None:
107 raise TypeError("nb is None")
107 raise TypeError("nb is None")
108
108
109 #Create the exporter
109 #Create the exporter
110 resources = kw.pop('resources', None)
110 resources = kw.pop('resources', None)
111 if isinstance(exporter, Exporter):
111 if isinstance(exporter, Exporter):
112 exporter_instance = exporter
112 exporter_instance = exporter
113 else:
113 else:
114 exporter_instance = exporter(**kw)
114 exporter_instance = exporter(**kw)
115
115
116 #Try to convert the notebook using the appropriate conversion function.
116 #Try to convert the notebook using the appropriate conversion function.
117 if isinstance(nb, NotebookNode):
117 if isinstance(nb, NotebookNode):
118 output, resources = exporter_instance.from_notebook_node(nb, resources)
118 output, resources = exporter_instance.from_notebook_node(nb, resources)
119 elif isinstance(nb, string_types):
119 elif isinstance(nb, string_types):
120 output, resources = exporter_instance.from_filename(nb, resources)
120 output, resources = exporter_instance.from_filename(nb, resources)
121 else:
121 else:
122 output, resources = exporter_instance.from_file(nb, resources)
122 output, resources = exporter_instance.from_file(nb, resources)
123 return output, resources
123 return output, resources
124
124
125 exporter_map = dict(
125 exporter_map = dict(
126 custom=TemplateExporter,
126 custom=TemplateExporter,
127 html=HTMLExporter,
127 html=HTMLExporter,
128 slides=SlidesExporter,
128 slides=SlidesExporter,
129 latex=LatexExporter,
129 latex=LatexExporter,
130 pdf=PDFExporter,
130 pdf=PDFExporter,
131 markdown=MarkdownExporter,
131 markdown=MarkdownExporter,
132 python=PythonExporter,
132 python=PythonExporter,
133 rst=RSTExporter,
133 rst=RSTExporter,
134 notebook=NotebookExporter,
134 notebook=NotebookExporter,
135 )
135 )
136
136
137 def _make_exporter(name, E):
137 def _make_exporter(name, E):
138 """make an export_foo function from a short key and Exporter class E"""
138 """make an export_foo function from a short key and Exporter class E"""
139 def _export(nb, **kw):
139 def _export(nb, **kw):
140 return export(E, nb, **kw)
140 return export(E, nb, **kw)
141 _export.__doc__ = """Export a notebook object to {0} format""".format(name)
141 _export.__doc__ = """Export a notebook object to {0} format""".format(name)
142 return _export
142 return _export
143
143
144 g = globals()
144 g = globals()
145
145
146 for name, E in exporter_map.items():
146 for name, E in exporter_map.items():
147 g['export_%s' % name] = DocDecorator(_make_exporter(name, E))
147 g['export_%s' % name] = DocDecorator(_make_exporter(name, E))
148
148
149 @DocDecorator
149 @DocDecorator
150 def export_by_name(format_name, nb, **kw):
150 def export_by_name(format_name, nb, **kw):
151 """
151 """
152 Export a notebook object to a template type by its name. Reflection
152 Export a notebook object to a template type by its name. Reflection
153 (Inspect) is used to find the template's corresponding explicit export
153 (Inspect) is used to find the template's corresponding explicit export
154 method defined in this module. That method is then called directly.
154 method defined in this module. That method is then called directly.
155
155
156 Parameters
156 Parameters
157 ----------
157 ----------
158 format_name : str
158 format_name : str
159 Name of the template style to export to.
159 Name of the template style to export to.
160 """
160 """
161
161
162 function_name = "export_" + format_name.lower()
162 function_name = "export_" + format_name.lower()
163
163
164 if function_name in globals():
164 if function_name in globals():
165 return globals()[function_name](nb, **kw)
165 return globals()[function_name](nb, **kw)
166 else:
166 else:
167 raise ExporterNameError("template for `%s` not found" % function_name)
167 raise ExporterNameError("template for `%s` not found" % function_name)
168
168
169
169
170 def get_export_names():
170 def get_export_names():
171 """Return a list of the currently supported export targets
171 """Return a list of the currently supported export targets
172
172
173 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT"""
173 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT"""
174 return sorted(exporter_map.keys())
174 return sorted(exporter_map.keys())
@@ -1,259 +1,259 b''
1 """This module defines a base Exporter class. For Jinja template-based export,
1 """This module defines a base Exporter class. For Jinja template-based export,
2 see templateexporter.py.
2 see templateexporter.py.
3 """
3 """
4
4
5
5
6 from __future__ import print_function, absolute_import
6 from __future__ import print_function, absolute_import
7
7
8 import io
8 import io
9 import os
9 import os
10 import copy
10 import copy
11 import collections
11 import collections
12 import datetime
12 import datetime
13
13
14 from IPython.config.configurable import LoggingConfigurable
14 from IPython.config.configurable import LoggingConfigurable
15 from IPython.config import Config
15 from IPython.config import Config
16 from IPython import nbformat
16 from IPython import nbformat
17 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
17 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
18 from IPython.utils.importstring import import_item
18 from IPython.utils.importstring import import_item
19 from IPython.utils import text, py3compat
19 from IPython.utils import text, py3compat
20
20
21
21
22 class ResourcesDict(collections.defaultdict):
22 class ResourcesDict(collections.defaultdict):
23 def __missing__(self, key):
23 def __missing__(self, key):
24 return ''
24 return ''
25
25
26
26
27 class Exporter(LoggingConfigurable):
27 class Exporter(LoggingConfigurable):
28 """
28 """
29 Class containing methods that sequentially run a list of preprocessors on a
29 Class containing methods that sequentially run a list of preprocessors on a
30 NotebookNode object and then return the modified NotebookNode object and
30 NotebookNode object and then return the modified NotebookNode object and
31 accompanying resources dict.
31 accompanying resources dict.
32 """
32 """
33
33
34 file_extension = Unicode(
34 file_extension = Unicode(
35 'txt', config=True,
35 'txt', config=True,
36 help="Extension of the file that should be written to disk"
36 help="Extension of the file that should be written to disk"
37 )
37 )
38
38
39 # MIME type of the result file, for HTTP response headers.
39 # MIME type of the result file, for HTTP response headers.
40 # This is *not* a traitlet, because we want to be able to access it from
40 # This is *not* a traitlet, because we want to be able to access it from
41 # the class, not just on instances.
41 # the class, not just on instances.
42 output_mimetype = ''
42 output_mimetype = ''
43
43
44 #Configurability, allows the user to easily add filters and preprocessors.
44 #Configurability, allows the user to easily add filters and preprocessors.
45 preprocessors = List(config=True,
45 preprocessors = List(config=True,
46 help="""List of preprocessors, by name or namespace, to enable.""")
46 help="""List of preprocessors, by name or namespace, to enable.""")
47
47
48 _preprocessors = List()
48 _preprocessors = List()
49
49
50 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
50 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
51 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
51 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
52 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
52 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
53 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
53 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
54 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
54 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
55 'IPython.nbconvert.preprocessors.LatexPreprocessor',
55 'IPython.nbconvert.preprocessors.LatexPreprocessor',
56 'IPython.nbconvert.preprocessors.ClearOutputPreprocessor',
56 'IPython.nbconvert.preprocessors.ClearOutputPreprocessor',
57 'IPython.nbconvert.preprocessors.ExecutePreprocessor',
57 'IPython.nbconvert.preprocessors.ExecutePreprocessor',
58 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
58 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
59 config=True,
59 config=True,
60 help="""List of preprocessors available by default, by name, namespace,
60 help="""List of preprocessors available by default, by name, namespace,
61 instance, or type.""")
61 instance, or type.""")
62
62
63
63
64 def __init__(self, config=None, **kw):
64 def __init__(self, config=None, **kw):
65 """
65 """
66 Public constructor
66 Public constructor
67
67
68 Parameters
68 Parameters
69 ----------
69 ----------
70 config : config
70 config : config
71 User configuration instance.
71 User configuration instance.
72 """
72 """
73 with_default_config = self.default_config
73 with_default_config = self.default_config
74 if config:
74 if config:
75 with_default_config.merge(config)
75 with_default_config.merge(config)
76
76
77 super(Exporter, self).__init__(config=with_default_config, **kw)
77 super(Exporter, self).__init__(config=with_default_config, **kw)
78
78
79 self._init_preprocessors()
79 self._init_preprocessors()
80
80
81
81
82 @property
82 @property
83 def default_config(self):
83 def default_config(self):
84 return Config()
84 return Config()
85
85
86 def from_notebook_node(self, nb, resources=None, **kw):
86 def from_notebook_node(self, nb, resources=None, **kw):
87 """
87 """
88 Convert a notebook from a notebook node instance.
88 Convert a notebook from a notebook node instance.
89
89
90 Parameters
90 Parameters
91 ----------
91 ----------
92 nb : :class:`~IPython.nbformat.current.NotebookNode`
92 nb : :class:`~IPython.nbformat.NotebookNode`
93 Notebook node (dict-like with attr-access)
93 Notebook node (dict-like with attr-access)
94 resources : dict
94 resources : dict
95 Additional resources that can be accessed read/write by
95 Additional resources that can be accessed read/write by
96 preprocessors and filters.
96 preprocessors and filters.
97 **kw
97 **kw
98 Ignored (?)
98 Ignored (?)
99 """
99 """
100 nb_copy = copy.deepcopy(nb)
100 nb_copy = copy.deepcopy(nb)
101 resources = self._init_resources(resources)
101 resources = self._init_resources(resources)
102
102
103 if 'language' in nb['metadata']:
103 if 'language' in nb['metadata']:
104 resources['language'] = nb['metadata']['language'].lower()
104 resources['language'] = nb['metadata']['language'].lower()
105
105
106 # Preprocess
106 # Preprocess
107 nb_copy, resources = self._preprocess(nb_copy, resources)
107 nb_copy, resources = self._preprocess(nb_copy, resources)
108
108
109 return nb_copy, resources
109 return nb_copy, resources
110
110
111
111
112 def from_filename(self, filename, resources=None, **kw):
112 def from_filename(self, filename, resources=None, **kw):
113 """
113 """
114 Convert a notebook from a notebook file.
114 Convert a notebook from a notebook file.
115
115
116 Parameters
116 Parameters
117 ----------
117 ----------
118 filename : str
118 filename : str
119 Full filename of the notebook file to open and convert.
119 Full filename of the notebook file to open and convert.
120 """
120 """
121
121
122 # Pull the metadata from the filesystem.
122 # Pull the metadata from the filesystem.
123 if resources is None:
123 if resources is None:
124 resources = ResourcesDict()
124 resources = ResourcesDict()
125 if not 'metadata' in resources or resources['metadata'] == '':
125 if not 'metadata' in resources or resources['metadata'] == '':
126 resources['metadata'] = ResourcesDict()
126 resources['metadata'] = ResourcesDict()
127 basename = os.path.basename(filename)
127 basename = os.path.basename(filename)
128 notebook_name = basename[:basename.rfind('.')]
128 notebook_name = basename[:basename.rfind('.')]
129 resources['metadata']['name'] = notebook_name
129 resources['metadata']['name'] = notebook_name
130
130
131 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
131 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
132 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
132 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
133
133
134 with io.open(filename, encoding='utf-8') as f:
134 with io.open(filename, encoding='utf-8') as f:
135 return self.from_notebook_node(nbformat.read(f, as_version=4), resources=resources, **kw)
135 return self.from_notebook_node(nbformat.read(f, as_version=4), resources=resources, **kw)
136
136
137
137
138 def from_file(self, file_stream, resources=None, **kw):
138 def from_file(self, file_stream, resources=None, **kw):
139 """
139 """
140 Convert a notebook from a notebook file.
140 Convert a notebook from a notebook file.
141
141
142 Parameters
142 Parameters
143 ----------
143 ----------
144 file_stream : file-like object
144 file_stream : file-like object
145 Notebook file-like object to convert.
145 Notebook file-like object to convert.
146 """
146 """
147 return self.from_notebook_node(nbformat.read(file_stream, as_version=4), resources=resources, **kw)
147 return self.from_notebook_node(nbformat.read(file_stream, as_version=4), resources=resources, **kw)
148
148
149
149
150 def register_preprocessor(self, preprocessor, enabled=False):
150 def register_preprocessor(self, preprocessor, enabled=False):
151 """
151 """
152 Register a preprocessor.
152 Register a preprocessor.
153 Preprocessors are classes that act upon the notebook before it is
153 Preprocessors are classes that act upon the notebook before it is
154 passed into the Jinja templating engine. preprocessors are also
154 passed into the Jinja templating engine. preprocessors are also
155 capable of passing additional information to the Jinja
155 capable of passing additional information to the Jinja
156 templating engine.
156 templating engine.
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 preprocessor : preprocessor
160 preprocessor : preprocessor
161 """
161 """
162 if preprocessor is None:
162 if preprocessor is None:
163 raise TypeError('preprocessor')
163 raise TypeError('preprocessor')
164 isclass = isinstance(preprocessor, type)
164 isclass = isinstance(preprocessor, type)
165 constructed = not isclass
165 constructed = not isclass
166
166
167 # Handle preprocessor's registration based on it's type
167 # Handle preprocessor's registration based on it's type
168 if constructed and isinstance(preprocessor, py3compat.string_types):
168 if constructed and isinstance(preprocessor, py3compat.string_types):
169 # Preprocessor is a string, import the namespace and recursively call
169 # Preprocessor is a string, import the namespace and recursively call
170 # this register_preprocessor method
170 # this register_preprocessor method
171 preprocessor_cls = import_item(preprocessor)
171 preprocessor_cls = import_item(preprocessor)
172 return self.register_preprocessor(preprocessor_cls, enabled)
172 return self.register_preprocessor(preprocessor_cls, enabled)
173
173
174 if constructed and hasattr(preprocessor, '__call__'):
174 if constructed and hasattr(preprocessor, '__call__'):
175 # Preprocessor is a function, no need to construct it.
175 # Preprocessor is a function, no need to construct it.
176 # Register and return the preprocessor.
176 # Register and return the preprocessor.
177 if enabled:
177 if enabled:
178 preprocessor.enabled = True
178 preprocessor.enabled = True
179 self._preprocessors.append(preprocessor)
179 self._preprocessors.append(preprocessor)
180 return preprocessor
180 return preprocessor
181
181
182 elif isclass and isinstance(preprocessor, MetaHasTraits):
182 elif isclass and isinstance(preprocessor, MetaHasTraits):
183 # Preprocessor is configurable. Make sure to pass in new default for
183 # Preprocessor is configurable. Make sure to pass in new default for
184 # the enabled flag if one was specified.
184 # the enabled flag if one was specified.
185 self.register_preprocessor(preprocessor(parent=self), enabled)
185 self.register_preprocessor(preprocessor(parent=self), enabled)
186
186
187 elif isclass:
187 elif isclass:
188 # Preprocessor is not configurable, construct it
188 # Preprocessor is not configurable, construct it
189 self.register_preprocessor(preprocessor(), enabled)
189 self.register_preprocessor(preprocessor(), enabled)
190
190
191 else:
191 else:
192 # Preprocessor is an instance of something without a __call__
192 # Preprocessor is an instance of something without a __call__
193 # attribute.
193 # attribute.
194 raise TypeError('preprocessor')
194 raise TypeError('preprocessor')
195
195
196
196
197 def _init_preprocessors(self):
197 def _init_preprocessors(self):
198 """
198 """
199 Register all of the preprocessors needed for this exporter, disabled
199 Register all of the preprocessors needed for this exporter, disabled
200 unless specified explicitly.
200 unless specified explicitly.
201 """
201 """
202 self._preprocessors = []
202 self._preprocessors = []
203
203
204 # Load default preprocessors (not necessarly enabled by default).
204 # Load default preprocessors (not necessarly enabled by default).
205 for preprocessor in self.default_preprocessors:
205 for preprocessor in self.default_preprocessors:
206 self.register_preprocessor(preprocessor)
206 self.register_preprocessor(preprocessor)
207
207
208 # Load user-specified preprocessors. Enable by default.
208 # Load user-specified preprocessors. Enable by default.
209 for preprocessor in self.preprocessors:
209 for preprocessor in self.preprocessors:
210 self.register_preprocessor(preprocessor, enabled=True)
210 self.register_preprocessor(preprocessor, enabled=True)
211
211
212
212
213 def _init_resources(self, resources):
213 def _init_resources(self, resources):
214
214
215 #Make sure the resources dict is of ResourcesDict type.
215 #Make sure the resources dict is of ResourcesDict type.
216 if resources is None:
216 if resources is None:
217 resources = ResourcesDict()
217 resources = ResourcesDict()
218 if not isinstance(resources, ResourcesDict):
218 if not isinstance(resources, ResourcesDict):
219 new_resources = ResourcesDict()
219 new_resources = ResourcesDict()
220 new_resources.update(resources)
220 new_resources.update(resources)
221 resources = new_resources
221 resources = new_resources
222
222
223 #Make sure the metadata extension exists in resources
223 #Make sure the metadata extension exists in resources
224 if 'metadata' in resources:
224 if 'metadata' in resources:
225 if not isinstance(resources['metadata'], ResourcesDict):
225 if not isinstance(resources['metadata'], ResourcesDict):
226 resources['metadata'] = ResourcesDict(resources['metadata'])
226 resources['metadata'] = ResourcesDict(resources['metadata'])
227 else:
227 else:
228 resources['metadata'] = ResourcesDict()
228 resources['metadata'] = ResourcesDict()
229 if not resources['metadata']['name']:
229 if not resources['metadata']['name']:
230 resources['metadata']['name'] = 'Notebook'
230 resources['metadata']['name'] = 'Notebook'
231
231
232 #Set the output extension
232 #Set the output extension
233 resources['output_extension'] = self.file_extension
233 resources['output_extension'] = self.file_extension
234 return resources
234 return resources
235
235
236
236
237 def _preprocess(self, nb, resources):
237 def _preprocess(self, nb, resources):
238 """
238 """
239 Preprocess the notebook before passing it into the Jinja engine.
239 Preprocess the notebook before passing it into the Jinja engine.
240 To preprocess the notebook is to apply all of the
240 To preprocess the notebook is to apply all of the
241
241
242 Parameters
242 Parameters
243 ----------
243 ----------
244 nb : notebook node
244 nb : notebook node
245 notebook that is being exported.
245 notebook that is being exported.
246 resources : a dict of additional resources that
246 resources : a dict of additional resources that
247 can be accessed read/write by preprocessors
247 can be accessed read/write by preprocessors
248 """
248 """
249
249
250 # Do a copy.deepcopy first,
250 # Do a copy.deepcopy first,
251 # we are never safe enough with what the preprocessors could do.
251 # we are never safe enough with what the preprocessors could do.
252 nbc = copy.deepcopy(nb)
252 nbc = copy.deepcopy(nb)
253 resc = copy.deepcopy(resources)
253 resc = copy.deepcopy(resources)
254
254
255 #Run each preprocessor on the notebook. Carry the output along
255 #Run each preprocessor on the notebook. Carry the output along
256 #to each preprocessor
256 #to each preprocessor
257 for preprocessor in self._preprocessors:
257 for preprocessor in self._preprocessors:
258 nbc, resc = preprocessor(nbc, resc)
258 nbc, resc = preprocessor(nbc, resc)
259 return nbc, resc
259 return nbc, resc
@@ -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.info("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.current.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,143 +1,144 b''
1 """The IPython notebook format
1 """The IPython notebook format
2
2
3 Use this module to read or write notebook files as particular nbformat versions.
3 Use this module to read or write notebook files as particular nbformat versions.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 from IPython.utils.log import get_logger
9 from IPython.utils.log import get_logger
10
10
11 from . import v1
11 from . import v1
12 from . import v2
12 from . import v2
13 from . import v3
13 from . import v3
14 from . import v4
14 from . import v4
15
15
16 versions = {
16 versions = {
17 1: v1,
17 1: v1,
18 2: v2,
18 2: v2,
19 3: v3,
19 3: v3,
20 4: v4,
20 4: v4,
21 }
21 }
22
22
23 from .validator import validate, ValidationError
23 from .validator import validate, ValidationError
24 from .converter import convert
24 from .converter import convert
25 from . import reader
25 from . import reader
26 from .notebooknode import from_dict, NotebookNode
26
27
27 from .v4 import (
28 from .v4 import (
28 nbformat as current_nbformat,
29 nbformat as current_nbformat,
29 nbformat_minor as current_nbformat_minor,
30 nbformat_minor as current_nbformat_minor,
30 )
31 )
31
32
32 class NBFormatError(ValueError):
33 class NBFormatError(ValueError):
33 pass
34 pass
34
35
35 # no-conversion singleton
36 # no-conversion singleton
36 NO_CONVERT = object()
37 NO_CONVERT = object()
37
38
38 def reads(s, as_version, **kwargs):
39 def reads(s, as_version, **kwargs):
39 """Read a notebook from a string and return the NotebookNode object as the given version.
40 """Read a notebook from a string and return the NotebookNode object as the given version.
40
41
41 The string can contain a notebook of any version.
42 The string can contain a notebook of any version.
42 The notebook will be returned `as_version`, converting, if necessary.
43 The notebook will be returned `as_version`, converting, if necessary.
43
44
44 Notebook format errors will be logged.
45 Notebook format errors will be logged.
45
46
46 Parameters
47 Parameters
47 ----------
48 ----------
48 s : unicode
49 s : unicode
49 The raw unicode string to read the notebook from.
50 The raw unicode string to read the notebook from.
50 as_version : int
51 as_version : int
51 The version of the notebook format to return.
52 The version of the notebook format to return.
52 The notebook will be converted, if necessary.
53 The notebook will be converted, if necessary.
53 Pass nbformat.NO_CONVERT to prevent conversion.
54 Pass nbformat.NO_CONVERT to prevent conversion.
54
55
55 Returns
56 Returns
56 -------
57 -------
57 nb : NotebookNode
58 nb : NotebookNode
58 The notebook that was read.
59 The notebook that was read.
59 """
60 """
60 nb = reader.reads(s, **kwargs)
61 nb = reader.reads(s, **kwargs)
61 if as_version is not NO_CONVERT:
62 if as_version is not NO_CONVERT:
62 nb = convert(nb, as_version)
63 nb = convert(nb, as_version)
63 try:
64 try:
64 validate(nb)
65 validate(nb)
65 except ValidationError as e:
66 except ValidationError as e:
66 get_logger().error("Notebook JSON is invalid: %s", e)
67 get_logger().error("Notebook JSON is invalid: %s", e)
67 return nb
68 return nb
68
69
69
70
70 def writes(nb, version, **kwargs):
71 def writes(nb, version, **kwargs):
71 """Write a notebook to a string in a given format in the given nbformat version.
72 """Write a notebook to a string in a given format in the given nbformat version.
72
73
73 Any notebook format errors will be logged.
74 Any notebook format errors will be logged.
74
75
75 Parameters
76 Parameters
76 ----------
77 ----------
77 nb : NotebookNode
78 nb : NotebookNode
78 The notebook to write.
79 The notebook to write.
79 version : int
80 version : int
80 The nbformat version to write.
81 The nbformat version to write.
81 If nb is not this version, it will be converted.
82 If nb is not this version, it will be converted.
82 Pass nbformat.NO_CONVERT to prevent conversion.
83 Pass nbformat.NO_CONVERT to prevent conversion.
83
84
84 Returns
85 Returns
85 -------
86 -------
86 s : unicode
87 s : unicode
87 The notebook as a JSON string.
88 The notebook as a JSON string.
88 """
89 """
89 if version is not NO_CONVERT:
90 if version is not NO_CONVERT:
90 nb = convert(nb, version)
91 nb = convert(nb, version)
91 else:
92 else:
92 version, _ = reader.get_version(nb)
93 version, _ = reader.get_version(nb)
93 try:
94 try:
94 validate(nb)
95 validate(nb)
95 except ValidationError as e:
96 except ValidationError as e:
96 get_logger().error("Notebook JSON is invalid: %s", e)
97 get_logger().error("Notebook JSON is invalid: %s", e)
97 return versions[version].writes_json(nb, **kwargs)
98 return versions[version].writes_json(nb, **kwargs)
98
99
99
100
100 def read(fp, as_version, **kwargs):
101 def read(fp, as_version, **kwargs):
101 """Read a notebook from a file as a NotebookNode of the given version.
102 """Read a notebook from a file as a NotebookNode of the given version.
102
103
103 The string can contain a notebook of any version.
104 The string can contain a notebook of any version.
104 The notebook will be returned `as_version`, converting, if necessary.
105 The notebook will be returned `as_version`, converting, if necessary.
105
106
106 Notebook format errors will be logged.
107 Notebook format errors will be logged.
107
108
108 Parameters
109 Parameters
109 ----------
110 ----------
110 fp : file
111 fp : file
111 Any file-like object with a read method.
112 Any file-like object with a read method.
112 as_version: int
113 as_version: int
113 The version of the notebook format to return.
114 The version of the notebook format to return.
114 The notebook will be converted, if necessary.
115 The notebook will be converted, if necessary.
115 Pass nbformat.NO_CONVERT to prevent conversion.
116 Pass nbformat.NO_CONVERT to prevent conversion.
116
117
117 Returns
118 Returns
118 -------
119 -------
119 nb : NotebookNode
120 nb : NotebookNode
120 The notebook that was read.
121 The notebook that was read.
121 """
122 """
122 return reads(fp.read(), as_version, **kwargs)
123 return reads(fp.read(), as_version, **kwargs)
123
124
124
125
125 def write(fp, nb, version, **kwargs):
126 def write(fp, nb, version, **kwargs):
126 """Write a notebook to a file in a given nbformat version.
127 """Write a notebook to a file in a given nbformat version.
127
128
128 The file-like object must accept unicode input.
129 The file-like object must accept unicode input.
129
130
130 Parameters
131 Parameters
131 ----------
132 ----------
132 fp : file
133 fp : file
133 Any file-like object with a write method that accepts unicode.
134 Any file-like object with a write method that accepts unicode.
134 nb : NotebookNode
135 nb : NotebookNode
135 The notebook to write.
136 The notebook to write.
136 version : int
137 version : int
137 The nbformat version to write.
138 The nbformat version to write.
138 If nb is not this version, it will be converted.
139 If nb is not this version, it will be converted.
139 """
140 """
140 s = writes(nb, version, **kwargs)
141 s = writes(nb, version, **kwargs)
141 if isinstance(s, bytes):
142 if isinstance(s, bytes):
142 s = s.decode('utf8')
143 s = s.decode('utf8')
143 return fp.write(s)
144 return fp.write(s)
@@ -1,19 +1,19 b''
1 """The main API for the v4 notebook format."""
1 """The main API for the v4 notebook format."""
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 from .nbbase import (
6 from .nbbase import (
7 NotebookNode, from_dict,
8 nbformat, nbformat_minor, nbformat_schema,
7 nbformat, nbformat_minor, nbformat_schema,
9 new_code_cell, new_markdown_cell, new_notebook,
8 new_code_cell, new_markdown_cell, new_notebook,
10 new_output, output_from_msg,
9 new_output, output_from_msg,
11 )
10 )
12
11
13 from .nbjson import reads as reads_json, writes as writes_json
12 from .nbjson import reads, writes, to_notebook
14 from .nbjson import reads as read_json, writes as write_json
13 reads_json = reads
15 from .nbjson import to_notebook as to_notebook_json
14 writes_json = writes
15 to_notebook_json = to_notebook
16
16
17 from .convert import downgrade, upgrade
17 from .convert import downgrade, upgrade
18
18
19
19
@@ -1,149 +1,137 b''
1 """Python API for composing notebook elements
1 """Python API for composing notebook elements
2
2
3 The Python representation of a notebook is a nested structure of
3 The Python representation of a notebook is a nested structure of
4 dictionary subclasses that support attribute access
4 dictionary subclasses that support attribute access
5 (IPython.utils.ipstruct.Struct). The functions in this module are merely
5 (IPython.utils.ipstruct.Struct). The functions in this module are merely
6 helpers to build the structs in the right form.
6 helpers to build the structs in the right form.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 from IPython.utils.ipstruct import Struct
12 from ..notebooknode import from_dict, NotebookNode
13
13
14 # Change this when incrementing the nbformat version
14 # Change this when incrementing the nbformat version
15 nbformat = 4
15 nbformat = 4
16 nbformat_minor = 0
16 nbformat_minor = 0
17 nbformat_schema = 'nbformat.v4.schema.json'
17 nbformat_schema = 'nbformat.v4.schema.json'
18
18
19
19
20 def validate(node, ref=None):
20 def validate(node, ref=None):
21 """validate a v4 node"""
21 """validate a v4 node"""
22 from .. import validate
22 from .. import validate
23 return validate(node, ref=ref, version=nbformat)
23 return validate(node, ref=ref, version=nbformat)
24
24
25
25
26 class NotebookNode(Struct):
27 pass
28
29 def from_dict(d):
30 if isinstance(d, dict):
31 return NotebookNode({k:from_dict(v) for k,v in d.items()})
32 elif isinstance(d, (tuple, list)):
33 return [from_dict(i) for i in d]
34 else:
35 return d
36
37
38 def new_output(output_type, data=None, **kwargs):
26 def new_output(output_type, data=None, **kwargs):
39 """Create a new output, to go in the ``cell.outputs`` list of a code cell."""
27 """Create a new output, to go in the ``cell.outputs`` list of a code cell."""
40 output = NotebookNode(output_type=output_type)
28 output = NotebookNode(output_type=output_type)
41
29
42 # populate defaults:
30 # populate defaults:
43 if output_type == 'stream':
31 if output_type == 'stream':
44 output.name = u'stdout'
32 output.name = u'stdout'
45 output.text = u''
33 output.text = u''
46 elif output_type in {'execute_result', 'display_data'}:
34 elif output_type in {'execute_result', 'display_data'}:
47 output.metadata = NotebookNode()
35 output.metadata = NotebookNode()
48 output.data = NotebookNode()
36 output.data = NotebookNode()
49 # load from args:
37 # load from args:
50 output.update(from_dict(kwargs))
38 output.update(from_dict(kwargs))
51 if data is not None:
39 if data is not None:
52 output.data = from_dict(data)
40 output.data = from_dict(data)
53 # validate
41 # validate
54 validate(output, output_type)
42 validate(output, output_type)
55 return output
43 return output
56
44
57
45
58 def output_from_msg(msg):
46 def output_from_msg(msg):
59 """Create a NotebookNode for an output from a kernel's IOPub message.
47 """Create a NotebookNode for an output from a kernel's IOPub message.
60
48
61 Returns
49 Returns
62 -------
50 -------
63
51
64 NotebookNode: the output as a notebook node.
52 NotebookNode: the output as a notebook node.
65
53
66 Raises
54 Raises
67 ------
55 ------
68
56
69 ValueError: if the message is not an output message.
57 ValueError: if the message is not an output message.
70
58
71 """
59 """
72 msg_type = msg['header']['msg_type']
60 msg_type = msg['header']['msg_type']
73 content = msg['content']
61 content = msg['content']
74
62
75 if msg_type == 'execute_result':
63 if msg_type == 'execute_result':
76 return new_output(output_type=msg_type,
64 return new_output(output_type=msg_type,
77 metadata=content['metadata'],
65 metadata=content['metadata'],
78 data=content['data'],
66 data=content['data'],
79 execution_count=content['execution_count'],
67 execution_count=content['execution_count'],
80 )
68 )
81 elif msg_type == 'stream':
69 elif msg_type == 'stream':
82 return new_output(output_type=msg_type,
70 return new_output(output_type=msg_type,
83 name=content['name'],
71 name=content['name'],
84 text=content['text'],
72 text=content['text'],
85 )
73 )
86 elif msg_type == 'display_data':
74 elif msg_type == 'display_data':
87 return new_output(output_type=msg_type,
75 return new_output(output_type=msg_type,
88 metadata=content['metadata'],
76 metadata=content['metadata'],
89 data=content['data'],
77 data=content['data'],
90 )
78 )
91 elif msg_type == 'error':
79 elif msg_type == 'error':
92 return new_output(output_type=msg_type,
80 return new_output(output_type=msg_type,
93 ename=content['ename'],
81 ename=content['ename'],
94 evalue=content['evalue'],
82 evalue=content['evalue'],
95 traceback=content['traceback'],
83 traceback=content['traceback'],
96 )
84 )
97 else:
85 else:
98 raise ValueError("Unrecognized output msg type: %r" % msg_type)
86 raise ValueError("Unrecognized output msg type: %r" % msg_type)
99
87
100
88
101 def new_code_cell(source='', **kwargs):
89 def new_code_cell(source='', **kwargs):
102 """Create a new code cell"""
90 """Create a new code cell"""
103 cell = NotebookNode(
91 cell = NotebookNode(
104 cell_type='code',
92 cell_type='code',
105 metadata=NotebookNode(),
93 metadata=NotebookNode(),
106 execution_count=None,
94 execution_count=None,
107 source=source,
95 source=source,
108 outputs=[],
96 outputs=[],
109 )
97 )
110 cell.update(from_dict(kwargs))
98 cell.update(from_dict(kwargs))
111
99
112 validate(cell, 'code_cell')
100 validate(cell, 'code_cell')
113 return cell
101 return cell
114
102
115 def new_markdown_cell(source='', **kwargs):
103 def new_markdown_cell(source='', **kwargs):
116 """Create a new markdown cell"""
104 """Create a new markdown cell"""
117 cell = NotebookNode(
105 cell = NotebookNode(
118 cell_type='markdown',
106 cell_type='markdown',
119 source=source,
107 source=source,
120 metadata=NotebookNode(),
108 metadata=NotebookNode(),
121 )
109 )
122 cell.update(from_dict(kwargs))
110 cell.update(from_dict(kwargs))
123
111
124 validate(cell, 'markdown_cell')
112 validate(cell, 'markdown_cell')
125 return cell
113 return cell
126
114
127 def new_raw_cell(source='', **kwargs):
115 def new_raw_cell(source='', **kwargs):
128 """Create a new raw cell"""
116 """Create a new raw cell"""
129 cell = NotebookNode(
117 cell = NotebookNode(
130 cell_type='raw',
118 cell_type='raw',
131 source=source,
119 source=source,
132 metadata=NotebookNode(),
120 metadata=NotebookNode(),
133 )
121 )
134 cell.update(from_dict(kwargs))
122 cell.update(from_dict(kwargs))
135
123
136 validate(cell, 'raw_cell')
124 validate(cell, 'raw_cell')
137 return cell
125 return cell
138
126
139 def new_notebook(**kwargs):
127 def new_notebook(**kwargs):
140 """Create a new notebook"""
128 """Create a new notebook"""
141 nb = NotebookNode(
129 nb = NotebookNode(
142 nbformat=nbformat,
130 nbformat=nbformat,
143 nbformat_minor=nbformat_minor,
131 nbformat_minor=nbformat_minor,
144 metadata=NotebookNode(),
132 metadata=NotebookNode(),
145 cells=[],
133 cells=[],
146 )
134 )
147 nb.update(from_dict(kwargs))
135 nb.update(from_dict(kwargs))
148 validate(nb)
136 validate(nb)
149 return nb
137 return nb
@@ -1,60 +1,67 b''
1 """Read and write notebooks in JSON format."""
1 """Read and write notebooks in JSON format."""
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 copy
6 import copy
7 import json
7 import json
8
8
9 from IPython.utils import py3compat
9 from IPython.utils import py3compat
10
10
11 from .nbbase import from_dict
11 from .nbbase import from_dict
12 from .rwbase import (
12 from .rwbase import (
13 NotebookReader, NotebookWriter, rejoin_lines, split_lines, strip_transient
13 NotebookReader, NotebookWriter, rejoin_lines, split_lines, strip_transient
14 )
14 )
15
15
16
16
17 class BytesEncoder(json.JSONEncoder):
17 class BytesEncoder(json.JSONEncoder):
18 """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""
18 """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""
19 def default(self, obj):
19 def default(self, obj):
20 if isinstance(obj, bytes):
20 if isinstance(obj, bytes):
21 return obj.decode('ascii')
21 return obj.decode('ascii')
22 return json.JSONEncoder.default(self, obj)
22 return json.JSONEncoder.default(self, obj)
23
23
24
24
25 class JSONReader(NotebookReader):
25 class JSONReader(NotebookReader):
26
26
27 def reads(self, s, **kwargs):
27 def reads(self, s, **kwargs):
28 """Read a JSON string into a Notebook object"""
28 nb = json.loads(s, **kwargs)
29 nb = json.loads(s, **kwargs)
29 nb = self.to_notebook(nb, **kwargs)
30 nb = self.to_notebook(nb, **kwargs)
30 return nb
31 return nb
31
32
32 def to_notebook(self, d, **kwargs):
33 def to_notebook(self, d, **kwargs):
33 nb = rejoin_lines(from_dict(d))
34 """Convert a disk-format notebook dict to in-memory NotebookNode
35
36 handles multi-line values as strings, scrubbing of transient values, etc.
37 """
38 nb = from_dict(d)
39 nb = rejoin_lines(nb)
34 nb = strip_transient(nb)
40 nb = strip_transient(nb)
35 return nb
41 return nb
36
42
37
43
38 class JSONWriter(NotebookWriter):
44 class JSONWriter(NotebookWriter):
39
45
40 def writes(self, nb, **kwargs):
46 def writes(self, nb, **kwargs):
47 """Serialize a NotebookNode object as a JSON string"""
41 kwargs['cls'] = BytesEncoder
48 kwargs['cls'] = BytesEncoder
42 kwargs['indent'] = 1
49 kwargs['indent'] = 1
43 kwargs['sort_keys'] = True
50 kwargs['sort_keys'] = True
44 kwargs['separators'] = (',',': ')
51 kwargs['separators'] = (',',': ')
45 # don't modify in-memory dict
52 # don't modify in-memory dict
46 nb = copy.deepcopy(nb)
53 nb = copy.deepcopy(nb)
47 if kwargs.pop('split_lines', True):
54 if kwargs.pop('split_lines', True):
48 nb = split_lines(nb)
55 nb = split_lines(nb)
49 nb = strip_transient(nb)
56 nb = strip_transient(nb)
50 return py3compat.str_to_unicode(json.dumps(nb, **kwargs), 'utf-8')
57 return py3compat.str_to_unicode(json.dumps(nb, **kwargs), 'utf-8')
51
58
52
59
53 _reader = JSONReader()
60 _reader = JSONReader()
54 _writer = JSONWriter()
61 _writer = JSONWriter()
55
62
56 reads = _reader.reads
63 reads = _reader.reads
57 read = _reader.read
64 read = _reader.read
58 to_notebook = _reader.to_notebook
65 to_notebook = _reader.to_notebook
59 write = _writer.write
66 write = _writer.write
60 writes = _writer.writes
67 writes = _writer.writes
General Comments 0
You need to be logged in to leave comments. Login now