##// END OF EJS Templates
add posix_path filter...
MinRK -
Show More
@@ -1,517 +1,518 b''
1 1 """This module defines Exporter, a highly configurable converter
2 2 that uses Jinja2 to export notebook files into different formats.
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 from __future__ import print_function, absolute_import
18 18
19 19 # Stdlib imports
20 20 import io
21 21 import os
22 22 import inspect
23 23 import copy
24 24 import collections
25 25 import datetime
26 26
27 27 # other libs/dependencies
28 28 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
29 29
30 30 # IPython imports
31 31 from IPython.config.configurable import LoggingConfigurable
32 32 from IPython.config import Config
33 33 from IPython.nbformat import current as nbformat
34 34 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict, Any
35 35 from IPython.utils.importstring import import_item
36 36 from IPython.utils.text import indent
37 37 from IPython.utils import py3compat
38 38
39 39 from IPython.nbconvert import transformers as nbtransformers
40 40 from IPython.nbconvert import filters
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Globals and constants
44 44 #-----------------------------------------------------------------------------
45 45
46 46 #Jinja2 extensions to load.
47 47 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
48 48
49 49 default_filters = {
50 50 'indent': indent,
51 51 'markdown2html': filters.markdown2html,
52 52 'ansi2html': filters.ansi2html,
53 53 'filter_data_type': filters.DataTypeFilter,
54 54 'get_lines': filters.get_lines,
55 55 'highlight2html': filters.highlight2html,
56 56 'highlight2latex': filters.highlight2latex,
57 57 'ipython2python': filters.ipython2python,
58 'posix_path': filters.posix_path,
58 59 'markdown2latex': filters.markdown2latex,
59 60 'markdown2rst': filters.markdown2rst,
60 61 'comment_lines': filters.comment_lines,
61 62 'strip_ansi': filters.strip_ansi,
62 63 'strip_dollars': filters.strip_dollars,
63 64 'strip_files_prefix': filters.strip_files_prefix,
64 65 'html2text' : filters.html2text,
65 66 'add_anchor': filters.add_anchor,
66 67 'ansi2latex': filters.ansi2latex,
67 68 'strip_math_space': filters.strip_math_space,
68 69 'wrap_text': filters.wrap_text,
69 'escape_latex': filters.escape_latex
70 'escape_latex': filters.escape_latex,
70 71 }
71 72
72 73 #-----------------------------------------------------------------------------
73 74 # Class
74 75 #-----------------------------------------------------------------------------
75 76
76 77 class ResourcesDict(collections.defaultdict):
77 78 def __missing__(self, key):
78 79 return ''
79 80
80 81
81 82 class Exporter(LoggingConfigurable):
82 83 """
83 84 Exports notebooks into other file formats. Uses Jinja 2 templating engine
84 85 to output new formats. Inherit from this class if you are creating a new
85 86 template type along with new filters/transformers. If the filters/
86 87 transformers provided by default suffice, there is no need to inherit from
87 88 this class. Instead, override the template_file and file_extension
88 89 traits via a config file.
89 90
90 91 {filters}
91 92 """
92 93
93 94 # finish the docstring
94 95 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
95 96
96 97
97 98 template_file = Unicode(u'default',
98 99 config=True,
99 100 help="Name of the template file to use")
100 101 def _template_file_changed(self, name, old, new):
101 102 if new=='default':
102 103 self.template_file = self.default_template
103 104 else:
104 105 self.template_file = new
105 106 self.template = None
106 107 self._load_template()
107 108
108 109 default_template = Unicode(u'')
109 110 template = Any()
110 111 environment = Any()
111 112
112 113 file_extension = Unicode(
113 114 'txt', config=True,
114 115 help="Extension of the file that should be written to disk"
115 116 )
116 117
117 118 template_path = List(['.'], config=True)
118 119 def _template_path_changed(self, name, old, new):
119 120 self._load_template()
120 121
121 122 default_template_path = Unicode(
122 123 os.path.join("..", "templates"),
123 124 help="Path where the template files are located.")
124 125
125 126 template_skeleton_path = Unicode(
126 127 os.path.join("..", "templates", "skeleton"),
127 128 help="Path where the template skeleton files are located.")
128 129
129 130 #Jinja block definitions
130 131 jinja_comment_block_start = Unicode("", config=True)
131 132 jinja_comment_block_end = Unicode("", config=True)
132 133 jinja_variable_block_start = Unicode("", config=True)
133 134 jinja_variable_block_end = Unicode("", config=True)
134 135 jinja_logic_block_start = Unicode("", config=True)
135 136 jinja_logic_block_end = Unicode("", config=True)
136 137
137 138 #Extension that the template files use.
138 139 template_extension = Unicode(".tpl", config=True)
139 140
140 141 #Configurability, allows the user to easily add filters and transformers.
141 142 transformers = List(config=True,
142 143 help="""List of transformers, by name or namespace, to enable.""")
143 144
144 145 filters = Dict(config=True,
145 146 help="""Dictionary of filters, by name and namespace, to add to the Jinja
146 147 environment.""")
147 148
148 149 default_transformers = List([nbtransformers.coalesce_streams,
149 150 nbtransformers.SVG2PDFTransformer,
150 151 nbtransformers.ExtractOutputTransformer,
151 152 nbtransformers.CSSHTMLHeaderTransformer,
152 153 nbtransformers.RevealHelpTransformer,
153 154 nbtransformers.LatexTransformer,
154 155 nbtransformers.SphinxTransformer],
155 156 config=True,
156 157 help="""List of transformers available by default, by name, namespace,
157 158 instance, or type.""")
158 159
159 160
160 161 def __init__(self, config=None, extra_loaders=None, **kw):
161 162 """
162 163 Public constructor
163 164
164 165 Parameters
165 166 ----------
166 167 config : config
167 168 User configuration instance.
168 169 extra_loaders : list[of Jinja Loaders]
169 170 ordered list of Jinja loader to find templates. Will be tried in order
170 171 before the default FileSystem ones.
171 172 template : str (optional, kw arg)
172 173 Template to use when exporting.
173 174 """
174 175 if not config:
175 176 config = self.default_config
176 177
177 178 super(Exporter, self).__init__(config=config, **kw)
178 179
179 180 #Init
180 181 self._init_template()
181 182 self._init_environment(extra_loaders=extra_loaders)
182 183 self._init_transformers()
183 184 self._init_filters()
184 185
185 186
186 187 @property
187 188 def default_config(self):
188 189 return Config()
189 190
190 191 def _config_changed(self, name, old, new):
191 192 """When setting config, make sure to start with our default_config"""
192 193 c = self.default_config
193 194 if new:
194 195 c.merge(new)
195 196 if c != old:
196 197 self.config = c
197 198 super(Exporter, self)._config_changed(name, old, c)
198 199
199 200
200 201 def _load_template(self):
201 202 """Load the Jinja template object from the template file
202 203
203 204 This is a no-op if the template attribute is already defined,
204 205 or the Jinja environment is not setup yet.
205 206
206 207 This is triggered by various trait changes that would change the template.
207 208 """
208 209 if self.template is not None:
209 210 return
210 211 # called too early, do nothing
211 212 if self.environment is None:
212 213 return
213 214 # Try different template names during conversion. First try to load the
214 215 # template by name with extension added, then try loading the template
215 216 # as if the name is explicitly specified, then try the name as a
216 217 # 'flavor', and lastly just try to load the template by module name.
217 218 module_name = self.__module__.rsplit('.', 1)[-1]
218 219 try_names = []
219 220 if self.template_file:
220 221 try_names.extend([
221 222 self.template_file + self.template_extension,
222 223 self.template_file,
223 224 module_name + '_' + self.template_file + self.template_extension,
224 225 ])
225 226 try_names.append(module_name + self.template_extension)
226 227 for try_name in try_names:
227 228 self.log.debug("Attempting to load template %s", try_name)
228 229 try:
229 230 self.template = self.environment.get_template(try_name)
230 231 except (TemplateNotFound, IOError):
231 232 pass
232 233 except Exception as e:
233 234 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
234 235 else:
235 236 self.log.info("Loaded template %s", try_name)
236 237 break
237 238
238 239 def from_notebook_node(self, nb, resources=None, **kw):
239 240 """
240 241 Convert a notebook from a notebook node instance.
241 242
242 243 Parameters
243 244 ----------
244 245 nb : Notebook node
245 246 resources : dict (**kw)
246 247 of additional resources that can be accessed read/write by
247 248 transformers and filters.
248 249 """
249 250 nb_copy = copy.deepcopy(nb)
250 251 resources = self._init_resources(resources)
251 252
252 253 # Preprocess
253 254 nb_copy, resources = self._transform(nb_copy, resources)
254 255
255 256 self._load_template()
256 257
257 258 if self.template is not None:
258 259 output = self.template.render(nb=nb_copy, resources=resources)
259 260 else:
260 261 raise IOError('template file "%s" could not be found' % self.template_file)
261 262 return output, resources
262 263
263 264
264 265 def from_filename(self, filename, resources=None, **kw):
265 266 """
266 267 Convert a notebook from a notebook file.
267 268
268 269 Parameters
269 270 ----------
270 271 filename : str
271 272 Full filename of the notebook file to open and convert.
272 273 """
273 274
274 275 #Pull the metadata from the filesystem.
275 276 if resources is None:
276 277 resources = ResourcesDict()
277 278 if not 'metadata' in resources or resources['metadata'] == '':
278 279 resources['metadata'] = ResourcesDict()
279 280 basename = os.path.basename(filename)
280 281 notebook_name = basename[:basename.rfind('.')]
281 282 resources['metadata']['name'] = notebook_name
282 283
283 284 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
284 285 resources['metadata']['modified_date'] = modified_date.strftime("%B %d, %Y")
285 286
286 287 with io.open(filename) as f:
287 288 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
288 289
289 290
290 291 def from_file(self, file_stream, resources=None, **kw):
291 292 """
292 293 Convert a notebook from a notebook file.
293 294
294 295 Parameters
295 296 ----------
296 297 file_stream : file-like object
297 298 Notebook file-like object to convert.
298 299 """
299 300 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
300 301
301 302
302 303 def register_transformer(self, transformer, enabled=False):
303 304 """
304 305 Register a transformer.
305 306 Transformers are classes that act upon the notebook before it is
306 307 passed into the Jinja templating engine. Transformers are also
307 308 capable of passing additional information to the Jinja
308 309 templating engine.
309 310
310 311 Parameters
311 312 ----------
312 313 transformer : transformer
313 314 """
314 315 if transformer is None:
315 316 raise TypeError('transformer')
316 317 isclass = isinstance(transformer, type)
317 318 constructed = not isclass
318 319
319 320 #Handle transformer's registration based on it's type
320 321 if constructed and isinstance(transformer, py3compat.string_types):
321 322 #Transformer is a string, import the namespace and recursively call
322 323 #this register_transformer method
323 324 transformer_cls = import_item(transformer)
324 325 return self.register_transformer(transformer_cls, enabled)
325 326
326 327 if constructed and hasattr(transformer, '__call__'):
327 328 #Transformer is a function, no need to construct it.
328 329 #Register and return the transformer.
329 330 if enabled:
330 331 transformer.enabled = True
331 332 self._transformers.append(transformer)
332 333 return transformer
333 334
334 335 elif isclass and isinstance(transformer, MetaHasTraits):
335 336 #Transformer is configurable. Make sure to pass in new default for
336 337 #the enabled flag if one was specified.
337 338 self.register_transformer(transformer(parent=self), enabled)
338 339
339 340 elif isclass:
340 341 #Transformer is not configurable, construct it
341 342 self.register_transformer(transformer(), enabled)
342 343
343 344 else:
344 345 #Transformer is an instance of something without a __call__
345 346 #attribute.
346 347 raise TypeError('transformer')
347 348
348 349
349 350 def register_filter(self, name, jinja_filter):
350 351 """
351 352 Register a filter.
352 353 A filter is a function that accepts and acts on one string.
353 354 The filters are accesible within the Jinja templating engine.
354 355
355 356 Parameters
356 357 ----------
357 358 name : str
358 359 name to give the filter in the Jinja engine
359 360 filter : filter
360 361 """
361 362 if jinja_filter is None:
362 363 raise TypeError('filter')
363 364 isclass = isinstance(jinja_filter, type)
364 365 constructed = not isclass
365 366
366 367 #Handle filter's registration based on it's type
367 368 if constructed and isinstance(jinja_filter, py3compat.string_types):
368 369 #filter is a string, import the namespace and recursively call
369 370 #this register_filter method
370 371 filter_cls = import_item(jinja_filter)
371 372 return self.register_filter(name, filter_cls)
372 373
373 374 if constructed and hasattr(jinja_filter, '__call__'):
374 375 #filter is a function, no need to construct it.
375 376 self.environment.filters[name] = jinja_filter
376 377 return jinja_filter
377 378
378 379 elif isclass and isinstance(jinja_filter, MetaHasTraits):
379 380 #filter is configurable. Make sure to pass in new default for
380 381 #the enabled flag if one was specified.
381 382 filter_instance = jinja_filter(parent=self)
382 383 self.register_filter(name, filter_instance )
383 384
384 385 elif isclass:
385 386 #filter is not configurable, construct it
386 387 filter_instance = jinja_filter()
387 388 self.register_filter(name, filter_instance)
388 389
389 390 else:
390 391 #filter is an instance of something without a __call__
391 392 #attribute.
392 393 raise TypeError('filter')
393 394
394 395
395 396 def _init_template(self):
396 397 """
397 398 Make sure a template name is specified. If one isn't specified, try to
398 399 build one from the information we know.
399 400 """
400 401 self._template_file_changed('template_file', self.template_file, self.template_file)
401 402
402 403
403 404 def _init_environment(self, extra_loaders=None):
404 405 """
405 406 Create the Jinja templating environment.
406 407 """
407 408 here = os.path.dirname(os.path.realpath(__file__))
408 409 loaders = []
409 410 if extra_loaders:
410 411 loaders.extend(extra_loaders)
411 412
412 413 paths = self.template_path
413 414 paths.extend([os.path.join(here, self.default_template_path),
414 415 os.path.join(here, self.template_skeleton_path)])
415 416 loaders.append(FileSystemLoader(paths))
416 417
417 418 self.environment = Environment(
418 419 loader= ChoiceLoader(loaders),
419 420 extensions=JINJA_EXTENSIONS
420 421 )
421 422
422 423 #Set special Jinja2 syntax that will not conflict with latex.
423 424 if self.jinja_logic_block_start:
424 425 self.environment.block_start_string = self.jinja_logic_block_start
425 426 if self.jinja_logic_block_end:
426 427 self.environment.block_end_string = self.jinja_logic_block_end
427 428 if self.jinja_variable_block_start:
428 429 self.environment.variable_start_string = self.jinja_variable_block_start
429 430 if self.jinja_variable_block_end:
430 431 self.environment.variable_end_string = self.jinja_variable_block_end
431 432 if self.jinja_comment_block_start:
432 433 self.environment.comment_start_string = self.jinja_comment_block_start
433 434 if self.jinja_comment_block_end:
434 435 self.environment.comment_end_string = self.jinja_comment_block_end
435 436
436 437
437 438 def _init_transformers(self):
438 439 """
439 440 Register all of the transformers needed for this exporter, disabled
440 441 unless specified explicitly.
441 442 """
442 443 self._transformers = []
443 444
444 445 #Load default transformers (not necessarly enabled by default).
445 446 if self.default_transformers:
446 447 for transformer in self.default_transformers:
447 448 self.register_transformer(transformer)
448 449
449 450 #Load user transformers. Enable by default.
450 451 if self.transformers:
451 452 for transformer in self.transformers:
452 453 self.register_transformer(transformer, enabled=True)
453 454
454 455
455 456 def _init_filters(self):
456 457 """
457 458 Register all of the filters required for the exporter.
458 459 """
459 460
460 461 #Add default filters to the Jinja2 environment
461 462 for key, value in default_filters.items():
462 463 self.register_filter(key, value)
463 464
464 465 #Load user filters. Overwrite existing filters if need be.
465 466 if self.filters:
466 467 for key, user_filter in self.filters.items():
467 468 self.register_filter(key, user_filter)
468 469
469 470
470 471 def _init_resources(self, resources):
471 472
472 473 #Make sure the resources dict is of ResourcesDict type.
473 474 if resources is None:
474 475 resources = ResourcesDict()
475 476 if not isinstance(resources, ResourcesDict):
476 477 new_resources = ResourcesDict()
477 478 new_resources.update(resources)
478 479 resources = new_resources
479 480
480 481 #Make sure the metadata extension exists in resources
481 482 if 'metadata' in resources:
482 483 if not isinstance(resources['metadata'], ResourcesDict):
483 484 resources['metadata'] = ResourcesDict(resources['metadata'])
484 485 else:
485 486 resources['metadata'] = ResourcesDict()
486 487 if not resources['metadata']['name']:
487 488 resources['metadata']['name'] = 'Notebook'
488 489
489 490 #Set the output extension
490 491 resources['output_extension'] = self.file_extension
491 492 return resources
492 493
493 494
494 495 def _transform(self, nb, resources):
495 496 """
496 497 Preprocess the notebook before passing it into the Jinja engine.
497 498 To preprocess the notebook is to apply all of the
498 499
499 500 Parameters
500 501 ----------
501 502 nb : notebook node
502 503 notebook that is being exported.
503 504 resources : a dict of additional resources that
504 505 can be accessed read/write by transformers
505 506 and filters.
506 507 """
507 508
508 509 # Do a copy.deepcopy first,
509 510 # we are never safe enough with what the transformers could do.
510 511 nbc = copy.deepcopy(nb)
511 512 resc = copy.deepcopy(resources)
512 513
513 514 #Run each transformer on the notebook. Carry the output along
514 515 #to each transformer
515 516 for transformer in self._transformers:
516 517 nbc, resc = transformer(nbc, resc)
517 518 return nbc, resc
@@ -1,171 +1,183 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 import os
19 20 import re
20 21 import textwrap
21 22 from xml.etree import ElementTree
22 23
23 24 from IPython.core.interactiveshell import InteractiveShell
24 25 from IPython.utils import py3compat
25 26
26 27 #-----------------------------------------------------------------------------
27 28 # Functions
28 29 #-----------------------------------------------------------------------------
29 30
30 31 __all__ = [
31 32 'wrap_text',
32 33 'html2text',
33 34 'add_anchor',
34 35 'strip_dollars',
35 36 'strip_files_prefix',
36 37 'comment_lines',
37 38 'get_lines',
38 39 'ipython2python',
40 'posix_path',
39 41 ]
40 42
41 43
42 44 def wrap_text(text, width=100):
43 45 """
44 46 Intelligently wrap text.
45 47 Wrap text without breaking words if possible.
46 48
47 49 Parameters
48 50 ----------
49 51 text : str
50 52 Text to wrap.
51 53 width : int, optional
52 54 Number of characters to wrap to, default 100.
53 55 """
54 56
55 57 split_text = text.split('\n')
56 58 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
57 59 wrpd = map('\n'.join, wrp)
58 60 return '\n'.join(wrpd)
59 61
60 62
61 63 def html2text(element):
62 64 """extract inner text from html
63 65
64 66 Analog of jQuery's $(element).text()
65 67 """
66 68 if isinstance(element, py3compat.string_types):
67 69 element = ElementTree.fromstring(element)
68 70
69 71 text = element.text or ""
70 72 for child in element:
71 73 text += html2text(child)
72 74 text += (element.tail or "")
73 75 return text
74 76
75 77
76 78 def add_anchor(html):
77 79 """Add an anchor-link to an html header tag
78 80
79 81 For use in heading cells
80 82 """
81 83 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html))
82 84 link = html2text(h).replace(' ', '-')
83 85 h.set('id', link)
84 86 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
85 87 a.text = u'ΒΆ'
86 88 h.append(a)
87 89
88 90 # Known issue of Python3.x, ElementTree.tostring() returns a byte string
89 91 # instead of a text string. See issue http://bugs.python.org/issue10942
90 92 # Workaround is to make sure the bytes are casted to a string.
91 93 return py3compat.decode(ElementTree.tostring(h), 'utf-8')
92 94
93 95
94 96 def strip_dollars(text):
95 97 """
96 98 Remove all dollar symbols from text
97 99
98 100 Parameters
99 101 ----------
100 102 text : str
101 103 Text to remove dollars from
102 104 """
103 105
104 106 return text.strip('$')
105 107
106 108
107 109 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)files/')
108 110
109 111 def strip_files_prefix(text):
110 112 """
111 113 Fix all fake URLs that start with `files/`,
112 114 stripping out the `files/` prefix.
113 115
114 116 Parameters
115 117 ----------
116 118 text : str
117 119 Text in which to replace 'src="files/real...' with 'src="real...'
118 120 """
119 121 return files_url_pattern.sub(r"\1=\2", text)
120 122
121 123
122 124 def comment_lines(text, prefix='# '):
123 125 """
124 126 Build a Python comment line from input text.
125 127
126 128 Parameters
127 129 ----------
128 130 text : str
129 131 Text to comment out.
130 132 prefix : str
131 133 Character to append to the start of each line.
132 134 """
133 135
134 136 #Replace line breaks with line breaks and comment symbols.
135 137 #Also add a comment symbol at the beginning to comment out
136 138 #the first line.
137 139 return prefix + ('\n'+prefix).join(text.split('\n'))
138 140
139 141
140 142 def get_lines(text, start=None,end=None):
141 143 """
142 144 Split the input text into separate lines and then return the
143 145 lines that the caller is interested in.
144 146
145 147 Parameters
146 148 ----------
147 149 text : str
148 150 Text to parse lines from.
149 151 start : int, optional
150 152 First line to grab from.
151 153 end : int, optional
152 154 Last line to grab from.
153 155 """
154 156
155 157 # Split the input into lines.
156 158 lines = text.split("\n")
157 159
158 160 # Return the right lines.
159 161 return "\n".join(lines[start:end]) #re-join
160 162
161 163 def ipython2python(code):
162 164 """Transform IPython syntax to pure Python syntax
163 165
164 166 Parameters
165 167 ----------
166 168
167 169 code : str
168 170 IPython code, to be transformed to pure Python
169 171 """
170 172 shell = InteractiveShell.instance()
171 173 return shell.input_transformer_manager.transform_cell(code)
174
175 def posix_path(path):
176 """Turn a path into posix-style path/to/etc
177
178 Mainly for use in latex on Windows,
179 where native Windows paths are not allowed.
180 """
181 if os.path.sep != '/':
182 return path.replace(os.path.sep, '/')
183 return path
@@ -1,119 +1,127 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
17 17 from IPython.testing import decorators as dec
18 18 from ...tests.base import TestsBase
19 19 from ..strings import (wrap_text, html2text, add_anchor, strip_dollars,
20 strip_files_prefix, get_lines, comment_lines, ipython2python)
20 strip_files_prefix, get_lines, comment_lines, ipython2python, posix_path,
21 )
21 22
22 23
23 24 #-----------------------------------------------------------------------------
24 25 # Class
25 26 #-----------------------------------------------------------------------------
26 27
27 28 class TestStrings(TestsBase):
28 29
29 30 def test_wrap_text(self):
30 31 """wrap_text test"""
31 32 test_text = """
32 33 Tush! never tell me; I take it much unkindly
33 34 That thou, Iago, who hast had my purse
34 35 As if the strings were thine, shouldst know of this.
35 36 """
36 37 for length in [30,5,1]:
37 38 yield self._confirm_wrap_text(test_text, length)
38 39
39 40
40 41 def _confirm_wrap_text(self, text, length):
41 42 for line in wrap_text(text, length).split('\n'):
42 43 assert len(line) <= length
43 44
44 45
45 46 def test_html2text(self):
46 47 """html2text test"""
47 48 #TODO: More tests
48 49 self.assertEqual(html2text('<name>joe</name>'), 'joe')
49 50
50 51
51 52 def test_add_anchor(self):
52 53 """add_anchor test"""
53 54 #TODO: More tests
54 55 results = add_anchor('<b>Hello World!</b>')
55 56 assert 'Hello World!' in results
56 57 assert 'id="' in results
57 58 assert 'class="anchor-link"' in results
58 59 assert '<b' in results
59 60 assert '</b>' in results
60 61
61 62
62 63 def test_strip_dollars(self):
63 64 """strip_dollars test"""
64 65 tests = [
65 66 ('', ''),
66 67 ('$$', ''),
67 68 ('$H$', 'H'),
68 69 ('$He', 'He'),
69 70 ('H$el', 'H$el'),
70 71 ('Hell$', 'Hell'),
71 72 ('Hello', 'Hello'),
72 73 ('W$o$rld', 'W$o$rld')]
73 74 for test in tests:
74 75 yield self._try_strip_dollars(test[0], test[1])
75 76
76 77
77 78 def _try_strip_dollars(self, test, result):
78 79 self.assertEqual(strip_dollars(test), result)
79 80
80 81
81 82 def test_strip_files_prefix(self):
82 83 """strip_files_prefix test"""
83 84 tests = [
84 85 ('', ''),
85 86 ('/files', '/files'),
86 87 ('test="/files"', 'test="/files"'),
87 88 ('My files are in `files/`', 'My files are in `files/`'),
88 89 ('<a href="files/test.html">files/test.html</a>', '<a href="test.html">files/test.html</a>')]
89 90 for test in tests:
90 91 yield self._try_files_prefix(test[0], test[1])
91 92
92 93
93 94 def _try_files_prefix(self, test, result):
94 95 self.assertEqual(strip_files_prefix(test), result)
95 96
96 97
97 98 def test_comment_lines(self):
98 99 """comment_lines test"""
99 100 for line in comment_lines('hello\nworld\n!').split('\n'):
100 101 assert line.startswith('# ')
101 102 for line in comment_lines('hello\nworld\n!', 'beep').split('\n'):
102 103 assert line.startswith('beep')
103 104
104 105
105 106 def test_get_lines(self):
106 107 """get_lines test"""
107 108 text = "hello\nworld\n!"
108 109 self.assertEqual(get_lines(text, start=1), "world\n!")
109 110 self.assertEqual(get_lines(text, end=2), "hello\nworld")
110 111 self.assertEqual(get_lines(text, start=2, end=5), "!")
111 112 self.assertEqual(get_lines(text, start=-2), "world\n!")
112 113
113 114
114 115 def test_ipython2python(self):
115 116 """ipython2python test"""
116 117 #TODO: More tests
117 118 results = ipython2python(u'%%pylab\nprint("Hello-World")').replace("u'", "'")
118 119 self.fuzzy_compare(results, u"get_ipython().run_cell_magic('pylab', '', 'print(\"Hello-World\")')",
119 120 ignore_spaces=True, ignore_newlines=True)
121
122 def test_posix_path(self):
123 path_list = ['foo', 'bar']
124 expected = '/'.join(path_list)
125 native = os.path.join(path_list)
126 filtered = posix_path(native)
127 self.assertEqual(filtered, expected)
General Comments 0
You need to be logged in to leave comments. Login now