##// END OF EJS Templates
make default language highlight configurable for html
Matthias BUSSONNIER -
Show More
@@ -1,315 +1,315 b''
1 1 """This module defines Exporter, 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
23 23 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
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 'highlight2html': filters.highlight2html,
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 }
65 65
66 66 #-----------------------------------------------------------------------------
67 67 # Class
68 68 #-----------------------------------------------------------------------------
69 69
70 70 class TemplateExporter(Exporter):
71 71 """
72 72 Exports notebooks into other file formats. Uses Jinja 2 templating engine
73 73 to output new formats. Inherit from this class if you are creating a new
74 74 template type along with new filters/preprocessors. If the filters/
75 75 preprocessors provided by default suffice, there is no need to inherit from
76 76 this class. Instead, override the template_file and file_extension
77 77 traits via a config file.
78 78
79 79 {filters}
80 80 """
81 81
82 82 # finish the docstring
83 83 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
84 84
85 85
86 86 template_file = Unicode(u'default',
87 87 config=True,
88 88 help="Name of the template file to use")
89 89 def _template_file_changed(self, name, old, new):
90 90 if new == 'default':
91 91 self.template_file = self.default_template
92 92 else:
93 93 self.template_file = new
94 94 self.template = None
95 95 self._load_template()
96 96
97 97 default_template = Unicode(u'')
98 98 template = Any()
99 99 environment = Any()
100 100
101 101 template_path = List(['.'], config=True)
102 102 def _template_path_changed(self, name, old, new):
103 103 self._load_template()
104 104
105 105 default_template_path = Unicode(
106 106 os.path.join("..", "templates"),
107 107 help="Path where the template files are located.")
108 108
109 109 template_skeleton_path = Unicode(
110 110 os.path.join("..", "templates", "skeleton"),
111 111 help="Path where the template skeleton files are located.")
112 112
113 113 #Jinja block definitions
114 114 jinja_comment_block_start = Unicode("", config=True)
115 115 jinja_comment_block_end = Unicode("", config=True)
116 116 jinja_variable_block_start = Unicode("", config=True)
117 117 jinja_variable_block_end = Unicode("", config=True)
118 118 jinja_logic_block_start = Unicode("", config=True)
119 119 jinja_logic_block_end = Unicode("", config=True)
120 120
121 121 #Extension that the template files use.
122 122 template_extension = Unicode(".tpl", config=True)
123 123
124 124 filters = Dict(config=True,
125 125 help="""Dictionary of filters, by name and namespace, to add to the Jinja
126 126 environment.""")
127 127
128 128
129 129 def __init__(self, config=None, extra_loaders=None, **kw):
130 130 """
131 131 Public constructor
132 132
133 133 Parameters
134 134 ----------
135 135 config : config
136 136 User configuration instance.
137 137 extra_loaders : list[of Jinja Loaders]
138 138 ordered list of Jinja loader to find templates. Will be tried in order
139 139 before the default FileSystem ones.
140 140 template : str (optional, kw arg)
141 141 Template to use when exporting.
142 142 """
143 143 if not config:
144 144 config = self.default_config
145 145
146 146 super(Exporter, self).__init__(config=config, **kw)
147 147
148 148 #Init
149 149 self._init_template()
150 150 self._init_environment(extra_loaders=extra_loaders)
151 151 self._init_preprocessors()
152 152 self._init_filters()
153 153
154 154
155 155 def _load_template(self):
156 156 """Load the Jinja template object from the template file
157 157
158 158 This is a no-op if the template attribute is already defined,
159 159 or the Jinja environment is not setup yet.
160 160
161 161 This is triggered by various trait changes that would change the template.
162 162 """
163 163 if self.template is not None:
164 164 return
165 165 # called too early, do nothing
166 166 if self.environment is None:
167 167 return
168 168 # Try different template names during conversion. First try to load the
169 169 # template by name with extension added, then try loading the template
170 170 # as if the name is explicitly specified, then try the name as a
171 171 # 'flavor', and lastly just try to load the template by module name.
172 172 module_name = self.__module__.rsplit('.', 1)[-1]
173 173 try_names = []
174 174 if self.template_file:
175 175 try_names.extend([
176 176 self.template_file + self.template_extension,
177 177 self.template_file,
178 178 module_name + '_' + self.template_file + self.template_extension,
179 179 ])
180 180 try_names.append(module_name + self.template_extension)
181 181 for try_name in try_names:
182 182 self.log.debug("Attempting to load template %s", try_name)
183 183 try:
184 184 self.template = self.environment.get_template(try_name)
185 185 except (TemplateNotFound, IOError):
186 186 pass
187 187 except Exception as e:
188 188 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
189 189 else:
190 190 self.log.info("Loaded template %s", try_name)
191 191 break
192 192
193 193 def from_notebook_node(self, nb, resources=None, **kw):
194 194 """
195 195 Convert a notebook from a notebook node instance.
196 196
197 197 Parameters
198 198 ----------
199 199 nb : Notebook node
200 200 resources : dict (**kw)
201 201 of additional resources that can be accessed read/write by
202 202 preprocessors and filters.
203 203 """
204 204 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
205 205
206 206 self._load_template()
207 207
208 208 if self.template is not None:
209 209 output = self.template.render(nb=nb_copy, resources=resources)
210 210 else:
211 211 raise IOError('template file "%s" could not be found' % self.template_file)
212 212 return output, resources
213 213
214 214
215 215 def register_filter(self, name, jinja_filter):
216 216 """
217 217 Register a filter.
218 218 A filter is a function that accepts and acts on one string.
219 219 The filters are accesible within the Jinja templating engine.
220 220
221 221 Parameters
222 222 ----------
223 223 name : str
224 224 name to give the filter in the Jinja engine
225 225 filter : filter
226 226 """
227 227 if jinja_filter is None:
228 228 raise TypeError('filter')
229 229 isclass = isinstance(jinja_filter, type)
230 230 constructed = not isclass
231 231
232 232 #Handle filter's registration based on it's type
233 233 if constructed and isinstance(jinja_filter, py3compat.string_types):
234 234 #filter is a string, import the namespace and recursively call
235 235 #this register_filter method
236 236 filter_cls = import_item(jinja_filter)
237 237 return self.register_filter(name, filter_cls)
238 238
239 239 if constructed and hasattr(jinja_filter, '__call__'):
240 240 #filter is a function, no need to construct it.
241 241 self.environment.filters[name] = jinja_filter
242 242 return jinja_filter
243 243
244 244 elif isclass and isinstance(jinja_filter, MetaHasTraits):
245 245 #filter is configurable. Make sure to pass in new default for
246 246 #the enabled flag if one was specified.
247 247 filter_instance = jinja_filter(parent=self)
248 248 self.register_filter(name, filter_instance )
249 249
250 250 elif isclass:
251 251 #filter is not configurable, construct it
252 252 filter_instance = jinja_filter()
253 253 self.register_filter(name, filter_instance)
254 254
255 255 else:
256 256 #filter is an instance of something without a __call__
257 257 #attribute.
258 258 raise TypeError('filter')
259 259
260 260
261 261 def _init_template(self):
262 262 """
263 263 Make sure a template name is specified. If one isn't specified, try to
264 264 build one from the information we know.
265 265 """
266 266 self._template_file_changed('template_file', self.template_file, self.template_file)
267 267
268 268
269 269 def _init_environment(self, extra_loaders=None):
270 270 """
271 271 Create the Jinja templating environment.
272 272 """
273 273 here = os.path.dirname(os.path.realpath(__file__))
274 274 loaders = []
275 275 if extra_loaders:
276 276 loaders.extend(extra_loaders)
277 277
278 278 paths = self.template_path
279 279 paths.extend([os.path.join(here, self.default_template_path),
280 280 os.path.join(here, self.template_skeleton_path)])
281 281 loaders.append(FileSystemLoader(paths))
282 282
283 283 self.environment = Environment(
284 284 loader= ChoiceLoader(loaders),
285 285 extensions=JINJA_EXTENSIONS
286 286 )
287 287
288 288 #Set special Jinja2 syntax that will not conflict with latex.
289 289 if self.jinja_logic_block_start:
290 290 self.environment.block_start_string = self.jinja_logic_block_start
291 291 if self.jinja_logic_block_end:
292 292 self.environment.block_end_string = self.jinja_logic_block_end
293 293 if self.jinja_variable_block_start:
294 294 self.environment.variable_start_string = self.jinja_variable_block_start
295 295 if self.jinja_variable_block_end:
296 296 self.environment.variable_end_string = self.jinja_variable_block_end
297 297 if self.jinja_comment_block_start:
298 298 self.environment.comment_start_string = self.jinja_comment_block_start
299 299 if self.jinja_comment_block_end:
300 300 self.environment.comment_end_string = self.jinja_comment_block_end
301 301
302 302
303 303 def _init_filters(self):
304 304 """
305 305 Register all of the filters required for the exporter.
306 306 """
307 307
308 308 #Add default filters to the Jinja2 environment
309 309 for key, value in default_filters.items():
310 310 self.register_filter(key, value)
311 311
312 312 #Load user filters. Overwrite existing filters if need be.
313 313 if self.filters:
314 314 for key, user_filter in self.filters.items():
315 315 self.register_filter(key, user_filter)
@@ -1,110 +1,118 b''
1 1 """
2 2 Module containing filter functions that allow code to be highlighted
3 3 from within Jinja templates.
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 pygments import highlight as pygements_highlight
18 18 from pygments.lexers import get_lexer_by_name
19 19 from pygments.formatters import HtmlFormatter
20 20 from pygments.formatters import LatexFormatter
21 from IPython.config import catch_config_error, Configurable
22 from IPython.utils.traitlets import Unicode
21 23
22 24 # Our own imports
23 25 from IPython.nbconvert.utils.lexers import IPythonLexer
24 26
25 27 #-----------------------------------------------------------------------------
26 28 # Globals and constants
27 29 #-----------------------------------------------------------------------------
28 30
29 31 MULTILINE_OUTPUTS = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
30 32
31 33 #-----------------------------------------------------------------------------
32 34 # Utility functions
33 35 #-----------------------------------------------------------------------------
34 36
35 37 __all__ = [
36 'highlight2html',
38 'Highlight2Html',
37 39 'highlight2latex'
38 40 ]
39 41
40 42
41 def highlight2html(source, language='ipython', metadata=None):
43 class Highlight2Html(Configurable):
44
45 language = Unicode('ipython', config=True, help='default highlight language')
46
47 def __call__(self, source, language=None, metadata=None):
42 48 """
43 49 Return a syntax-highlighted version of the input source as html output.
44 50
45 51 Parameters
46 52 ----------
47 53 source : str
48 54 source of the cell to highlight
49 55 language : str
50 56 language to highlight the syntax of
51 57 metadata : NotebookNode cell metadata
52 58 metadata of the cell to highlight
53 59 """
60 if not language:
61 language=self.language
54 62
55 63 return _pygment_highlight(source, HtmlFormatter(), language, metadata)
56 64
57 65
58 66 def highlight2latex(source, language='ipython', metadata=None, strip_verbatim=False):
59 67 """
60 68 Return a syntax-highlighted version of the input source as latex output.
61 69
62 70 Parameters
63 71 ----------
64 72 source : str
65 73 source of the cell to highlight
66 74 language : str
67 75 language to highlight the syntax of
68 76 metadata : NotebookNode cell metadata
69 77 metadata of the cell to highlight
70 78 strip_verbatim : bool
71 79 remove the Verbatim environment that pygments provides by default
72 80 """
73 81 latex = _pygment_highlight(source, LatexFormatter(), language, metadata)
74 82 if strip_verbatim:
75 83 latex = latex.replace(r'\begin{Verbatim}[commandchars=\\\{\}]' + '\n', '')
76 84 return latex.replace('\n\\end{Verbatim}\n', '')
77 85 else:
78 86 return latex
79 87
80 88
81 89
82 90 def _pygment_highlight(source, output_formatter, language='ipython', metadata=None):
83 91 """
84 92 Return a syntax-highlighted version of the input source
85 93
86 94 Parameters
87 95 ----------
88 96 source : str
89 97 source of the cell to highlight
90 98 output_formatter : Pygments formatter
91 99 language : str
92 100 language to highlight the syntax of
93 101 metadata : NotebookNode cell metadata
94 102 metadata of the cell to highlight
95 103 """
96 104
97 105 # If the cell uses a magic extension language,
98 106 # use the magic language instead.
99 107 if language == 'ipython' \
100 108 and metadata \
101 109 and 'magics_language' in metadata:
102 110
103 111 language = metadata['magics_language']
104 112
105 113 if language == 'ipython':
106 114 lexer = IPythonLexer()
107 115 else:
108 116 lexer = get_lexer_by_name(language, stripall=True)
109 117
110 118 return pygements_highlight(source, lexer, output_formatter)
General Comments 0
You need to be logged in to leave comments. Login now