##// END OF EJS Templates
Merge pull request #6588 from benjaminabel/master...
Min RK -
r18564:3ccc8dfd merge
parent child Browse files
Show More
@@ -1,29 +1,31 b''
1 # http://travis-ci.org/#!/ipython/ipython
1 # http://travis-ci.org/#!/ipython/ipython
2 language: python
2 language: python
3 python:
3 python:
4 - 3.4
4 - 3.4
5 - 2.7
5 - 2.7
6 - 3.3
6 - 3.3
7 env:
7 env:
8 - GROUP=js
8 - GROUP=js
9 - GROUP=
9 - GROUP=
10 before_install:
10 before_install:
11 # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155
11 # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155
12 - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
12 - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
14 - time sudo add-apt-repository -y ppa:pcarrier/ppa
14 - time sudo add-apt-repository -y ppa:pcarrier/ppa
15 # Needed to get recent version of pandoc in ubntu 12.04
16 - time sudo add-apt-repository -y ppa:marutter/c2d4u
15 - time sudo apt-get update
17 - time sudo apt-get update
16 - time sudo apt-get install pandoc casperjs libzmq3-dev
18 - time sudo apt-get install pandoc casperjs libzmq3-dev
17 # pin tornado < 4 for js tests while phantom is on super old webkit
19 # pin tornado < 4 for js tests while phantom is on super old webkit
18 - if [[ $GROUP == 'js' ]]; then pip install 'tornado<4'; fi
20 - if [[ $GROUP == 'js' ]]; then pip install 'tornado<4'; fi
19 - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests!=2.4.2 mock pyzmq jsonschema jsonpointer mistune
21 - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests!=2.4.2 mock pyzmq jsonschema jsonpointer mistune
20 - if [[ $GROUP == 'js' ]]; then python -m IPython.external.mathjax; fi
22 - if [[ $GROUP == 'js' ]]; then python -m IPython.external.mathjax; fi
21 install:
23 install:
22 - time python setup.py install -q
24 - time python setup.py install -q
23 script:
25 script:
24 - cd /tmp && iptest $GROUP
26 - cd /tmp && iptest $GROUP
25
27
26 matrix:
28 matrix:
27 exclude:
29 exclude:
28 - python: 3.3
30 - python: 3.3
29 env: GROUP=js
31 env: GROUP=js
@@ -1,320 +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 }
66 }
66
67
67 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
68 # Class
69 # Class
69 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
70
71
71 class TemplateExporter(Exporter):
72 class TemplateExporter(Exporter):
72 """
73 """
73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
74 Exports notebooks into other file formats. Uses Jinja 2 templating engine
74 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
75 template type along with new filters/preprocessors. If the filters/
76 template type along with new filters/preprocessors. If the filters/
76 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
77 this class. Instead, override the template_file and file_extension
78 this class. Instead, override the template_file and file_extension
78 traits via a config file.
79 traits via a config file.
79
80
80 {filters}
81 {filters}
81 """
82 """
82
83
83 # finish the docstring
84 # finish the docstring
84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85
86
86
87
87 template_file = Unicode(u'default',
88 template_file = Unicode(u'default',
88 config=True,
89 config=True,
89 help="Name of the template file to use")
90 help="Name of the template file to use")
90 def _template_file_changed(self, name, old, new):
91 def _template_file_changed(self, name, old, new):
91 if new == 'default':
92 if new == 'default':
92 self.template_file = self.default_template
93 self.template_file = self.default_template
93 else:
94 else:
94 self.template_file = new
95 self.template_file = new
95 self.template = None
96 self.template = None
96 self._load_template()
97 self._load_template()
97
98
98 default_template = Unicode(u'')
99 default_template = Unicode(u'')
99 template = Any()
100 template = Any()
100 environment = Any()
101 environment = Any()
101
102
102 template_path = List(['.'], config=True)
103 template_path = List(['.'], config=True)
103 def _template_path_changed(self, name, old, new):
104 def _template_path_changed(self, name, old, new):
104 self._load_template()
105 self._load_template()
105
106
106 default_template_path = Unicode(
107 default_template_path = Unicode(
107 os.path.join("..", "templates"),
108 os.path.join("..", "templates"),
108 help="Path where the template files are located.")
109 help="Path where the template files are located.")
109
110
110 template_skeleton_path = Unicode(
111 template_skeleton_path = Unicode(
111 os.path.join("..", "templates", "skeleton"),
112 os.path.join("..", "templates", "skeleton"),
112 help="Path where the template skeleton files are located.")
113 help="Path where the template skeleton files are located.")
113
114
114 #Jinja block definitions
115 #Jinja block definitions
115 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_end = Unicode("", config=True)
117 jinja_comment_block_end = Unicode("", config=True)
117 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_end = Unicode("", config=True)
119 jinja_variable_block_end = Unicode("", config=True)
119 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_end = Unicode("", config=True)
121 jinja_logic_block_end = Unicode("", config=True)
121
122
122 #Extension that the template files use.
123 #Extension that the template files use.
123 template_extension = Unicode(".tpl", config=True)
124 template_extension = Unicode(".tpl", config=True)
124
125
125 filters = Dict(config=True,
126 filters = Dict(config=True,
126 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
127 environment.""")
128 environment.""")
128
129
129 raw_mimetypes = List(config=True,
130 raw_mimetypes = List(config=True,
130 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."""
131 )
132 )
132 def _raw_mimetypes_default(self):
133 def _raw_mimetypes_default(self):
133 return [self.output_mimetype, '']
134 return [self.output_mimetype, '']
134
135
135
136
136 def __init__(self, config=None, extra_loaders=None, **kw):
137 def __init__(self, config=None, extra_loaders=None, **kw):
137 """
138 """
138 Public constructor
139 Public constructor
139
140
140 Parameters
141 Parameters
141 ----------
142 ----------
142 config : config
143 config : config
143 User configuration instance.
144 User configuration instance.
144 extra_loaders : list[of Jinja Loaders]
145 extra_loaders : list[of Jinja Loaders]
145 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
146 before the default FileSystem ones.
147 before the default FileSystem ones.
147 template : str (optional, kw arg)
148 template : str (optional, kw arg)
148 Template to use when exporting.
149 Template to use when exporting.
149 """
150 """
150 super(TemplateExporter, self).__init__(config=config, **kw)
151 super(TemplateExporter, self).__init__(config=config, **kw)
151
152
152 #Init
153 #Init
153 self._init_template()
154 self._init_template()
154 self._init_environment(extra_loaders=extra_loaders)
155 self._init_environment(extra_loaders=extra_loaders)
155 self._init_filters()
156 self._init_filters()
156
157
157
158
158 def _load_template(self):
159 def _load_template(self):
159 """Load the Jinja template object from the template file
160 """Load the Jinja template object from the template file
160
161
161 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,
162 or the Jinja environment is not setup yet.
163 or the Jinja environment is not setup yet.
163
164
164 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.
165 """
166 """
166 from jinja2 import TemplateNotFound
167 from jinja2 import TemplateNotFound
167
168
168 if self.template is not None:
169 if self.template is not None:
169 return
170 return
170 # called too early, do nothing
171 # called too early, do nothing
171 if self.environment is None:
172 if self.environment is None:
172 return
173 return
173 # Try different template names during conversion. First try to load the
174 # Try different template names during conversion. First try to load the
174 # template by name with extension added, then try loading the template
175 # template by name with extension added, then try loading the template
175 # 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
176 # '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.
177 try_names = []
178 try_names = []
178 if self.template_file:
179 if self.template_file:
179 try_names.extend([
180 try_names.extend([
180 self.template_file + self.template_extension,
181 self.template_file + self.template_extension,
181 self.template_file,
182 self.template_file,
182 ])
183 ])
183 for try_name in try_names:
184 for try_name in try_names:
184 self.log.debug("Attempting to load template %s", try_name)
185 self.log.debug("Attempting to load template %s", try_name)
185 try:
186 try:
186 self.template = self.environment.get_template(try_name)
187 self.template = self.environment.get_template(try_name)
187 except (TemplateNotFound, IOError):
188 except (TemplateNotFound, IOError):
188 pass
189 pass
189 except Exception as e:
190 except Exception as e:
190 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)
191 else:
192 else:
192 self.log.info("Loaded template %s", try_name)
193 self.log.info("Loaded template %s", try_name)
193 break
194 break
194
195
195 def from_notebook_node(self, nb, resources=None, **kw):
196 def from_notebook_node(self, nb, resources=None, **kw):
196 """
197 """
197 Convert a notebook from a notebook node instance.
198 Convert a notebook from a notebook node instance.
198
199
199 Parameters
200 Parameters
200 ----------
201 ----------
201 nb : :class:`~IPython.nbformat.current.NotebookNode`
202 nb : :class:`~IPython.nbformat.current.NotebookNode`
202 Notebook node
203 Notebook node
203 resources : dict
204 resources : dict
204 Additional resources that can be accessed read/write by
205 Additional resources that can be accessed read/write by
205 preprocessors and filters.
206 preprocessors and filters.
206 """
207 """
207 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)
208 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
209 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
209
210
210 self._load_template()
211 self._load_template()
211
212
212 if self.template is not None:
213 if self.template is not None:
213 output = self.template.render(nb=nb_copy, resources=resources)
214 output = self.template.render(nb=nb_copy, resources=resources)
214 else:
215 else:
215 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)
216 return output, resources
217 return output, resources
217
218
218
219
219 def register_filter(self, name, jinja_filter):
220 def register_filter(self, name, jinja_filter):
220 """
221 """
221 Register a filter.
222 Register a filter.
222 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.
223 The filters are accesible within the Jinja templating engine.
224 The filters are accesible within the Jinja templating engine.
224
225
225 Parameters
226 Parameters
226 ----------
227 ----------
227 name : str
228 name : str
228 name to give the filter in the Jinja engine
229 name to give the filter in the Jinja engine
229 filter : filter
230 filter : filter
230 """
231 """
231 if jinja_filter is None:
232 if jinja_filter is None:
232 raise TypeError('filter')
233 raise TypeError('filter')
233 isclass = isinstance(jinja_filter, type)
234 isclass = isinstance(jinja_filter, type)
234 constructed = not isclass
235 constructed = not isclass
235
236
236 #Handle filter's registration based on it's type
237 #Handle filter's registration based on it's type
237 if constructed and isinstance(jinja_filter, py3compat.string_types):
238 if constructed and isinstance(jinja_filter, py3compat.string_types):
238 #filter is a string, import the namespace and recursively call
239 #filter is a string, import the namespace and recursively call
239 #this register_filter method
240 #this register_filter method
240 filter_cls = import_item(jinja_filter)
241 filter_cls = import_item(jinja_filter)
241 return self.register_filter(name, filter_cls)
242 return self.register_filter(name, filter_cls)
242
243
243 if constructed and hasattr(jinja_filter, '__call__'):
244 if constructed and hasattr(jinja_filter, '__call__'):
244 #filter is a function, no need to construct it.
245 #filter is a function, no need to construct it.
245 self.environment.filters[name] = jinja_filter
246 self.environment.filters[name] = jinja_filter
246 return jinja_filter
247 return jinja_filter
247
248
248 elif isclass and isinstance(jinja_filter, MetaHasTraits):
249 elif isclass and isinstance(jinja_filter, MetaHasTraits):
249 #filter is configurable. Make sure to pass in new default for
250 #filter is configurable. Make sure to pass in new default for
250 #the enabled flag if one was specified.
251 #the enabled flag if one was specified.
251 filter_instance = jinja_filter(parent=self)
252 filter_instance = jinja_filter(parent=self)
252 self.register_filter(name, filter_instance )
253 self.register_filter(name, filter_instance )
253
254
254 elif isclass:
255 elif isclass:
255 #filter is not configurable, construct it
256 #filter is not configurable, construct it
256 filter_instance = jinja_filter()
257 filter_instance = jinja_filter()
257 self.register_filter(name, filter_instance)
258 self.register_filter(name, filter_instance)
258
259
259 else:
260 else:
260 #filter is an instance of something without a __call__
261 #filter is an instance of something without a __call__
261 #attribute.
262 #attribute.
262 raise TypeError('filter')
263 raise TypeError('filter')
263
264
264
265
265 def _init_template(self):
266 def _init_template(self):
266 """
267 """
267 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
268 build one from the information we know.
269 build one from the information we know.
269 """
270 """
270 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)
271
272
272
273
273 def _init_environment(self, extra_loaders=None):
274 def _init_environment(self, extra_loaders=None):
274 """
275 """
275 Create the Jinja templating environment.
276 Create the Jinja templating environment.
276 """
277 """
277 from jinja2 import Environment, ChoiceLoader, FileSystemLoader
278 from jinja2 import Environment, ChoiceLoader, FileSystemLoader
278 here = os.path.dirname(os.path.realpath(__file__))
279 here = os.path.dirname(os.path.realpath(__file__))
279 loaders = []
280 loaders = []
280 if extra_loaders:
281 if extra_loaders:
281 loaders.extend(extra_loaders)
282 loaders.extend(extra_loaders)
282
283
283 paths = self.template_path
284 paths = self.template_path
284 paths.extend([os.path.join(here, self.default_template_path),
285 paths.extend([os.path.join(here, self.default_template_path),
285 os.path.join(here, self.template_skeleton_path)])
286 os.path.join(here, self.template_skeleton_path)])
286 loaders.append(FileSystemLoader(paths))
287 loaders.append(FileSystemLoader(paths))
287
288
288 self.environment = Environment(
289 self.environment = Environment(
289 loader= ChoiceLoader(loaders),
290 loader= ChoiceLoader(loaders),
290 extensions=JINJA_EXTENSIONS
291 extensions=JINJA_EXTENSIONS
291 )
292 )
292
293
293 #Set special Jinja2 syntax that will not conflict with latex.
294 #Set special Jinja2 syntax that will not conflict with latex.
294 if self.jinja_logic_block_start:
295 if self.jinja_logic_block_start:
295 self.environment.block_start_string = self.jinja_logic_block_start
296 self.environment.block_start_string = self.jinja_logic_block_start
296 if self.jinja_logic_block_end:
297 if self.jinja_logic_block_end:
297 self.environment.block_end_string = self.jinja_logic_block_end
298 self.environment.block_end_string = self.jinja_logic_block_end
298 if self.jinja_variable_block_start:
299 if self.jinja_variable_block_start:
299 self.environment.variable_start_string = self.jinja_variable_block_start
300 self.environment.variable_start_string = self.jinja_variable_block_start
300 if self.jinja_variable_block_end:
301 if self.jinja_variable_block_end:
301 self.environment.variable_end_string = self.jinja_variable_block_end
302 self.environment.variable_end_string = self.jinja_variable_block_end
302 if self.jinja_comment_block_start:
303 if self.jinja_comment_block_start:
303 self.environment.comment_start_string = self.jinja_comment_block_start
304 self.environment.comment_start_string = self.jinja_comment_block_start
304 if self.jinja_comment_block_end:
305 if self.jinja_comment_block_end:
305 self.environment.comment_end_string = self.jinja_comment_block_end
306 self.environment.comment_end_string = self.jinja_comment_block_end
306
307
307
308
308 def _init_filters(self):
309 def _init_filters(self):
309 """
310 """
310 Register all of the filters required for the exporter.
311 Register all of the filters required for the exporter.
311 """
312 """
312
313
313 #Add default filters to the Jinja2 environment
314 #Add default filters to the Jinja2 environment
314 for key, value in default_filters.items():
315 for key, value in default_filters.items():
315 self.register_filter(key, value)
316 self.register_filter(key, value)
316
317
317 #Load user filters. Overwrite existing filters if need be.
318 #Load user filters. Overwrite existing filters if need be.
318 if self.filters:
319 if self.filters:
319 for key, user_filter in self.filters.items():
320 for key, user_filter in self.filters.items():
320 self.register_filter(key, user_filter)
321 self.register_filter(key, user_filter)
@@ -1,227 +1,231 b''
1 """Markdown filters
1 """Markdown filters
2
2
3 This file contains a collection of utility filters for dealing with
3 This file contains a collection of utility filters for dealing with
4 markdown within Jinja templates.
4 markdown within Jinja templates.
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 __future__ import print_function
9 from __future__ import print_function
10
10
11 # Stdlib imports
11 # Stdlib imports
12 import os
12 import os
13 import subprocess
13 import subprocess
14 from io import TextIOWrapper, BytesIO
14 from io import TextIOWrapper, BytesIO
15 import re
15 import re
16
16
17 import mistune
17 import mistune
18 from pygments import highlight
18 from pygments import highlight
19 from pygments.lexers import get_lexer_by_name
19 from pygments.lexers import get_lexer_by_name
20 from pygments.formatters import HtmlFormatter
20 from pygments.formatters import HtmlFormatter
21 from pygments.util import ClassNotFound
21 from pygments.util import ClassNotFound
22
22
23 # IPython imports
23 # IPython imports
24 from IPython.nbconvert.utils.pandoc import pandoc
24 from IPython.nbconvert.utils.pandoc import pandoc
25 from IPython.nbconvert.utils.exceptions import ConversionException
25 from IPython.nbconvert.utils.exceptions import ConversionException
26 from IPython.utils.decorators import undoc
26 from IPython.utils.decorators import undoc
27 from IPython.utils.process import get_output_error_code
27 from IPython.utils.process import get_output_error_code
28 from IPython.utils.py3compat import cast_bytes
28 from IPython.utils.py3compat import cast_bytes
29 from IPython.utils.version import check_version
29 from IPython.utils.version import check_version
30
30
31
31
32 marked = os.path.join(os.path.dirname(__file__), "marked.js")
32 marked = os.path.join(os.path.dirname(__file__), "marked.js")
33 _node = None
33 _node = None
34
34
35 __all__ = [
35 __all__ = [
36 'markdown2html',
36 'markdown2html',
37 'markdown2html_pandoc',
37 'markdown2html_pandoc',
38 'markdown2html_marked',
38 'markdown2html_marked',
39 'markdown2html_mistune',
39 'markdown2html_mistune',
40 'markdown2latex',
40 'markdown2latex',
41 'markdown2rst',
41 'markdown2rst',
42 ]
42 ]
43
43
44 class NodeJSMissing(ConversionException):
44 class NodeJSMissing(ConversionException):
45 """Exception raised when node.js is missing."""
45 """Exception raised when node.js is missing."""
46 pass
46 pass
47
47
48 def markdown2latex(source, extra_args=None):
48 def markdown2latex(source, markup='markdown', extra_args=None):
49 """Convert a markdown string to LaTeX via pandoc.
49 """Convert a markdown string to LaTeX via pandoc.
50
50
51 This function will raise an error if pandoc is not installed.
51 This function will raise an error if pandoc is not installed.
52 Any error messages generated by pandoc are printed to stderr.
52 Any error messages generated by pandoc are printed to stderr.
53
53
54 Parameters
54 Parameters
55 ----------
55 ----------
56 source : string
56 source : string
57 Input string, assumed to be valid markdown.
57 Input string, assumed to be valid markdown.
58 markup : string
59 Markup used by pandoc's reader
60 default : pandoc extended markdown
61 (see http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown)
58
62
59 Returns
63 Returns
60 -------
64 -------
61 out : string
65 out : string
62 Output as returned by pandoc.
66 Output as returned by pandoc.
63 """
67 """
64 return pandoc(source, 'markdown', 'latex', extra_args=extra_args)
68 return pandoc(source, markup, 'latex', extra_args=extra_args)
65
69
66
70
67 @undoc
71 @undoc
68 class MathBlockGrammar(mistune.BlockGrammar):
72 class MathBlockGrammar(mistune.BlockGrammar):
69 block_math = re.compile("^\$\$(.*?)\$\$", re.DOTALL)
73 block_math = re.compile("^\$\$(.*?)\$\$", re.DOTALL)
70 latex_environment = re.compile(r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}",
74 latex_environment = re.compile(r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}",
71 re.DOTALL)
75 re.DOTALL)
72
76
73 @undoc
77 @undoc
74 class MathBlockLexer(mistune.BlockLexer):
78 class MathBlockLexer(mistune.BlockLexer):
75 default_features = ['block_math', 'latex_environment'] + mistune.BlockLexer.default_features
79 default_features = ['block_math', 'latex_environment'] + mistune.BlockLexer.default_features
76
80
77 def __init__(self, rules=None, **kwargs):
81 def __init__(self, rules=None, **kwargs):
78 if rules is None:
82 if rules is None:
79 rules = MathBlockGrammar()
83 rules = MathBlockGrammar()
80 super(MathBlockLexer, self).__init__(rules, **kwargs)
84 super(MathBlockLexer, self).__init__(rules, **kwargs)
81
85
82 def parse_block_math(self, m):
86 def parse_block_math(self, m):
83 """Parse a $$math$$ block"""
87 """Parse a $$math$$ block"""
84 self.tokens.append({
88 self.tokens.append({
85 'type': 'block_math',
89 'type': 'block_math',
86 'text': m.group(1)
90 'text': m.group(1)
87 })
91 })
88
92
89 def parse_latex_environment(self, m):
93 def parse_latex_environment(self, m):
90 self.tokens.append({
94 self.tokens.append({
91 'type': 'latex_environment',
95 'type': 'latex_environment',
92 'name': m.group(1),
96 'name': m.group(1),
93 'text': m.group(2)
97 'text': m.group(2)
94 })
98 })
95
99
96 @undoc
100 @undoc
97 class MathInlineGrammar(mistune.InlineGrammar):
101 class MathInlineGrammar(mistune.InlineGrammar):
98 math = re.compile("^\$(.+?)\$")
102 math = re.compile("^\$(.+?)\$")
99 text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)')
103 text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)')
100
104
101 @undoc
105 @undoc
102 class MathInlineLexer(mistune.InlineLexer):
106 class MathInlineLexer(mistune.InlineLexer):
103 default_features = ['math'] + mistune.InlineLexer.default_features
107 default_features = ['math'] + mistune.InlineLexer.default_features
104
108
105 def __init__(self, renderer, rules=None, **kwargs):
109 def __init__(self, renderer, rules=None, **kwargs):
106 if rules is None:
110 if rules is None:
107 rules = MathInlineGrammar()
111 rules = MathInlineGrammar()
108 super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)
112 super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)
109
113
110 def output_math(self, m):
114 def output_math(self, m):
111 return self.renderer.inline_math(m.group(1))
115 return self.renderer.inline_math(m.group(1))
112
116
113 @undoc
117 @undoc
114 class MarkdownWithMath(mistune.Markdown):
118 class MarkdownWithMath(mistune.Markdown):
115 def __init__(self, renderer, **kwargs):
119 def __init__(self, renderer, **kwargs):
116 if 'inline' not in kwargs:
120 if 'inline' not in kwargs:
117 kwargs['inline'] = MathInlineLexer
121 kwargs['inline'] = MathInlineLexer
118 if 'block' not in kwargs:
122 if 'block' not in kwargs:
119 kwargs['block'] = MathBlockLexer
123 kwargs['block'] = MathBlockLexer
120 super(MarkdownWithMath, self).__init__(renderer, **kwargs)
124 super(MarkdownWithMath, self).__init__(renderer, **kwargs)
121
125
122 def parse_block_math(self):
126 def parse_block_math(self):
123 return self.renderer.block_math(self.token['text'])
127 return self.renderer.block_math(self.token['text'])
124
128
125 def parse_latex_environment(self):
129 def parse_latex_environment(self):
126 return self.renderer.latex_environment(self.token['name'], self.token['text'])
130 return self.renderer.latex_environment(self.token['name'], self.token['text'])
127
131
128 @undoc
132 @undoc
129 class IPythonRenderer(mistune.Renderer):
133 class IPythonRenderer(mistune.Renderer):
130 def block_code(self, code, lang):
134 def block_code(self, code, lang):
131 if lang:
135 if lang:
132 try:
136 try:
133 lexer = get_lexer_by_name(lang, stripall=True)
137 lexer = get_lexer_by_name(lang, stripall=True)
134 except ClassNotFound:
138 except ClassNotFound:
135 code = lang + '\n' + code
139 code = lang + '\n' + code
136 lang = None
140 lang = None
137
141
138 if not lang:
142 if not lang:
139 return '\n<pre><code>%s</code></pre>\n' % \
143 return '\n<pre><code>%s</code></pre>\n' % \
140 mistune.escape(code)
144 mistune.escape(code)
141
145
142 formatter = HtmlFormatter()
146 formatter = HtmlFormatter()
143 return highlight(code, lexer, formatter)
147 return highlight(code, lexer, formatter)
144
148
145 # Pass math through unaltered - mathjax does the rendering in the browser
149 # Pass math through unaltered - mathjax does the rendering in the browser
146 def block_math(self, text):
150 def block_math(self, text):
147 return '$$%s$$' % text
151 return '$$%s$$' % text
148
152
149 def latex_environment(self, name, text):
153 def latex_environment(self, name, text):
150 return r'\begin{%s}%s\end{%s}' % (name, text, name)
154 return r'\begin{%s}%s\end{%s}' % (name, text, name)
151
155
152 def inline_math(self, text):
156 def inline_math(self, text):
153 return '$%s$' % text
157 return '$%s$' % text
154
158
155 def markdown2html_mistune(source):
159 def markdown2html_mistune(source):
156 """Convert a markdown string to HTML using mistune"""
160 """Convert a markdown string to HTML using mistune"""
157 return MarkdownWithMath(renderer=IPythonRenderer()).render(source)
161 return MarkdownWithMath(renderer=IPythonRenderer()).render(source)
158
162
159 def markdown2html_pandoc(source, extra_args=None):
163 def markdown2html_pandoc(source, extra_args=None):
160 """Convert a markdown string to HTML via pandoc"""
164 """Convert a markdown string to HTML via pandoc"""
161 extra_args = extra_args or ['--mathjax']
165 extra_args = extra_args or ['--mathjax']
162 return pandoc(source, 'markdown', 'html', extra_args=extra_args)
166 return pandoc(source, 'markdown', 'html', extra_args=extra_args)
163
167
164 def _find_nodejs():
168 def _find_nodejs():
165 global _node
169 global _node
166 if _node is None:
170 if _node is None:
167 # prefer md2html via marked if node.js >= 0.9.12 is available
171 # prefer md2html via marked if node.js >= 0.9.12 is available
168 # node is called nodejs on debian, so try that first
172 # node is called nodejs on debian, so try that first
169 _node = 'nodejs'
173 _node = 'nodejs'
170 if not _verify_node(_node):
174 if not _verify_node(_node):
171 _node = 'node'
175 _node = 'node'
172 return _node
176 return _node
173
177
174 def markdown2html_marked(source, encoding='utf-8'):
178 def markdown2html_marked(source, encoding='utf-8'):
175 """Convert a markdown string to HTML via marked"""
179 """Convert a markdown string to HTML via marked"""
176 command = [_find_nodejs(), marked]
180 command = [_find_nodejs(), marked]
177 try:
181 try:
178 p = subprocess.Popen(command,
182 p = subprocess.Popen(command,
179 stdin=subprocess.PIPE, stdout=subprocess.PIPE
183 stdin=subprocess.PIPE, stdout=subprocess.PIPE
180 )
184 )
181 except OSError as e:
185 except OSError as e:
182 raise NodeJSMissing(
186 raise NodeJSMissing(
183 "The command '%s' returned an error: %s.\n" % (" ".join(command), e) +
187 "The command '%s' returned an error: %s.\n" % (" ".join(command), e) +
184 "Please check that Node.js is installed."
188 "Please check that Node.js is installed."
185 )
189 )
186 out, _ = p.communicate(cast_bytes(source, encoding))
190 out, _ = p.communicate(cast_bytes(source, encoding))
187 out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
191 out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
188 return out.rstrip('\n')
192 return out.rstrip('\n')
189
193
190 # The mistune renderer is the default, because it's simple to depend on it
194 # The mistune renderer is the default, because it's simple to depend on it
191 markdown2html = markdown2html_mistune
195 markdown2html = markdown2html_mistune
192
196
193 def markdown2rst(source, extra_args=None):
197 def markdown2rst(source, extra_args=None):
194 """Convert a markdown string to ReST via pandoc.
198 """Convert a markdown string to ReST via pandoc.
195
199
196 This function will raise an error if pandoc is not installed.
200 This function will raise an error if pandoc is not installed.
197 Any error messages generated by pandoc are printed to stderr.
201 Any error messages generated by pandoc are printed to stderr.
198
202
199 Parameters
203 Parameters
200 ----------
204 ----------
201 source : string
205 source : string
202 Input string, assumed to be valid markdown.
206 Input string, assumed to be valid markdown.
203
207
204 Returns
208 Returns
205 -------
209 -------
206 out : string
210 out : string
207 Output as returned by pandoc.
211 Output as returned by pandoc.
208 """
212 """
209 return pandoc(source, 'markdown', 'rst', extra_args=extra_args)
213 return pandoc(source, 'markdown', 'rst', extra_args=extra_args)
210
214
211 def _verify_node(cmd):
215 def _verify_node(cmd):
212 """Verify that the node command exists and is at least the minimum supported
216 """Verify that the node command exists and is at least the minimum supported
213 version of node.
217 version of node.
214
218
215 Parameters
219 Parameters
216 ----------
220 ----------
217 cmd : string
221 cmd : string
218 Node command to verify (i.e 'node')."""
222 Node command to verify (i.e 'node')."""
219 try:
223 try:
220 out, err, return_code = get_output_error_code([cmd, '--version'])
224 out, err, return_code = get_output_error_code([cmd, '--version'])
221 except OSError:
225 except OSError:
222 # Command not found
226 # Command not found
223 return False
227 return False
224 if return_code:
228 if return_code:
225 # Command error
229 # Command error
226 return False
230 return False
227 return check_version(out.lstrip('v'), '0.9.12')
231 return check_version(out.lstrip('v'), '0.9.12')
@@ -1,221 +1,232 b''
1 # coding: utf-8
1 # coding: utf-8
2 """String filters.
2 """String filters.
3
3
4 Contains a collection of useful string manipulation filters for use in Jinja
4 Contains a collection of useful string manipulation filters for use in Jinja
5 templates.
5 templates.
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2013, the IPython Development Team.
8 # Copyright (c) 2013, the IPython Development Team.
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import re
20 import re
21 import textwrap
21 import textwrap
22 try:
22 try:
23 from urllib.parse import quote # Py 3
23 from urllib.parse import quote # Py 3
24 except ImportError:
24 except ImportError:
25 from urllib2 import quote # Py 2
25 from urllib2 import quote # Py 2
26 from xml.etree import ElementTree
26 from xml.etree import ElementTree
27
27
28 from IPython.core.interactiveshell import InteractiveShell
28 from IPython.core.interactiveshell import InteractiveShell
29 from IPython.utils import py3compat
29 from IPython.utils import py3compat
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Functions
32 # Functions
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 __all__ = [
35 __all__ = [
36 'wrap_text',
36 'wrap_text',
37 'html2text',
37 'html2text',
38 'add_anchor',
38 'add_anchor',
39 'strip_dollars',
39 'strip_dollars',
40 'strip_files_prefix',
40 'strip_files_prefix',
41 'comment_lines',
41 'comment_lines',
42 'get_lines',
42 'get_lines',
43 'ipython2python',
43 'ipython2python',
44 'posix_path',
44 'posix_path',
45 'path2url',
45 'path2url',
46 'add_prompts',
46 'add_prompts',
47 'ascii_only',
47 'ascii_only',
48 'prevent_list_blocks',
48 ]
49 ]
49
50
50
51
51 def wrap_text(text, width=100):
52 def wrap_text(text, width=100):
52 """
53 """
53 Intelligently wrap text.
54 Intelligently wrap text.
54 Wrap text without breaking words if possible.
55 Wrap text without breaking words if possible.
55
56
56 Parameters
57 Parameters
57 ----------
58 ----------
58 text : str
59 text : str
59 Text to wrap.
60 Text to wrap.
60 width : int, optional
61 width : int, optional
61 Number of characters to wrap to, default 100.
62 Number of characters to wrap to, default 100.
62 """
63 """
63
64
64 split_text = text.split('\n')
65 split_text = text.split('\n')
65 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
66 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
66 wrpd = map('\n'.join, wrp)
67 wrpd = map('\n'.join, wrp)
67 return '\n'.join(wrpd)
68 return '\n'.join(wrpd)
68
69
69
70
70 def html2text(element):
71 def html2text(element):
71 """extract inner text from html
72 """extract inner text from html
72
73
73 Analog of jQuery's $(element).text()
74 Analog of jQuery's $(element).text()
74 """
75 """
75 if isinstance(element, py3compat.string_types):
76 if isinstance(element, py3compat.string_types):
76 try:
77 try:
77 element = ElementTree.fromstring(element)
78 element = ElementTree.fromstring(element)
78 except Exception:
79 except Exception:
79 # failed to parse, just return it unmodified
80 # failed to parse, just return it unmodified
80 return element
81 return element
81
82
82 text = element.text or ""
83 text = element.text or ""
83 for child in element:
84 for child in element:
84 text += html2text(child)
85 text += html2text(child)
85 text += (element.tail or "")
86 text += (element.tail or "")
86 return text
87 return text
87
88
88
89
89 def add_anchor(html):
90 def add_anchor(html):
90 """Add an anchor-link to an html header tag
91 """Add an anchor-link to an html header tag
91
92
92 For use in heading cells
93 For use in heading cells
93 """
94 """
94 try:
95 try:
95 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
96 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
96 except Exception:
97 except Exception:
97 # failed to parse, just return it unmodified
98 # failed to parse, just return it unmodified
98 return html
99 return html
99 link = html2text(h).replace(' ', '-')
100 link = html2text(h).replace(' ', '-')
100 h.set('id', link)
101 h.set('id', link)
101 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
102 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
102 a.text = u'ΒΆ'
103 a.text = u'ΒΆ'
103 h.append(a)
104 h.append(a)
104
105
105 # Known issue of Python3.x, ElementTree.tostring() returns a byte string
106 # Known issue of Python3.x, ElementTree.tostring() returns a byte string
106 # instead of a text string. See issue http://bugs.python.org/issue10942
107 # instead of a text string. See issue http://bugs.python.org/issue10942
107 # Workaround is to make sure the bytes are casted to a string.
108 # Workaround is to make sure the bytes are casted to a string.
108 return py3compat.decode(ElementTree.tostring(h), 'utf-8')
109 return py3compat.decode(ElementTree.tostring(h), 'utf-8')
109
110
110
111
111 def add_prompts(code, first='>>> ', cont='... '):
112 def add_prompts(code, first='>>> ', cont='... '):
112 """Add prompts to code snippets"""
113 """Add prompts to code snippets"""
113 new_code = []
114 new_code = []
114 code_list = code.split('\n')
115 code_list = code.split('\n')
115 new_code.append(first + code_list[0])
116 new_code.append(first + code_list[0])
116 for line in code_list[1:]:
117 for line in code_list[1:]:
117 new_code.append(cont + line)
118 new_code.append(cont + line)
118 return '\n'.join(new_code)
119 return '\n'.join(new_code)
119
120
120
121
121 def strip_dollars(text):
122 def strip_dollars(text):
122 """
123 """
123 Remove all dollar symbols from text
124 Remove all dollar symbols from text
124
125
125 Parameters
126 Parameters
126 ----------
127 ----------
127 text : str
128 text : str
128 Text to remove dollars from
129 Text to remove dollars from
129 """
130 """
130
131
131 return text.strip('$')
132 return text.strip('$')
132
133
133
134
134 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)/?files/')
135 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)/?files/')
135 markdown_url_pattern = re.compile(r'(!?)\[(?P<caption>.*?)\]\(/?files/(?P<location>.*?)\)')
136 markdown_url_pattern = re.compile(r'(!?)\[(?P<caption>.*?)\]\(/?files/(?P<location>.*?)\)')
136
137
137 def strip_files_prefix(text):
138 def strip_files_prefix(text):
138 """
139 """
139 Fix all fake URLs that start with `files/`, stripping out the `files/` prefix.
140 Fix all fake URLs that start with `files/`, stripping out the `files/` prefix.
140 Applies to both urls (for html) and relative paths (for markdown paths).
141 Applies to both urls (for html) and relative paths (for markdown paths).
141
142
142 Parameters
143 Parameters
143 ----------
144 ----------
144 text : str
145 text : str
145 Text in which to replace 'src="files/real...' with 'src="real...'
146 Text in which to replace 'src="files/real...' with 'src="real...'
146 """
147 """
147 cleaned_text = files_url_pattern.sub(r"\1=\2", text)
148 cleaned_text = files_url_pattern.sub(r"\1=\2", text)
148 cleaned_text = markdown_url_pattern.sub(r'\1[\2](\3)', cleaned_text)
149 cleaned_text = markdown_url_pattern.sub(r'\1[\2](\3)', cleaned_text)
149 return cleaned_text
150 return cleaned_text
150
151
151
152
152 def comment_lines(text, prefix='# '):
153 def comment_lines(text, prefix='# '):
153 """
154 """
154 Build a Python comment line from input text.
155 Build a Python comment line from input text.
155
156
156 Parameters
157 Parameters
157 ----------
158 ----------
158 text : str
159 text : str
159 Text to comment out.
160 Text to comment out.
160 prefix : str
161 prefix : str
161 Character to append to the start of each line.
162 Character to append to the start of each line.
162 """
163 """
163
164
164 #Replace line breaks with line breaks and comment symbols.
165 #Replace line breaks with line breaks and comment symbols.
165 #Also add a comment symbol at the beginning to comment out
166 #Also add a comment symbol at the beginning to comment out
166 #the first line.
167 #the first line.
167 return prefix + ('\n'+prefix).join(text.split('\n'))
168 return prefix + ('\n'+prefix).join(text.split('\n'))
168
169
169
170
170 def get_lines(text, start=None,end=None):
171 def get_lines(text, start=None,end=None):
171 """
172 """
172 Split the input text into separate lines and then return the
173 Split the input text into separate lines and then return the
173 lines that the caller is interested in.
174 lines that the caller is interested in.
174
175
175 Parameters
176 Parameters
176 ----------
177 ----------
177 text : str
178 text : str
178 Text to parse lines from.
179 Text to parse lines from.
179 start : int, optional
180 start : int, optional
180 First line to grab from.
181 First line to grab from.
181 end : int, optional
182 end : int, optional
182 Last line to grab from.
183 Last line to grab from.
183 """
184 """
184
185
185 # Split the input into lines.
186 # Split the input into lines.
186 lines = text.split("\n")
187 lines = text.split("\n")
187
188
188 # Return the right lines.
189 # Return the right lines.
189 return "\n".join(lines[start:end]) #re-join
190 return "\n".join(lines[start:end]) #re-join
190
191
191 def ipython2python(code):
192 def ipython2python(code):
192 """Transform IPython syntax to pure Python syntax
193 """Transform IPython syntax to pure Python syntax
193
194
194 Parameters
195 Parameters
195 ----------
196 ----------
196
197
197 code : str
198 code : str
198 IPython code, to be transformed to pure Python
199 IPython code, to be transformed to pure Python
199 """
200 """
200 shell = InteractiveShell.instance()
201 shell = InteractiveShell.instance()
201 return shell.input_transformer_manager.transform_cell(code)
202 return shell.input_transformer_manager.transform_cell(code)
202
203
203 def posix_path(path):
204 def posix_path(path):
204 """Turn a path into posix-style path/to/etc
205 """Turn a path into posix-style path/to/etc
205
206
206 Mainly for use in latex on Windows,
207 Mainly for use in latex on Windows,
207 where native Windows paths are not allowed.
208 where native Windows paths are not allowed.
208 """
209 """
209 if os.path.sep != '/':
210 if os.path.sep != '/':
210 return path.replace(os.path.sep, '/')
211 return path.replace(os.path.sep, '/')
211 return path
212 return path
212
213
213 def path2url(path):
214 def path2url(path):
214 """Turn a file path into a URL"""
215 """Turn a file path into a URL"""
215 parts = path.split(os.path.sep)
216 parts = path.split(os.path.sep)
216 return '/'.join(quote(part) for part in parts)
217 return '/'.join(quote(part) for part in parts)
217
218
218 def ascii_only(s):
219 def ascii_only(s):
219 """ensure a string is ascii"""
220 """ensure a string is ascii"""
220 s = py3compat.cast_unicode(s)
221 s = py3compat.cast_unicode(s)
221 return s.encode('ascii', 'replace').decode('ascii')
222 return s.encode('ascii', 'replace').decode('ascii')
223
224 def prevent_list_blocks(s):
225 """
226 Prevent presence of enumerate or itemize blocks in latex headings cells
227 """
228 out = re.sub('(^\s*\d*)\.', '\\1\.', s)
229 out = re.sub('(^\s*)\-', '\\1\-', out)
230 out = re.sub('(^\s*)\+', '\\1\+', out)
231 out = re.sub('(^\s*)\*', '\\1\*', out)
232 return out
@@ -1,115 +1,129 b''
1 """Tests for conversions from markdown to other formats"""
1 """Tests for conversions from markdown to other formats"""
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 copy import copy
6 from copy import copy
7
7
8 from IPython.utils.py3compat import string_types
8 from IPython.utils.py3compat import string_types
9 from IPython.testing import decorators as dec
9 from IPython.testing import decorators as dec
10
10
11 from ...tests.base import TestsBase
11 from ...tests.base import TestsBase
12 from ..markdown import markdown2latex, markdown2html, markdown2rst
12 from ..markdown import markdown2latex, markdown2html, markdown2rst
13
13
14 from jinja2 import Environment
14 from jinja2 import Environment
15
15
16 class TestMarkdown(TestsBase):
16 class TestMarkdown(TestsBase):
17
17
18 tests = [
18 tests = [
19 '*test',
19 '*test',
20 '**test',
20 '**test',
21 '*test*',
21 '*test*',
22 '_test_',
22 '_test_',
23 '__test__',
23 '__test__',
24 '__*test*__',
24 '__*test*__',
25 '**test**',
25 '**test**',
26 '#test',
26 '#test',
27 '##test',
27 '##test',
28 'test\n----',
28 'test\n----',
29 'test [link](https://google.com/)']
29 'test [link](https://google.com/)']
30
30
31 tokens = [
31 tokens = [
32 '*test',
32 '*test',
33 '**test',
33 '**test',
34 'test',
34 'test',
35 'test',
35 'test',
36 'test',
36 'test',
37 'test',
37 'test',
38 'test',
38 'test',
39 'test',
39 'test',
40 'test',
40 'test',
41 'test',
41 'test',
42 ('test', 'https://google.com/')]
42 ('test', 'https://google.com/')]
43
43
44
44
45 @dec.onlyif_cmds_exist('pandoc')
45 @dec.onlyif_cmds_exist('pandoc')
46 def test_markdown2latex(self):
46 def test_markdown2latex(self):
47 """markdown2latex test"""
47 """markdown2latex test"""
48 for index, test in enumerate(self.tests):
48 for index, test in enumerate(self.tests):
49 self._try_markdown(markdown2latex, test, self.tokens[index])
49 self._try_markdown(markdown2latex, test, self.tokens[index])
50
50
51 @dec.onlyif_cmds_exist('pandoc')
51 @dec.onlyif_cmds_exist('pandoc')
52 def test_markdown2latex_markup(self):
53 """markdown2latex with markup kwarg test"""
54 # This string should be passed through unaltered with pandoc's
55 # markdown_strict reader
56 s = '1) arabic number with parenthesis'
57 self.assertEqual(markdown2latex(s, markup='markdown_strict'), s)
58 # This string should be passed through unaltered with pandoc's
59 # markdown_strict+tex_math_dollars reader
60 s = '$\\alpha$ latex math'
61 self.assertEqual(
62 markdown2latex(s, markup='markdown_strict+tex_math_dollars'),
63 s)
64
65 @dec.onlyif_cmds_exist('pandoc')
52 def test_pandoc_extra_args(self):
66 def test_pandoc_extra_args(self):
53 # pass --no-wrap
67 # pass --no-wrap
54 s = '\n'.join([
68 s = '\n'.join([
55 "#latex {{long_line | md2l(['--no-wrap'])}}",
69 "#latex {{long_line | md2l('markdown', ['--no-wrap'])}}",
56 "#rst {{long_line | md2r(['--columns', '5'])}}",
70 "#rst {{long_line | md2r(['--columns', '5'])}}",
57 ])
71 ])
58 long_line = ' '.join(['long'] * 30)
72 long_line = ' '.join(['long'] * 30)
59 env = Environment()
73 env = Environment()
60 env.filters.update({
74 env.filters.update({
61 'md2l': markdown2latex,
75 'md2l': markdown2latex,
62 'md2r': markdown2rst,
76 'md2r': markdown2rst,
63 })
77 })
64 tpl = env.from_string(s)
78 tpl = env.from_string(s)
65 rendered = tpl.render(long_line=long_line)
79 rendered = tpl.render(long_line=long_line)
66 _, latex, rst = rendered.split('#')
80 _, latex, rst = rendered.split('#')
67
81
68 self.assertEqual(latex.strip(), 'latex %s' % long_line)
82 self.assertEqual(latex.strip(), 'latex %s' % long_line)
69 self.assertEqual(rst.strip(), 'rst %s' % long_line.replace(' ', '\n'))
83 self.assertEqual(rst.strip(), 'rst %s' % long_line.replace(' ', '\n'))
70
84
71 def test_markdown2html(self):
85 def test_markdown2html(self):
72 """markdown2html test"""
86 """markdown2html test"""
73 for index, test in enumerate(self.tests):
87 for index, test in enumerate(self.tests):
74 self._try_markdown(markdown2html, test, self.tokens[index])
88 self._try_markdown(markdown2html, test, self.tokens[index])
75
89
76 def test_markdown2html_math(self):
90 def test_markdown2html_math(self):
77 # Mathematical expressions should be passed through unaltered
91 # Mathematical expressions should be passed through unaltered
78 cases = [("\\begin{equation*}\n"
92 cases = [("\\begin{equation*}\n"
79 "\\left( \\sum_{k=1}^n a_k b_k \\right)^2 \\leq \\left( \\sum_{k=1}^n a_k^2 \\right) \\left( \\sum_{k=1}^n b_k^2 \\right)\n"
93 "\\left( \\sum_{k=1}^n a_k b_k \\right)^2 \\leq \\left( \\sum_{k=1}^n a_k^2 \\right) \\left( \\sum_{k=1}^n b_k^2 \\right)\n"
80 "\\end{equation*}"),
94 "\\end{equation*}"),
81 ("$$\n"
95 ("$$\n"
82 "a = 1 *3* 5\n"
96 "a = 1 *3* 5\n"
83 "$$"),
97 "$$"),
84 "$ a = 1 *3* 5 $",
98 "$ a = 1 *3* 5 $",
85 ]
99 ]
86 for case in cases:
100 for case in cases:
87 self.assertIn(case, markdown2html(case))
101 self.assertIn(case, markdown2html(case))
88
102
89 def test_markdown2html_math_paragraph(self):
103 def test_markdown2html_math_paragraph(self):
90 # https://github.com/ipython/ipython/issues/6724
104 # https://github.com/ipython/ipython/issues/6724
91 a = """Water that is stored in $t$, $s_t$, must equal the storage content of the previous stage,
105 a = """Water that is stored in $t$, $s_t$, must equal the storage content of the previous stage,
92 $s_{t-1}$, plus a stochastic inflow, $I_t$, minus what is being released in $t$, $r_t$.
106 $s_{t-1}$, plus a stochastic inflow, $I_t$, minus what is being released in $t$, $r_t$.
93 With $s_0$ defined as the initial storage content in $t=1$, we have"""
107 With $s_0$ defined as the initial storage content in $t=1$, we have"""
94 self.assertIn(a, markdown2html(a))
108 self.assertIn(a, markdown2html(a))
95
109
96 @dec.onlyif_cmds_exist('pandoc')
110 @dec.onlyif_cmds_exist('pandoc')
97 def test_markdown2rst(self):
111 def test_markdown2rst(self):
98 """markdown2rst test"""
112 """markdown2rst test"""
99
113
100 #Modify token array for rst, escape asterik
114 #Modify token array for rst, escape asterik
101 tokens = copy(self.tokens)
115 tokens = copy(self.tokens)
102 tokens[0] = r'\*test'
116 tokens[0] = r'\*test'
103 tokens[1] = r'\*\*test'
117 tokens[1] = r'\*\*test'
104
118
105 for index, test in enumerate(self.tests):
119 for index, test in enumerate(self.tests):
106 self._try_markdown(markdown2rst, test, tokens[index])
120 self._try_markdown(markdown2rst, test, tokens[index])
107
121
108
122
109 def _try_markdown(self, method, test, tokens):
123 def _try_markdown(self, method, test, tokens):
110 results = method(test)
124 results = method(test)
111 if isinstance(tokens, string_types):
125 if isinstance(tokens, string_types):
112 assert tokens in results
126 assert tokens in results
113 else:
127 else:
114 for token in tokens:
128 for token in tokens:
115 assert token in results
129 assert token in results
@@ -1,153 +1,165 b''
1 """
1 """
2 Module with tests for Strings
2 Module with tests for Strings
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 import os
16 import os
17
17
18 from ...tests.base import TestsBase
18 from ...tests.base import TestsBase
19 from ..strings import (wrap_text, html2text, add_anchor, strip_dollars,
19 from ..strings import (wrap_text, html2text, add_anchor, strip_dollars,
20 strip_files_prefix, get_lines, comment_lines, ipython2python, posix_path,
20 strip_files_prefix, get_lines, comment_lines, ipython2python, posix_path,
21 add_prompts
21 add_prompts, prevent_list_blocks
22 )
22 )
23
23
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Class
26 # Class
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class TestStrings(TestsBase):
29 class TestStrings(TestsBase):
30
30
31 def test_wrap_text(self):
31 def test_wrap_text(self):
32 """wrap_text test"""
32 """wrap_text test"""
33 test_text = """
33 test_text = """
34 Tush! never tell me; I take it much unkindly
34 Tush! never tell me; I take it much unkindly
35 That thou, Iago, who hast had my purse
35 That thou, Iago, who hast had my purse
36 As if the strings were thine, shouldst know of this.
36 As if the strings were thine, shouldst know of this.
37 """
37 """
38 for length in [30,5,1]:
38 for length in [30,5,1]:
39 self._confirm_wrap_text(test_text, length)
39 self._confirm_wrap_text(test_text, length)
40
40
41
41
42 def _confirm_wrap_text(self, text, length):
42 def _confirm_wrap_text(self, text, length):
43 for line in wrap_text(text, length).split('\n'):
43 for line in wrap_text(text, length).split('\n'):
44 assert len(line) <= length
44 assert len(line) <= length
45
45
46
46
47 def test_html2text(self):
47 def test_html2text(self):
48 """html2text test"""
48 """html2text test"""
49 #TODO: More tests
49 #TODO: More tests
50 self.assertEqual(html2text('<name>joe</name>'), 'joe')
50 self.assertEqual(html2text('<name>joe</name>'), 'joe')
51
51
52
52
53 def test_add_anchor(self):
53 def test_add_anchor(self):
54 """add_anchor test"""
54 """add_anchor test"""
55 #TODO: More tests
55 #TODO: More tests
56 results = add_anchor('<b>Hello World!</b>')
56 results = add_anchor('<b>Hello World!</b>')
57 assert 'Hello World!' in results
57 assert 'Hello World!' in results
58 assert 'id="' in results
58 assert 'id="' in results
59 assert 'class="anchor-link"' in results
59 assert 'class="anchor-link"' in results
60 assert '<b' in results
60 assert '<b' in results
61 assert '</b>' in results
61 assert '</b>' in results
62
62
63 def test_add_anchor_fail(self):
63 def test_add_anchor_fail(self):
64 """add_anchor does nothing when it fails"""
64 """add_anchor does nothing when it fails"""
65 html = '<h1>Hello <br>World!</h1>'
65 html = '<h1>Hello <br>World!</h1>'
66 results = add_anchor(html)
66 results = add_anchor(html)
67 self.assertEqual(html, results)
67 self.assertEqual(html, results)
68
68
69 def test_strip_dollars(self):
69 def test_strip_dollars(self):
70 """strip_dollars test"""
70 """strip_dollars test"""
71 tests = [
71 tests = [
72 ('', ''),
72 ('', ''),
73 ('$$', ''),
73 ('$$', ''),
74 ('$H$', 'H'),
74 ('$H$', 'H'),
75 ('$He', 'He'),
75 ('$He', 'He'),
76 ('H$el', 'H$el'),
76 ('H$el', 'H$el'),
77 ('Hell$', 'Hell'),
77 ('Hell$', 'Hell'),
78 ('Hello', 'Hello'),
78 ('Hello', 'Hello'),
79 ('W$o$rld', 'W$o$rld')]
79 ('W$o$rld', 'W$o$rld')]
80 for test in tests:
80 for test in tests:
81 self._try_strip_dollars(test[0], test[1])
81 self._try_strip_dollars(test[0], test[1])
82
82
83
83
84 def _try_strip_dollars(self, test, result):
84 def _try_strip_dollars(self, test, result):
85 self.assertEqual(strip_dollars(test), result)
85 self.assertEqual(strip_dollars(test), result)
86
86
87
87
88 def test_strip_files_prefix(self):
88 def test_strip_files_prefix(self):
89 """strip_files_prefix test"""
89 """strip_files_prefix test"""
90 tests = [
90 tests = [
91 ('', ''),
91 ('', ''),
92 ('/files', '/files'),
92 ('/files', '/files'),
93 ('test="/files"', 'test="/files"'),
93 ('test="/files"', 'test="/files"'),
94 ('My files are in `files/`', 'My files are in `files/`'),
94 ('My files are in `files/`', 'My files are in `files/`'),
95 ('<a href="files/test.html">files/test.html</a>', '<a href="test.html">files/test.html</a>'),
95 ('<a href="files/test.html">files/test.html</a>', '<a href="test.html">files/test.html</a>'),
96 ('<a href="/files/test.html">files/test.html</a>', '<a href="test.html">files/test.html</a>'),
96 ('<a href="/files/test.html">files/test.html</a>', '<a href="test.html">files/test.html</a>'),
97 ("<a href='files/test.html'>files/test.html</a>", "<a href='test.html'>files/test.html</a>"),
97 ("<a href='files/test.html'>files/test.html</a>", "<a href='test.html'>files/test.html</a>"),
98 ('<img src="files/url/location.gif">', '<img src="url/location.gif">'),
98 ('<img src="files/url/location.gif">', '<img src="url/location.gif">'),
99 ('<img src="/files/url/location.gif">', '<img src="url/location.gif">'),
99 ('<img src="/files/url/location.gif">', '<img src="url/location.gif">'),
100 ('hello![caption]', 'hello![caption]'),
100 ('hello![caption]', 'hello![caption]'),
101 ('hello![caption](/url/location.gif)', 'hello![caption](/url/location.gif)'),
101 ('hello![caption](/url/location.gif)', 'hello![caption](/url/location.gif)'),
102 ('hello![caption](url/location.gif)', 'hello![caption](url/location.gif)'),
102 ('hello![caption](url/location.gif)', 'hello![caption](url/location.gif)'),
103 ('hello![caption](url/location.gif)', 'hello![caption](url/location.gif)'),
103 ('hello![caption](url/location.gif)', 'hello![caption](url/location.gif)'),
104 ('hello![caption](files/url/location.gif)', 'hello![caption](url/location.gif)'),
104 ('hello![caption](files/url/location.gif)', 'hello![caption](url/location.gif)'),
105 ('hello![caption](/files/url/location.gif)', 'hello![caption](url/location.gif)'),
105 ('hello![caption](/files/url/location.gif)', 'hello![caption](url/location.gif)'),
106 ('hello [text](/files/url/location.gif)', 'hello [text](url/location.gif)'),
106 ('hello [text](/files/url/location.gif)', 'hello [text](url/location.gif)'),
107 ('hello [text space](files/url/location.gif)', 'hello [text space](url/location.gif)'),
107 ('hello [text space](files/url/location.gif)', 'hello [text space](url/location.gif)'),
108 ]
108 ]
109 for test in tests:
109 for test in tests:
110 self._try_files_prefix(test[0], test[1])
110 self._try_files_prefix(test[0], test[1])
111
111
112
112
113 def _try_files_prefix(self, test, result):
113 def _try_files_prefix(self, test, result):
114 self.assertEqual(strip_files_prefix(test), result)
114 self.assertEqual(strip_files_prefix(test), result)
115
115
116
116
117 def test_comment_lines(self):
117 def test_comment_lines(self):
118 """comment_lines test"""
118 """comment_lines test"""
119 for line in comment_lines('hello\nworld\n!').split('\n'):
119 for line in comment_lines('hello\nworld\n!').split('\n'):
120 assert line.startswith('# ')
120 assert line.startswith('# ')
121 for line in comment_lines('hello\nworld\n!', 'beep').split('\n'):
121 for line in comment_lines('hello\nworld\n!', 'beep').split('\n'):
122 assert line.startswith('beep')
122 assert line.startswith('beep')
123
123
124
124
125 def test_get_lines(self):
125 def test_get_lines(self):
126 """get_lines test"""
126 """get_lines test"""
127 text = "hello\nworld\n!"
127 text = "hello\nworld\n!"
128 self.assertEqual(get_lines(text, start=1), "world\n!")
128 self.assertEqual(get_lines(text, start=1), "world\n!")
129 self.assertEqual(get_lines(text, end=2), "hello\nworld")
129 self.assertEqual(get_lines(text, end=2), "hello\nworld")
130 self.assertEqual(get_lines(text, start=2, end=5), "!")
130 self.assertEqual(get_lines(text, start=2, end=5), "!")
131 self.assertEqual(get_lines(text, start=-2), "world\n!")
131 self.assertEqual(get_lines(text, start=-2), "world\n!")
132
132
133
133
134 def test_ipython2python(self):
134 def test_ipython2python(self):
135 """ipython2python test"""
135 """ipython2python test"""
136 #TODO: More tests
136 #TODO: More tests
137 results = ipython2python(u'%%pylab\nprint("Hello-World")').replace("u'", "'")
137 results = ipython2python(u'%%pylab\nprint("Hello-World")').replace("u'", "'")
138 self.fuzzy_compare(results, u"get_ipython().run_cell_magic('pylab', '', 'print(\"Hello-World\")')",
138 self.fuzzy_compare(results, u"get_ipython().run_cell_magic('pylab', '', 'print(\"Hello-World\")')",
139 ignore_spaces=True, ignore_newlines=True)
139 ignore_spaces=True, ignore_newlines=True)
140
140
141 def test_posix_path(self):
141 def test_posix_path(self):
142 """posix_path test"""
142 """posix_path test"""
143 path_list = ['foo', 'bar']
143 path_list = ['foo', 'bar']
144 expected = '/'.join(path_list)
144 expected = '/'.join(path_list)
145 native = os.path.join(*path_list)
145 native = os.path.join(*path_list)
146 filtered = posix_path(native)
146 filtered = posix_path(native)
147 self.assertEqual(filtered, expected)
147 self.assertEqual(filtered, expected)
148
148
149 def test_add_prompts(self):
149 def test_add_prompts(self):
150 """add_prompts test"""
150 """add_prompts test"""
151 text1 = """for i in range(10):\n i += 1\n print i"""
151 text1 = """for i in range(10):\n i += 1\n print i"""
152 text2 = """>>> for i in range(10):\n... i += 1\n... print i"""
152 text2 = """>>> for i in range(10):\n... i += 1\n... print i"""
153 self.assertEqual(text2, add_prompts(text1))
153 self.assertEqual(text2, add_prompts(text1))
154
155 def test_prevent_list_blocks(self):
156 """prevent_list_blocks test"""
157 tests = [
158 ('1. arabic point', '1\\. arabic point'),
159 ('* bullet asterisk', '\\* bullet asterisk'),
160 ('+ bullet Plus Sign', '\\+ bullet Plus Sign'),
161 ('- bullet Hyphen-Minus', '\\- bullet Hyphen-Minus'),
162 (' 1. spaces + arabic point', ' 1\\. spaces + arabic point'),
163 ]
164 for test in tests:
165 self.assertEqual(prevent_list_blocks(test[0]), test[1])
@@ -1,222 +1,223 b''
1 ((= Latex base template (must inherit)
1 ((= Latex base template (must inherit)
2 This template builds upon the abstract template, adding common latex output
2 This template builds upon the abstract template, adding common latex output
3 functions. Figures, data_text,
3 functions. Figures, data_text,
4 This template does not define a docclass, the inheriting class must define this.=))
4 This template does not define a docclass, the inheriting class must define this.=))
5
5
6 ((*- extends 'display_priority.tplx' -*))
6 ((*- extends 'display_priority.tplx' -*))
7
7
8 %===============================================================================
8 %===============================================================================
9 % Abstract overrides
9 % Abstract overrides
10 %===============================================================================
10 %===============================================================================
11
11
12 ((* block header *))
12 ((* block header *))
13 ((* block docclass *))((* endblock docclass *))
13 ((* block docclass *))((* endblock docclass *))
14
14
15 ((* block packages *))
15 ((* block packages *))
16 \usepackage{graphicx} % Used to insert images
16 \usepackage{graphicx} % Used to insert images
17 \usepackage{adjustbox} % Used to constrain images to a maximum size
17 \usepackage{adjustbox} % Used to constrain images to a maximum size
18 \usepackage{color} % Allow colors to be defined
18 \usepackage{color} % Allow colors to be defined
19 \usepackage{enumerate} % Needed for markdown enumerations to work
19 \usepackage{enumerate} % Needed for markdown enumerations to work
20 \usepackage{geometry} % Used to adjust the document margins
20 \usepackage{geometry} % Used to adjust the document margins
21 \usepackage{amsmath} % Equations
21 \usepackage{amsmath} % Equations
22 \usepackage{amssymb} % Equations
22 \usepackage{amssymb} % Equations
23 \usepackage[mathletters]{ucs} % Extended unicode (utf-8) support
23 \usepackage[mathletters]{ucs} % Extended unicode (utf-8) support
24 \usepackage[utf8x]{inputenc} % Allow utf-8 characters in the tex document
24 \usepackage[utf8x]{inputenc} % Allow utf-8 characters in the tex document
25 \usepackage{fancyvrb} % verbatim replacement that allows latex
25 \usepackage{fancyvrb} % verbatim replacement that allows latex
26 \usepackage{grffile} % extends the file name processing of package graphics
26 \usepackage{grffile} % extends the file name processing of package graphics
27 % to support a larger range
27 % to support a larger range
28 % The hyperref package gives us a pdf with properly built
28 % The hyperref package gives us a pdf with properly built
29 % internal navigation ('pdf bookmarks' for the table of contents,
29 % internal navigation ('pdf bookmarks' for the table of contents,
30 % internal cross-reference links, web links for URLs, etc.)
30 % internal cross-reference links, web links for URLs, etc.)
31 \usepackage{hyperref}
31 \usepackage{hyperref}
32 \usepackage{longtable} % longtable support required by pandoc >1.10
32 \usepackage{longtable} % longtable support required by pandoc >1.10
33 \usepackage{booktabs} % table support for pandoc > 1.12.2
33 \usepackage{booktabs} % table support for pandoc > 1.12.2
34 ((* endblock packages *))
34 ((* endblock packages *))
35
35
36 ((* block definitions *))
36 ((* block definitions *))
37 \definecolor{orange}{cmyk}{0,0.4,0.8,0.2}
37 \definecolor{orange}{cmyk}{0,0.4,0.8,0.2}
38 \definecolor{darkorange}{rgb}{.71,0.21,0.01}
38 \definecolor{darkorange}{rgb}{.71,0.21,0.01}
39 \definecolor{darkgreen}{rgb}{.12,.54,.11}
39 \definecolor{darkgreen}{rgb}{.12,.54,.11}
40 \definecolor{myteal}{rgb}{.26, .44, .56}
40 \definecolor{myteal}{rgb}{.26, .44, .56}
41 \definecolor{gray}{gray}{0.45}
41 \definecolor{gray}{gray}{0.45}
42 \definecolor{lightgray}{gray}{.95}
42 \definecolor{lightgray}{gray}{.95}
43 \definecolor{mediumgray}{gray}{.8}
43 \definecolor{mediumgray}{gray}{.8}
44 \definecolor{inputbackground}{rgb}{.95, .95, .85}
44 \definecolor{inputbackground}{rgb}{.95, .95, .85}
45 \definecolor{outputbackground}{rgb}{.95, .95, .95}
45 \definecolor{outputbackground}{rgb}{.95, .95, .95}
46 \definecolor{traceback}{rgb}{1, .95, .95}
46 \definecolor{traceback}{rgb}{1, .95, .95}
47 % ansi colors
47 % ansi colors
48 \definecolor{red}{rgb}{.6,0,0}
48 \definecolor{red}{rgb}{.6,0,0}
49 \definecolor{green}{rgb}{0,.65,0}
49 \definecolor{green}{rgb}{0,.65,0}
50 \definecolor{brown}{rgb}{0.6,0.6,0}
50 \definecolor{brown}{rgb}{0.6,0.6,0}
51 \definecolor{blue}{rgb}{0,.145,.698}
51 \definecolor{blue}{rgb}{0,.145,.698}
52 \definecolor{purple}{rgb}{.698,.145,.698}
52 \definecolor{purple}{rgb}{.698,.145,.698}
53 \definecolor{cyan}{rgb}{0,.698,.698}
53 \definecolor{cyan}{rgb}{0,.698,.698}
54 \definecolor{lightgray}{gray}{0.5}
54 \definecolor{lightgray}{gray}{0.5}
55
55
56 % bright ansi colors
56 % bright ansi colors
57 \definecolor{darkgray}{gray}{0.25}
57 \definecolor{darkgray}{gray}{0.25}
58 \definecolor{lightred}{rgb}{1.0,0.39,0.28}
58 \definecolor{lightred}{rgb}{1.0,0.39,0.28}
59 \definecolor{lightgreen}{rgb}{0.48,0.99,0.0}
59 \definecolor{lightgreen}{rgb}{0.48,0.99,0.0}
60 \definecolor{lightblue}{rgb}{0.53,0.81,0.92}
60 \definecolor{lightblue}{rgb}{0.53,0.81,0.92}
61 \definecolor{lightpurple}{rgb}{0.87,0.63,0.87}
61 \definecolor{lightpurple}{rgb}{0.87,0.63,0.87}
62 \definecolor{lightcyan}{rgb}{0.5,1.0,0.83}
62 \definecolor{lightcyan}{rgb}{0.5,1.0,0.83}
63
63
64 % commands and environments needed by pandoc snippets
64 % commands and environments needed by pandoc snippets
65 % extracted from the output of `pandoc -s`
65 % extracted from the output of `pandoc -s`
66 \DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
66 \DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
67 % Add ',fontsize=\small' for more characters per line
67 % Add ',fontsize=\small' for more characters per line
68 \newenvironment{Shaded}{}{}
68 \newenvironment{Shaded}{}{}
69 \newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}}
69 \newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}}
70 \newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}}
70 \newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}}
71 \newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
71 \newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
72 \newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
72 \newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
73 \newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
73 \newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
74 \newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
74 \newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
75 \newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
75 \newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
76 \newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}}
76 \newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}}
77 \newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}}
77 \newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}}
78 \newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
78 \newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
79 \newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}}
79 \newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}}
80 \newcommand{\RegionMarkerTok}[1]{{#1}}
80 \newcommand{\RegionMarkerTok}[1]{{#1}}
81 \newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
81 \newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
82 \newcommand{\NormalTok}[1]{{#1}}
82 \newcommand{\NormalTok}[1]{{#1}}
83
83
84 % Define a nice break command that doesn't care if a line doesn't already
84 % Define a nice break command that doesn't care if a line doesn't already
85 % exist.
85 % exist.
86 \def\br{\hspace*{\fill} \\* }
86 \def\br{\hspace*{\fill} \\* }
87 % Math Jax compatability definitions
87 % Math Jax compatability definitions
88 \def\gt{>}
88 \def\gt{>}
89 \def\lt{<}
89 \def\lt{<}
90 % Document parameters
90 % Document parameters
91 ((* block title *))\title{((( resources.metadata.name | ascii_only | escape_latex )))}((* endblock title *))
91 ((* block title *))\title{((( resources.metadata.name | ascii_only | escape_latex )))}((* endblock title *))
92 ((* block date *))((* endblock date *))
92 ((* block date *))((* endblock date *))
93 ((* block author *))((* endblock author *))
93 ((* block author *))((* endblock author *))
94 ((* endblock definitions *))
94 ((* endblock definitions *))
95
95
96 ((* block commands *))
96 ((* block commands *))
97 % Prevent overflowing lines due to hard-to-break entities
97 % Prevent overflowing lines due to hard-to-break entities
98 \sloppy
98 \sloppy
99 % Setup hyperref package
99 % Setup hyperref package
100 \hypersetup{
100 \hypersetup{
101 breaklinks=true, % so long urls are correctly broken across lines
101 breaklinks=true, % so long urls are correctly broken across lines
102 colorlinks=true,
102 colorlinks=true,
103 urlcolor=blue,
103 urlcolor=blue,
104 linkcolor=darkorange,
104 linkcolor=darkorange,
105 citecolor=darkgreen,
105 citecolor=darkgreen,
106 }
106 }
107 % Slightly bigger margins than the latex defaults
107 % Slightly bigger margins than the latex defaults
108 ((* block margins *))
108 ((* block margins *))
109 \geometry{verbose,tmargin=1in,bmargin=1in,lmargin=1in,rmargin=1in}
109 \geometry{verbose,tmargin=1in,bmargin=1in,lmargin=1in,rmargin=1in}
110 ((* endblock margins *))
110 ((* endblock margins *))
111 ((* endblock commands *))
111 ((* endblock commands *))
112 ((* endblock header *))
112 ((* endblock header *))
113
113
114 ((* block body *))
114 ((* block body *))
115 \begin{document}
115 \begin{document}
116
116
117 ((* block predoc *))
117 ((* block predoc *))
118 ((* block maketitle *))\maketitle((* endblock maketitle *))
118 ((* block maketitle *))\maketitle((* endblock maketitle *))
119 ((* block abstract *))((* endblock abstract *))
119 ((* block abstract *))((* endblock abstract *))
120 ((* endblock predoc *))
120 ((* endblock predoc *))
121
121
122 ((( super() )))
122 ((( super() )))
123
123
124 % Add a bibliography block to the postdoc
124 % Add a bibliography block to the postdoc
125 ((* block postdoc *))
125 ((* block postdoc *))
126 ((* block bibliography *))((* endblock bibliography *))
126 ((* block bibliography *))((* endblock bibliography *))
127 ((* endblock postdoc *))
127 ((* endblock postdoc *))
128 \end{document}
128 \end{document}
129 ((* endblock body *))
129 ((* endblock body *))
130
130
131 %===============================================================================
131 %===============================================================================
132 % Support blocks
132 % Support blocks
133 %===============================================================================
133 %===============================================================================
134
134
135 % Displaying simple data text
135 % Displaying simple data text
136 ((* block data_text *))
136 ((* block data_text *))
137 \begin{verbatim}
137 \begin{verbatim}
138 ((( output.text )))
138 ((( output.text )))
139 \end{verbatim}
139 \end{verbatim}
140 ((* endblock data_text *))
140 ((* endblock data_text *))
141
141
142 % Display python error text as-is
142 % Display python error text as-is
143 ((* block pyerr *))
143 ((* block pyerr *))
144 \begin{Verbatim}[commandchars=\\\{\}]
144 \begin{Verbatim}[commandchars=\\\{\}]
145 ((( super() )))
145 ((( super() )))
146 \end{Verbatim}
146 \end{Verbatim}
147 ((* endblock pyerr *))
147 ((* endblock pyerr *))
148 ((* block traceback_line *))
148 ((* block traceback_line *))
149 ((( line | indent | strip_ansi | escape_latex )))
149 ((( line | indent | strip_ansi | escape_latex )))
150 ((* endblock traceback_line *))
150 ((* endblock traceback_line *))
151
151
152 % Display stream ouput with coloring
152 % Display stream ouput with coloring
153 ((* block stream *))
153 ((* block stream *))
154 \begin{Verbatim}[commandchars=\\\{\}]
154 \begin{Verbatim}[commandchars=\\\{\}]
155 ((( output.text | escape_latex | ansi2latex )))
155 ((( output.text | escape_latex | ansi2latex )))
156 \end{Verbatim}
156 \end{Verbatim}
157 ((* endblock stream *))
157 ((* endblock stream *))
158
158
159 % Display latex
159 % Display latex
160 ((* block data_latex -*))
160 ((* block data_latex -*))
161 ((*- if output.latex.startswith('$'): -*))
161 ((*- if output.latex.startswith('$'): -*))
162 ((= Replace $ symbols with more explicit, equation block. =))
162 ((= Replace $ symbols with more explicit, equation block. =))
163 \begin{equation*}\adjustbox{max width=\hsize}{$
163 \begin{equation*}\adjustbox{max width=\hsize}{$
164 ((( output.latex | strip_dollars )))
164 ((( output.latex | strip_dollars )))
165 $}\end{equation*}
165 $}\end{equation*}
166 ((*- else -*))
166 ((*- else -*))
167 ((( output.latex )))
167 ((( output.latex )))
168 ((*- endif *))
168 ((*- endif *))
169 ((* endblock data_latex *))
169 ((* endblock data_latex *))
170
170
171 % Default mechanism for rendering figures
171 % Default mechanism for rendering figures
172 ((*- block data_png -*))((( draw_figure(output.png_filename) )))((*- endblock -*))
172 ((*- block data_png -*))((( draw_figure(output.png_filename) )))((*- endblock -*))
173 ((*- block data_jpg -*))((( draw_figure(output.jpeg_filename) )))((*- endblock -*))
173 ((*- block data_jpg -*))((( draw_figure(output.jpeg_filename) )))((*- endblock -*))
174 ((*- block data_svg -*))((( draw_figure(output.svg_filename) )))((*- endblock -*))
174 ((*- block data_svg -*))((( draw_figure(output.svg_filename) )))((*- endblock -*))
175 ((*- block data_pdf -*))((( draw_figure(output['application/pdf_filename']) )))((*- endblock -*))
175 ((*- block data_pdf -*))((( draw_figure(output['application/pdf_filename']) )))((*- endblock -*))
176
176
177 % Draw a figure using the graphicx package.
177 % Draw a figure using the graphicx package.
178 ((* macro draw_figure(filename) -*))
178 ((* macro draw_figure(filename) -*))
179 ((* set filename = filename | posix_path *))
179 ((* set filename = filename | posix_path *))
180 ((*- block figure scoped -*))
180 ((*- block figure scoped -*))
181 \begin{center}
181 \begin{center}
182 \adjustimage{max size={0.9\linewidth}{0.9\paperheight}}{((( filename )))}
182 \adjustimage{max size={0.9\linewidth}{0.9\paperheight}}{((( filename )))}
183 \end{center}
183 \end{center}
184 { \hspace*{\fill} \\}
184 { \hspace*{\fill} \\}
185 ((*- endblock figure -*))
185 ((*- endblock figure -*))
186 ((*- endmacro *))
186 ((*- endmacro *))
187
187
188 % Draw heading cell. Explicitly map different cell levels.
188 % Draw heading cell. Explicitly map different cell levels.
189 ((* block headingcell scoped *))
189 ((* block headingcell scoped *))
190
190
191 ((* if cell.level == 1 -*))
191 ((* if cell.level == 1 -*))
192 ((* block h1 -*))\section((* endblock h1 -*))
192 ((* block h1 -*))\section((* endblock h1 -*))
193 ((* elif cell.level == 2 -*))
193 ((* elif cell.level == 2 -*))
194 ((* block h2 -*))\subsection((* endblock h2 -*))
194 ((* block h2 -*))\subsection((* endblock h2 -*))
195 ((* elif cell.level == 3 -*))
195 ((* elif cell.level == 3 -*))
196 ((* block h3 -*))\subsubsection((* endblock h3 -*))
196 ((* block h3 -*))\subsubsection((* endblock h3 -*))
197 ((* elif cell.level == 4 -*))
197 ((* elif cell.level == 4 -*))
198 ((* block h4 -*))\paragraph((* endblock h4 -*))
198 ((* block h4 -*))\paragraph((* endblock h4 -*))
199 ((* elif cell.level == 5 -*))
199 ((* elif cell.level == 5 -*))
200 ((* block h5 -*))\subparagraph((* endblock h5 -*))
200 ((* block h5 -*))\subparagraph((* endblock h5 -*))
201 ((* elif cell.level == 6 -*))
201 ((* elif cell.level == 6 -*))
202 ((* block h6 -*))\\*\textit((* endblock h6 -*))
202 ((* block h6 -*))\\*\textit((* endblock h6 -*))
203 ((*- endif -*))
203 ((*- endif -*))
204 {((( cell.source | replace('\n', ' ') | citation2latex | strip_files_prefix | markdown2latex )))}
204 {((( cell.source | replace('\n', ' ') | citation2latex | strip_files_prefix | prevent_list_blocks | markdown2latex(markup='markdown_strict+tex_math_dollars') )))}
205
205
206
206 ((* endblock headingcell *))
207 ((* endblock headingcell *))
207
208
208 % Redirect pyout to display data priority.
209 % Redirect pyout to display data priority.
209 ((* block pyout scoped *))
210 ((* block pyout scoped *))
210 ((* block data_priority scoped *))
211 ((* block data_priority scoped *))
211 ((( super() )))
212 ((( super() )))
212 ((* endblock *))
213 ((* endblock *))
213 ((* endblock pyout *))
214 ((* endblock pyout *))
214
215
215 % Render markdown
216 % Render markdown
216 ((* block markdowncell scoped *))
217 ((* block markdowncell scoped *))
217 ((( cell.source | citation2latex | strip_files_prefix | markdown2latex )))
218 ((( cell.source | citation2latex | strip_files_prefix | markdown2latex )))
218 ((* endblock markdowncell *))
219 ((* endblock markdowncell *))
219
220
220 % Don't display unknown types
221 % Don't display unknown types
221 ((* block unknowncell scoped *))
222 ((* block unknowncell scoped *))
222 ((* endblock unknowncell *))
223 ((* endblock unknowncell *))
General Comments 0
You need to be logged in to leave comments. Login now