##// END OF EJS Templates
flavor=template
Jonathan Frederic -
Show More
@@ -1,471 +1,480
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
29 29
30 30 # IPython imports
31 31 from IPython.config.configurable import Configurable
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
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 58 'markdown2latex': filters.markdown2latex,
59 59 'markdown2rst': filters.markdown2rst,
60 60 'comment_lines': filters.comment_lines,
61 61 'strip_ansi': filters.strip_ansi,
62 62 'strip_dollars': filters.strip_dollars,
63 63 'strip_files_prefix': filters.strip_files_prefix,
64 64 'html2text' : filters.html2text,
65 65 'add_anchor': filters.add_anchor,
66 66 'ansi2latex': filters.ansi2latex,
67 67 'strip_math_space': filters.strip_math_space,
68 68 'wrap_text': filters.wrap_text,
69 69 'escape_latex': filters.escape_latex
70 70 }
71 71
72 72 #-----------------------------------------------------------------------------
73 73 # Class
74 74 #-----------------------------------------------------------------------------
75 75
76 76 class ResourcesDict(collections.defaultdict):
77 77 def __missing__(self, key):
78 78 return ''
79 79
80 80
81 81 class Exporter(Configurable):
82 82 """
83 83 Exports notebooks into other file formats. Uses Jinja 2 templating engine
84 84 to output new formats. Inherit from this class if you are creating a new
85 85 template type along with new filters/transformers. If the filters/
86 86 transformers provided by default suffice, there is no need to inherit from
87 87 this class. Instead, override the template_file and file_extension
88 88 traits via a config file.
89 89 """
90 90
91 91 # finish the docstring
92 92 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
93 93
94 94
95 flavor = Unicode(config=True, help="""Flavor of the data format to use.
96 I.E. 'full' or 'basic'""")
97
98 template_file = Unicode(
95 template_file = Unicode(u'default',
99 96 config=True,
100 97 help="Name of the template file to use")
98 def _template_file_changed(self, name, old, new):
99 if new=='default':
100 self.template_file = self.default_template
101 else:
102 self.template_file = new
103 default_template = Unicode(u'')
101 104
102 105 file_extension = Unicode(
103 106 'txt', config=True,
104 107 help="Extension of the file that should be written to disk"
105 108 )
106 109
107 110 template_path = List(['.'], config=True)
108 111
109 112 default_template_path = Unicode(
110 113 os.path.join("..", "templates"),
111 114 help="Path where the template files are located.")
112 115
113 116 template_skeleton_path = Unicode(
114 117 os.path.join("..", "templates", "skeleton"),
115 118 help="Path where the template skeleton files are located.")
116 119
117 120 #Jinja block definitions
118 121 jinja_comment_block_start = Unicode("", config=True)
119 122 jinja_comment_block_end = Unicode("", config=True)
120 123 jinja_variable_block_start = Unicode("", config=True)
121 124 jinja_variable_block_end = Unicode("", config=True)
122 125 jinja_logic_block_start = Unicode("", config=True)
123 126 jinja_logic_block_end = Unicode("", config=True)
124 127
125 128 #Extension that the template files use.
126 129 template_extension = Unicode(".tpl", config=True)
127 130
128 131 #Configurability, allows the user to easily add filters and transformers.
129 132 transformers = List(config=True,
130 133 help="""List of transformers, by name or namespace, to enable.""")
131 134
132 135 filters = Dict(config=True,
133 136 help="""Dictionary of filters, by name and namespace, to add to the Jinja
134 137 environment.""")
135 138
136 139 default_transformers = List([nbtransformers.coalesce_streams,
137 140 nbtransformers.SVG2PDFTransformer,
138 141 nbtransformers.ExtractOutputTransformer,
139 142 nbtransformers.CSSHTMLHeaderTransformer,
140 143 nbtransformers.RevealHelpTransformer,
141 144 nbtransformers.LatexTransformer,
142 145 nbtransformers.SphinxTransformer],
143 146 config=True,
144 147 help="""List of transformers available by default, by name, namespace,
145 148 instance, or type.""")
146 149
147 150
148 151 def __init__(self, config=None, extra_loaders=None, **kw):
149 152 """
150 153 Public constructor
151 154
152 155 Parameters
153 156 ----------
154 157 config : config
155 158 User configuration instance.
156 159 extra_loaders : list[of Jinja Loaders]
157 160 ordered list of Jinja loder to find templates. Will be tried in order
158 161 before the default FileSysteme ones.
159 flavor : str
160 Flavor to use when exporting. This determines what template to use
161 if one hasn't been specifically provided.
162 template : str (optional, kw arg)
163 Template to use when exporting.
162 164 """
163 165
164 166 #Call the base class constructor
165 167 c = self.default_config
166 168 if config:
167 169 c.merge(config)
168 170
169 171 super(Exporter, self).__init__(config=c, **kw)
170 172
171 173 #Init
172 174 self._init_template(**kw)
173 175 self._init_environment(extra_loaders=extra_loaders)
174 176 self._init_transformers()
175 177 self._init_filters()
176 178
177 179
178 180 @property
179 181 def default_config(self):
180 182 return Config()
181 183
182 184
183 185 def from_notebook_node(self, nb, resources=None, **kw):
184 186 """
185 187 Convert a notebook from a notebook node instance.
186 188
187 189 Parameters
188 190 ----------
189 191 nb : Notebook node
190 192 resources : dict (**kw)
191 193 of additional resources that can be accessed read/write by
192 194 transformers and filters.
193 195 """
194 196 nb_copy = copy.deepcopy(nb)
195 197 resources = self._init_resources(resources)
196 198
197 #Preprocess
199 # Preprocess
198 200 nb_copy, resources = self._transform(nb_copy, resources)
199 201
200 #Convert
201 self.template = self.environment.get_template(self.template_file + self.template_extension)
202 output = self.template.render(nb=nb_copy, resources=resources)
202 # Try different template names during conversion. First try to load the
203 # template by name with extension added, then try loading the template
204 # as if the name is explicitly specified, then try the name as a
205 # 'flavor', and lastly just try to load the template by module name.
206 module_name = self.__module__.split('.')[-1]
207 try_names = [self.template_file + self.template_extension,
208 self.template_file,
209 module_name + '_' + self.template_file + self.template_extension,
210 module_name + self.template_extension]
211 for try_name in try_names:
212 try:
213 self.template = self.environment.get_template(try_name)
214 break
215 except:
216 pass
217
218 if hasattr(self, 'template'):
219 output = self.template.render(nb=nb_copy, resources=resources)
220 else:
221 raise IOError('template file "%s" could not be found' % self.template_file)
203 222 return output, resources
204 223
205 224
206 225 def from_filename(self, filename, resources=None, **kw):
207 226 """
208 227 Convert a notebook from a notebook file.
209 228
210 229 Parameters
211 230 ----------
212 231 filename : str
213 232 Full filename of the notebook file to open and convert.
214 233 """
215 234
216 235 #Pull the metadata from the filesystem.
217 236 if resources is None:
218 237 resources = ResourcesDict()
219 238 if not 'metadata' in resources or resources['metadata'] == '':
220 239 resources['metadata'] = ResourcesDict()
221 240 basename = os.path.basename(filename)
222 241 notebook_name = basename[:basename.rfind('.')]
223 242 resources['metadata']['name'] = notebook_name
224 243
225 244 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
226 245 resources['metadata']['modified_date'] = modified_date.strftime("%B %-d, %Y")
227 246
228 247 with io.open(filename) as f:
229 248 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
230 249
231 250
232 251 def from_file(self, file_stream, resources=None, **kw):
233 252 """
234 253 Convert a notebook from a notebook file.
235 254
236 255 Parameters
237 256 ----------
238 257 file_stream : file-like object
239 258 Notebook file-like object to convert.
240 259 """
241 260 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
242 261
243 262
244 263 def register_transformer(self, transformer, enabled=False):
245 264 """
246 265 Register a transformer.
247 266 Transformers are classes that act upon the notebook before it is
248 267 passed into the Jinja templating engine. Transformers are also
249 268 capable of passing additional information to the Jinja
250 269 templating engine.
251 270
252 271 Parameters
253 272 ----------
254 273 transformer : transformer
255 274 """
256 275 if transformer is None:
257 276 raise TypeError('transformer')
258 277 isclass = isinstance(transformer, type)
259 278 constructed = not isclass
260 279
261 280 #Handle transformer's registration based on it's type
262 281 if constructed and isinstance(transformer, py3compat.string_types):
263 282 #Transformer is a string, import the namespace and recursively call
264 283 #this register_transformer method
265 284 transformer_cls = import_item(transformer)
266 285 return self.register_transformer(transformer_cls, enabled)
267 286
268 287 if constructed and hasattr(transformer, '__call__'):
269 288 #Transformer is a function, no need to construct it.
270 289 #Register and return the transformer.
271 290 if enabled:
272 291 transformer.enabled = True
273 292 self._transformers.append(transformer)
274 293 return transformer
275 294
276 295 elif isclass and isinstance(transformer, MetaHasTraits):
277 296 #Transformer is configurable. Make sure to pass in new default for
278 297 #the enabled flag if one was specified.
279 298 self.register_transformer(transformer(parent=self), enabled)
280 299
281 300 elif isclass:
282 301 #Transformer is not configurable, construct it
283 302 self.register_transformer(transformer(), enabled)
284 303
285 304 else:
286 305 #Transformer is an instance of something without a __call__
287 306 #attribute.
288 307 raise TypeError('transformer')
289 308
290 309
291 310 def register_filter(self, name, jinja_filter):
292 311 """
293 312 Register a filter.
294 313 A filter is a function that accepts and acts on one string.
295 314 The filters are accesible within the Jinja templating engine.
296 315
297 316 Parameters
298 317 ----------
299 318 name : str
300 319 name to give the filter in the Jinja engine
301 320 filter : filter
302 321 """
303 322 if jinja_filter is None:
304 323 raise TypeError('filter')
305 324 isclass = isinstance(jinja_filter, type)
306 325 constructed = not isclass
307 326
308 327 #Handle filter's registration based on it's type
309 328 if constructed and isinstance(jinja_filter, py3compat.string_types):
310 329 #filter is a string, import the namespace and recursively call
311 330 #this register_filter method
312 331 filter_cls = import_item(jinja_filter)
313 332 return self.register_filter(name, filter_cls)
314 333
315 334 if constructed and hasattr(jinja_filter, '__call__'):
316 335 #filter is a function, no need to construct it.
317 336 self.environment.filters[name] = jinja_filter
318 337 return jinja_filter
319 338
320 339 elif isclass and isinstance(jinja_filter, MetaHasTraits):
321 340 #filter is configurable. Make sure to pass in new default for
322 341 #the enabled flag if one was specified.
323 342 filter_instance = jinja_filter(parent=self)
324 343 self.register_filter(name, filter_instance )
325 344
326 345 elif isclass:
327 346 #filter is not configurable, construct it
328 347 filter_instance = jinja_filter()
329 348 self.register_filter(name, filter_instance)
330 349
331 350 else:
332 351 #filter is an instance of something without a __call__
333 352 #attribute.
334 353 raise TypeError('filter')
335 354
336 355
337 356 def _init_template(self, **kw):
338 357 """
339 358 Make sure a template name is specified. If one isn't specified, try to
340 359 build one from the information we know.
341 360 """
342
343 # Set the template_file if it has not been set explicitly.
344 if not self.template_file:
345
346 # Build the template file name from the name of the exporter and the
347 # flavor (if available). The flavor can be set on the traitlet
348 # or passed in as a kw arg. The flavor specified in kw overrides
349 # what is set in the flavor traitlet.
350 module_name = self.__module__.split('.')[-1]
351 if self.flavor or 'flavor' in kw:
352 self.template_file = module_name + '_' + kw.get('flavor', self.flavor)
353 else:
354 self.template_file = module_name
361 self._template_file_changed('template_file', self.template_file, self.template_file)
362 if 'template' in kw:
363 self.template_file = kw['template']
355 364
356 365
357 366 def _init_environment(self, extra_loaders=None):
358 367 """
359 368 Create the Jinja templating environment.
360 369 """
361 370 here = os.path.dirname(os.path.realpath(__file__))
362 371 loaders = []
363 372 if extra_loaders:
364 373 loaders.extend(extra_loaders)
365 374
366 375 paths = self.template_path
367 376 paths.extend([os.path.join(here, self.default_template_path),
368 377 os.path.join(here, self.template_skeleton_path)])
369 378 loaders.append(FileSystemLoader(paths))
370 379
371 380 self.environment = Environment(
372 381 loader= ChoiceLoader(loaders),
373 382 extensions=JINJA_EXTENSIONS
374 383 )
375 384
376 385 #Set special Jinja2 syntax that will not conflict with latex.
377 386 if self.jinja_logic_block_start:
378 387 self.environment.block_start_string = self.jinja_logic_block_start
379 388 if self.jinja_logic_block_end:
380 389 self.environment.block_end_string = self.jinja_logic_block_end
381 390 if self.jinja_variable_block_start:
382 391 self.environment.variable_start_string = self.jinja_variable_block_start
383 392 if self.jinja_variable_block_end:
384 393 self.environment.variable_end_string = self.jinja_variable_block_end
385 394 if self.jinja_comment_block_start:
386 395 self.environment.comment_start_string = self.jinja_comment_block_start
387 396 if self.jinja_comment_block_end:
388 397 self.environment.comment_end_string = self.jinja_comment_block_end
389 398
390 399
391 400 def _init_transformers(self):
392 401 """
393 402 Register all of the transformers needed for this exporter, disabled
394 403 unless specified explicitly.
395 404 """
396 405 self._transformers = []
397 406
398 407 #Load default transformers (not necessarly enabled by default).
399 408 if self.default_transformers:
400 409 for transformer in self.default_transformers:
401 410 self.register_transformer(transformer)
402 411
403 412 #Load user transformers. Enable by default.
404 413 if self.transformers:
405 414 for transformer in self.transformers:
406 415 self.register_transformer(transformer, enabled=True)
407 416
408 417
409 418 def _init_filters(self):
410 419 """
411 420 Register all of the filters required for the exporter.
412 421 """
413 422
414 423 #Add default filters to the Jinja2 environment
415 424 for key, value in default_filters.items():
416 425 self.register_filter(key, value)
417 426
418 427 #Load user filters. Overwrite existing filters if need be.
419 428 if self.filters:
420 429 for key, user_filter in self.filters.items():
421 430 self.register_filter(key, user_filter)
422 431
423 432
424 433 def _init_resources(self, resources):
425 434
426 435 #Make sure the resources dict is of ResourcesDict type.
427 436 if resources is None:
428 437 resources = ResourcesDict()
429 438 if not isinstance(resources, ResourcesDict):
430 439 new_resources = ResourcesDict()
431 440 new_resources.update(resources)
432 441 resources = new_resources
433 442
434 443 #Make sure the metadata extension exists in resources
435 444 if 'metadata' in resources:
436 445 if not isinstance(resources['metadata'], ResourcesDict):
437 446 resources['metadata'] = ResourcesDict(resources['metadata'])
438 447 else:
439 448 resources['metadata'] = ResourcesDict()
440 449 if not resources['metadata']['name']:
441 450 resources['metadata']['name'] = 'Notebook'
442 451
443 452 #Set the output extension
444 453 resources['output_extension'] = self.file_extension
445 454 return resources
446 455
447 456
448 457 def _transform(self, nb, resources):
449 458 """
450 459 Preprocess the notebook before passing it into the Jinja engine.
451 460 To preprocess the notebook is to apply all of the
452 461
453 462 Parameters
454 463 ----------
455 464 nb : notebook node
456 465 notebook that is being exported.
457 466 resources : a dict of additional resources that
458 467 can be accessed read/write by transformers
459 468 and filters.
460 469 """
461 470
462 471 # Do a copy.deepcopy first,
463 472 # we are never safe enough with what the transformers could do.
464 473 nbc = copy.deepcopy(nb)
465 474 resc = copy.deepcopy(resources)
466 475
467 476 #Run each transformer on the notebook. Carry the output along
468 477 #to each transformer
469 478 for transformer in self._transformers:
470 479 nbc, resc = transformer(nbc, resc)
471 480 return nbc, resc
@@ -1,55 +1,52
1 1 """
2 2 Exporter that exports Basic HTML.
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.utils.traitlets import Unicode, List
18 18
19 19 from IPython.nbconvert import transformers
20 20 from IPython.config import Config
21 21
22 22 from .exporter import Exporter
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Classes
26 26 #-----------------------------------------------------------------------------
27 27
28 28 class HTMLExporter(Exporter):
29 29 """
30 30 Exports a basic HTML document. This exporter assists with the export of
31 31 HTML. Inherit from it if you are writing your own HTML template and need
32 32 custom transformers/filters. If you don't need custom transformers/
33 33 filters, just change the 'template_file' config option.
34 34 """
35 35
36 36 file_extension = Unicode(
37 37 'html', config=True,
38 38 help="Extension of the file that should be written to disk"
39 39 )
40 40
41 flavor = Unicode('full', config=True, help="""Flavor of the data format to
42 use. I.E. 'full' or 'basic'""")
41 default_template = Unicode('full', config=True, help="""Flavor of the data
42 format to use. I.E. 'full' or 'basic'""")
43 43
44 44 @property
45 45 def default_config(self):
46 46 c = Config({
47 47 'CSSHTMLHeaderTransformer':{
48 48 'enabled':True
49 },
50 'RevealHelpTransformer':{
51 'enabled':True,
52 },
49 }
53 50 })
54 51 c.merge(super(HTMLExporter,self).default_config)
55 52 return c
@@ -1,91 +1,91
1 1 """
2 2 Exporter that allows Latex Jinja templates to work. Contains logic to
3 3 appropriately prepare IPYNB files for export to LaTeX. Including but
4 4 not limited to escaping LaTeX, fixing math region tags, using special
5 5 tags to circumvent Jinja/Latex syntax conflicts.
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 # Stdlib imports
20 20 import os
21 21
22 22 # IPython imports
23 23 from IPython.utils.traitlets import Unicode, List
24 24 from IPython.config import Config
25 25
26 26 from IPython.nbconvert import filters, transformers
27 27 from .exporter import Exporter
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Classes and functions
31 31 #-----------------------------------------------------------------------------
32 32
33 33 class LatexExporter(Exporter):
34 34 """
35 35 Exports to a Latex template. Inherit from this class if your template is
36 36 LaTeX based and you need custom tranformers/filters. Inherit from it if
37 37 you are writing your own HTML template and need custom tranformers/filters.
38 38 If you don't need custom tranformers/filters, just change the
39 39 'template_file' config option. Place your template in the special "/latex"
40 40 subfolder of the "../templates" folder.
41 41 """
42 42
43 43 file_extension = Unicode(
44 44 'tex', config=True,
45 45 help="Extension of the file that should be written to disk")
46 46
47 flavor = Unicode('article', config=True, help="""Flavor of the data format to
48 use. I.E. 'full' or 'basic'""")
47 default_template = Unicode('article', config=True, help="""Template of the
48 data format to use. I.E. 'full' or 'basic'""")
49 49
50 50 #Latex constants
51 51 default_template_path = Unicode(
52 52 os.path.join("..", "templates", "latex"), config=True,
53 53 help="Path where the template files are located.")
54 54
55 55 template_skeleton_path = Unicode(
56 56 os.path.join("..", "templates", "latex", "skeleton"), config=True,
57 57 help="Path where the template skeleton files are located.")
58 58
59 59 #Special Jinja2 syntax that will not conflict when exporting latex.
60 60 jinja_comment_block_start = Unicode("((=", config=True)
61 61 jinja_comment_block_end = Unicode("=))", config=True)
62 62 jinja_variable_block_start = Unicode("(((", config=True)
63 63 jinja_variable_block_end = Unicode(")))", config=True)
64 64 jinja_logic_block_start = Unicode("((*", config=True)
65 65 jinja_logic_block_end = Unicode("*))", config=True)
66 66
67 67 #Extension that the template files use.
68 68 template_extension = Unicode(".tplx", config=True)
69 69
70 70
71 71 @property
72 72 def default_config(self):
73 73 c = Config({
74 74 'NbConvertBase': {
75 75 'display_data_priority' : ['latex', 'pdf', 'png', 'jpg', 'svg', 'jpeg', 'text']
76 76 },
77 77 'ExtractOutputTransformer': {
78 78 'enabled':True
79 79 },
80 80 'SVG2PDFTransformer': {
81 81 'enabled':True
82 82 },
83 83 'LatexTransformer': {
84 84 'enabled':True
85 85 },
86 86 'SphinxTransformer': {
87 87 'enabled':True
88 88 }
89 89 })
90 90 c.merge(super(LatexExporter,self).default_config)
91 91 return c
@@ -1,52 +1,52
1 1 """
2 2 Exporter that exports Basic HTML.
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.utils.traitlets import Unicode
18 18
19 19 from IPython.nbconvert import transformers
20 20 from IPython.config import Config
21 21
22 22 from .exporter import Exporter
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Classes
26 26 #-----------------------------------------------------------------------------
27 27
28 28 class SlidesExporter(Exporter):
29 29 """
30 30 Exports slides
31 31 """
32 32
33 33 file_extension = Unicode(
34 34 'html', config=True,
35 35 help="Extension of the file that should be written to disk"
36 36 )
37 37
38 flavor = Unicode('reveal', config=True, help="""Flavor of the data format to
39 use. I.E. 'reveal'""")
38 default_template = Unicode('reveal', config=True, help="""Template of the
39 data format to use. I.E. 'reveal'""")
40 40
41 41 @property
42 42 def default_config(self):
43 43 c = Config({
44 44 'CSSHTMLHeaderTransformer':{
45 45 'enabled':True
46 46 },
47 47 'RevealHelpTransformer':{
48 48 'enabled':True,
49 49 },
50 50 })
51 51 c.merge(super(SlidesExporter,self).default_config)
52 52 return c
@@ -1,56 +1,56
1 1 """
2 2 Module with tests for html.py
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 .base import ExportersTestsBase
18 18 from ..html import HTMLExporter
19 19 from IPython.testing.decorators import onlyif_cmds_exist
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Class
23 23 #-----------------------------------------------------------------------------
24 24
25 25 class TestHTMLExporter(ExportersTestsBase):
26 26 """Contains test functions for html.py"""
27 27
28 28 def test_constructor(self):
29 29 """
30 30 Can a HTMLExporter be constructed?
31 31 """
32 32 HTMLExporter()
33 33
34 34 @onlyif_cmds_exist('pandoc')
35 35 def test_export(self):
36 36 """
37 37 Can a HTMLExporter export something?
38 38 """
39 39 (output, resources) = HTMLExporter().from_filename(self._get_notebook())
40 40 assert len(output) > 0
41 41
42 42
43 43 def test_export_basic(self):
44 44 """
45 Can a HTMLExporter export using the 'basic' flavor?
45 Can a HTMLExporter export using the 'basic' template?
46 46 """
47 (output, resources) = HTMLExporter(flavor='basic').from_filename(self._get_notebook())
47 (output, resources) = HTMLExporter(template='basic').from_filename(self._get_notebook())
48 48 assert len(output) > 0
49 49
50 50
51 51 def test_export_full(self):
52 52 """
53 Can a HTMLExporter export using the 'full' flavor?
53 Can a HTMLExporter export using the 'full' template?
54 54 """
55 (output, resources) = HTMLExporter(flavor='full').from_filename(self._get_notebook())
55 (output, resources) = HTMLExporter(template='full').from_filename(self._get_notebook())
56 56 assert len(output) > 0
@@ -1,65 +1,65
1 1 """
2 2 Module with tests for latex.py
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 .base import ExportersTestsBase
18 18 from ..latex import LatexExporter
19 19 from IPython.testing.decorators import onlyif_cmds_exist
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Class
23 23 #-----------------------------------------------------------------------------
24 24
25 25 class TestLatexExporter(ExportersTestsBase):
26 26 """Contains test functions for latex.py"""
27 27
28 28 def test_constructor(self):
29 29 """
30 30 Can a LatexExporter be constructed?
31 31 """
32 32 LatexExporter()
33 33
34 34
35 35 @onlyif_cmds_exist('pandoc')
36 36 def test_export(self):
37 37 """
38 38 Can a LatexExporter export something?
39 39 """
40 40 (output, resources) = LatexExporter().from_filename(self._get_notebook())
41 41 assert len(output) > 0
42 42
43 43
44 44 def test_export_book(self):
45 45 """
46 Can a LatexExporter export using 'book' flavor?
46 Can a LatexExporter export using 'book' template?
47 47 """
48 (output, resources) = LatexExporter(flavor='book').from_filename(self._get_notebook())
48 (output, resources) = LatexExporter(template='book').from_filename(self._get_notebook())
49 49 assert len(output) > 0
50 50
51 51
52 52 def test_export_basic(self):
53 53 """
54 Can a LatexExporter export using 'basic' flavor?
54 Can a LatexExporter export using 'basic' template?
55 55 """
56 (output, resources) = LatexExporter(flavor='basic').from_filename(self._get_notebook())
56 (output, resources) = LatexExporter(template='basic').from_filename(self._get_notebook())
57 57 assert len(output) > 0
58 58
59 59
60 60 def test_export_article(self):
61 61 """
62 Can a LatexExporter export using 'article' flavor?
62 Can a LatexExporter export using 'article' template?
63 63 """
64 (output, resources) = LatexExporter(flavor='article').from_filename(self._get_notebook())
64 (output, resources) = LatexExporter(template='article').from_filename(self._get_notebook())
65 65 assert len(output) > 0 No newline at end of file
@@ -1,47 +1,47
1 1 """
2 2 Module with tests for slides.py
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 .base import ExportersTestsBase
18 18 from ..slides import SlidesExporter
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Class
22 22 #-----------------------------------------------------------------------------
23 23
24 24 class TestSlidesExporter(ExportersTestsBase):
25 25 """Contains test functions for slides.py"""
26 26
27 27 def test_constructor(self):
28 28 """
29 29 Can a SlidesExporter be constructed?
30 30 """
31 31 SlidesExporter()
32 32
33 33
34 34 def test_export(self):
35 35 """
36 36 Can a SlidesExporter export something?
37 37 """
38 38 (output, resources) = SlidesExporter().from_filename(self._get_notebook())
39 39 assert len(output) > 0
40 40
41 41
42 42 def test_export_reveal(self):
43 43 """
44 Can a SlidesExporter export using the 'reveal' flavor?
44 Can a SlidesExporter export using the 'reveal' template?
45 45 """
46 (output, resources) = SlidesExporter(flavor='reveal').from_filename(self._get_notebook())
46 (output, resources) = SlidesExporter(template='reveal').from_filename(self._get_notebook())
47 47 assert len(output) > 0
@@ -1,259 +1,258
1 1 #!/usr/bin/env python
2 2 """NBConvert is a utility for conversion of .ipynb files.
3 3
4 4 Command-line interface for the NbConvert conversion utility.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 #Copyright (c) 2013, the IPython Development Team.
8 8 #
9 9 #Distributed under the terms of the Modified BSD License.
10 10 #
11 11 #The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 #Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Stdlib imports
19 19 from __future__ import print_function
20 20 import sys
21 21 import os
22 22 import glob
23 23
24 24 # From IPython
25 25 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
26 26 from IPython.config import catch_config_error, Configurable
27 27 from IPython.utils.traitlets import (
28 28 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
29 29 )
30 30 from IPython.utils.importstring import import_item
31 31
32 32 from .exporters.export import export_by_name, get_export_names, ExporterNameError
33 33 from IPython.nbconvert import exporters, transformers, writers
34 34 from .utils.base import NbConvertBase
35 35 from .utils.exceptions import ConversionException
36 36
37 37 #-----------------------------------------------------------------------------
38 38 #Classes and functions
39 39 #-----------------------------------------------------------------------------
40 40
41 41 nbconvert_aliases = {}
42 42 nbconvert_aliases.update(base_aliases)
43 43 nbconvert_aliases.update({
44 44 'to' : 'NbConvertApp.export_format',
45 'flavor' : 'Exporter.flavor',
46 45 'template' : 'Exporter.template_file',
47 46 'notebooks' : 'NbConvertApp.notebooks',
48 47 'writer' : 'NbConvertApp.writer_class',
49 48 })
50 49
51 50 nbconvert_flags = {}
52 51 nbconvert_flags.update(base_flags)
53 52 nbconvert_flags.update({
54 53 'stdout' : (
55 54 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
56 55 "Write notebook output to stdout instead of files."
57 56 ),
58 57
59 58 'pdf' : (
60 59 {'NbConvertApp' : {'writer_class' : "PDFWriter"}},
61 "Compile notebook output to a PDF."
60 "Compile notebook output to a PDF (requires `--to latex`)."
62 61 )
63 62 })
64 63
65 64
66 65 class NbConvertApp(BaseIPythonApplication):
67 66 """Application used to convert to and from notebook file type (*.ipynb)"""
68 67
69 68 name = 'ipython-nbconvert'
70 69 aliases = nbconvert_aliases
71 70 flags = nbconvert_flags
72 71
73 72 def _classes_default(self):
74 73 classes = [NbConvertBase]
75 74 for pkg in (exporters, transformers, writers):
76 75 for name in dir(pkg):
77 76 cls = getattr(pkg, name)
78 77 if isinstance(cls, type) and issubclass(cls, Configurable):
79 78 classes.append(cls)
80 79 return classes
81 80
82 81 description = Unicode(
83 82 u"""This application is used to convert notebook files (*.ipynb)
84 83 to various other formats.""")
85 84
86 85 examples = Unicode(u"""
87 86 The simplest way to use nbconvert is
88 87
89 88 > ipython nbconvert mynotebook.ipynb
90 89
91 90 which will convert mynotebook.ipynb to the default format (probably HTML).
92 91
93 92 You can specify the export format with `--to`.
94 93 Options include {0}
95 94
96 95 > ipython nbconvert --to latex mynotebook.ipnynb
97 96
98 Both HTML and LaTeX support multiple flavors of output. LaTeX includes
97 Both HTML and LaTeX support multiple output templates. LaTeX includes
99 98 'basic', 'book', and 'article'. HTML includes 'basic' and 'full'. You
100 99 can specify the flavor of the format used.
101 100
102 > ipython nbconvert --to html --flavor reveal mynotebook.ipnynb
101 > ipython nbconvert --to html --template reveal mynotebook.ipnynb
103 102
104 103 You can also pipe the output to stdout, rather than a file
105 104
106 105 > ipython nbconvert mynotebook.ipynb --stdout
107 106
108 107 or to a PDF
109 108
110 > ipython nbconvert mynotebook.ipynb --pdf
109 > ipython nbconvert mynotebook.ipynb --to latex --pdf
111 110
112 111 Multiple notebooks can be given at the command line in a couple of
113 112 different ways:
114 113
115 114 > ipython nbconvert notebook*.ipynb
116 115 > ipython nbconvert notebook1.ipynb notebook2.ipynb
117 116
118 117 or you can specify the notebooks list in a config file, containing::
119 118
120 119 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
121 120
122 121 > ipython nbconvert --config mycfg.py
123 122 """.format(get_export_names()))
124 123 # Writer specific variables
125 124 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
126 125 help="""Instance of the writer class used to write the
127 126 results of the conversion.""")
128 127 writer_class = DottedObjectName('FilesWriter', config=True,
129 128 help="""Writer class used to write the
130 129 results of the conversion""")
131 130 writer_aliases = {'FilesWriter': 'IPython.nbconvert.writers.files.FilesWriter',
132 131 'PDFWriter': 'IPython.nbconvert.writers.pdf.PDFWriter',
133 132 'DebugWriter': 'IPython.nbconvert.writers.debug.DebugWriter',
134 133 'StdoutWriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
135 134 writer_factory = Type()
136 135
137 136 def _writer_class_changed(self, name, old, new):
138 137 if new in self.writer_aliases:
139 138 new = self.writer_aliases[new]
140 139 self.writer_factory = import_item(new)
141 140
142 141
143 142 # Other configurable variables
144 143 export_format = CaselessStrEnum(get_export_names(),
145 144 default_value="html",
146 145 config=True,
147 146 help="""The export format to be used."""
148 147 )
149 148
150 149 notebooks = List([], config=True, help="""List of notebooks to convert.
151 150 Wildcards are supported.
152 151 Filenames passed positionally will be added to the list.
153 152 """)
154 153
155 154 @catch_config_error
156 155 def initialize(self, argv=None):
157 156 super(NbConvertApp, self).initialize(argv)
158 157 self.init_syspath()
159 158 self.init_notebooks()
160 159 self.init_writer()
161 160
162 161
163 162 def init_syspath(self):
164 163 """
165 164 Add the cwd to the sys.path ($PYTHONPATH)
166 165 """
167 166 sys.path.insert(0, os.getcwd())
168 167
169 168
170 169 def init_notebooks(self):
171 170 """Construct the list of notebooks.
172 171 If notebooks are passed on the command-line,
173 172 they override notebooks specified in config files.
174 173 Glob each notebook to replace notebook patterns with filenames.
175 174 """
176 175
177 176 # Specifying notebooks on the command-line overrides (rather than adds)
178 177 # the notebook list
179 178 if self.extra_args:
180 179 patterns = self.extra_args
181 180 else:
182 181 patterns = self.notebooks
183 182
184 183 # Use glob to replace all the notebook patterns with filenames.
185 184 filenames = []
186 185 for pattern in patterns:
187 186
188 187 # Use glob to find matching filenames. Allow the user to convert
189 188 # notebooks without having to type the extension.
190 189 globbed_files = glob.glob(pattern)
191 190 globbed_files.extend(glob.glob(pattern + '.ipynb'))
192 191
193 192 for filename in globbed_files:
194 193 if not filename in filenames:
195 194 filenames.append(filename)
196 195 self.notebooks = filenames
197 196
198 197 def init_writer(self):
199 198 """
200 199 Initialize the writer (which is stateless)
201 200 """
202 201 self._writer_class_changed(None, self.writer_class, self.writer_class)
203 202 self.writer = self.writer_factory(parent=self)
204 203
205 204 def start(self):
206 205 """
207 206 Ran after initialization completed
208 207 """
209 208 super(NbConvertApp, self).start()
210 209 self.convert_notebooks()
211 210
212 211 def convert_notebooks(self):
213 212 """
214 213 Convert the notebooks in the self.notebook traitlet
215 214 """
216 215 # Export each notebook
217 216 conversion_success = 0
218 217 for notebook_filename in self.notebooks:
219 218
220 219 # Get a unique key for the notebook and set it in the resources object.
221 220 basename = os.path.basename(notebook_filename)
222 221 notebook_name = basename[:basename.rfind('.')]
223 222 resources = {}
224 223 resources['unique_key'] = notebook_name
225 224 resources['output_files_dir'] = '%s_files' % notebook_name
226 225
227 226 # Try to export
228 227 try:
229 228 output, resources = export_by_name(self.export_format,
230 229 notebook_filename,
231 230 resources=resources,
232 231 config=self.config)
233 232 except ExporterNameError as e:
234 233 print("Error while converting '%s': '%s' exporter not found."
235 234 %(notebook_filename, self.export_format),
236 235 file=sys.stderr)
237 236 print("Known exporters are:",
238 237 "\n\t" + "\n\t".join(get_export_names()),
239 238 file=sys.stderr)
240 239 self.exit(1)
241 240 except ConversionException as e:
242 241 print("Error while converting '%s': %s" %(notebook_filename, e),
243 242 file=sys.stderr)
244 243 self.exit(1)
245 244 else:
246 245 self.writer.write(output, resources, notebook_name=notebook_name)
247 246 conversion_success += 1
248 247
249 248 # If nothing was converted successfully, help the user.
250 249 if conversion_success == 0:
251 250 self.print_help()
252 251 sys.exit(-1)
253 252
254 253
255 254 #-----------------------------------------------------------------------------
256 255 # Main entry point
257 256 #-----------------------------------------------------------------------------
258 257
259 258 launch_new_instance = NbConvertApp.launch_instance
@@ -1,135 +1,135
1 1 """
2 2 Contains tests for the nbconvertapp
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 #Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 #Distributed under the terms of the Modified BSD License.
8 8 #
9 9 #The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 import os
17 17 from .base import TestsBase
18 18
19 19 from IPython.utils import py3compat
20 20
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Constants
24 24 #-----------------------------------------------------------------------------
25 25
26 26 # Define ipython commandline name
27 27 if py3compat.PY3:
28 28 IPYTHON = 'ipython3'
29 29 else:
30 30 IPYTHON = 'ipython'
31 31
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Classes and functions
35 35 #-----------------------------------------------------------------------------
36 36
37 37 class TestNbConvertApp(TestsBase):
38 38 """Collection of NbConvertApp tests"""
39 39
40 40
41 41 def test_notebook_help(self):
42 42 """
43 43 Will help show if no notebooks are specified?
44 44 """
45 45 with self.create_temp_cwd():
46 46 assert "see '--help-all'" in self.call([IPYTHON, 'nbconvert'])
47 47
48 48
49 49 def test_glob(self):
50 50 """
51 51 Do search patterns work for notebook names?
52 52 """
53 53 with self.create_temp_cwd(['notebook*.ipynb']):
54 54 assert not 'error' in self.call([IPYTHON, 'nbconvert',
55 55 '--to="python"', '--notebooks=["*.ipynb"]']).lower()
56 56 assert os.path.isfile('notebook1.py')
57 57 assert os.path.isfile('notebook2.py')
58 58
59 59
60 60 def test_glob_subdir(self):
61 61 """
62 62 Do search patterns work for subdirectory notebook names?
63 63 """
64 64 with self.create_temp_cwd() as cwd:
65 65 self.copy_files_to(['notebook*.ipynb'], 'subdir/')
66 66 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
67 67 '--notebooks=["%s"]' % os.path.join('subdir', '*.ipynb')]).lower()
68 68 assert os.path.isfile('notebook1.py')
69 69 assert os.path.isfile('notebook2.py')
70 70
71 71
72 72 def test_explicit(self):
73 73 """
74 74 Do explicit notebook names work?
75 75 """
76 76 with self.create_temp_cwd(['notebook*.ipynb']):
77 77 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
78 78 '--notebooks=["notebook2.ipynb"]']).lower()
79 79 assert not os.path.isfile('notebook1.py')
80 80 assert os.path.isfile('notebook2.py')
81 81
82 82
83 def test_flavor(self):
83 def test_template(self):
84 84 """
85 Do export flavors work?
85 Do export templates work?
86 86 """
87 87 with self.create_temp_cwd(['notebook*.ipynb']):
88 88 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="slides"',
89 '--notebooks=["notebook2.ipynb"]', '--flavor="reveal"']).lower()
89 '--notebooks=["notebook2.ipynb"]', '--template="reveal"']).lower()
90 90 assert os.path.isfile('notebook2.html')
91 91 with open('notebook2.html') as f:
92 92 assert '/reveal.css' in f.read()
93 93
94 94
95 95 def test_glob_explicit(self):
96 96 """
97 97 Can a search pattern be used along with matching explicit notebook names?
98 98 """
99 99 with self.create_temp_cwd(['notebook*.ipynb']):
100 100 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
101 101 '--notebooks=["*.ipynb", "notebook1.ipynb", "notebook2.ipynb"]']).lower()
102 102 assert os.path.isfile('notebook1.py')
103 103 assert os.path.isfile('notebook2.py')
104 104
105 105
106 106 def test_explicit_glob(self):
107 107 """
108 108 Can explicit notebook names be used and then a matching search pattern?
109 109 """
110 110 with self.create_temp_cwd(['notebook*.ipynb']):
111 111 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
112 112 '--notebooks=["notebook1.ipynb", "notebook2.ipynb", "*.ipynb"]']).lower()
113 113 assert os.path.isfile('notebook1.py')
114 114 assert os.path.isfile('notebook2.py')
115 115
116 116
117 117 def test_default_config(self):
118 118 """
119 119 Does the default config work?
120 120 """
121 121 with self.create_temp_cwd(['notebook*.ipynb', 'ipython_nbconvert_config.py']):
122 122 assert not 'error' in self.call([IPYTHON, 'nbconvert']).lower()
123 123 assert os.path.isfile('notebook1.py')
124 124 assert not os.path.isfile('notebook2.py')
125 125
126 126
127 127 def test_override_config(self):
128 128 """
129 129 Can the default config be overriden?
130 130 """
131 131 with self.create_temp_cwd(['notebook*.ipynb', 'ipython_nbconvert_config.py',
132 132 'override.py']):
133 133 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--config="override.py"']).lower()
134 134 assert not os.path.isfile('notebook1.py')
135 135 assert os.path.isfile('notebook2.py')
@@ -1,42 +1,47
1 1 #!/usr/bin/env python
2 2 """
3 3 Contains writer for writing nbconvert output to PDF.
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 import subprocess
18 18 import os
19 19
20 from IPython.utils.traitlets import Integer
20 from IPython.utils.traitlets import Integer, Unicode
21 21
22 22 from .files import FilesWriter
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Classes
26 26 #-----------------------------------------------------------------------------
27 27 class PDFWriter(FilesWriter):
28 28 """Writer designed to write to PDF files"""
29 29
30 30 iteration_count = Integer(3, config=True, help="""
31 31 How many times pdflatex will be called.
32 32 """)
33 33
34 compiler = Unicode(u'pdflatex {0}', config=True, help="""
35 Shell command used to compile PDF.""")
36
34 37 def write(self, output, resources, notebook_name=None, **kw):
35 38 """
36 39 Consume and write Jinja output a PDF.
37 40 See files.py for more...
38 41 """
39 dest = super(PDFWriter, self).write(output, resources, notebook_name=notebook_name, **kw)
40 command = 'pdflatex ' + dest
42 dest = super(PDFWriter, self).write(output, resources,
43 notebook_name=notebook_name, **kw)
44 command = self.compiler.format(dest)
45
41 46 for index in range(self.iteration_count):
42 47 subprocess.Popen(command, shell=True, stdout=open(os.devnull, 'wb'))
General Comments 0
You need to be logged in to leave comments. Login now