##// END OF EJS Templates
stateless converter
Matthias BUSSONNIER -
Show More
@@ -1,227 +1,237 b''
1 """Base classes for the notebook conversion pipeline.
1 """Base classes for the notebook conversion pipeline.
2
2
3 This module defines Converter, from which all objects designed to implement
3 This module defines Converter, from which all objects designed to implement
4 a conversion of IPython notebooks to some other format should inherit.
4 a conversion of IPython notebooks to some other format should inherit.
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2012, the IPython Development Team.
7 # Copyright (c) 2012, the IPython Development Team.
8 #
8 #
9 # Distributed under the terms of the Modified BSD License.
9 # Distributed under the terms of the Modified BSD License.
10 #
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import print_function, absolute_import
18 from __future__ import print_function, absolute_import
19 import converters.transformers as trans
19 import converters.transformers as trans
20 from converters.jinja_filters import (python_comment, indent,
20 from converters.jinja_filters import (python_comment, indent,
21 rm_fake, remove_ansi, markdown, highlight,
21 rm_fake, remove_ansi, markdown, highlight,
22 ansi2html, markdown2latex, escape_tex)
22 ansi2html, markdown2latex, escape_tex)
23
23
24
24
25
25
26 # Stdlib imports
26 # Stdlib imports
27 import io
27 import io
28 import os
28 import os
29 from IPython.utils import path
29 from IPython.utils import path
30 from IPython.utils.traitlets import MetaHasTraits
30 from IPython.utils.traitlets import MetaHasTraits
31
31
32 from jinja2 import Environment, FileSystemLoader
32 from jinja2 import Environment, FileSystemLoader
33 env = Environment(
33 env = Environment(
34 loader=FileSystemLoader([
34 loader=FileSystemLoader([
35 './templates/',
35 './templates/',
36 './templates/skeleton/',
36 './templates/skeleton/',
37 ]),
37 ]),
38 extensions=['jinja2.ext.loopcontrols']
38 extensions=['jinja2.ext.loopcontrols']
39 )
39 )
40
40
41 texenv = Environment(
41 texenv = Environment(
42 loader=FileSystemLoader([
42 loader=FileSystemLoader([
43 './templates/tex/',
43 './templates/tex/',
44 './templates/skeleton/tex/',
44 './templates/skeleton/tex/',
45 ]),
45 ]),
46 extensions=['jinja2.ext.loopcontrols']
46 extensions=['jinja2.ext.loopcontrols']
47 )
47 )
48
48
49 # IPython imports
49 # IPython imports
50 from IPython.nbformat import current as nbformat
50 from IPython.nbformat import current as nbformat
51 from IPython.config.configurable import Configurable
51 from IPython.config.configurable import Configurable
52 from IPython.utils.traitlets import ( Unicode, Any, List, Bool)
52 from IPython.utils.traitlets import ( Unicode, Any, List, Bool)
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Class declarations
55 # Class declarations
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 class ConversionException(Exception):
57 class ConversionException(Exception):
58 pass
58 pass
59
59
60
60 #todo move this out
61 def header_body():
61 def header_body():
62 """Return the body of the header as a list of strings."""
62 """Return the body of the header as a list of strings."""
63
63
64 from pygments.formatters import HtmlFormatter
64 from pygments.formatters import HtmlFormatter
65
65
66 header = []
66 header = []
67 static = os.path.join(path.get_ipython_package_dir(),
67 static = os.path.join(path.get_ipython_package_dir(),
68 'frontend', 'html', 'notebook', 'static',
68 'frontend', 'html', 'notebook', 'static',
69 )
69 )
70 here = os.path.split(os.path.realpath(__file__))[0]
70 here = os.path.split(os.path.realpath(__file__))[0]
71 css = os.path.join(static, 'css')
71 css = os.path.join(static, 'css')
72 for sheet in [
72 for sheet in [
73 # do we need jquery and prettify?
73 # do we need jquery and prettify?
74 # os.path.join(static, 'jquery', 'css', 'themes', 'base',
74 # os.path.join(static, 'jquery', 'css', 'themes', 'base',
75 # 'jquery-ui.min.css'),
75 # 'jquery-ui.min.css'),
76 # os.path.join(static, 'prettify', 'prettify.css'),
76 # os.path.join(static, 'prettify', 'prettify.css'),
77 os.path.join(css, 'boilerplate.css'),
77 os.path.join(css, 'boilerplate.css'),
78 os.path.join(css, 'fbm.css'),
78 os.path.join(css, 'fbm.css'),
79 os.path.join(css, 'notebook.css'),
79 os.path.join(css, 'notebook.css'),
80 os.path.join(css, 'renderedhtml.css'),
80 os.path.join(css, 'renderedhtml.css'),
81 # our overrides:
81 # our overrides:
82 os.path.join(here, '..', 'css', 'static_html.css'),
82 os.path.join(here, '..', 'css', 'static_html.css'),
83 ]:
83 ]:
84
84
85 with io.open(sheet, encoding='utf-8') as f:
85 with io.open(sheet, encoding='utf-8') as f:
86 s = f.read()
86 s = f.read()
87 header.append(s)
87 header.append(s)
88
88
89 pygments_css = HtmlFormatter().get_style_defs('.highlight')
89 pygments_css = HtmlFormatter().get_style_defs('.highlight')
90 header.append(pygments_css)
90 header.append(pygments_css)
91 return header
91 return header
92
92
93
93
94
94
95
95
96
96
97 inlining = {}
97 inlining = {}
98 inlining['css'] = header_body()
98 inlining['css'] = header_body()
99
99
100
100
101
101
102 texenv.block_start_string = '((*'
102 texenv.block_start_string = '((*'
103 texenv.block_end_string = '*))'
103 texenv.block_end_string = '*))'
104 texenv.variable_start_string = '((('
104 texenv.variable_start_string = '((('
105 texenv.variable_end_string = ')))'
105 texenv.variable_end_string = ')))'
106 texenv.comment_start_string = '((='
106 texenv.comment_start_string = '((='
107 texenv.comment_end_string = '=))'
107 texenv.comment_end_string = '=))'
108 texenv.filters['escape_tex'] = escape_tex
108 texenv.filters['escape_tex'] = escape_tex
109
109
110
110
111 class ConverterTemplate(Configurable):
111 class ConverterTemplate(Configurable):
112 """ A Jinja2 base converter templates"""
112 """ A Jinja2 base converter templates
113
114 Preprocess the ipynb files, feed it throug jinja templates,
115 and spit an converted files and a data object with other data
116
117 shoudl be mostly configurable
118 """
113
119
114 display_data_priority = List(['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'],
120 display_data_priority = List(['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'],
115 config=True,
121 config=True,
116 help= """
122 help= """
117 An ordered list of prefered output type, the first
123 An ordered list of prefered output type, the first
118 encounterd will usually be used when converting discarding
124 encounterd will usually be used when converting discarding
119 the others.
125 the others.
120 """
126 """
121 )
127 )
122
128
123 pre_transformer_order = List(['haspyout_transformer', 'Foobar'],
129 pre_transformer_order = List(['haspyout_transformer'],
124 config=True,
130 config=True,
125 help= """
131 help= """
126 An ordered list of pre transformer to apply to the ipynb
132 An ordered list of pre transformer to apply to the ipynb
127 file befor running through templates
133 file befor running through templates
128 """
134 """
129 )
135 )
130
136
131 extract_figures = Bool(False,
137 extract_figures = Bool(False,
132 config=True,
138 config=True,
133 help= """
139 help= """
134 wether to remove figure data from ipynb and store them in auxiliary
140 wether to remove figure data from ipynb and store them in auxiliary
135 dictionnary
141 dictionnary
136 """
142 """
137 )
143 )
138
144
139 tex_environement = Bool(False,
145 tex_environement = Bool(False,
140 config=True,
146 config=True,
141 help=""" is this a tex environment or not """)
147 help=""" is this a tex environment or not """)
142
148
143 template_file = Unicode('',
149 template_file = Unicode('',
144 config=True,
150 config=True,
145 help=""" whetever """ )
151 help=""" Name of the template file to use """ )
146 #-------------------------------------------------------------------------
152 #-------------------------------------------------------------------------
147 # Instance-level attributes that are set in the constructor for this
153 # Instance-level attributes that are set in the constructor for this
148 # class.
154 # class.
149 #-------------------------------------------------------------------------
155 #-------------------------------------------------------------------------
150 infile = Any()
156 infile = Any()
151
157
152
158
153 infile_dir = Unicode()
159 infile_dir = Unicode()
154
160
161 #todo: move to filter
155 def filter_data_type(self, output):
162 def filter_data_type(self, output):
163 """ return the first availlable format in priority """
156 for fmt in self.display_data_priority:
164 for fmt in self.display_data_priority:
157 if fmt in output:
165 if fmt in output:
158 return [fmt]
166 return [fmt]
159
167
160 def __init__(self, preprocessors=[], config=None, **kw):
168 preprocessors = []
169
170 def __init__(self, preprocessors={}, jinja_filters={}, config=None, **kw):
161 """
171 """
162 config: the Configurable confg object to pass around
172 config: the Configurable confg object to pass around
163
173
164 preprocessors: list of function to run on ipynb json data before conversion
174 preprocessors: dict of **availlable** key/value function to run on ipynb json data before conversion
165 to extract/inline file,
175 to extract/inline file,
166
176
167 """
177 """
168 super(ConverterTemplate, self).__init__(config=config, **kw)
178 super(ConverterTemplate, self).__init__(config=config, **kw)
169 self.env = texenv if self.tex_environement else env
179 self.env = texenv if self.tex_environement else env
170 self.ext = '.tplx' if self.tex_environement else '.tpl'
180 self.ext = '.tplx' if self.tex_environement else '.tpl'
171 self.nb = None
172 self.preprocessors = preprocessors
173
181
174 for name in self.pre_transformer_order:
182 for name in self.pre_transformer_order:
175 tr = getattr(trans, name)
183 transformer = getattr(preprocessors, name, getattr(trans, name, None))
176 if isinstance(tr, MetaHasTraits):
184 if isinstance(transformer, MetaHasTraits):
177 tr = tr(config=config)
185 transformer = transformer(config=config)
178 self.preprocessors.append(tr)
186 self.preprocessors.append(transformer)
187
188 ## for compat, remove later
179 if self.extract_figures:
189 if self.extract_figures:
180 self.preprocessors.append(trans.ExtractFigureTransformer(config=config))
190 self.preprocessors.append(trans.ExtractFigureTransformer(config=config))
181
191
192 ##
182 self.env.filters['filter_data_type'] = self.filter_data_type
193 self.env.filters['filter_data_type'] = self.filter_data_type
183 self.env.filters['pycomment'] = python_comment
194 self.env.filters['pycomment'] = python_comment
184 self.env.filters['indent'] = indent
195 self.env.filters['indent'] = indent
185 self.env.filters['rm_fake'] = rm_fake
196 self.env.filters['rm_fake'] = rm_fake
186 self.env.filters['rm_ansi'] = remove_ansi
197 self.env.filters['rm_ansi'] = remove_ansi
187 self.env.filters['markdown'] = markdown
198 self.env.filters['markdown'] = markdown
188 self.env.filters['highlight'] = highlight
199 self.env.filters['highlight'] = highlight
189 self.env.filters['ansi2html'] = ansi2html
200 self.env.filters['ansi2html'] = ansi2html
190 self.env.filters['markdown2latex'] = markdown2latex
201 self.env.filters['markdown2latex'] = markdown2latex
202 for k, v in jinja_filters.iteritems():
203 self.env.filters[k] = v
191
204
192 self.template = self.env.get_template(self.template_file+self.ext)
205 self.template = self.env.get_template(self.template_file+self.ext)
193
206
194
207
195 def process(self):
208 def process(self, nb):
196 """
209 """
197 preprocess the notebook json for easier use with the templates.
210 preprocess the notebook json for easier use with the templates.
198 will call all the `preprocessor`s in order before returning it.
211 will call all the `preprocessor`s in order before returning it.
199 """
212 """
200 nb = self.nb
201
213
202 # dict of 'resources' that could be made by the preprocessors
214 # dict of 'resources' that could be made by the preprocessors
203 # like key/value data to extract files from ipynb like in latex conversion
215 # like key/value data to extract files from ipynb like in latex conversion
204 resources = {}
216 resources = {}
205
217
206 for preprocessor in self.preprocessors:
218 for preprocessor in self.preprocessors:
207 nb, resources = preprocessor(nb, resources)
219 nb, resources = preprocessor(nb, resources)
208
220
209 return nb, resources
221 return nb, resources
210
222
211 def convert(self):
223 def convert(self, nb):
212 """ convert the ipynb file
224 """ convert the ipynb file
213
225
214 return both the converted ipynb file and a dict containing potential
226 return both the converted ipynb file and a dict containing potential
215 other resources
227 other resources
216 """
228 """
217 nb, resources = self.process()
229 nb, resources = self.process(nb)
218 return self.template.render(nb=nb, inlining=inlining), resources
230 return self.template.render(nb=nb, inlining=inlining), resources
219
231
220
232
221 def read(self, filename):
233 def from_filename(self, filename):
222 "read and parse notebook into NotebookNode called self.nb"
234 "read and parse notebook into NotebookNode called self.nb"
223 with io.open(filename) as f:
235 with io.open(filename) as f:
224 self.nb = nbformat.read(f, 'json')
236 return self.convert(nbformat.read(f, 'json'))
225
226
227
237
@@ -1,137 +1,136 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 ================================================================================
3 ================================================================================
4
4
5 |,---. | | , .| |
5 |,---. | | , .| |
6 ||---', .|--- |---.,---.,---. |\ ||---.,---.,---.,---.. ,,---.,---.|---
6 ||---', .|--- |---.,---.,---. |\ ||---.,---.,---.,---.. ,,---.,---.|---
7 || | || | || || | | \ || || | || | \ / |---'| |
7 || | || | || || | | \ || || | || | \ / |---'| |
8 `` `---|`---'` '`---'` ' ` `'`---'`---'`---'` ' `' `---'` `---'
8 `` `---|`---'` '`---'` ' ` `'`---'`---'`---'` ' `' `---'` `---'
9 `---'
9 `---'
10 ================================================================================
10 ================================================================================
11
11
12 Highly experimental for now
12 Highly experimental for now
13
13
14 """
14 """
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 from __future__ import print_function
18 from __future__ import print_function
19 import sys
19 import sys
20 import io
20 import io
21 import os
21 import os
22
22
23 from converters.template import *
23 from converters.template import *
24 from converters.template import ConverterTemplate
24 from converters.template import ConverterTemplate
25 from converters.html import ConverterHTML
25 from converters.html import ConverterHTML
26 # From IPython
26 # From IPython
27
27
28 # All the stuff needed for the configurable things
28 # All the stuff needed for the configurable things
29 from IPython.config.application import Application
29 from IPython.config.application import Application
30 from IPython.config.loader import ConfigFileNotFound
30 from IPython.config.loader import ConfigFileNotFound
31 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, CaselessStrEnum
31 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, CaselessStrEnum
32
32
33 from converters.transformers import (ConfigurableTransformers,Foobar,ExtractFigureTransformer)
33 from converters.transformers import (ConfigurableTransformers,Foobar,ExtractFigureTransformer)
34
34
35
35
36 class NbconvertApp(Application):
36 class NbconvertApp(Application):
37
37
38 stdout = Bool(True, config=True)
38 stdout = Bool(True, config=True)
39 write = Bool(False, config=True)
39 write = Bool(False, config=True)
40
40
41 fileext = Unicode('txt', config=True)
41 fileext = Unicode('txt', config=True)
42
42
43 aliases = {
43 aliases = {
44 'stdout':'NbconvertApp.stdout',
44 'stdout':'NbconvertApp.stdout',
45 'write':'NbconvertApp.write',
45 'write':'NbconvertApp.write',
46 }
46 }
47
47
48 flags= {}
48 flags= {}
49 flags['no-stdout'] = (
49 flags['no-stdout'] = (
50 {'NbconvertApp' : {'stdout' : False}},
50 {'NbconvertApp' : {'stdout' : False}},
51 """the doc for this flag
51 """the doc for this flag
52
52
53 """
53 """
54 )
54 )
55
55
56 def __init__(self, **kwargs):
56 def __init__(self, **kwargs):
57 super(NbconvertApp, self).__init__(**kwargs)
57 super(NbconvertApp, self).__init__(**kwargs)
58 self.classes.insert(0,ConverterTemplate)
58 self.classes.insert(0,ConverterTemplate)
59 # register class here to have help with help all
59 # register class here to have help with help all
60 self.classes.insert(0,ExtractFigureTransformer)
60 self.classes.insert(0,ExtractFigureTransformer)
61 self.classes.insert(0,Foobar)
61 self.classes.insert(0,Foobar)
62 # ensure those are registerd
62 # ensure those are registerd
63
63
64 def load_config_file(self, profile_name):
64 def load_config_file(self, profile_name):
65 try:
65 try:
66 Application.load_config_file(
66 Application.load_config_file(
67 self,
67 self,
68 profile_name+'.nbcv',
68 profile_name+'.nbcv',
69 path=[os.path.join(os.getcwdu(),'profile')]
69 path=[os.path.join(os.getcwdu(),'profile')]
70 )
70 )
71 except ConfigFileNotFound:
71 except ConfigFileNotFound:
72 self.log.warn("Config file for profile '%s' not found, giving up ",profile_name)
72 self.log.warn("Config file for profile '%s' not found, giving up ",profile_name)
73 exit(1)
73 exit(1)
74
74
75
75
76 def initialize(self, argv=None):
76 def initialize(self, argv=None):
77 self.parse_command_line(argv)
77 self.parse_command_line(argv)
78 cl_config = self.config
78 cl_config = self.config
79 profile_file = sys.argv[1]
79 profile_file = sys.argv[1]
80 self.load_config_file(profile_file)
80 self.load_config_file(profile_file)
81 self.update_config(cl_config)
81 self.update_config(cl_config)
82
82
83
83
84
84
85 def run(self):
85 def run(self):
86 """Convert a notebook to html in one step"""
86 """Convert a notebook to html in one step"""
87 template_file = (self.extra_args or [None])[0]
87 template_file = (self.extra_args or [None])[0]
88 ipynb_file = (self.extra_args or [None])[1]
88 ipynb_file = (self.extra_args or [None])[1]
89
89
90 template_file = sys.argv[1]
90 template_file = sys.argv[1]
91
91
92 C = ConverterTemplate(config=self.config)
92 C = ConverterTemplate(config=self.config)
93 C.read(ipynb_file)
94
93
95 output,resources = C.convert()
94 output,resources = C.from_filename(ipynb_file)
96 if self.stdout :
95 if self.stdout :
97 print(output.encode('utf-8'))
96 print(output.encode('utf-8'))
98
97
99 out_root = ipynb_file[:-6].replace('.','_').replace(' ','_')
98 out_root = ipynb_file[:-6].replace('.','_').replace(' ','_')
100
99
101 keys = resources.get('figures',{}).keys()
100 keys = resources.get('figures',{}).keys()
102 if self.write :
101 if self.write :
103 with io.open(os.path.join(out_root+'.'+self.fileext),'w') as f:
102 with io.open(os.path.join(out_root+'.'+self.fileext),'w') as f:
104 f.write(output)
103 f.write(output)
105 if keys :
104 if keys :
106 if self.write and not os.path.exists(out_root+'_files'):
105 if self.write and not os.path.exists(out_root+'_files'):
107 os.mkdir(out_root+'_files')
106 os.mkdir(out_root+'_files')
108 for key in keys:
107 for key in keys:
109 if self.write:
108 if self.write:
110 with io.open(os.path.join(out_root+'_files',key),'wb') as f:
109 with io.open(os.path.join(out_root+'_files',key),'wb') as f:
111 print(' writing to ',os.path.join(out_root,key))
110 print(' writing to ',os.path.join(out_root,key))
112 f.write(resources['figures'][key])
111 f.write(resources['figures'][key])
113 if self.stdout:
112 if self.stdout:
114 print('''
113 print('''
115 ====================== Keys in Resources ==================================
114 ====================== Keys in Resources ==================================
116 ''')
115 ''')
117 print(resources['figures'].keys())
116 print(resources['figures'].keys())
118 print("""
117 print("""
119 ===========================================================================
118 ===========================================================================
120 you are responsible from writing those data do a file in the right place if
119 you are responsible from writing those data do a file in the right place if
121 they need to be.
120 they need to be.
122 ===========================================================================
121 ===========================================================================
123 """)
122 """)
124
123
125 def main():
124 def main():
126 """Convert a notebook to html in one step"""
125 """Convert a notebook to html in one step"""
127 app = NbconvertApp.instance()
126 app = NbconvertApp.instance()
128 app.description = __doc__
127 app.description = __doc__
129 app.initialize()
128 app.initialize()
130 app.start()
129 app.start()
131 app.run()
130 app.run()
132 #-----------------------------------------------------------------------------
131 #-----------------------------------------------------------------------------
133 # Script main
132 # Script main
134 #-----------------------------------------------------------------------------
133 #-----------------------------------------------------------------------------
135
134
136 if __name__ == '__main__':
135 if __name__ == '__main__':
137 main()
136 main()
@@ -1,30 +1,29 b''
1 import io
1 import io
2 import nose.tools as nt
2 import nose.tools as nt
3 import os
3 import os
4 from nose.tools import nottest
4 from nose.tools import nottest
5 from converters.template import ConverterTemplate
5 from converters.template import ConverterTemplate
6 from IPython.config.loader import PyFileConfigLoader
6 from IPython.config.loader import PyFileConfigLoader
7
7
8
8
9
9
10
10
11 def test_evens():
11 def test_evens():
12 reflist = [
12 reflist = [
13 'tests/ipynbref/IntroNumPy.orig'
13 'tests/ipynbref/IntroNumPy.orig'
14 ]
14 ]
15
15
16 test_profiles = [prof for prof in os.listdir('profile/test/') if prof.endswith('.py')]
16 test_profiles = [prof for prof in os.listdir('profile/test/') if prof.endswith('.py')]
17
17
18 ### null template should return empty
18 ### null template should return empty
19 for prof in test_profiles :
19 for prof in test_profiles :
20 yield check_null_profile,prof
20 yield check_null_profile,prof
21 ### end null test
21 ### end null test
22
22
23 def check_null_profile(profile):
23 def check_null_profile(profile):
24 loader = PyFileConfigLoader(profile, path=[os.path.join(os.getcwdu(),'profile/test')])
24 loader = PyFileConfigLoader(profile, path=[os.path.join(os.getcwdu(),'profile/test')])
25 config = loader.load_config()
25 config = loader.load_config()
26 C = ConverterTemplate(config=config)
26 C = ConverterTemplate(config=config)
27 C.read('tests/ipynbref/IntroNumPy.orig.ipynb')
27 result,_ = C.from_filename('tests/ipynbref/IntroNumPy.orig.ipynb')
28 result,_ = C.convert()
29 nt.assert_equal(result,'')
28 nt.assert_equal(result,'')
30
29
General Comments 0
You need to be logged in to leave comments. Login now