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