##// END OF EJS Templates
remove heading cells in v4
MinRK -
Show More
@@ -11,7 +11,7 b' import requests'
11 from IPython.html.utils import url_path_join
11 from IPython.html.utils import url_path_join
12 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
12 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
13 from IPython.nbformat.current import (new_notebook, write,
13 from IPython.nbformat.current import (new_notebook, write,
14 new_heading_cell, new_code_cell,
14 new_markdown_cell, new_code_cell,
15 new_output)
15 new_output)
16
16
17 from IPython.testing.decorators import onlyif_cmds_exist
17 from IPython.testing.decorators import onlyif_cmds_exist
@@ -55,7 +55,7 b' class APITest(NotebookTestBase):'
55
55
56 nb = new_notebook()
56 nb = new_notebook()
57
57
58 nb.cells.append(new_heading_cell(u'Created by test Β³'))
58 nb.cells.append(new_markdown_cell(u'Created by test Β³'))
59 cc1 = new_code_cell(source=u'print(2*6)')
59 cc1 = new_code_cell(source=u'print(2*6)')
60 cc1.outputs.append(new_output(output_type="stream", text=u'12'))
60 cc1.outputs.append(new_output(output_type="stream", text=u'12'))
61 cc1.outputs.append(new_output(output_type="execute_result",
61 cc1.outputs.append(new_output(output_type="execute_result",
@@ -255,7 +255,7 b' class FileContentsManager(ContentsManager):'
255 try:
255 try:
256 nb = current.read(f, u'json')
256 nb = current.read(f, u'json')
257 except Exception as e:
257 except Exception as e:
258 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
258 raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e))
259 self.mark_trusted_cells(nb, name, path)
259 self.mark_trusted_cells(nb, name, path)
260 model['content'] = nb
260 model['content'] = nb
261 model['format'] = 'json'
261 model['format'] = 'json'
@@ -16,7 +16,7 b' from IPython.html.utils import url_path_join, url_escape'
16 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
16 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
17 from IPython.nbformat import current
17 from IPython.nbformat import current
18 from IPython.nbformat.current import (new_notebook, write, read,
18 from IPython.nbformat.current import (new_notebook, write, read,
19 new_heading_cell, to_notebook_json)
19 new_markdown_cell, to_notebook_json)
20 from IPython.nbformat import v2
20 from IPython.nbformat import v2
21 from IPython.utils import py3compat
21 from IPython.utils import py3compat
22 from IPython.utils.data import uniq_stable
22 from IPython.utils.data import uniq_stable
@@ -415,7 +415,7 b' class APITest(NotebookTestBase):'
415 resp = self.api.read('a.ipynb', 'foo')
415 resp = self.api.read('a.ipynb', 'foo')
416 nbcontent = json.loads(resp.text)['content']
416 nbcontent = json.loads(resp.text)['content']
417 nb = to_notebook_json(nbcontent)
417 nb = to_notebook_json(nbcontent)
418 nb.cells.append(new_heading_cell(u'Created by test Β³'))
418 nb.cells.append(new_markdown_cell(u'Created by test Β³'))
419
419
420 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
420 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
421 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
421 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
@@ -452,7 +452,7 b' class APITest(NotebookTestBase):'
452 # Modify it
452 # Modify it
453 nbcontent = json.loads(resp.text)['content']
453 nbcontent = json.loads(resp.text)['content']
454 nb = to_notebook_json(nbcontent)
454 nb = to_notebook_json(nbcontent)
455 hcell = new_heading_cell('Created by test')
455 hcell = new_markdown_cell('Created by test')
456 nb.cells.append(hcell)
456 nb.cells.append(hcell)
457 # Save
457 # Save
458 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
458 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
@@ -139,12 +139,6 b' define(['
139 .append($('<option/>').attr('value','code').text('Code'))
139 .append($('<option/>').attr('value','code').text('Code'))
140 .append($('<option/>').attr('value','markdown').text('Markdown'))
140 .append($('<option/>').attr('value','markdown').text('Markdown'))
141 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
141 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
142 .append($('<option/>').attr('value','heading1').text('Heading 1'))
143 .append($('<option/>').attr('value','heading2').text('Heading 2'))
144 .append($('<option/>').attr('value','heading3').text('Heading 3'))
145 .append($('<option/>').attr('value','heading4').text('Heading 4'))
146 .append($('<option/>').attr('value','heading5').text('Heading 5'))
147 .append($('<option/>').attr('value','heading6').text('Heading 6'))
148 );
142 );
149 };
143 };
150
144
@@ -190,24 +184,18 b' define(['
190
184
191 this.element.find('#cell_type').change(function () {
185 this.element.find('#cell_type').change(function () {
192 var cell_type = $(this).val();
186 var cell_type = $(this).val();
193 if (cell_type === 'code') {
187 switch (cell_type) {
188 case 'code':
194 that.notebook.to_code();
189 that.notebook.to_code();
195 } else if (cell_type === 'markdown') {
190 break;
191 case 'markdown':
196 that.notebook.to_markdown();
192 that.notebook.to_markdown();
197 } else if (cell_type === 'raw') {
193 break;
194 case 'raw':
198 that.notebook.to_raw();
195 that.notebook.to_raw();
199 } else if (cell_type === 'heading1') {
196 break;
200 that.notebook.to_heading(undefined, 1);
197 default:
201 } else if (cell_type === 'heading2') {
198 console.log("unrecognized cell type:", cell_type);
202 that.notebook.to_heading(undefined, 2);
203 } else if (cell_type === 'heading3') {
204 that.notebook.to_heading(undefined, 3);
205 } else if (cell_type === 'heading4') {
206 that.notebook.to_heading(undefined, 4);
207 } else if (cell_type === 'heading5') {
208 that.notebook.to_heading(undefined, 5);
209 } else if (cell_type === 'heading6') {
210 that.notebook.to_heading(undefined, 6);
211 }
199 }
212 });
200 });
213 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
201 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
@@ -824,7 +824,7 b' define(['
824 * Index will be brought back into the accessible range [0,n]
824 * Index will be brought back into the accessible range [0,n]
825 *
825 *
826 * @method insert_cell_at_index
826 * @method insert_cell_at_index
827 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
827 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
828 * @param [index] {int} a valid index where to insert cell
828 * @param [index] {int} a valid index where to insert cell
829 *
829 *
830 * @return cell {cell|null} created cell or null
830 * @return cell {cell|null} created cell or null
@@ -860,15 +860,19 b' define(['
860 notebook: this,
860 notebook: this,
861 tooltip: this.tooltip,
861 tooltip: this.tooltip,
862 };
862 };
863 if (type === 'code') {
863 switch(type) {
864 case 'code':
864 cell = new codecell.CodeCell(this.kernel, cell_options);
865 cell = new codecell.CodeCell(this.kernel, cell_options);
865 cell.set_input_prompt();
866 cell.set_input_prompt();
866 } else if (type === 'markdown') {
867 break;
868 case 'markdown':
867 cell = new textcell.MarkdownCell(cell_options);
869 cell = new textcell.MarkdownCell(cell_options);
868 } else if (type === 'raw') {
870 break;
871 case 'raw':
869 cell = new textcell.RawCell(cell_options);
872 cell = new textcell.RawCell(cell_options);
870 } else if (type === 'heading') {
873 break;
871 cell = new textcell.HeadingCell(cell_options);
874 default:
875 console.log("invalid cell type: ", type);
872 }
876 }
873
877
874 if(this._insert_element_at_index(cell.element,index)) {
878 if(this._insert_element_at_index(cell.element,index)) {
@@ -1090,10 +1094,10 b' define(['
1090 if (this.is_valid_cell_index(i)) {
1094 if (this.is_valid_cell_index(i)) {
1091 var source_cell = this.get_cell(i);
1095 var source_cell = this.get_cell(i);
1092 var target_cell = null;
1096 var target_cell = null;
1093 if (source_cell instanceof textcell.HeadingCell) {
1097 if (source_cell instanceof textcell.MarkdownCell) {
1094 source_cell.set_level(level);
1098 source_cell.set_heading_level(level);
1095 } else {
1099 } else {
1096 target_cell = this.insert_cell_below('heading',i);
1100 target_cell = this.insert_cell_below('markdown',i);
1097 var text = source_cell.get_text();
1101 var text = source_cell.get_text();
1098 if (text === source_cell.placeholder) {
1102 if (text === source_cell.placeholder) {
1099 text = '';
1103 text = '';
@@ -1101,9 +1105,9 b' define(['
1101 //metadata
1105 //metadata
1102 target_cell.metadata = source_cell.metadata;
1106 target_cell.metadata = source_cell.metadata;
1103 // We must show the editor before setting its contents
1107 // We must show the editor before setting its contents
1104 target_cell.set_level(level);
1105 target_cell.unrender();
1108 target_cell.unrender();
1106 target_cell.set_text(text);
1109 target_cell.set_text(text);
1110 target_cell.set_heading_level(level);
1107 // make this value the starting point, so that we can only undo
1111 // make this value the starting point, so that we can only undo
1108 // to this state, instead of a blank cell
1112 // to this state, instead of a blank cell
1109 target_cell.code_mirror.clearHistory();
1113 target_cell.code_mirror.clearHistory();
@@ -1117,7 +1121,7 b' define(['
1117 }
1121 }
1118 this.set_dirty(true);
1122 this.set_dirty(true);
1119 this.events.trigger('selected_cell_type_changed.Notebook',
1123 this.events.trigger('selected_cell_type_changed.Notebook',
1120 {'cell_type':'heading',level:level}
1124 {'cell_type':'markdown',level:level}
1121 );
1125 );
1122 }
1126 }
1123 };
1127 };
@@ -1526,8 +1530,8 b' define(['
1526 }
1530 }
1527 this.codemirror_mode = newmode;
1531 this.codemirror_mode = newmode;
1528 codecell.CodeCell.options_default.cm_config.mode = newmode;
1532 codecell.CodeCell.options_default.cm_config.mode = newmode;
1529 modename = newmode.mode || newmode.name || newmode
1533 modename = newmode.mode || newmode.name || newmode;
1530
1534
1531 that = this;
1535 that = this;
1532 utils.requireCodeMirrorMode(modename, function () {
1536 utils.requireCodeMirrorMode(modename, function () {
1533 $.map(that.get_cells(), function(cell, i) {
1537 $.map(that.get_cells(), function(cell, i) {
@@ -1539,7 +1543,7 b' define(['
1539 cell.cm_config.mode = newmode;
1543 cell.cm_config.mode = newmode;
1540 }
1544 }
1541 });
1545 });
1542 })
1546 });
1543 };
1547 };
1544
1548
1545 // Session related things
1549 // Session related things
@@ -2383,13 +2387,13 b' define(['
2383 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2387 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2384 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2388 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2385 utils.log_ajax_error(xhr, status, error);
2389 utils.log_ajax_error(xhr, status, error);
2386 var msg;
2390 var msg = $("<div>");
2387 if (xhr.status === 400) {
2391 if (xhr.status === 400) {
2388 msg = escape(utils.ajax_error_msg(xhr));
2392 msg.text(utils.ajax_error_msg(xhr));
2389 } else if (xhr.status === 500) {
2393 } else if (xhr.status === 500) {
2390 msg = "An unknown error occurred while loading this notebook. " +
2394 msg.text("An unknown error occurred while loading this notebook. " +
2391 "This version can load notebook formats " +
2395 "This version can load notebook formats " +
2392 "v" + this.nbformat + " or earlier. See the server log for details.";
2396 "v" + this.nbformat + " or earlier. See the server log for details.");
2393 }
2397 }
2394 dialog.modal({
2398 dialog.modal({
2395 notebook: this,
2399 notebook: this,
@@ -222,6 +222,26 b' define(['
222
222
223 MarkdownCell.prototype = Object.create(TextCell.prototype);
223 MarkdownCell.prototype = Object.create(TextCell.prototype);
224
224
225 MarkdownCell.prototype.set_heading_level = function (level) {
226 // make a markdown cell a heading
227 level = level || 1;
228 var source = this.get_text();
229 // \s\S appears to be the js version of multi-line dot-all
230 var match = source.match(/(#*)\s*([\s\S]*)/);
231 // strip the leading `#` if it's already there
232 if (match) {
233 source = match[2];
234 }
235 // add `#` markdown heading prefix
236 var new_text = new Array(level + 1).join('#') + ' ' + source;
237
238 this.set_text(new_text);
239 this.refresh();
240 if (this.rendered) {
241 this.render();
242 }
243 };
244
225 /**
245 /**
226 * @method render
246 * @method render
227 */
247 */
@@ -238,6 +258,19 b' define(['
238 html = mathjaxutils.replace_math(html, math);
258 html = mathjaxutils.replace_math(html, math);
239 html = security.sanitize_html(html);
259 html = security.sanitize_html(html);
240 html = $($.parseHTML(html));
260 html = $($.parseHTML(html));
261 // add anchors to headings
262 // console.log(html);
263 html.find(":header").addBack(":header").each(function (i, h) {
264 h = $(h);
265 var hash = h.text().replace(/ /g, '-');
266 h.attr('id', hash);
267 h.append(
268 $('<a/>')
269 .addClass('anchor-link')
270 .attr('href', '#' + hash)
271 .text('ΒΆ')
272 );
273 })
241 // links in markdown cells should open in new tabs
274 // links in markdown cells should open in new tabs
242 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
275 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
243 this.set_rendered(html);
276 this.set_rendered(html);
@@ -305,121 +338,15 b' define(['
305 return cont;
338 return cont;
306 };
339 };
307
340
308
309 var HeadingCell = function (options) {
310 // Constructor
311 //
312 // Parameters:
313 // options: dictionary
314 // Dictionary of keyword arguments.
315 // events: $(Events) instance
316 // config: dictionary
317 // keyboard_manager: KeyboardManager instance
318 // notebook: Notebook instance
319 options = options || {};
320 var config = utils.mergeopt(HeadingCell, options.config);
321 TextCell.apply(this, [$.extend({}, options, {config: config})]);
322
323 this.level = 1;
324 this.cell_type = 'heading';
325 };
326
327 HeadingCell.options_default = {
328 cm_config: {
329 theme: 'heading-1'
330 },
331 placeholder: "Type Heading Here"
332 };
333
334 HeadingCell.prototype = Object.create(TextCell.prototype);
335
336 /** @method fromJSON */
337 HeadingCell.prototype.fromJSON = function (data) {
338 if (data.level !== undefined){
339 this.level = data.level;
340 }
341 TextCell.prototype.fromJSON.apply(this, arguments);
342 this.code_mirror.setOption("theme", "heading-"+this.level);
343 };
344
345
346 /** @method toJSON */
347 HeadingCell.prototype.toJSON = function () {
348 var data = TextCell.prototype.toJSON.apply(this);
349 data.level = this.get_level();
350 return data;
351 };
352
353 /**
354 * Change heading level of cell, and re-render
355 * @method set_level
356 */
357 HeadingCell.prototype.set_level = function (level) {
358 this.level = level;
359 this.code_mirror.setOption("theme", "heading-"+level);
360
361 if (this.rendered) {
362 this.rendered = false;
363 this.render();
364 }
365 };
366
367 /** The depth of header cell, based on html (h1 to h6)
368 * @method get_level
369 * @return {integer} level - for 1 to 6
370 */
371 HeadingCell.prototype.get_level = function () {
372 return this.level;
373 };
374
375
376 HeadingCell.prototype.get_rendered = function () {
377 var r = this.element.find("div.text_cell_render");
378 return r.children().first().html();
379 };
380
381 HeadingCell.prototype.render = function () {
382 var cont = TextCell.prototype.render.apply(this);
383 if (cont) {
384 var text = this.get_text();
385 var math = null;
386 // Markdown headings must be a single line
387 text = text.replace(/\n/g, ' ');
388 if (text === "") { text = this.placeholder; }
389 text = new Array(this.level + 1).join("#") + " " + text;
390 var text_and_math = mathjaxutils.remove_math(text);
391 text = text_and_math[0];
392 math = text_and_math[1];
393 var html = marked.parser(marked.lexer(text));
394 html = mathjaxutils.replace_math(html, math);
395 html = security.sanitize_html(html);
396 var h = $($.parseHTML(html));
397 // add id and linkback anchor
398 var hash = h.text().trim().replace(/ /g, '-');
399 h.attr('id', hash);
400 h.append(
401 $('<a/>')
402 .addClass('anchor-link')
403 .attr('href', '#' + hash)
404 .text('ΒΆ')
405 );
406 this.set_rendered(h);
407 this.typeset();
408 }
409 return cont;
410 };
411
412 // Backwards compatability.
341 // Backwards compatability.
413 IPython.TextCell = TextCell;
342 IPython.TextCell = TextCell;
414 IPython.MarkdownCell = MarkdownCell;
343 IPython.MarkdownCell = MarkdownCell;
415 IPython.RawCell = RawCell;
344 IPython.RawCell = RawCell;
416 IPython.HeadingCell = HeadingCell;
417
345
418 var textcell = {
346 var textcell = {
419 'TextCell': TextCell,
347 TextCell: TextCell,
420 'MarkdownCell': MarkdownCell,
348 MarkdownCell: MarkdownCell,
421 'RawCell': RawCell,
349 RawCell: RawCell,
422 'HeadingCell': HeadingCell,
423 };
350 };
424 return textcell;
351 return textcell;
425 });
352 });
@@ -189,12 +189,6 b' class="notebook_app"'
189 <li id="to_raw"
189 <li id="to_raw"
190 title="Contents will pass through nbconvert unmodified">
190 title="Contents will pass through nbconvert unmodified">
191 <a href="#">Raw NBConvert</a></li>
191 <a href="#">Raw NBConvert</a></li>
192 <li id="to_heading1"><a href="#">Heading 1</a></li>
193 <li id="to_heading2"><a href="#">Heading 2</a></li>
194 <li id="to_heading3"><a href="#">Heading 3</a></li>
195 <li id="to_heading4"><a href="#">Heading 4</a></li>
196 <li id="to_heading5"><a href="#">Heading 5</a></li>
197 <li id="to_heading6"><a href="#">Heading 6</a></li>
198 </ul>
192 </ul>
199 </li>
193 </li>
200 <li class="divider"></li>
194 <li class="divider"></li>
@@ -52,12 +52,12 b' casper.notebook_test(function () {'
52
52
53 this.then(function () {
53 this.then(function () {
54 this.select_cell(2);
54 this.select_cell(2);
55 this.trigger_keydown('1'); // switch it to heading for the next test
55 this.trigger_keydown('y'); // switch it to code for the next test
56 this.test.assertEquals(this.get_cell(2).cell_type, 'heading', 'test cell is heading');
56 this.test.assertEquals(this.get_cell(2).cell_type, 'code', 'test cell is code');
57 this.trigger_keydown('b'); // new cell below
57 this.trigger_keydown('b'); // new cell below
58 this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'b; inserts a heading cell below heading cell');
58 this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'b; inserts a code cell below code cell');
59 this.trigger_keydown('a'); // new cell above
59 this.trigger_keydown('a'); // new cell above
60 this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'a; inserts a heading cell below heading cell');
60 this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'a; inserts a code cell below code cell');
61 });
61 });
62
62
63 this.thenEvaluate(function() {
63 this.thenEvaluate(function() {
@@ -4,25 +4,38 b''
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 this.then(function () {
5 this.then(function () {
6 // Cell mode change
6 // Cell mode change
7 this.select_cell(0);
7 var index = 0;
8 this.select_cell(index);
9 var a = 'hello\nmulti\nline';
10 this.set_cell_text(index, a);
8 this.trigger_keydown('esc','r');
11 this.trigger_keydown('esc','r');
9 this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw');
12 this.test.assertEquals(this.get_cell(index).cell_type, 'raw', 'r; cell is raw');
10 this.trigger_keydown('1');
13 this.trigger_keydown('1');
11 this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading');
14 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown');
12 this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading');
15 this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading');
13 this.trigger_keydown('2');
16 this.trigger_keydown('2');
14 this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading');
17 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '2; cell is markdown');
18 this.test.assertEquals(this.get_cell_text(index), '## ' + a, '2; markdown heading');
15 this.trigger_keydown('3');
19 this.trigger_keydown('3');
16 this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading');
20 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '3; cell is markdown');
21 this.test.assertEquals(this.get_cell_text(index), '### ' + a, '3; markdown heading');
17 this.trigger_keydown('4');
22 this.trigger_keydown('4');
18 this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading');
23 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '4; cell is markdown');
24 this.test.assertEquals(this.get_cell_text(index), '#### ' + a, '4; markdown heading');
19 this.trigger_keydown('5');
25 this.trigger_keydown('5');
20 this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading');
26 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '5; cell is markdown');
27 this.test.assertEquals(this.get_cell_text(index), '##### ' + a, '5; markdown heading');
21 this.trigger_keydown('6');
28 this.trigger_keydown('6');
22 this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading');
29 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '6; cell is markdown');
30 this.test.assertEquals(this.get_cell_text(index), '###### ' + a, '6; markdown heading');
23 this.trigger_keydown('m');
31 this.trigger_keydown('m');
24 this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown');
32 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', 'm; cell is markdown');
33 this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'm; still markdown heading');
25 this.trigger_keydown('y');
34 this.trigger_keydown('y');
26 this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code');
35 this.test.assertEquals(this.get_cell(index).cell_type, 'code', 'y; cell is code');
36 this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'y; still has hashes');
37 this.trigger_keydown('1');
38 this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown');
39 this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading');
27 });
40 });
28 }); No newline at end of file
41 });
@@ -10,38 +10,53 b' casper.notebook_test(function () {'
10 cell.render();
10 cell.render();
11 return cell.get_rendered();
11 return cell.get_rendered();
12 });
12 });
13 this.test.assertEquals(output.trim(), '<h1 id=\"foo\">Foo</h1>', 'Markdown JS API works.');
13 this.test.assertEquals(output.trim(), '<h1 id=\"Foo\">Foo<a class=\"anchor-link\" href=\"#Foo\">ΒΆ</a></h1>', 'Markdown JS API works.');
14
14
15 // Test menubar entries.
15 // Test menubar entries.
16 output = this.evaluate(function () {
16 output = this.evaluate(function () {
17 $('#to_code').mouseenter().click();
17 $('#to_code').mouseenter().click();
18 $('#to_markdown').mouseenter().click();
18 $('#to_markdown').mouseenter().click();
19 var cell = IPython.notebook.get_selected_cell();
19 var cell = IPython.notebook.get_selected_cell();
20 cell.set_text('# Bar');
20 cell.set_text('**Bar**');
21 $('#run_cell').mouseenter().click();
21 $('#run_cell').mouseenter().click();
22 return cell.get_rendered();
22 return cell.get_rendered();
23 });
23 });
24 this.test.assertEquals(output.trim(), '<h1 id=\"bar\">Bar</h1>', 'Markdown menubar items work.');
24 this.test.assertEquals(output.trim(), '<p><strong>Bar</strong></p>', 'Markdown menubar items work.');
25
25
26 // Test toolbar buttons.
26 // Test toolbar buttons.
27 output = this.evaluate(function () {
27 output = this.evaluate(function () {
28 $('#cell_type').val('code').change();
28 $('#cell_type').val('code').change();
29 $('#cell_type').val('markdown').change();
29 $('#cell_type').val('markdown').change();
30 var cell = IPython.notebook.get_selected_cell();
30 var cell = IPython.notebook.get_selected_cell();
31 cell.set_text('# Baz');
31 cell.set_text('*Baz*');
32 $('#run_b').click();
32 $('#run_b').click();
33 return cell.get_rendered();
33 return cell.get_rendered();
34 });
34 });
35 this.test.assertEquals(output.trim(), '<h1 id=\"baz\">Baz</h1>', 'Markdown toolbar items work.');
35 this.test.assertEquals(output.trim(), '<p><em>Baz</em></p>', 'Markdown toolbar items work.');
36
36
37 // Test JavaScript models.
37 // Test markdown headings
38 var output = this.evaluate(function () {
39
38
39 var text = 'multi\nline';
40
41 this.evaluate(function (text) {
40 var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
42 var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
41 cell.set_text('# Qux');
43 cell.set_text(text);
42 cell.render();
44 }, {text: text});
43 return cell.get_rendered();
45
44 });
46 var set_level = function (level) {
45 this.test.assertEquals(output.trim(), '<h1 id=\"qux\">Qux</h1>', 'Markdown JS API works.');
47 return casper.evaluate(function (level) {
48 var cell = IPython.notebook.get_cell(0);
49 cell.set_heading_level(level);
50 return cell.get_text();
51 }, {level: level});
52 };
46
53
54 var level_text;
55 var levels = [ 1, 2, 3, 4, 5, 6, 2, 1 ];
56 for (var idx=0; idx < levels.length; idx++) {
57 var level = levels[idx];
58 level_text = set_level(level);
59 hashes = new Array(level + 1).join('#');
60 this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level);
61 }
47 });
62 });
@@ -1,11 +1,10 b''
1 {
1 {
2 "cells": [
2 "cells": [
3 {
3 {
4 "cell_type": "heading",
4 "cell_type": "markdown",
5 "level": 1,
6 "metadata": {},
5 "metadata": {},
7 "source": [
6 "source": [
8 "NumPy and Matplotlib examples"
7 "# NumPy and Matplotlib examples"
9 ]
8 ]
10 },
9 },
11 {
10 {
@@ -8,6 +8,7 b' import json'
8 from .base import ExportersTestsBase
8 from .base import ExportersTestsBase
9 from ..notebook import NotebookExporter
9 from ..notebook import NotebookExporter
10
10
11 from IPython.nbformat.current import validate
11 from IPython.testing.tools import assert_big_text_equal
12 from IPython.testing.tools import assert_big_text_equal
12
13
13 class TestNotebookExporter(ExportersTestsBase):
14 class TestNotebookExporter(ExportersTestsBase):
@@ -29,7 +30,7 b' class TestNotebookExporter(ExportersTestsBase):'
29 exporter = self.exporter_class(nbformat_version=3)
30 exporter = self.exporter_class(nbformat_version=3)
30 (output, resources) = exporter.from_filename(self._get_notebook())
31 (output, resources) = exporter.from_filename(self._get_notebook())
31 nb = json.loads(output)
32 nb = json.loads(output)
32 self.assertEqual(nb['nbformat'], 3)
33 validate(nb)
33
34
34 def test_downgrade_2(self):
35 def test_downgrade_2(self):
35 exporter = self.exporter_class(nbformat_version=2)
36 exporter = self.exporter_class(nbformat_version=2)
@@ -21,6 +21,7 b' from pygments.formatters import HtmlFormatter'
21 from pygments.util import ClassNotFound
21 from pygments.util import ClassNotFound
22
22
23 # IPython imports
23 # IPython imports
24 from IPython.nbconvert.filters.strings import add_anchor
24 from IPython.nbconvert.utils.pandoc import pandoc
25 from IPython.nbconvert.utils.pandoc import pandoc
25 from IPython.nbconvert.utils.exceptions import ConversionException
26 from IPython.nbconvert.utils.exceptions import ConversionException
26 from IPython.utils.decorators import undoc
27 from IPython.utils.decorators import undoc
@@ -146,6 +147,10 b' class IPythonRenderer(mistune.Renderer):'
146 formatter = HtmlFormatter()
147 formatter = HtmlFormatter()
147 return highlight(code, lexer, formatter)
148 return highlight(code, lexer, formatter)
148
149
150 def header(self, text, level, raw=None):
151 html = super(IPythonRenderer, self).header(text, level, raw=raw)
152 return add_anchor(html)
153
149 # Pass math through unaltered - mathjax does the rendering in the browser
154 # Pass math through unaltered - mathjax does the rendering in the browser
150 def block_math(self, text):
155 def block_math(self, text):
151 return '$$%s$$' % text
156 return '$$%s$$' % text
@@ -4,17 +4,9 b''
4 Contains a collection of useful string manipulation filters for use in Jinja
4 Contains a collection of useful string manipulation filters for use in Jinja
5 templates.
5 templates.
6 """
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2013, the IPython Development Team.
9 #
10 # Distributed under the terms of the Modified BSD License.
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
14
7
15 #-----------------------------------------------------------------------------
8 # Copyright (c) IPython Development Team.
16 # Imports
9 # Distributed under the terms of the Modified BSD License.
17 #-----------------------------------------------------------------------------
18
10
19 import os
11 import os
20 import re
12 import re
@@ -28,9 +20,6 b' from xml.etree import ElementTree'
28 from IPython.core.interactiveshell import InteractiveShell
20 from IPython.core.interactiveshell import InteractiveShell
29 from IPython.utils import py3compat
21 from IPython.utils import py3compat
30
22
31 #-----------------------------------------------------------------------------
32 # Functions
33 #-----------------------------------------------------------------------------
34
23
35 __all__ = [
24 __all__ = [
36 'wrap_text',
25 'wrap_text',
@@ -88,9 +77,9 b' def html2text(element):'
88
77
89
78
90 def add_anchor(html):
79 def add_anchor(html):
91 """Add an anchor-link to an html header tag
80 """Add an anchor-link to an html header
92
81
93 For use in heading cells
82 For use on markdown headings
94 """
83 """
95 try:
84 try:
96 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
85 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
@@ -1,3 +1,4 b''
1 # coding: utf-8
1 """Tests for conversions from markdown to other formats"""
2 """Tests for conversions from markdown to other formats"""
2
3
3 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
@@ -26,7 +27,8 b' class TestMarkdown(TestsBase):'
26 '#test',
27 '#test',
27 '##test',
28 '##test',
28 'test\n----',
29 'test\n----',
29 'test [link](https://google.com/)']
30 'test [link](https://google.com/)',
31 ]
30
32
31 tokens = [
33 tokens = [
32 '*test',
34 '*test',
@@ -39,7 +41,8 b' class TestMarkdown(TestsBase):'
39 'test',
41 'test',
40 'test',
42 'test',
41 'test',
43 'test',
42 ('test', 'https://google.com/')]
44 ('test', 'https://google.com/'),
45 ]
43
46
44
47
45 @dec.onlyif_cmds_exist('pandoc')
48 @dec.onlyif_cmds_exist('pandoc')
@@ -87,6 +90,17 b' class TestMarkdown(TestsBase):'
87 for index, test in enumerate(self.tests):
90 for index, test in enumerate(self.tests):
88 self._try_markdown(markdown2html, test, self.tokens[index])
91 self._try_markdown(markdown2html, test, self.tokens[index])
89
92
93 def test_markdown2html_heading_anchors(self):
94 for md, tokens in [
95 ('# test',
96 ('<h1', '>test', 'id="test"', u'&#182;</a>', "anchor-link")
97 ),
98 ('###test head space',
99 ('<h3', '>test head space', 'id="test-head-space"', u'&#182;</a>', "anchor-link")
100 )
101 ]:
102 self._try_markdown(markdown2html, md, tokens)
103
90 def test_markdown2html_math(self):
104 def test_markdown2html_math(self):
91 # Mathematical expressions should be passed through unaltered
105 # Mathematical expressions should be passed through unaltered
92 cases = [("\\begin{equation*}\n"
106 cases = [("\\begin{equation*}\n"
@@ -79,17 +79,6 b' In&nbsp;[&nbsp;]:'
79 </div>
79 </div>
80 {%- endblock markdowncell %}
80 {%- endblock markdowncell %}
81
81
82 {% block headingcell scoped %}
83 <div class="cell border-box-sizing text_cell rendered">
84 {{ self.empty_in_prompt() }}
85 <div class="inner_cell">
86 <div class="text_cell_render border-box-sizing rendered_html">
87 {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | markdown2html | strip_files_prefix | add_anchor }}
88 </div>
89 </div>
90 </div>
91 {% endblock headingcell %}
92
93 {% block unknowncell scoped %}
82 {% block unknowncell scoped %}
94 unknown type {{ cell.type }}
83 unknown type {{ cell.type }}
95 {% endblock unknowncell %}
84 {% endblock unknowncell %}
@@ -185,27 +185,6 b' This template does not define a docclass, the inheriting class must define this.'
185 ((*- endblock figure -*))
185 ((*- endblock figure -*))
186 ((*- endmacro *))
186 ((*- endmacro *))
187
187
188 % Draw heading cell. Explicitly map different cell levels.
189 ((* block headingcell scoped *))
190
191 ((* if cell.level == 1 -*))
192 ((* block h1 -*))\section((* endblock h1 -*))
193 ((* elif cell.level == 2 -*))
194 ((* block h2 -*))\subsection((* endblock h2 -*))
195 ((* elif cell.level == 3 -*))
196 ((* block h3 -*))\subsubsection((* endblock h3 -*))
197 ((* elif cell.level == 4 -*))
198 ((* block h4 -*))\paragraph((* endblock h4 -*))
199 ((* elif cell.level == 5 -*))
200 ((* block h5 -*))\subparagraph((* endblock h5 -*))
201 ((* elif cell.level == 6 -*))
202 ((* block h6 -*))\\*\textit((* endblock h6 -*))
203 ((*- endif -*))
204 {((( cell.source | replace('\n', ' ') | citation2latex | strip_files_prefix | prevent_list_blocks | markdown2latex(markup='markdown_strict+tex_math_dollars') )))}
205
206
207 ((* endblock headingcell *))
208
209 % Redirect execute_result to display data priority.
188 % Redirect execute_result to display data priority.
210 ((* block execute_result scoped *))
189 ((* block execute_result scoped *))
211 ((* block data_priority scoped *))
190 ((* block data_priority scoped *))
@@ -75,9 +75,6 b' consider calling super even if it is a leave block, we might insert more blocks '
75 ((*- elif cell.cell_type in ['markdown'] -*))
75 ((*- elif cell.cell_type in ['markdown'] -*))
76 ((*- block markdowncell scoped-*))
76 ((*- block markdowncell scoped-*))
77 ((*- endblock markdowncell -*))
77 ((*- endblock markdowncell -*))
78 ((*- elif cell.cell_type in ['heading'] -*))
79 ((*- block headingcell scoped-*))
80 ((*- endblock headingcell -*))
81 ((*- elif cell.cell_type in ['raw'] -*))
78 ((*- elif cell.cell_type in ['raw'] -*))
82 ((*- block rawcell scoped -*))
79 ((*- block rawcell scoped -*))
83 ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *))
80 ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *))
@@ -58,11 +58,6 b''
58 {{ cell.source }}
58 {{ cell.source }}
59 {% endblock markdowncell %}
59 {% endblock markdowncell %}
60
60
61
62 {% block headingcell scoped %}
63 {{ '#' * cell.level }} {{ cell.source | replace('\n', ' ') }}
64 {% endblock headingcell %}
65
66 {% block unknowncell scoped %}
61 {% block unknowncell scoped %}
67 unknown type {{ cell.type }}
62 unknown type {{ cell.type }}
68 {% endblock unknowncell %} No newline at end of file
63 {% endblock unknowncell %}
@@ -15,7 +15,3 b''
15 {% block markdowncell scoped %}
15 {% block markdowncell scoped %}
16 {{ cell.source | comment_lines }}
16 {{ cell.source | comment_lines }}
17 {% endblock markdowncell %}
17 {% endblock markdowncell %}
18
19 {% block headingcell scoped %}
20 {{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }}
21 {% endblock headingcell %}
@@ -71,9 +71,6 b' consider calling super even if it is a leave block, we might insert more blocks '
71 {%- elif cell.cell_type in ['markdown'] -%}
71 {%- elif cell.cell_type in ['markdown'] -%}
72 {%- block markdowncell scoped-%}
72 {%- block markdowncell scoped-%}
73 {%- endblock markdowncell -%}
73 {%- endblock markdowncell -%}
74 {%- elif cell.cell_type in ['heading'] -%}
75 {%- block headingcell scoped-%}
76 {%- endblock headingcell -%}
77 {%- elif cell.cell_type in ['raw'] -%}
74 {%- elif cell.cell_type in ['raw'] -%}
78 {%- block rawcell scoped -%}
75 {%- block rawcell scoped -%}
79 {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %}
76 {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %}
@@ -1,11 +1,10 b''
1 {
1 {
2 "cells": [
2 "cells": [
3 {
3 {
4 "cell_type": "heading",
4 "cell_type": "markdown",
5 "level": 1,
6 "metadata": {},
5 "metadata": {},
7 "source": [
6 "source": [
8 "A simple SymPy example"
7 "# A simple SymPy example"
9 ]
8 ]
10 },
9 },
11 {
10 {
@@ -1,11 +1,10 b''
1 {
1 {
2 "cells": [
2 "cells": [
3 {
3 {
4 "cell_type": "heading",
4 "cell_type": "markdown",
5 "level": 1,
6 "metadata": {},
5 "metadata": {},
7 "source": [
6 "source": [
8 "NumPy and Matplotlib examples"
7 "# NumPy and Matplotlib examples"
9 ]
8 ]
10 },
9 },
11 {
10 {
@@ -157,11 +156,10 b''
157 ]
156 ]
158 },
157 },
159 {
158 {
160 "cell_type": "heading",
159 "cell_type": "markdown",
161 "level": 2,
162 "metadata": {},
160 "metadata": {},
163 "source": [
161 "source": [
164 "Here is a very long heading that pandoc will wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap"
162 "## Here is a very long heading that pandoc will wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap"
165 ]
163 ]
166 },
164 },
167 {
165 {
@@ -1,145 +1,306 b''
1 {
1 {
2 "metadata": {
2 "cells": [
3 "name": 0
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
3 {
9 "cells": [
4 "cell_type": "markdown",
10 {
5 "metadata": {}
11 "cell_type": "heading",
6 },
12 "level": 1,
7 {
13 "source": [
8 "cell_type": "markdown",
14 "nbconvert latex test"
9 "metadata": {},
15 ]
10 "source": [
16 },
11 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
17 {
12 ]
18 "cell_type": "markdown",
13 },
19 "metadata": {},
14 {
20 "source": [
15 "cell_type": "heading",
21 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
16 "level": 2,
22 ]
17 "metadata": {},
23 },
18 "source": [
24 {
19 "Printed Using Python"
25 "cell_type": "heading",
20 ]
26 "level": 2,
21 },
27 "metadata": {},
22 {
28 "source": [
23 "cell_type": "code",
29 "Printed Using Python"
24 "execution_count": 1,
30 ]
25 "metadata": {
31 },
26 "collapsed": false
32 {
27 },
33 "cell_type": "code",
28 "outputs": [
34 "collapsed": false,
35 "input": [
36 "print(\"hello\")"
37 ],
38 "language": "python",
39 "outputs": [
40 {
41 "output_type": "stream",
42 "stream": "stdout",
43 "text": [
44 "hello\n"
45 ]
46 }
47 ],
48 "prompt_number": 1
49 },
50 {
29 {
51 "cell_type": "heading",
30 "name": "stdout",
52 "level": 1000,
31 "output_type": "bad stream",
53 "metadata": {},
32 "text": [
54 "source": [
33 "hello\n"
55 "Pyout"
56 ]
34 ]
57 },
35 }
36 ],
37 "source": [
38 "print(\"hello\")"
39 ]
40 },
41 {
42 "cell_type": "markdown",
43 "metadata": {},
44 "source": [
45 "## Pyout"
46 ]
47 },
48 {
49 "cell_type": "code",
50 "execution_count": 3,
51 "metadata": {
52 "collapsed": false
53 },
54 "outputs": [
58 {
55 {
59 "cell_type": "code",
56 "data": {
60 "collapsed": false,
57 "text/html": [
61 "input": [
58 "\n",
62 "from IPython.display import HTML\n",
59 "<script>\n",
63 "HTML(\"\"\"\n",
60 "console.log(\"hello\");\n",
64 "<script>\n",
61 "</script>\n",
65 "console.log(\"hello\");\n",
62 "<b>HTML</b>\n"
66 "</script>\n",
63 ],
67 "<b>HTML</b>\n",
64 "text/plain": [
68 "\"\"\")"
65 "<IPython.core.display.HTML at 0x1112757d0>"
69 ],
66 ]
70 "language": "python",
67 },
68 "execution_count": 3,
71 "metadata": {},
69 "metadata": {},
72 "outputs": [
70 "output_type": "execute_result"
73 {
71 }
74 "html": [
72 ],
75 "\n",
73 "source": [
76 "<script>\n",
74 "from IPython.display import HTML\n",
77 "console.log(\"hello\");\n",
75 "HTML(\"\"\"\n",
78 "</script>\n",
76 "<script>\n",
79 "<b>HTML</b>\n"
77 "console.log(\"hello\");\n",
80 ],
78 "</script>\n",
81 "metadata": {},
79 "<b>HTML</b>\n",
82 "output_type": "pyout",
80 "\"\"\")"
83 "prompt_number": 3,
81 ]
84 "text": [
82 },
85 "<IPython.core.display.HTML at 0x1112757d0>"
83 {
86 ]
84 "cell_type": "code",
87 }
85 "execution_count": 7,
88 ],
86 "metadata": {
89 "prompt_number": 3
87 "collapsed": false
90 },
88 },
89 "outputs": [
91 {
90 {
92 "cell_type": "code",
91 "data": {
93 "collapsed": false,
92 "application/javascript": [
94 "input": [
93 "console.log(\"hi\");"
95 "%%javascript\n",
94 ],
96 "console.log(\"hi\");"
95 "text/plain": [
97 ],
96 "<IPython.core.display.Javascript at 0x1112b4b50>"
98 "language": "python",
97 ]
98 },
99 "metadata": {},
99 "metadata": {},
100 "outputs": [
100 "output_type": "display_data"
101 {
101 }
102 "javascript": [
102 ],
103 "console.log(\"hi\");"
103 "source": [
104 ],
104 "%%javascript\n",
105 "metadata": {},
105 "console.log(\"hi\");"
106 "output_type": "display_data",
106 ]
107 "text": [
107 },
108 "<IPython.core.display.Javascript at 0x1112b4b50>"
108 {
109 ]
109 "cell_type": "markdown",
110 }
110 "metadata": {},
111 ],
111 "source": [
112 "prompt_number": 7
112 "### Image"
113 },
113 ]
114 {
114 },
115 "cell_type": "heading",
115 {
116 "level": 3,
116 "cell_type": "code",
117 "metadata": {}
117 "execution_count": 6,
118 },
118 "metadata": {
119 "collapsed": false
120 },
121 "outputs": [
119 {
122 {
120 "cell_type": "code",
123 "data": {
121 "collapsed": false,
124 "image/png": [
122 "input": [
125 "iVBORw0KGgoAAAANSUhEUgAAAggAAABDCAYAAAD5/P3lAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
123 "from IPython.display import Image\n",
126 "AAAH3AAAB9wBYvxo6AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURB\n",
124 "Image(\"http://ipython.org/_static/IPy_header.png\")"
127 "VHic7Z15uBxF1bjfugkJhCWBsCSAJGACNg4QCI3RT1lEAVE+UEBNOmwCDcjHT1wQgU+WD3dFxA1o\n",
125 ],
128 "CAikAZFFVlnCjizpsCUjHQjBIAkQlpCFJGS79fvjdGf69vTsc2fuza33eeaZmeqq6jM9vZw6dc4p\n",
126 "language": "python",
129 "BUwC+tE+fqW1fqmRDpRSHjCggS40sBxYDCxKvL8KzNBaL21EPoPB0DPIWVY/4NlE0ffzYfhgu+Qx\n",
130 "GHoy/YFjaK+CcB3QkIIAHAWs3wRZsuhUSs0CXgQeBm7UWi/spn0Z+jA5yxpEfYruqnwYllRic5a1\n",
131 "MaWv8U5gaT4M19Sx396IAnZLfB/SLkEMhp5O/3YL0AvoAHaKXl8HLlZK3QZcpbWe0lbJDOsaHuDU\n",
132 "0e4u4JAy2wPk/C1JzrKWArOQ0fUtwH35MOysQxaDwbCO0NFuAXoh6wPjgQeUUvcqpUa0WyCDoQls\n",
133 "CIwBjgfuAV7KWdY+7RWpmJxlXZezrEdylvXxdstiMKzrGAtCYxwI/EspdZbW+g/tFsbQ67kQuBHY\n",
134 "FNgseh9FV6vCbUAeWBC9PgBeq2EfS6J2MQOBrRDTe5KdgAdzlvW1fBjeUUP/3UbOsoYBE6OvG7VT\n",
135 "FoOhL9Af+BUwFLkZpV+DaY6V4UPkRpb1+ncT+m8nGwK/V0oN01qf025hDL2XfBi+DLycLMtZVo6u\n",
136 "CsKfGnSq8/NheEpqHwOBEcDBwJnAsGhTP2ByzrJG5cPwnQb22Sy+0G4BDIa+RH+t9dmlNiqlFKIk\n",
137 "JJWGi+jq5JPmq8BbJJQArfXqpkncczlbKbVQa/3rdgtiMNRCPgxXAK8Ar+Qs63LgXmDvaPPGwPeA\n",
138 "H7VJvCRfbLcABkNfouwUg9ZaAwuj178BlFLvVejzgR4WFviM1npcuQpKqf6IyXIjxLS7GzAWuUnu\n",
139 "XsO+fqWUellr3ZBJdq/jr9+BDn1uve07O9Rz0y6f8PtGZGgWe53oT6SBkZ/q1/nHZy47aloTRTKU\n",
140 "IR+Gy3OWNR6Zxtg0Kv4KRkEwGPocxgcBiCwcsSI0F5iOhF+ilPok8C3gVGS+thK/VErdrbWuO2ys\n",
141 "s/+aLZTuOKbe9krrIUCPUBB0B+PQ1P1bdKe6EzAKQgvJh+GbOct6gkJkxM45y+qXDIWMHBhjBWJe\n",
142 "PgyDWvaRs6zPIVObAG/nw/DpEvUGAp8E9gGGJzbtl7Os7cvs4skqp0V0Yl8jgcOBjyMDhbmIZeWl\n",
143 "fBg+UUVfReQsayhwELAnsAXi6/E28BxwTz4MP6iyn92RaSCA+/NhuCwqXx9R4MYhU0MfRTK/AjyW\n",
144 "D8MFGd0ZDFVhFIQKaK3/BXxfKXUlklTq0xWafAI4Driyu2UzGLqRlygoCArYHJif2H4gcFb0+Z2c\n",
145 "ZW2bD8NV1XScs6yNgH8g/jsAPwCeTmzfFPgjYsnbiez71MUVdnMQcF8V4nyUs6whwB8QX4+0s2Ys\n",
146 "0yPAt/NhGFbRZ/wbzgO+DaxXotqqnGX9GbigCkXhf5CBCsDngYdzljURGQhsWqLN+znL+iFwdT4M\n",
147 "dYk6BkNJTJhjlWitQ2Bf4P4qqv848t8wGHor6Yd9+ruHJFkC2BI4rIa+D6egHKwmstYlGAxMQCwH\n",
148 "rRjEPI5ER5S7ZvcFXsxZ1phKneUsawSi8HyH0soB0bbvAM9Ebaplt5xlnYkct1LKAYiFZhJwSQ19\n",
149 "GwxrMRaEGtBar1RKfRX4JxIzXortou3PN1mE+YgJsSwaeoLHOQCqUy3QSr9eqZ6G/gq2aYVMhqrY\n",
150 "OfF5FeJwvJZ8GM7JWdY/gC9HRS7wtyr7Pjrx+e6MqYC3KLbU7Qhck/h+FJIKvRRVjfSREXicU8EH\n",
151 "pgAvIIqLBZwGfC7avl5Uf29KkLOsTZCMq8npj9sQx89no37HIlaAODplNPBIzrJ2z4dhNVlaT0HC\n",
152 "XwFmIkrAC4if2PaIz8/3KCgn385Z1pX5MJxeRd8Gw1qMglAjWutlSqnTgUcqVP0SzVYQtP5mcMXE\n",
153 "SvvtUUy9YsK5QEWHy7EnTB6lOtSsFohkqEDOsgYAdqJoagkT9Z8pKAj75yzr4/kwnF2h748ho/GY\n",
154 "q9J1oqiKLj4JOctKK8Yz8mH4Yrl9VcnHkXVYTsyHoZ8WJWdZNyPThbF5/3M5yzowH4alpi9+T0E5\n",
155 "WA18Nx+Gf0zVeRG4KmdZ90R9bwCMRKwyX69C5h2j91uA4/JhuCSxbTYwJWdZtwNPIFbifsAFSISZ\n",
156 "wVA1ZoqhDrTWjyIjjXIc3ApZDIZu4ELgY4nvt5Wody8wJ/qsgBOr6HsihfvOfCRrY7v5dYZyAECk\n",
157 "GP0ISEZmZYZ55yxrB8SyEXNxhnKQ7Pt64H8TRUfmLGuXKmWeC4xPKQfJvp9CLCJlZTYYymEUhPq5\n",
158 "tcL2XVsihcHQJHKWtU3Osi5GnAZj5iKWgiKitRouTxQdl7OscnPu0HV64dp8GLY7R8pyxEGxJPkw\n",
159 "fBcZ9ceUSvN8IoV76upK/UZcgawcG3NKqYopfleFU+gDic/b5SzLWIwNNWFOmPqp5CG9sVJqPa11\n",
160 "VZ7dBkOL2D1nWcmcBkOR8MFtgM/QdTXJZcCR+TBcXqa/SYj5egAFZ8VMX4ScZe2FRPnEXF2z9M3n\n",
161 "3nwYVsrtAmK6/0z0uVR4ZXLtivvzYfhGpU7zYbgkZ1k3ACdHRQdWIQsUO3ZmkUzB3Q/xjaolLbeh\n",
162 "j2MUhDrRWr+mlFpJ+eV5hyIxz4YWs98Fj/Rf8uZbozo0/ZYt7D8rf9ORK9stUw/hU9GrEnMAp1R+\n",
163 "gph8GL4bzdNPiIpOorSzYtJ68FS1IYPdTLWp3hcnPm+Q3pizrA7E+TCmFn+aZN0dcpY1LB+G5e4b\n",
164 "y6rM8bA49X39GmQyGMwUQ4NUGnkMrbDd0A3sdeLk4z6cN+89pTtDTWd+gyErF+7pTv5eu+XqJbyK\n",
165 "TDHsmg/DJ6tsc2ni8+dzljUqXSGaevhmoqjIObFNVBzlV8kQug4W5tbQNl13WGatAv+poW+DoW6M\n",
166 "BaExPgC2LrO9nHWhpSilDqI4NPMhrfXUJvS9M/DfqeJXtdY3N9p3rex50uQ9lFKT6BrTvoFCXbTX\n",
167 "yZNfmnrZxHtbLVMP4xng74nvK5DzeD7wfIWRayb5MHwiZ1kzgF0oOCuemar2ZQoK8zLgr7Xup5t4\n",
168 "s0n9DEl9b0RBSPeV5q0a+jYY6sYoCI1RacnZ91siRXUMAH6eKnsYicdulDOAY1NlpzWh35pRqG9R\n",
169 "IuGN7uw4AfG878s8nw/DX3RDv5dScGY8NmdZP86HYXJaJzm9cHMp7/s2UHdK9BTpKaxBNbRN163k\n",
170 "t9Rux05DH8FMMTTGZhW2v9sSKarjbopNk/sqpUY30qlSahCSGS/JCuD6RvqtF6UpMm/HaHTJbYaG\n",
171 "mQzED/0umRVzlrUZhXwJ0HOmF5pJOlXyxzJrZbNt6rtZP8HQIzAKQp0opTZAlsItxTKtdTnv75YS\n",
172 "LR7lpYqrjV0vx2EUH4fbtdZtucnpMqOrDjPy6jYii8DkRFHSYnAEhem22cBjrZKrVeTDcCldTf/p\n",
173 "h345ksrEGprnF2EwNIRREOrnMxW2z2uJFLVxJcXmy2OVUo34ShydUda+EaIq7T2u0SZTY/eSdFY8\n",
174 "MGdZm0efk86J6/LCQUnFp5pIkZjkcvQz8mH4YZPkMRgawigI9VNp7v7BlkhRA1rr+RQneNqC2hba\n",
175 "WYtSajiS9z3JXLomaGktq/VllLIUdKqSWe0MjZMPwxlIel8Q/6Zv5CxrGIX8AJ10XU+hFtIRQ+UW\n",
176 "KWoXyYyTu+Qsa79KDXKWNRpJyx5zZ9OlMhjqxCgIdaCU6g98o0K1npBCNotLM8rcOvuagCRgSXKN\n",
177 "1rozq3IrCCZNfFkrfRjotWsCaJinUBODK51/tkuuPkTy/DoYOIDCfeb+fBjW4t2/lqhdcmRdbUri\n",
178 "VnILXS2HZ1WRvfAcCk61K4A/dYdgBkM9GAWhPr5F6XSrIBf6Qy2SpSaidSReShV/XilV7veUIj29\n",
179 "oOkB2fGmXT7x7sCbOGpFf7VZx4A1m0/znG2nehMyc+0bms7NFJxzxwH7J7Y1OvWUPG9/mLOsLRvs\n",
180 "r6lEaaOT0TtfBB5ITLWsJWdZg3KWdRNwTKL4wnwYzu9mMQ2GqjFhjjWilBqBpJYtx51a66UV6rST\n",
181 "S+maJz52VvxRdvVilFK7UbzexGNa67Kr+bWS6X+ekPYs79HkLGt34JOI+Xyz6D2d1vfMnGUdini6\n",
182 "L0C851/Oh2HD+SyaQT4MV+YsaxJyLm1Gwf9gAXBHg93/JNHHtsArOcuajCztPBDYCkkytBXg5sOw\n",
183 "5QmF8mF4W86yLgK+HxXtC8zKWVaALMm8CslHsicS7RFzL8VhyAZDWzEKQg0opbYE7qd8prPVdF2h\n",
184 "rSdyLfALYMNE2XFKqR/XsHbEURll62L4Wiv5PuBUqPPF6JXkLuCQbpGoPi4HfohYKGMHWD9axrlu\n",
185 "8mF4Z7RuwfioaDBwaonqRemQW0U+DH+Qs6xFwHnIFNwQsv+3mMnA8dHiVwZDj8FMMVSJUuow4DkK\n",
186 "a7GX4gqt9cstEKlutNaL6boULMho5tBq2iul+lH8IFuCmJcNfZx8GM6hOCFVU5THfBhOQHxfylkH\n",
187 "3gY+asb+6iUfhhcCewC3l5BlFbJk/P75MDwqlVTKYOgRKK1rizhSSk2h67ximo1abV5XSi2n9EIk\n",
188 "z2itx5XYVqnfQcjI7DiqW2XtfeCTUbRA3ex50nWfUrqjeJEcrfcLrpj4SCN9xyilxgDPp4of0Fof\n",
189 "UEXbg4B/pIqv1FrXnVNh7AmTR3V0qIwwRH1E4E28pd5+De0hZ1m/Bb4bfX0+H4Z7dMM+hgGjkDwC\n",
190 "S5FpjFk9bR4/Z1mDkGmF4VHR20g4Y3oxJYOhR9EXphg6lFLlVjFbH0mZvDGwCTAayCFe0ntTOZ1y\n",
191 "zDLgkEaVg1ahtX5BKfUU8OlE8ReUUjtorSstCduzch8YehSR5/6ERFG3nBvRuhE9frXUfBguA6pd\n",
192 "+Mpg6DH0BQXBBro7o+Ea4Bta66e6eT/N5lK6KggKOAE4u1QDpdTGFOdNmNkLf7uh+zgYcRQEMa+3\n",
193 "Je22wWBoDOOD0DhLgYla67vaLUgd3ETxglLHRXkeSnEExQ5gbQ9tNPQokis5TsqHoVlbwGDohRgF\n",
194 "oTECYHet9Y3tFqQetNYrKDb/DqN46eYk6emF1UhUhMFAzrImUEhDvgr4VRvFMRgMDWAUhPpYAvwf\n",
195 "8Bmte31+/8uQBEdJMjMrKqW2o5A2N+YfWusePw9s6F5yltWRs6zxwKRE8RXtyEVgMBiaQ1/wQWgm\n",
196 "eWTe/jqtdU9Zz74htNavKaXuAw5KFB+glBqptZ6Tqj6RQlrYGDO90AfJWdY5wNeQFQwHIAmetk5U\n",
197 "eZFCsiCDwdALMQpCed5AphEC4NF12BHvUroqCAoJ7TwvVS+d++BdJEmPoe+xKRLnn0UeODwfhm3N\n",
198 "RWAwGBqjLygIbwN/LbNdI1MGH6ReL/eWkMUmcDeSeGa7RNlRSqnzdZQoQym1C7Bzqt11NWReNKxb\n",
199 "zEMU6GHAesBiYCaSLOviaF0Cg8HQi+kLCsLrWuvT2y1ET0ZrvUYp5SG57mO2Bz4LPB59/2ZRQ5P7\n",
200 "oM+SD8OLgYvbLYfBYOg+jJOiIeZKxOs8STJiIb28daC1/lf3imQwGAyGdmEUBAMA0XTKraniI5VS\n",
201 "A6O0zOnloI31wGAwGNZhjIJgSHJp6vtgJBNlehW65cANLZHIYDAYDG3BKAiGtWitHwVeShV/muLF\n",
202 "uW7VWi9qjVQGg8FgaAd9wUnRUBuXAn9IfN8f+FyqTo/OfbDnSX8brDpXnqEUe2ropzQvdtDx66ev\n",
203 "GN9XolIMPQDb9T8LrBd4zsPtlsXQe7Bd/0BgQeA5QbtlMQqCIc21wC+ADaPv6WWu5wAPtVKgWtjt\n",
204 "6Os2XG/9jhdQjIzTQ2rFF9bQecy4E2/I9UQlwXb9LYDDK1R7K/Cc21shj6FxbNcfDjwGKNv1Rwae\n",
205 "83q7ZWo2tusPBb6ELGW9BbAICX99Gngs8Jx0hlZDBWzXHwvcC6ywXX9o4DlL2ymPURAMXdBaL1ZK\n",
206 "+ZRItwz8Jc6N0BMZMFB9GxiZsWnzTjrPAH7QWomqYgTF/h9pngC6RUGwXf+XwC2B50ztjv57M7br\n",
207 "XwJMCjxneo1NP0SWgAfJq7LOYLv+esAFwOkUL9wWM912/d0Dz+lsnWQ9A9v1BwEXAT8PPKfWVOML\n",
208 "kPVt3kNWQm0rxgfBkEWph5UG/tJCOWqnQ40ttUkrvWcrRamWwHOmAZsguSfGAi9Hmy5AUhgPAz7f\n",
209 "Hfu2XX8k8ENgx+7ovzdju/4uwP9D/peaCDxnCbANsF3gOYubLVu7sF1/AHAHcBaiHDwI/C+ywNsE\n",
210 "4KfA68BdfVE5iNgbOBmxqtRE4Dn/BoYDnwg8Z02zBasVY0EwFKG1fkEp9RTioJjkIa11zzaVarYq\n",
211 "vVFt2TpBaiN6oCwB5tiu/2FUPCvwnLTTaLM5oJv77800dGwCz1kXHXkvRNKydwI/Cjzn1+kKtuuf\n",
212 "i2TX7Ks0et681yxBGsUoCIZSBBQrCL0h98EbdW7rddiuPwoYFJu/bdffFNgL2BZ4DZgWKR5ZbRWS\n",
213 "2+KIqGiE7fpjUtXmlrtZRdaHscBAYDowM/CckimWbdffFfgw8JzXou/9kfUccojV5MXAcz4s0XYw\n",
214 "sCsymu8PzAVmBJ7zVqn9pdoPRVKF7wSsAN4EgqzRve36HcAoZDEqgO0zjs3rged8kGo3gOJ05ADT\n",
215 "s0bTkan+k9HXGaVGjNFxykVf81nH2Hb9Ich/MRJJeT291H9fL7brj6CwANfPspQDgOi3rijRx/rI\n",
216 "b8kB7wPPBZ4zL6Ne/JvfCDzn/WhufhvgvsBzVkR1dgN2AR4JPGduom38P7wXeM7c6FzfCfgU4iMR\n",
217 "lFLebNfPIefXzMBzikz8tusPQyx676bljmTeCfhyVLST7frp//TV9Dluu/6GwOhUvTWB58zIkjFq\n",
218 "sykyNfmfwHMW2K7fLzoWeyDTFPnAc14t1T7qYwNgT+Rc/wi5ZyT/N20UBEMRSqn+wNdTxQspTqTU\n",
219 "41BaP6yVOipzGzzSYnG6m6uBz0YPv7OQm3dytc35tuuflHZutF3/BuArwEaJ4p/QNdU2wGnAH9M7\n",
220 "jRSTG5CbS5LQdv2joymTLKYBzwHjbNc/DomW2TCxfbXt+sMCz3k/sa8RwM+Qh/X6qf5W2q4/CTit\n",
221 "zMN1OPB7CopQktW2658YeM5fEvXvRKZzBiXqZaWUPha4JlW2NfB8Rt0hiANfmjWIuf5jiLPfvVm/\n",
222 "AfmvbgNmB54zKrkheuD+Bjg11Wap7fpnBJ5TybelFk4E+iE+Fb+ptbHt+scg//nGqfJbgeMDz1mY\n",
223 "KN4UOZYX2q7fSWHhuNdt198ZOBc4MypbbLv+5wPPeTb6PiJqe5ft+ichx3WXRN8rbdc/OfCcrGis\n",
224 "R4ChiHKSlSn2f4BzkOvitMRvCKJ9DEzU9TPafwGZlkkyBvExSrKUrtdnmoOBycA5tus/iCyat3li\n",
225 "u7Zd/0rk2ihS1mzXPwT4E3LulaLTKAiGLL6EaMlJbtBat91pphIjFw289t9DVh4N7Jva9EKnWnpJ\n",
226 "G0RqBXcjCa08YCqy/PJE4L8A33b9HQPPeTNR/0bgvujzGchoywPSq5U+nd6R7fp7IDfRjYDrEE99\n",
227 "DeyHrPb5lO364xI36zTb2q4/AUnt/SSyLHQHMvJZklQOIhYChyCLid2FWBoGIQrDfwGnAP8Gskzd\n",
228 "VvSbBgPvIMdpJjLHuxdikXgg1ewa4Jbo84+BHRAFI/3gT9/QQZa+/iIy9zwccVQrSeA5nbbrX4s8\n",
229 "cI6htIIQK7xdFJLIAvEEYjmYBlyP/E4LeXj92Xb94YHnnFtOjhrYJ3q/vtbpE9v1fwqcjYxUL0GO\n",
230 "51bI//g1YIzt+mNTSgJIivfNEIXgBOThfx0ySv8Nct7vgzgfj0+1HQf8E5iPKM/vI+vLHA9cZbs+\n",
231 "JZSEevgDBZ++3yIKzgVI1FeSrCnD6ci0zebAJxCfjmoZjxzXPPBL5By0gW8jCt3sqHwtkYL1N0RB\n",
232 "/R2ymOG2yHE5CLFAHAu8ahQEQxbfyijrDdML3HTTkWvUBRfsb88bPb6TzjEK+oHKL184YHL+Jmdl\n",
233 "u+XrJsYBhwaec0dcYLu+hzw0dkcu/AvjbUmLgu36DqIgPB54zuQq9nURMgI8LjnyBibZrj8z2s/l\n",
234 "tuvvVcJJbWvkXDoi8JzbKu0s8JxFtut/IqXgAPzOdv0/IiPnb5KhICAjpMGIEjAhPV1iu35HWsbA\n",
235 "c25ObD8ZURAeqibENBqpTYnark8FBSHiakRBOMx2/cHpB29kSv4KooSlLRYnIcrBHcBXk7/Fdv0b\n",
236 "gReAM23Xvz7wnJlVyFIJK3qfXUsj2/U/jiiiq4B9ktEytuv/Fhlpfx2xEnw31XxHYLfAc6bbrv8k\n",
237 "cny/Bnwz8Jy/2q6/DTLd9F8Zu94ceXAeEHhOvM7MNbbrT0UU4vNs15+c2FY3gedcm/hNP0EUhDvL\n",
238 "KMrJtkuIFPboWNWiIOSAO4HDE7/Dj67FSxEn21+m2pyOWDpuCDxn7fG2Xf8e4F1EIVsceE5oohgM\n",
239 "XVBKjURuSEke11qXMhv3OPR553VO9Sb407yJZwTexO8FnnNV/qYj11XlAOCfSeUA1s4D/y36mp7f\n",
240 "rAvb9fdGLDMzU8pBzMXIg2wsMhLKQiFhgxWVg5gM5SDm+uh9VHqD7fr7IlaNFcAJWb4UPcHLPvCc\n",
241 "2YgVZn3gyIwq30AsQg8lQ+aiefUfR1/PzlB08sD9Udusfmsi2t+Q6GutjspnIE6L16dDaSN/irMR\n",
242 "p8dTbddPOxK/nwgxTZr8747e30SsEkNL7PvXGQrAVYgvwggK/gK9mXMyfuON0fvWkY9Dkp2i97uT\n",
243 "hYHnLKNgURsDxknRUMz5FJ8XP22DHIbqSc9pxsSOW8ObtJ89ovdXbNcvpQC8j4zcdiTbnAoy4q2b\n",
244 "6Ia3CYV5/Y0zqsXOf4/WEYveaq5GQuOOQaZekhydqJNkW2BLZF2UzhL/R+xE2XAIa+A52nb9lUho\n",
245 "Y63hd7GD5d1ZGwPPmW27/iuIUrkLXc/n9xP13rZd/yNgVezoF8n1NjAyyyKETGGl97fGdv1/IlaL\n",
246 "3h7e+06WM2PgOQtt11+GTMcNo6vVJ1aWsyK+4nvFQjAKgiGBUmoshfnOmGe11vdl1Tf0GOaUKI9v\n",
247 "lqrE9lqJb6b/Hb3KsU2Zba/VslPb9bdDfA0ORLz0N62iWWxVqMkc3iZuRuawP2u7/g6JKI9RSCTR\n",
248 "YoodhOP/YgNKK2Ix2zZJzjnINMN2NbaL/4uiaIUE/0EUhB3pqiCkMwl2IscjXZZFJ/B2iW1xRtWR\n",
249 "ZWTqDcwps63U9f8Q0TSN7fp/iK0PtuvviPjmrCHyR1qrICilNkTmHjZDLsDke/JzOtwnzY1KqXcR\n",
250 "R4cFiBab9XlRT87I19dQSo1GNPz0tJOxHvR8mhrOVobB0XuAOBiWo1zmwaqdXW3X3x+4BzGVv4SM\n",
251 "pN9AnPEg21McxMIArTs2dRN4zoe26/8NOA6xGJwfbYqV9b8GnrM81Sz+Lz5A0qOXo2y4Ww3MoT4F\n",
252 "IY4+KTfNF58TaXN4VthstVNDitLKcdxvOjKmEj0tv0M953fs87E3Eul0B2JliBflOzfwnFcA+iul\n",
253 "5iEmwQFNEBaK569L0amUWggcqrXO8gg2FKHG2CdW4Uem9XvBlUflu7RUaiByU3lPa92ZKN8cSav8\n",
254 "fUQBTHKr1rrqueIsxp18/eg1azrLjSYB6NfRsY3G6Is9nDjDYxh4zundvbMotvtm5N50duA5P09t\n",
255 "T0faJIkfirU+zNrF1YiC4FBQECZE73/JqB//F+u14r+ImIVEOB1iu/6ZNfhwzEamp7YuU2e7RN1m\n",
256 "oZBnW5YVIfZ1qNWfotw51yuIph++hET0bAkcikwpTAEuCjxnSly3PzIP0a8NcnYgD6SBlSoaIhQX\n",
257 "V2UtVup24LBU6S7IyG+NUuodZP52awojrTSvIjeshlij9XdQKh2jXYRRDtpGfOCruQfEpmzbdn0V\n",
258 "dP9iPLsgjnEryI67Lzd/PCt6/5Tt+v3LJXAqQ/z7ut2ZO/Ccx23XfxUYZbt+7D8xCngl8Jwsa80s\n",
259 "ZBS8ke36O7cg4ybA5UgegJ0QE/XN5auvZRaiIMQRF12wXX8TCv9ls6eERpOtIMR+EXNS5YsRh8dS\n",
260 "To/V+CzUck21i6uR5++4wHNeKFXJRDH0PfoR5fqmtHKwDDhCa73O5JA3lCSeF04v6Z3FPRTMzBO7\n",
261 "S6AE8Q12PbomgYn5Xpm29yMPhu2RUK96iKMn9q6zfa38JXo/NHoly7oQeM5K4Iro60+jKINuJVJC\n",
262 "Yu/439uuX805A4VkWyfbrp+V/MdFnOmeCmpfFKsSRYMc2/U/DeyG3OfSjpOx5WmfVHmcuXFcFfus\n",
263 "5ZpqObbrb45EtswqpxyAcVI0FDMbOFxrXeT9a+heopvnEArzolvashT0wmbEapdgGpIU5XDb9R9F\n",
264 "YqrXQyyL8wPPeTeuGHjOMtv1T0VuqldH6W//jigNmyHOcAcBgwPPcZog20xkRLcJ8DPb9S9CRqM7\n",
265 "I7kDvoDE1hfdxwLPWWy7/plI7oCLbNffHXm4zUQeRtsjGRP/EXhOKSfcABkpj49i5+9G/putgHmB\n",
266 "5yxIN4iSF21C14V6Rtiu/yYSW15uHv4a4P8oKAedlPcvOAv4KmItfCTKKfAS8v8NR1ILHwnsl5GA\n",
267 "qF7ORdYaGA48HGWyfBqYgViDRwCfQR72PkDgOU9E2TvHI4m0TgeeRczb30DyH2iKcyA0ymrgWNv1\n",
268 "FyDK1NvIQ3tStN3LCH+9HUl29UPb9echFo8BUbtLEKfJtJ9EmgA59ifbrj8bCR3cGDlvZqdTLcPa\n",
269 "9NCbUMhs2GFLKvPFSAKxZl7/CxEL8pgoA+QMxD+kE3HenAHcHnjOGmNB6Dt8iGjHWSFKK4HHkcQr\n",
270 "OxvloLXYrr+77fqrEIejNyiE6P0WccZbabv+lFLtG+Ry5AY/BHkYfRDtR9M79QAAA3FJREFUcwYS\n",
271 "NdCFwHPuQR6a7wHfAR5GMhk+i9xcT6G6KIOKBJ6zFBn9r0GUmBlIWN9ziHf/5yjO/phsfy2yqt4i\n",
272 "xOJxF3INTI9k/Q7ZoV4xv0PC5LZCci4sQm6g08kYHdquvxy5lt4DwsSmF5EENCts1//Idv3M9LbR\n",
273 "egJTkEx4NvBA1joFifqLIjkeR6wcfwdeQfIFTEEcjHNU79RXkShvw95Ixs5+yOj/KuSh+ATiAHcq\n",
274 "xb4fxwOXRfJMQc6zlxGF6B3g4MBznmmWnBFzEUfP0xDFcCGiAG+JHKushESXIdanjRBF4l3EInAj\n",
275 "8vuOqWK/5yNRGaOQFNkfIhkOX6CQgwAA2/W3jkI3V0T7ejjatAFyXb2PXP/LbVnroWGi6bbzo697\n",
276 "IlaWk5Br93wkk+jztusP7o94Lna7eaoMZU0cVXIAped7eqGZfP2ZqmPFl+ptrVf3n19UpvVMYLRS\n",
277 "agBywxuEjLwWAe9qrTMXV2mUzs7OP/Xrp+6qt33Hmn5Zue3XNeZTOVoky5nqKiQkrNT883Qk3WvJ\n",
278 "sMLAc1bbrv9Z5AH6KWRkOB+5wRWlWo7a3Ga7/mOIomAho/GFyI30YeDREru7ELlOq07TG3jONbbr\n",
279 "T0Nu9KOQm+i/gFsDz3nTdv2fI2FbpdpfHnlpH4LcnHdAlIz5yLErqXgFnvOR7fo28lDYE7lu3kKO\n",
280 "TdZ9K52xrhTl7knnUVB6SqVeTsr4apQU6lDEbG4hCsFbROsRBE1ebjrwnNB2/XGIGf5gRBkYhPyv\n",
281 "7yDpjR9MtVkOnGK7/vWIgrFrVPcF4O8ZKbaXIuduWkH6KfL/JbkEsWClfWK2CDzHt10/jzhXjkGO\n",
282 "yzNIZEiRD00ga3ocaLv+kUh2xo8hSuVURKmIUyiXVGYCWVzKQlJD7xrJNg85b9LX8RLgF6X6SpFU\n",
283 "9Cpe28gaJgORqEEAbNffDLlvHIQoAndR8NEYilwjExD/nwuUiTQ0GAwGw7qC7fqjEUvKqsBzmhWd\n",
284 "t05gu/5pyNoifw48J9N5PForxQeeNFMMBoPBYDD0DWL/llvK1In9jt4zCoLBYDAYDH2DePo5MwrJ\n",
285 "dv0hFPwTnjBRDAaDwWAw9A3+hPgOHRPl25iK+FhsiuR4OARx0Lwf+J1REAwGg8Fg6AMEnvNklL78\n",
286 "HMRRca/E5hVINNIVwI2B56z6/3ExLRI31pXNAAAAAElFTkSuQmCC\n"
287 ],
288 "text/plain": [
289 "<IPython.core.display.Image at 0x111275490>"
290 ]
291 },
292 "execution_count": 6,
127 "metadata": {},
293 "metadata": {},
128 "outputs": [
294 "output_type": "execute_result"
129 {
130 "metadata": {},
131 "output_type": "pyout",
132 "png": "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\n",
133 "prompt_number": 6,
134 "text": [
135 "<IPython.core.display.Image at 0x111275490>"
136 ]
137 }
138 ],
139 "prompt_number": 6
140 }
295 }
141 ],
296 ],
142 "metadata": {}
297 "source": [
298 "from IPython.display import Image\n",
299 "Image(\"http://ipython.org/_static/IPy_header.png\")"
300 ]
143 }
301 }
144 ]
302 ],
145 }
303 "metadata": {},
304 "nbformat": 4,
305 "nbformat_minor": 0
306 } No newline at end of file
@@ -1,11 +1,10 b''
1 {
1 {
2 "cells": [
2 "cells": [
3 {
3 {
4 "cell_type": "heading",
4 "cell_type": "markdown",
5 "level": 1,
6 "metadata": {},
5 "metadata": {},
7 "source": [
6 "source": [
8 "nbconvert latex test"
7 "# nbconvert latex test"
9 ]
8 ]
10 },
9 },
11 {
10 {
@@ -16,11 +15,10 b''
16 ]
15 ]
17 },
16 },
18 {
17 {
19 "cell_type": "heading",
18 "cell_type": "markdown",
20 "level": 2,
21 "metadata": {},
19 "metadata": {},
22 "source": [
20 "source": [
23 "Printed Using Python"
21 "## Printed Using Python"
24 ]
22 ]
25 },
23 },
26 {
24 {
@@ -43,11 +41,10 b''
43 ]
41 ]
44 },
42 },
45 {
43 {
46 "cell_type": "heading",
44 "cell_type": "markdown",
47 "level": 2,
48 "metadata": {},
45 "metadata": {},
49 "source": [
46 "source": [
50 "Pyout"
47 "## Pyout"
51 ]
48 ]
52 },
49 },
53 {
50 {
@@ -111,11 +108,10 b''
111 ]
108 ]
112 },
109 },
113 {
110 {
114 "cell_type": "heading",
111 "cell_type": "markdown",
115 "level": 3,
116 "metadata": {},
112 "metadata": {},
117 "source": [
113 "source": [
118 "Image"
114 "### Image"
119 ]
115 ]
120 },
116 },
121 {
117 {
@@ -28,7 +28,7 b' class TestValidator(TestsBase):'
28 self.assertEqual(isvalid(nb), True)
28 self.assertEqual(isvalid(nb), True)
29
29
30 def test_nb4(self):
30 def test_nb4(self):
31 """Test that a v3 notebook passes validation"""
31 """Test that a v4 notebook passes validation"""
32 with self.fopen(u'test4.ipynb', u'r') as f:
32 with self.fopen(u'test4.ipynb', u'r') as f:
33 nb = read(f, u'json')
33 nb = read(f, u'json')
34 validate(nb)
34 validate(nb)
@@ -37,9 +37,9 b' class TestValidator(TestsBase):'
37 def test_invalid(self):
37 def test_invalid(self):
38 """Test than an invalid notebook does not pass validation"""
38 """Test than an invalid notebook does not pass validation"""
39 # this notebook has a few different errors:
39 # this notebook has a few different errors:
40 # - the name is an integer, rather than a string
41 # - one cell is missing its source
40 # - one cell is missing its source
42 # - one cell has an invalid level
41 # - invalid cell type
42 # - invalid output_type
43 with self.fopen(u'invalid.ipynb', u'r') as f:
43 with self.fopen(u'invalid.ipynb', u'r') as f:
44 nb = read(f, u'json')
44 nb = read(f, u'json')
45 with self.assertRaises(ValidationError):
45 with self.assertRaises(ValidationError):
@@ -6,7 +6,7 b''
6 from .nbbase import (
6 from .nbbase import (
7 NotebookNode, from_dict,
7 NotebookNode, from_dict,
8 nbformat, nbformat_minor, nbformat_schema,
8 nbformat, nbformat_minor, nbformat_schema,
9 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
9 new_code_cell, new_markdown_cell, new_notebook,
10 new_output, output_from_msg,
10 new_output, output_from_msg,
11 )
11 )
12
12
@@ -4,6 +4,7 b''
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import json
6 import json
7 import re
7
8
8 from .nbbase import (
9 from .nbbase import (
9 nbformat, nbformat_minor,
10 nbformat, nbformat_minor,
@@ -72,6 +73,7 b' def upgrade(nb, from_version=3, from_minor=0):'
72 def upgrade_cell(cell):
73 def upgrade_cell(cell):
73 """upgrade a cell from v3 to v4
74 """upgrade a cell from v3 to v4
74
75
76 heading cell -> markdown heading
75 code cell:
77 code cell:
76 - remove language metadata
78 - remove language metadata
77 - cell.input -> cell.source
79 - cell.input -> cell.source
@@ -82,9 +84,16 b' def upgrade_cell(cell):'
82 if cell.cell_type == 'code':
84 if cell.cell_type == 'code':
83 cell.pop('language', '')
85 cell.pop('language', '')
84 cell.metadata.collapsed = cell.pop('collapsed')
86 cell.metadata.collapsed = cell.pop('collapsed')
85 cell.source = cell.pop('input')
87 cell.source = cell.pop('input', '')
86 cell.execution_count = cell.pop('prompt_number', None)
88 cell.execution_count = cell.pop('prompt_number', None)
87 cell.outputs = upgrade_outputs(cell.outputs)
89 cell.outputs = upgrade_outputs(cell.outputs)
90 elif cell.cell_type == 'heading':
91 cell.cell_type = 'markdown'
92 level = cell.pop('level', 1)
93 cell.source = '{hashes} {single_line}'.format(
94 hashes='#' * level,
95 single_line = ' '.join(cell.get('source', '').splitlines()),
96 )
88 elif cell.cell_type == 'html':
97 elif cell.cell_type == 'html':
89 # Technically, this exists. It will never happen in practice.
98 # Technically, this exists. It will never happen in practice.
90 cell.cell_type = 'markdown'
99 cell.cell_type = 'markdown'
@@ -98,6 +107,8 b' def downgrade_cell(cell):'
98 - cell.input <- cell.source
107 - cell.input <- cell.source
99 - cell.prompt_number <- cell.execution_count
108 - cell.prompt_number <- cell.execution_count
100 - update outputs
109 - update outputs
110 markdown cell:
111 - single-line heading -> heading cell
101 """
112 """
102 if cell.cell_type == 'code':
113 if cell.cell_type == 'code':
103 cell.language = 'python'
114 cell.language = 'python'
@@ -105,6 +116,13 b' def downgrade_cell(cell):'
105 cell.prompt_number = cell.pop('execution_count', None)
116 cell.prompt_number = cell.pop('execution_count', None)
106 cell.collapsed = cell.metadata.pop('collapsed', False)
117 cell.collapsed = cell.metadata.pop('collapsed', False)
107 cell.outputs = downgrade_outputs(cell.outputs)
118 cell.outputs = downgrade_outputs(cell.outputs)
119 elif cell.cell_type == 'markdown':
120 source = cell.get('source', '')
121 if '\n' not in source and source.startswith('#'):
122 prefix, text = re.match(r'(#+)\s*(.*)', source).groups()
123 cell.cell_type = 'heading'
124 cell.source = text
125 cell.level = len(prefix)
108 return cell
126 return cell
109
127
110 _mime_map = {
128 _mime_map = {
@@ -121,16 +121,6 b" def new_markdown_cell(source='', **kwargs):"
121 validate(cell, 'markdown_cell')
121 validate(cell, 'markdown_cell')
122 return cell
122 return cell
123
123
124 def new_heading_cell(source='', **kwargs):
125 """Create a new heading cell"""
126 cell = NotebookNode(cell_type='heading', source=source)
127 cell.update(from_dict(kwargs))
128 cell.setdefault('metadata', NotebookNode())
129 cell.setdefault('level', 1)
130
131 validate(cell, 'heading_cell')
132 return cell
133
134 def new_raw_cell(source='', **kwargs):
124 def new_raw_cell(source='', **kwargs):
135 """Create a new raw cell"""
125 """Create a new raw cell"""
136 cell = NotebookNode(cell_type='raw', source=source)
126 cell = NotebookNode(cell_type='raw', source=source)
@@ -59,7 +59,6 b''
59 "oneOf": [
59 "oneOf": [
60 {"$ref": "#/definitions/raw_cell"},
60 {"$ref": "#/definitions/raw_cell"},
61 {"$ref": "#/definitions/markdown_cell"},
61 {"$ref": "#/definitions/markdown_cell"},
62 {"$ref": "#/definitions/heading_cell"},
63 {"$ref": "#/definitions/code_cell"}
62 {"$ref": "#/definitions/code_cell"}
64 ]
63 ]
65 }
64 }
@@ -118,34 +117,6 b''
118 }
117 }
119 },
118 },
120
119
121 "heading_cell": {
122 "description": "Notebook heading cell.",
123 "type": "object",
124 "additionalProperties": false,
125 "required": ["cell_type", "metadata", "source", "level"],
126 "properties": {
127 "cell_type": {
128 "description": "String identifying the type of cell.",
129 "enum": ["heading"]
130 },
131 "metadata": {
132 "description": "Cell-level metadata.",
133 "type": "object",
134 "properties": {
135 "name": {"$ref": "#/definitions/misc/metadata_name"},
136 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
137 },
138 "additionalProperties": true
139 },
140 "source": {"$ref": "#/definitions/misc/source"},
141 "level": {
142 "description": "Level of heading cells.",
143 "type": "integer",
144 "minimum": 1
145 }
146 }
147 },
148
149 "code_cell": {
120 "code_cell": {
150 "description": "Notebook code cell.",
121 "description": "Notebook code cell.",
151 "type": "object",
122 "type": "object",
@@ -44,14 +44,15 b' def rejoin_lines(nb):'
44 for cell in nb.cells:
44 for cell in nb.cells:
45 if 'source' in cell and isinstance(cell.source, list):
45 if 'source' in cell and isinstance(cell.source, list):
46 cell.source = _join_lines(cell.source)
46 cell.source = _join_lines(cell.source)
47 if cell.cell_type == 'code':
47 if cell.get('cell_type', None) == 'code':
48 for output in cell.outputs:
48 for output in cell.get('outputs', []):
49 if output.output_type in {'execute_result', 'display_data'}:
49 output_type = output.get('output_type', '')
50 for key, value in output.data.items():
50 if output_type in {'execute_result', 'display_data'}:
51 for key, value in output.get('data', {}).items():
51 if key != 'application/json' and isinstance(value, list):
52 if key != 'application/json' and isinstance(value, list):
52 output.data[key] = _join_lines(value)
53 output.data[key] = _join_lines(value)
53 elif output.output_type == 'stream':
54 elif output_type:
54 if isinstance(output.text, list):
55 if isinstance(output.get('text', ''), list):
55 output.text = _join_lines(output.text)
56 output.text = _join_lines(output.text)
56 return nb
57 return nb
57
58
@@ -4,7 +4,7 b' import os'
4 from base64 import encodestring
4 from base64 import encodestring
5
5
6 from ..nbbase import (
6 from ..nbbase import (
7 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
7 new_code_cell, new_markdown_cell, new_notebook,
8 new_output, new_raw_cell
8 new_output, new_raw_cell
9 )
9 )
10
10
@@ -31,9 +31,8 b' cells.append(new_raw_cell('
31 source='A random array',
31 source='A random array',
32 ))
32 ))
33
33
34 cells.append(new_heading_cell(
34 cells.append(new_markdown_cell(
35 source=u'My Heading',
35 source=u'## My Heading',
36 level=2,
37 ))
36 ))
38
37
39 cells.append(new_code_cell(
38 cells.append(new_code_cell(
@@ -1,10 +1,13 b''
1 import copy
1 import copy
2
2
3 import nose.tools as nt
4
3 from IPython.nbformat.current import validate
5 from IPython.nbformat.current import validate
4 from .. import convert
6 from .. import convert
5
7
6 from . import nbexamples
8 from . import nbexamples
7 from IPython.nbformat.v3.tests import nbexamples as v3examples
9 from IPython.nbformat.v3.tests import nbexamples as v3examples
10 from IPython.nbformat import v3, v4
8
11
9 def test_upgrade_notebook():
12 def test_upgrade_notebook():
10 nb03 = copy.deepcopy(v3examples.nb0)
13 nb03 = copy.deepcopy(v3examples.nb0)
@@ -17,3 +20,48 b' def test_downgrade_notebook():'
17 validate(nb04)
20 validate(nb04)
18 nb03 = convert.downgrade(nb04)
21 nb03 = convert.downgrade(nb04)
19 validate(nb03)
22 validate(nb03)
23
24 def test_upgrade_heading():
25 v3h = v3.new_heading_cell
26 v4m = v4.new_markdown_cell
27 for v3cell, expected in [
28 (
29 v3h(source='foo', level=1),
30 v4m(source='# foo'),
31 ),
32 (
33 v3h(source='foo\nbar\nmulti-line\n', level=4),
34 v4m(source='#### foo bar multi-line'),
35 ),
36 ]:
37 upgraded = convert.upgrade_cell(v3cell)
38 nt.assert_equal(upgraded, expected)
39
40 def test_downgrade_heading():
41 v3h = v3.new_heading_cell
42 v4m = v4.new_markdown_cell
43 v3m = lambda source: v3.new_text_cell('markdown', source)
44 for v4cell, expected in [
45 (
46 v4m(source='# foo'),
47 v3h(source='foo', level=1),
48 ),
49 (
50 v4m(source='#foo'),
51 v3h(source='foo', level=1),
52 ),
53 (
54 v4m(source='#\tfoo'),
55 v3h(source='foo', level=1),
56 ),
57 (
58 v4m(source='# \t foo'),
59 v3h(source='foo', level=1),
60 ),
61 (
62 v4m(source='# foo\nbar'),
63 v3m(source='# foo\nbar'),
64 ),
65 ]:
66 downgraded = convert.downgrade_cell(v4cell)
67 nt.assert_equal(downgraded, expected)
@@ -6,7 +6,7 b' import nose.tools as nt'
6 from IPython.nbformat.validator import isvalid, validate, ValidationError
6 from IPython.nbformat.validator import isvalid, validate, ValidationError
7 from ..nbbase import (
7 from ..nbbase import (
8 NotebookNode, nbformat,
8 NotebookNode, nbformat,
9 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
9 new_code_cell, new_markdown_cell, new_notebook,
10 new_output, new_raw_cell,
10 new_output, new_raw_cell,
11 )
11 )
12
12
@@ -34,17 +34,6 b' def test_raw_cell():'
34 cell = new_raw_cell('hi')
34 cell = new_raw_cell('hi')
35 nt.assert_equal(cell.source, u'hi')
35 nt.assert_equal(cell.source, u'hi')
36
36
37 def test_empty_heading_cell():
38 cell = new_heading_cell()
39 nt.assert_equal(cell.cell_type, u'heading')
40 nt.assert_equal(cell.source, '')
41 nt.assert_equal(cell.level, 1)
42
43 def test_heading_cell():
44 cell = new_heading_cell(u'hi', level=2)
45 nt.assert_equal(cell.source, u'hi')
46 nt.assert_equal(cell.level, 2)
47
48 def test_empty_code_cell():
37 def test_empty_code_cell():
49 cell = new_code_cell('hi')
38 cell = new_code_cell('hi')
50 nt.assert_equal(cell.cell_type, 'code')
39 nt.assert_equal(cell.cell_type, 'code')
@@ -12,7 +12,7 b' from IPython.nbformat.validator import validate, ValidationError'
12 from ..nbjson import reads
12 from ..nbjson import reads
13 from ..nbbase import (
13 from ..nbbase import (
14 nbformat,
14 nbformat,
15 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
15 new_code_cell, new_markdown_cell, new_notebook,
16 new_output, new_raw_cell,
16 new_output, new_raw_cell,
17 )
17 )
18
18
@@ -73,31 +73,6 b' def test_invalid_markdown_cell():'
73 with nt.assert_raises(ValidationError):
73 with nt.assert_raises(ValidationError):
74 validate4(cell, 'markdown_cell')
74 validate4(cell, 'markdown_cell')
75
75
76 def test_invalid_heading_cell():
77 cell = new_heading_cell()
78
79 cell['source'] = 5
80 with nt.assert_raises(ValidationError):
81 validate4(cell, 'heading_cell')
82
83 cell = new_heading_cell()
84 del cell['metadata']
85
86 with nt.assert_raises(ValidationError):
87 validate4(cell, 'heading_cell')
88
89 cell = new_heading_cell()
90 del cell['source']
91
92 with nt.assert_raises(ValidationError):
93 validate4(cell, 'heading_cell')
94
95 cell = new_heading_cell()
96 del cell['cell_type']
97
98 with nt.assert_raises(ValidationError):
99 validate4(cell, 'heading_cell')
100
101 def test_invalid_raw_cell():
76 def test_invalid_raw_cell():
102 cell = new_raw_cell()
77 cell = new_raw_cell()
103
78
@@ -82,24 +82,9 b' as defined in `GitHub-flavored markdown`_, and implemented in marked_.'
82 "source" : ["some *markdown*"],
82 "source" : ["some *markdown*"],
83 }
83 }
84
84
85 .. versionchanged:: 4.0
85
86
86 Heading cells
87 Heading cells have been removed, in favor of simple headings in markdown.
87 -------------
88
89 Heading cells are single lines describing a section header (mapping onto h1-h6 tags in HTML).
90 These cells indicate structure of the document,
91 and are used for things like outline-views and automatically generating HTML anchors
92 within the page for quick navigation.
93 They have a ``level`` field, with an integer value from 1-6 (inclusive).
94
95 .. sourcecode:: python
96
97 {
98 "cell_type" : "markdown",
99 "metadata" : {},
100 "level" : 1, # An integer on [1-6]
101 "source" : ["A simple heading"],
102 }
103
88
104
89
105 Code cells
90 Code cells
General Comments 0
You need to be logged in to leave comments. Login now