##// END OF EJS Templates
Merge branch 'master' of github.com:ipython/ipython
Brian E. Granger -
r16053:50c0a9b1 merge
parent child Browse files
Show More
@@ -0,0 +1,78 b''
1 // Test the notebook dual mode feature.
2
3 // Test
4 casper.notebook_test(function () {
5 var a = 'print("a")';
6 var index = this.append_cell(a);
7 this.execute_cell_then(index);
8
9 var b = 'print("b")';
10 index = this.append_cell(b);
11 this.execute_cell_then(index);
12
13 var c = 'print("c")';
14 index = this.append_cell(c);
15 this.execute_cell_then(index);
16
17 this.then(function () {
18 this.validate_notebook_state('initial state', 'edit', 0);
19 this.trigger_keydown('esc');
20 this.validate_notebook_state('esc', 'command', 0);
21 this.trigger_keydown('down');
22 this.validate_notebook_state('down', 'command', 1);
23 this.trigger_keydown('enter');
24 this.validate_notebook_state('enter', 'edit', 1);
25 this.trigger_keydown('j');
26 this.validate_notebook_state('j in edit mode', 'edit', 1);
27 this.trigger_keydown('esc');
28 this.validate_notebook_state('esc', 'command', 1);
29 this.trigger_keydown('j');
30 this.validate_notebook_state('j in command mode', 'command', 2);
31 this.click_cell_editor(0);
32 this.validate_notebook_state('click cell 0', 'edit', 0);
33 this.click_cell_editor(3);
34 this.validate_notebook_state('click cell 3', 'edit', 3);
35 this.trigger_keydown('esc');
36 this.validate_notebook_state('esc', 'command', 3);
37
38 // Open keyboard help
39 this.evaluate(function(){
40 $('#keyboard_shortcuts a').click();
41 }, {});
42
43 this.trigger_keydown('k');
44 this.validate_notebook_state('k in command mode while keyboard help is up', 'command', 3);
45
46 // Close keyboard help
47 this.evaluate(function(){
48 $('div.modal button.close').click();
49 }, {});
50
51 this.trigger_keydown('k');
52 this.validate_notebook_state('k in command mode', 'command', 2);
53 this.click_cell_editor(0);
54 this.validate_notebook_state('click cell 0', 'edit', 0);
55 this.focus_notebook();
56 this.validate_notebook_state('focus #notebook', 'command', 0);
57 this.click_cell_editor(0);
58 this.validate_notebook_state('click cell 0', 'edit', 0);
59 this.focus_notebook();
60 this.validate_notebook_state('focus #notebook', 'command', 0);
61 this.click_cell_editor(3);
62 this.validate_notebook_state('click cell 3', 'edit', 3);
63
64 // Cell deletion
65 this.trigger_keydown('esc', 'd', 'd');
66 this.test.assertEquals(this.get_cells_length(), 3, 'dd actually deletes a cell');
67 this.validate_notebook_state('dd', 'command', 2);
68
69 // Make sure that if the time between d presses is too long, nothing gets removed.
70 this.trigger_keydown('d');
71 });
72 this.wait(1000);
73 this.then(function () {
74 this.trigger_keydown('d');
75 this.test.assertEquals(this.get_cells_length(), 3, "d, 1 second wait, d doesn't delete a cell");
76 this.validate_notebook_state('d, 1 second wait, d', 'command', 2);
77 });
78 });
@@ -0,0 +1,51 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 var b = 'print("b")';
9 index = this.append_cell(b);
10 this.execute_cell_then(index);
11
12 var c = 'print("c")';
13 index = this.append_cell(c);
14 this.execute_cell_then(index);
15
16 this.then(function () {
17
18 // Up and down in command mode
19 this.select_cell(3);
20 this.trigger_keydown('j');
21 this.validate_notebook_state('j at end of notebook', 'command', 3);
22 this.trigger_keydown('down');
23 this.validate_notebook_state('down at end of notebook', 'command', 3);
24 this.trigger_keydown('up');
25 this.validate_notebook_state('up', 'command', 2);
26 this.select_cell(0);
27 this.validate_notebook_state('select 0', 'command', 0);
28 this.trigger_keydown('k');
29 this.validate_notebook_state('k at top of notebook', 'command', 0);
30 this.trigger_keydown('up');
31 this.validate_notebook_state('up at top of notebook', 'command', 0);
32 this.trigger_keydown('down');
33 this.validate_notebook_state('down', 'command', 1);
34
35 // Up and down in edit mode
36 this.click_cell_editor(3);
37 this.validate_notebook_state('click cell 3', 'edit', 3);
38 this.trigger_keydown('down');
39 this.validate_notebook_state('down at end of notebook', 'edit', 3);
40 this.set_cell_editor_cursor(3, 0, 0);
41 this.trigger_keydown('up');
42 this.validate_notebook_state('up', 'edit', 2);
43 this.click_cell_editor(0);
44 this.validate_notebook_state('click 0', 'edit', 0);
45 this.trigger_keydown('up');
46 this.validate_notebook_state('up at top of notebook', 'edit', 0);
47 this.set_cell_editor_cursor(0, 0, 10);
48 this.trigger_keydown('down');
49 this.validate_notebook_state('down', 'edit', 1);
50 });
51 });
@@ -0,0 +1,27 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 var b = 'print("b")';
9 index = this.append_cell(b);
10 this.execute_cell_then(index);
11
12 var c = 'print("c")';
13 index = this.append_cell(c);
14 this.execute_cell_then(index);
15
16 this.then(function () {
17 // Cell insertion
18 this.select_cell(2);
19 this.trigger_keydown('a'); // Creates one cell
20 this.test.assertEquals(this.get_cell_text(2), '', 'a; New cell 2 text is empty');
21 this.validate_notebook_state('a', 'command', 2);
22 this.trigger_keydown('b'); // Creates one cell
23 this.test.assertEquals(this.get_cell_text(2), '', 'b; Cell 2 text is still empty');
24 this.test.assertEquals(this.get_cell_text(3), '', 'b; New cell 3 text is empty');
25 this.validate_notebook_state('b', 'command', 3);
26 });
27 }); No newline at end of file
@@ -0,0 +1,28 b''
1 // Test keyboard shortcuts that change the cell's mode.
2
3 // Test
4 casper.notebook_test(function () {
5 this.then(function () {
6 // Cell mode change
7 this.select_cell(0);
8 this.trigger_keydown('esc','r');
9 this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw');
10 this.trigger_keydown('1');
11 this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading');
12 this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading');
13 this.trigger_keydown('2');
14 this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading');
15 this.trigger_keydown('3');
16 this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading');
17 this.trigger_keydown('4');
18 this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading');
19 this.trigger_keydown('5');
20 this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading');
21 this.trigger_keydown('6');
22 this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading');
23 this.trigger_keydown('m');
24 this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown');
25 this.trigger_keydown('y');
26 this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code');
27 });
28 }); No newline at end of file
@@ -0,0 +1,55 b''
1
2
3 // Test
4 casper.notebook_test(function () {
5 var a = 'print("a")';
6 var index = this.append_cell(a);
7 this.execute_cell_then(index);
8
9 var b = 'print("b")';
10 index = this.append_cell(b);
11 this.execute_cell_then(index);
12
13 var c = 'print("c")';
14 index = this.append_cell(c);
15 this.execute_cell_then(index);
16
17 this.then(function () {
18 // Copy/paste/cut
19 var num_cells = this.get_cells_length();
20 this.test.assertEquals(this.get_cell_text(1), a, 'Verify that cell 1 is a');
21 this.select_cell(1);
22 this.trigger_keydown('x'); // Cut
23 this.validate_notebook_state('x', 'command', 1);
24 this.test.assertEquals(this.get_cells_length(), num_cells-1, 'Verify that a cell was removed.');
25 this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 2 is now where cell 1 was.');
26 this.select_cell(2);
27 this.trigger_keydown('v'); // Paste
28 this.validate_notebook_state('v', 'command', 3); // Selection should move to pasted cell, below current cell.
29 this.test.assertEquals(this.get_cell_text(3), a, 'Verify that cell 3 has the cut contents.');
30 this.test.assertEquals(this.get_cells_length(), num_cells, 'Verify a the cell was added.');
31 this.trigger_keydown('v'); // Paste
32 this.validate_notebook_state('v', 'command', 4); // Selection should move to pasted cell, below current cell.
33 this.test.assertEquals(this.get_cell_text(4), a, 'Verify that cell 4 has the cut contents.');
34 this.test.assertEquals(this.get_cells_length(), num_cells+1, 'Verify a the cell was added.');
35 this.select_cell(1);
36 this.trigger_keydown('c'); // Copy
37 this.validate_notebook_state('c', 'command', 1);
38 this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 1 is b');
39 this.select_cell(2);
40 this.trigger_keydown('c'); // Copy
41 this.validate_notebook_state('c', 'command', 2);
42 this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 is c');
43 this.select_cell(4);
44 this.trigger_keydown('v'); // Paste
45 this.validate_notebook_state('v', 'command', 5);
46 this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 still has the copied contents.');
47 this.test.assertEquals(this.get_cell_text(5), c, 'Verify that cell 5 has the copied contents.');
48 this.test.assertEquals(this.get_cells_length(), num_cells+2, 'Verify a the cell was added.');
49 this.select_cell(0);
50 this.trigger_keydown('shift-v'); // Paste
51 this.validate_notebook_state('shift-v', 'command', 0);
52 this.test.assertEquals(this.get_cell_text(0), c, 'Verify that cell 0 has the copied contents.');
53 this.test.assertEquals(this.get_cells_length(), num_cells+3, 'Verify a the cell was added.');
54 });
55 }); No newline at end of file
@@ -0,0 +1,72 b''
1 // Test keyboard invoked execution.
2
3 // Test
4 casper.notebook_test(function () {
5 var a = 'print("a")';
6 var index = this.append_cell(a);
7 this.execute_cell_then(index);
8
9 var b = 'print("b")';
10 index = this.append_cell(b);
11 this.execute_cell_then(index);
12
13 var c = 'print("c")';
14 index = this.append_cell(c);
15 this.execute_cell_then(index);
16
17 this.then(function () {
18
19 // shift-enter
20 // last cell in notebook
21 var base_index = 3;
22 this.select_cell(base_index);
23 this.trigger_keydown('shift-enter'); // Creates one cell
24 this.validate_notebook_state('shift-enter (no cell below)', 'edit', base_index + 1);
25 // not last cell in notebook & starts in edit mode
26 this.click_cell_editor(base_index);
27 this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
28 this.trigger_keydown('shift-enter');
29 this.validate_notebook_state('shift-enter (cell exists below)', 'command', base_index + 1);
30 // starts in command mode
31 this.trigger_keydown('k');
32 this.validate_notebook_state('k in comand mode', 'command', base_index);
33 this.trigger_keydown('shift-enter');
34 this.validate_notebook_state('shift-enter (start in command mode)', 'command', base_index + 1);
35
36 // ctrl-enter
37 // last cell in notebook
38 base_index++;
39 this.trigger_keydown('ctrl-enter');
40 this.validate_notebook_state('ctrl-enter (no cell below)', 'command', base_index);
41 // not last cell in notebook & starts in edit mode
42 this.click_cell_editor(base_index-1);
43 this.validate_notebook_state('click cell ' + (base_index-1), 'edit', base_index-1);
44 this.trigger_keydown('ctrl-enter');
45 this.validate_notebook_state('ctrl-enter (cell exists below)', 'command', base_index-1);
46 // starts in command mode
47 this.trigger_keydown('j');
48 this.validate_notebook_state('j in comand mode', 'command', base_index);
49 this.trigger_keydown('ctrl-enter');
50 this.validate_notebook_state('ctrl-enter (start in command mode)', 'command', base_index);
51
52 // alt-enter
53 // last cell in notebook
54 this.trigger_keydown('alt-enter'); // Creates one cell
55 this.validate_notebook_state('alt-enter (no cell below)', 'edit', base_index + 1);
56 // not last cell in notebook & starts in edit mode
57 this.click_cell_editor(base_index);
58 this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
59 this.trigger_keydown('alt-enter'); // Creates one cell
60 this.validate_notebook_state('alt-enter (cell exists below)', 'edit', base_index + 1);
61 // starts in command mode
62 this.trigger_keydown('esc', 'k');
63 this.validate_notebook_state('k in comand mode', 'command', base_index);
64 this.trigger_keydown('alt-enter'); // Creates one cell
65 this.validate_notebook_state('alt-enter (start in command mode)', 'edit', base_index + 1);
66
67 // Notebook will now have 8 cells, the index of the last cell will be 7.
68 this.test.assertEquals(this.get_cells_length(), 8, '*-enter commands added cells where needed.');
69 this.select_cell(7);
70 this.validate_notebook_state('click cell ' + 7 + ' and esc', 'command', 7);
71 });
72 }); No newline at end of file
@@ -0,0 +1,39 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 this.then(function () {
9 // Markdown rendering / unredering
10 this.select_cell(1);
11 this.validate_notebook_state('select 1', 'command', 1);
12 this.trigger_keydown('m');
13 this.test.assertEquals(this.get_cell(1).cell_type, 'markdown', 'm; cell is markdown');
14 this.test.assertEquals(this.get_cell(1).rendered, false, 'm; cell is rendered');
15 this.trigger_keydown('enter');
16 this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
17 this.validate_notebook_state('enter', 'edit', 1);
18 this.trigger_keydown('ctrl-enter');
19 this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
20 this.validate_notebook_state('enter', 'command', 1);
21 this.trigger_keydown('enter');
22 this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
23 this.select_cell(0);
24 this.test.assertEquals(this.get_cell(1).rendered, false, 'select 0; cell 1 is still unrendered');
25 this.validate_notebook_state('select 0', 'command', 0);
26 this.select_cell(1);
27 this.validate_notebook_state('select 1', 'command', 1);
28 this.trigger_keydown('ctrl-enter');
29 this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
30 this.select_cell(0);
31 this.validate_notebook_state('select 0', 'command', 0);
32 this.trigger_keydown('shift-enter');
33 this.validate_notebook_state('shift-enter', 'command', 1);
34 this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
35 this.trigger_keydown('shift-enter'); // Creates one cell
36 this.validate_notebook_state('shift-enter', 'edit', 2);
37 this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
38 });
39 }); No newline at end of file
@@ -0,0 +1,21 b''
1
2 // Test
3 casper.notebook_test(function () {
4 this.then(function () {
5 // Split and merge cells
6 this.select_cell(0);
7 this.trigger_keydown('a', 'enter'); // Create cell above and enter edit mode.
8 this.validate_notebook_state('a, enter', 'edit', 0);
9 this.set_cell_text(0, 'abcd');
10 this.set_cell_editor_cursor(0, 0, 2);
11 this.test.assertEquals(this.get_cell_text(0), 'abcd', 'Verify that cell 0 has the new contents.');
12 this.trigger_keydown('ctrl-shift-subtract'); // Split
13 this.test.assertEquals(this.get_cell_text(0), 'ab', 'split; Verify that cell 0 has the first half.');
14 this.test.assertEquals(this.get_cell_text(1), 'cd', 'split; Verify that cell 1 has the second half.');
15 this.validate_notebook_state('split', 'edit', 1);
16 this.select_cell(0); // Move up to cell 0
17 this.trigger_keydown('shift-m'); // Merge
18 this.validate_notebook_state('merge', 'command', 0);
19 this.test.assertEquals(this.get_cell_text(0), 'ab\ncd', 'merge; Verify that cell 0 has the merged contents.');
20 });
21 }); No newline at end of file
@@ -0,0 +1,25 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 var b = 'print("b")';
9 index = this.append_cell(b);
10 this.execute_cell_then(index);
11
12 this.then(function () {
13 // Cell movement ( ctrl-(k or j) )
14 this.select_cell(2);
15 this.test.assertEquals(this.get_cell_text(2), b, 'select 2; Cell 2 text is correct');
16 this.trigger_keydown('ctrl-k'); // Move cell 2 up one
17 this.test.assertEquals(this.get_cell_text(1), b, 'ctrl-k; Cell 1 text is correct');
18 this.test.assertEquals(this.get_cell_text(2), a, 'ctrl-k; Cell 2 text is correct');
19 this.validate_notebook_state('ctrl-k', 'command', 1);
20 this.trigger_keydown('ctrl-j'); // Move cell 1 down one
21 this.test.assertEquals(this.get_cell_text(1), a, 'ctrl-j; Cell 1 text is correct');
22 this.test.assertEquals(this.get_cell_text(2), b, 'ctrl-j; Cell 2 text is correct');
23 this.validate_notebook_state('ctrl-j', 'command', 2);
24 });
25 }); No newline at end of file
@@ -0,0 +1,13 b''
1 ====================
2 The IPython notebook
3 ====================
4
5 .. toctree::
6 :maxdepth: 2
7
8 notebook
9 cm_keyboard
10 nbconvert
11 public_server
12 security
13
@@ -0,0 +1,52 b''
1 -----BEGIN PGP PUBLIC KEY BLOCK-----
2 Version: GnuPG v2.0.22 (GNU/Linux)
3
4 mQINBFMx2LoBEAC9xU8JiKI1VlCJ4PT9zqhU5nChQZ06/bj1BBftiMJG07fdGVO0
5 ibOn4TrCoRYaeRlet0UpHzxT4zDa5h3/usJaJNTSRwtWePw2o7Lik8J+F3LionRf
6 8Jz81WpJ+81Klg4UWKErXjBHsu/50aoQm6ZNYG4S2nwOmMVEC4nc44IAA0bb+6kW
7 saFKKzEDsASGyuvyutdyUHiCfvvh5GOC2h9mXYvl4FaMW7K+d2UgCYERcXDNy7C1
8 Bw+uepQ9ELKdG4ZpvonO6BNr1BWLln3wk93AQfD5qhfsYRJIyj0hJlaRLtBU3i6c
9 xs+gQNF4mPmybpPSGuOyUr4FYC7NfoG7IUMLj+DYa6d8LcMJO+9px4IbdhQvzGtC
10 qz5av1TX7/+gnS4L8C9i1g8xgI+MtvogngPmPY4repOlK6y3l/WtxUPkGkyYkn3s
11 RzYyE/GJgTwuxFXzMQs91s+/iELFQq/QwmEJf+g/QYfSAuM+lVGajEDNBYVAQkxf
12 gau4s8Gm0GzTZmINilk+7TxpXtKbFc/Yr4A/fMIHmaQ7KmJB84zKwONsQdVv7Jjj
13 0dpwu8EIQdHxX3k7/Q+KKubEivgoSkVwuoQTG15X9xrOsDZNwfOVQh+JKazPvJtd
14 SNfep96r9t/8gnXv9JI95CGCQ8lNhXBUSBM3BDPTbudc4b6lFUyMXN0mKQARAQAB
15 tCxJUHl0aG9uIFNlY3VyaXR5IFRlYW0gPHNlY3VyaXR5QGlweXRob24ub3JnPokC
16 OAQTAQIAIgUCUzHYugIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQEwJc
17 LcmZYkjuXg//R/t6nMNQmf9W1h52IVfUbRAVmvZ5d063hQHKV2dssxtnA2dRm/x5
18 JZu8Wz7ZrEZpyqwRJO14sxN1/lC3v+zs9XzYXr2lBTZuKCPIBypYVGIynCuWJBQJ
19 rWnfG4+u1RHahnjqlTWTY1C/le6v7SjAvCb6GbdA6k4ZL2EJjQlRaHDmzw3rV/+l
20 LLx6/tYzIsotuflm/bFumyOMmpQQpJjnCkWIVjnRICZvuAn97jLgtTI0+0Rzf4Zb
21 k2BwmHwDRqWCTTcRI9QvTl8AzjW+dNImN22TpGOBPfYj8BCZ9twrpKUbf+jNqJ1K
22 THQzFtpdJ6SzqiFVm74xW4TKqCLkbCQ/HtVjTGMGGz/y7KTtaLpGutQ6XE8SSy6P
23 EffSb5u+kKlQOWaH7Mc3B0yAojz6T3j5RSI8ts6pFi6pZhDg9hBfPK2dT0v/7Mkv
24 E1Z7q2IdjZnhhtGWjDAMtDDn2NbY2wuGoa5jAWAR0WvIbEZ3kOxuLE5/ZOG1FyYm
25 noJRliBz7038nT92EoD5g1pdzuxgXtGCpYyyjRZwaLmmi4CvA+oThKmnqWNY5lyY
26 ricdNHDiyEXK0YafJL1oZgM86MSb0jKJMp5U11nUkUGzkroFfpGDmzBwAzEPgeiF
27 40+qgsKB9lqwb3G7PxvfSi3XwxfXgpm1cTyEaPSzsVzve3d1xeqb7Yq5Ag0EUzHY
28 ugEQALQ5FtLdNoxTxMsgvrRr1ejLiUeRNUfXtN1TYttOfvAhfBVnszjtkpIW8DCB
29 JF/bA7ETiH8OYYn/Fm6MPI5H64IHEncpzxjf57jgpXd9CA9U2OMk/P1nve5zYchP
30 QmP2fJxeAWr0aRH0Mse5JS5nCkh8Xv4nAjsBYeLTJEVOb1gPQFXOiFcVp3gaKAzX
31 GWOZ/mtG/uaNsabH/3TkcQQEgJefd11DWgMB7575GU+eME7c6hn3FPITA5TC5HUX
32 azvjv/PsWGTTVAJluJ3fUDvhpbGwYOh1uV0rB68lPpqVIro18IIJhNDnccM/xqko
33 4fpJdokdg4L1wih+B04OEXnwgjWG8OIphR/oL/+M37VV2U7Om/GE6LGefaYccC9c
34 tIaacRQJmZpG/8RsimFIY2wJ07z8xYBITmhMmOt0bLBv0mU0ym5KH9Dnru1m9QDO
35 AHwcKrDgL85f9MCn+YYw0d1lYxjOXjf+moaeW3izXCJ5brM+MqVtixY6aos3YO29
36 J7SzQ4aEDv3h/oKdDfZny21jcVPQxGDui8sqaZCi8usCcyqWsKvFHcr6vkwaufcm
37 3Knr2HKVotOUF5CDZybopIz1sJvY/5Dx9yfRmtivJtglrxoDKsLi1rQTlEQcFhCS
38 ACjf7txLtv03vWHxmp4YKQFkkOlbyhIcvfPVLTvqGerdT2FHABEBAAGJAh8EGAEC
39 AAkFAlMx2LoCGwwACgkQEwJcLcmZYkgK0BAAny0YUugpZldiHzYNf8I6p2OpiDWv
40 ZHaguTTPg2LJSKaTd+5UHZwRFIWjcSiFu+qTGLNtZAdcr0D5f991CPvyDSLYgOwb
41 Jm2p3GM2KxfECWzFbB/n/PjbZ5iky3+5sPlOdBR4TkfG4fcu5GwUgCkVe5u3USAk
42 C6W5lpeaspDz39HAPRSIOFEX70+xV+6FZ17B7nixFGN+giTpGYOEdGFxtUNmHmf+
43 waJoPECyImDwJvmlMTeP9jfahlB6Pzaxt6TBZYHetI/JR9FU69EmA+XfCSGt5S+0
44 Eoc330gpsSzo2VlxwRCVNrcuKmG7PsFFANok05ssFq1/Djv5rJ++3lYb88b8HSP2
45 3pQJPrM7cQNU8iPku9yLXkY5qsoZOH+3yAia554Dgc8WBhp6fWh58R0dIONQxbbo
46 apNdwvlI8hKFB7TiUL6PNShE1yL+XD201iNkGAJXbLMIC1ImGLirUfU267A3Cop5
47 hoGs179HGBcyj/sKA3uUIFdNtP+NndaP3v4iYhCitdVCvBJMm6K3tW88qkyRGzOk
48 4PW422oyWKwbAPeMk5PubvEFuFAIoBAFn1zecrcOg85RzRnEeXaiemmmH8GOe1Xu
49 Kh+7h8XXyG6RPFy8tCcLOTk+miTqX+4VWy+kVqoS2cQ5IV8WsJ3S7aeIy0H89Z8n
50 5vmLc+Ibz+eT+rM=
51 =XVDe
52 -----END PGP PUBLIC KEY BLOCK-----
@@ -0,0 +1,146 b''
1 Security in IPython notebooks
2 =============================
3
4 As IPython notebooks become more popular for sharing and collaboration,
5 the potential for malicious people to attempt to exploit the notebook
6 for their nefarious purposes increases. IPython 2.0 introduces a
7 security model to prevent execution of untrusted code without explicit
8 user input.
9
10 The problem
11 -----------
12
13 The whole point of IPython is arbitrary code execution. We have no
14 desire to limit what can be done with a notebook, which would negatively
15 impact its utility.
16
17 Unlike other programs, an IPython notebook document includes output.
18 Unlike other documents, that output exists in a context that can execute
19 code (via Javascript).
20
21 The security problem we need to solve is that no code should execute
22 just because a user has **opened** a notebook that **they did not
23 write**. Like any other program, once a user decides to execute code in
24 a notebook, it is considered trusted, and should be allowed to do
25 anything.
26
27 Our security model
28 ------------------
29
30 - Untrusted HTML is always sanitized
31 - Untrusted Javascript is never executed
32 - HTML and Javascript in Markdown cells are never trusted
33 - **Outputs** generated by the user are trusted
34 - Any other HTML or Javascript (in Markdown cells, output generated by
35 others) is never trusted
36 - The central question of trust is "Did the current user do this?"
37
38 The details of trust
39 --------------------
40
41 IPython notebooks store a signature in metadata, which is used to answer
42 the question "Did the current user do this?"
43
44 This signature is a digest of the notebooks contents plus a secret key,
45 known only to the user. The secret key is a user-only readable file in
46 the IPython profile's security directory. By default, this is::
47
48 ~/.ipython/profile_default/security/notebook_secret
49
50 When a notebook is opened by a user, the server computes a signature
51 with the user's key, and compares it with the signature stored in the
52 notebook's metadata. If the signature matches, HTML and Javascript
53 output in the notebook will be trusted at load, otherwise it will be
54 untrusted.
55
56 Any output generated during an interactive session is trusted.
57
58 Updating trust
59 **************
60
61 A notebook's trust is updated when the notebook is saved. If there are
62 any untrusted outputs still in the notebook, the notebook will not be
63 trusted, and no signature will be stored. If all untrusted outputs have
64 been removed (either via ``Clear Output`` or re-execution), then the
65 notebook will become trusted.
66
67 While trust is updated per output, this is only for the duration of a
68 single session. A notebook file on disk is either trusted or not in its
69 entirety.
70
71 Explicit trust
72 **************
73
74 Sometimes re-executing a notebook to generate trusted output is not an
75 option, either because dependencies are unavailable, or it would take a
76 long time. Users can explicitly trust a notebook in two ways:
77
78 - At the command-line, with::
79
80 ipython trust /path/to/notebook.ipynb
81
82 - After loading the untrusted notebook, with ``File / Trust Notebook``
83
84 These two methods simply load the notebook, compute a new signature with
85 the user's key, and then store the newly signed notebook.
86
87 Reporting security issues
88 -------------------------
89
90 If you find a security vulnerability in IPython, either a failure of the
91 code to properly implement the model described here, or a failure of the
92 model itself, please report it to security@ipython.org.
93
94 If you prefer to encrypt your security reports,
95 you can use :download:`this PGP public key <ipython_security.asc>`.
96
97 Affected use cases
98 ------------------
99
100 Some use cases that work in IPython 1.0 will become less convenient in
101 2.0 as a result of the security changes. We do our best to minimize
102 these annoyance, but security is always at odds with convenience.
103
104 Javascript and CSS in Markdown cells
105 ************************************
106
107 While never officially supported, it had become common practice to put
108 hidden Javascript or CSS styling in Markdown cells, so that they would
109 not be visible on the page. Since Markdown cells are now sanitized (by
110 `Google Caja <https://developers.google.com/caja>`__), all Javascript
111 (including click event handlers, etc.) and CSS will be stripped.
112
113 We plan to provide a mechanism for notebook themes, but in the meantime
114 styling the notebook can only be done via either ``custom.css`` or CSS
115 in HTML output. The latter only have an effect if the notebook is
116 trusted, because otherwise the output will be sanitized just like
117 Markdown.
118
119 Collaboration
120 *************
121
122 When collaborating on a notebook, people probably want to see the
123 outputs produced by their colleagues' most recent executions. Since each
124 collaborator's key will differ, this will result in each share starting
125 in an untrusted state. There are three basic approaches to this:
126
127 - re-run notebooks when you get them (not always viable)
128 - explicitly trust notebooks via ``ipython trust`` or the notebook menu
129 (annoying, but easy)
130 - share a notebook secret, and use an IPython profile dedicated to the
131 collaboration while working on the project.
132
133 Multiple profiles or machines
134 *****************************
135
136 Since the notebook secret is stored in a profile directory by default,
137 opening a notebook with a different profile or on a different machine
138 will result in a different key, and thus be untrusted. The only current
139 way to address this is by sharing the notebook secret. This can be
140 facilitated by setting the configurable:
141
142 .. sourcecode:: python
143
144 c.NotebookApp.secret_file = "/path/to/notebook_secret"
145
146 in each profile, and only sharing the secret once per machine.
@@ -3,12 +3,12 b''
3 =============================
3 =============================
4
4
5 IPython is licensed under the terms of the Modified BSD License (also known as
5 IPython is licensed under the terms of the Modified BSD License (also known as
6 New or Revised BSD), as follows:
6 New or Revised or 3-Clause BSD), as follows:
7
7
8 Copyright (c) 2008-2010, IPython Development Team
8 - Copyright (c) 2008-2014, IPython Development Team
9 Copyright (c) 2001-2007, Fernando Perez. <fernando.perez@colorado.edu>
9 - Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
10 - Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
11 - Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12
12
13 All rights reserved.
13 All rights reserved.
14
14
@@ -50,15 +50,7 b' details is kept in the documentation directory, in the file'
50 ``about/credits.txt``.
50 ``about/credits.txt``.
51
51
52 The core team that coordinates development on GitHub can be found here:
52 The core team that coordinates development on GitHub can be found here:
53 http://github.com/ipython. As of late 2010, it consists of:
53 https://github.com/ipython/.
54
55 * Brian E. Granger
56 * Jonathan March
57 * Evan Patterson
58 * Fernando Perez
59 * Min Ragan-Kelley
60 * Robert Kern
61
62
54
63 Our Copyright Policy
55 Our Copyright Policy
64 --------------------
56 --------------------
@@ -73,13 +65,10 b' changes/contributions they have specific copyright on, they should indicate'
73 their copyright in the commit message of the change, when they commit the
65 their copyright in the commit message of the change, when they commit the
74 change to one of the IPython repositories.
66 change to one of the IPython repositories.
75
67
76 With this in mind, the following banner should be used in any source code file
68 With this in mind, the following banner should be used in any source code file
77 to indicate the copyright and license terms:
69 to indicate the copyright and license terms:
78
70
79 #-----------------------------------------------------------------------------
71 ::
80 # Copyright (c) 2010, IPython Development Team.
72
81 #
73 # Copyright (c) IPython Development Team.
82 # Distributed under the terms of the Modified BSD License.
74 # Distributed under the terms of the Modified BSD License.
83 #
84 # The full license is in the file COPYING.txt, distributed with this software.
85 #-----------------------------------------------------------------------------
@@ -29,6 +29,7 b' import threading'
29 # Our own packages
29 # Our own packages
30 from IPython.config.configurable import Configurable
30 from IPython.config.configurable import Configurable
31 from IPython.external.decorator import decorator
31 from IPython.external.decorator import decorator
32 from IPython.utils.decorators import undoc
32 from IPython.utils.path import locate_profile
33 from IPython.utils.path import locate_profile
33 from IPython.utils import py3compat
34 from IPython.utils import py3compat
34 from IPython.utils.traitlets import (
35 from IPython.utils.traitlets import (
@@ -40,6 +41,7 b' from IPython.utils.warn import warn'
40 # Classes and functions
41 # Classes and functions
41 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
42
43
44 @undoc
43 class DummyDB(object):
45 class DummyDB(object):
44 """Dummy DB that will act as a black hole for history.
46 """Dummy DB that will act as a black hole for history.
45
47
@@ -59,7 +61,7 b' class DummyDB(object):'
59
61
60 @decorator
62 @decorator
61 def needs_sqlite(f, self, *a, **kw):
63 def needs_sqlite(f, self, *a, **kw):
62 """return an empty list in the absence of sqlite"""
64 """Decorator: return an empty list in the absence of sqlite."""
63 if sqlite3 is None or not self.enabled:
65 if sqlite3 is None or not self.enabled:
64 return []
66 return []
65 else:
67 else:
@@ -69,6 +71,7 b' def needs_sqlite(f, self, *a, **kw):'
69 if sqlite3 is not None:
71 if sqlite3 is not None:
70 DatabaseError = sqlite3.DatabaseError
72 DatabaseError = sqlite3.DatabaseError
71 else:
73 else:
74 @undoc
72 class DatabaseError(Exception):
75 class DatabaseError(Exception):
73 "Dummy exception when sqlite could not be imported. Should never occur."
76 "Dummy exception when sqlite could not be imported. Should never occur."
74
77
@@ -159,7 +162,7 b' class HistoryAccessor(Configurable):'
159 hist_file : str
162 hist_file : str
160 Path to an SQLite history database stored by IPython. If specified,
163 Path to an SQLite history database stored by IPython. If specified,
161 hist_file overrides profile.
164 hist_file overrides profile.
162 config :
165 config : :class:`~IPython.config.loader.Config`
163 Config object. hist_file can also be set through this.
166 Config object. hist_file can also be set through this.
164 """
167 """
165 # We need a pointer back to the shell for various tasks.
168 # We need a pointer back to the shell for various tasks.
@@ -254,34 +257,43 b' class HistoryAccessor(Configurable):'
254
257
255 @needs_sqlite
258 @needs_sqlite
256 @catch_corrupt_db
259 @catch_corrupt_db
257 def get_session_info(self, session=0):
260 def get_session_info(self, session):
258 """get info about a session
261 """Get info about a session.
259
262
260 Parameters
263 Parameters
261 ----------
264 ----------
262
265
263 session : int
266 session : int
264 Session number to retrieve. The current session is 0, and negative
267 Session number to retrieve.
265 numbers count back from current session, so -1 is previous session.
266
268
267 Returns
269 Returns
268 -------
270 -------
269
271
270 (session_id [int], start [datetime], end [datetime], num_cmds [int],
272 session_id : int
271 remark [unicode])
273 Session ID number
272
274 start : datetime
273 Sessions that are running or did not exit cleanly will have `end=None`
275 Timestamp for the start of the session.
274 and `num_cmds=None`.
276 end : datetime
275
277 Timestamp for the end of the session, or None if IPython crashed.
278 num_cmds : int
279 Number of commands run, or None if IPython crashed.
280 remark : unicode
281 A manually set description.
276 """
282 """
277
278 if session <= 0:
279 session += self.session_number
280
281 query = "SELECT * from sessions where session == ?"
283 query = "SELECT * from sessions where session == ?"
282 return self.db.execute(query, (session,)).fetchone()
284 return self.db.execute(query, (session,)).fetchone()
283
285
284 @catch_corrupt_db
286 @catch_corrupt_db
287 def get_last_session_id(self):
288 """Get the last session ID currently in the database.
289
290 Within IPython, this should be the same as the value stored in
291 :attr:`HistoryManager.session_number`.
292 """
293 for record in self.get_tail(n=1, include_latest=True):
294 return record[0]
295
296 @catch_corrupt_db
285 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
297 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
286 """Get the last n lines from the history database.
298 """Get the last n lines from the history database.
287
299
@@ -374,9 +386,10 b' class HistoryAccessor(Configurable):'
374
386
375 Returns
387 Returns
376 -------
388 -------
377 An iterator over the desired lines. Each line is a 3-tuple, either
389 entries
378 (session, line, input) if output is False, or
390 An iterator over the desired lines. Each line is a 3-tuple, either
379 (session, line, (input, output)) if output is True.
391 (session, line, input) if output is False, or
392 (session, line, (input, output)) if output is True.
380 """
393 """
381 if stop:
394 if stop:
382 lineclause = "line >= ? AND line < ?"
395 lineclause = "line >= ? AND line < ?"
@@ -535,6 +548,35 b' class HistoryManager(HistoryAccessor):'
535 # ------------------------------
548 # ------------------------------
536 # Methods for retrieving history
549 # Methods for retrieving history
537 # ------------------------------
550 # ------------------------------
551 def get_session_info(self, session=0):
552 """Get info about a session.
553
554 Parameters
555 ----------
556
557 session : int
558 Session number to retrieve. The current session is 0, and negative
559 numbers count back from current session, so -1 is the previous session.
560
561 Returns
562 -------
563
564 session_id : int
565 Session ID number
566 start : datetime
567 Timestamp for the start of the session.
568 end : datetime
569 Timestamp for the end of the session, or None if IPython crashed.
570 num_cmds : int
571 Number of commands run, or None if IPython crashed.
572 remark : unicode
573 A manually set description.
574 """
575 if session <= 0:
576 session += self.session_number
577
578 return super(HistoryManager, self).get_session_info(session=session)
579
538 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
580 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
539 """Get input and output history from the current session. Called by
581 """Get input and output history from the current session. Called by
540 get_range, and takes similar parameters."""
582 get_range, and takes similar parameters."""
@@ -578,9 +620,10 b' class HistoryManager(HistoryAccessor):'
578
620
579 Returns
621 Returns
580 -------
622 -------
581 An iterator over the desired lines. Each line is a 3-tuple, either
623 entries
582 (session, line, input) if output is False, or
624 An iterator over the desired lines. Each line is a 3-tuple, either
583 (session, line, (input, output)) if output is True.
625 (session, line, input) if output is False, or
626 (session, line, (input, output)) if output is True.
584 """
627 """
585 if session <= 0:
628 if session <= 0:
586 session += self.session_number
629 session += self.session_number
@@ -594,7 +637,7 b' class HistoryManager(HistoryAccessor):'
594 ## ----------------------------
637 ## ----------------------------
595 def store_inputs(self, line_num, source, source_raw=None):
638 def store_inputs(self, line_num, source, source_raw=None):
596 """Store source and raw input in history and create input cache
639 """Store source and raw input in history and create input cache
597 variables _i*.
640 variables ``_i*``.
598
641
599 Parameters
642 Parameters
600 ----------
643 ----------
@@ -765,8 +808,8 b' def extract_hist_ranges(ranges_str):'
765
808
766 Examples
809 Examples
767 --------
810 --------
768 list(extract_input_ranges("~8/5-~7/4 2"))
811 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
769 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
812 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
770 """
813 """
771 for range_str in ranges_str.split():
814 for range_str in ranges_str.split():
772 rmatch = range_re.match(range_str)
815 rmatch = range_re.match(range_str)
@@ -97,6 +97,7 b' class ScriptMagics(Magics):'
97 'perl',
97 'perl',
98 'ruby',
98 'ruby',
99 'python',
99 'python',
100 'python2',
100 'python3',
101 'python3',
101 'pypy',
102 'pypy',
102 ]
103 ]
@@ -96,7 +96,10 b' def figsize(sizex, sizey):'
96
96
97
97
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
99 """Print a figure to an image, and return the resulting bytes
99 """Print a figure to an image, and return the resulting file data
100
101 Returned data will be bytes unless ``fmt='svg'``,
102 in which case it will be unicode.
100
103
101 Any keyword args are passed to fig.canvas.print_figure,
104 Any keyword args are passed to fig.canvas.print_figure,
102 such as ``quality`` or ``bbox_inches``.
105 such as ``quality`` or ``bbox_inches``.
@@ -125,7 +128,10 b" def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):"
125
128
126 bytes_io = BytesIO()
129 bytes_io = BytesIO()
127 fig.canvas.print_figure(bytes_io, **kw)
130 fig.canvas.print_figure(bytes_io, **kw)
128 return bytes_io.getvalue()
131 data = bytes_io.getvalue()
132 if fmt == 'svg':
133 data = data.decode('utf-8')
134 return data
129
135
130 def retina_figure(fig, **kwargs):
136 def retina_figure(fig, **kwargs):
131 """format a figure as a pixel-doubled (retina) PNG"""
137 """format a figure as a pixel-doubled (retina) PNG"""
@@ -26,7 +26,8 b" _version_extra = 'dev'"
26 # _version_extra = 'rc1'
26 # _version_extra = 'rc1'
27 # _version_extra = '' # Uncomment this for full releases
27 # _version_extra = '' # Uncomment this for full releases
28
28
29 codename = 'Work in Progress'
29 # release.codename is deprecated in 2.0, will be removed in 3.0
30 codename = ''
30
31
31 # Construct full version string from these.
32 # Construct full version string from these.
32 _ver = [_version_major, _version_minor, _version_patch]
33 _ver = [_version_major, _version_minor, _version_patch]
@@ -58,7 +58,7 b' def test_figure_to_svg():'
58 ax.plot([1,2,3])
58 ax.plot([1,2,3])
59 plt.draw()
59 plt.draw()
60 svg = pt.print_figure(fig, 'svg')[:100].lower()
60 svg = pt.print_figure(fig, 'svg')[:100].lower()
61 nt.assert_in(b'doctype svg', svg)
61 nt.assert_in(u'doctype svg', svg)
62
62
63 def _check_pil_jpeg_bytes():
63 def _check_pil_jpeg_bytes():
64 """Skip if PIL can't write JPEGs to BytesIO objects"""
64 """Skip if PIL can't write JPEGs to BytesIO objects"""
@@ -73,6 +73,7 b' from .services.sessions.sessionmanager import SessionManager'
73
73
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
75
75
76 from IPython.config import Config
76 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.profiledir import ProfileDir
79 from IPython.core.profiledir import ProfileDir
@@ -554,10 +555,12 b' class NotebookApp(BaseIPythonApplication):'
554
555
555 # Use config here, to ensure that it takes higher priority than
556 # Use config here, to ensure that it takes higher priority than
556 # anything that comes from the profile.
557 # anything that comes from the profile.
558 c = Config()
557 if os.path.isdir(f):
559 if os.path.isdir(f):
558 self.config.NotebookApp.notebook_dir = f
560 c.NotebookApp.notebook_dir = f
559 elif os.path.isfile(f):
561 elif os.path.isfile(f):
560 self.config.NotebookApp.file_to_run = f
562 c.NotebookApp.file_to_run = f
563 self.update_config(c)
561
564
562 def init_kernel_argv(self):
565 def init_kernel_argv(self):
563 """construct the kernel arguments"""
566 """construct the kernel arguments"""
@@ -128,15 +128,6 b' IPython.keyboard = (function (IPython) {'
128 return shortcut;
128 return shortcut;
129 };
129 };
130
130
131 var trigger_keydown = function (shortcut, element) {
132 // Trigger shortcut keydown on an element
133 element = element || document;
134 element = $(element);
135 var event = shortcut_to_event(shortcut, 'keydown');
136 element.trigger(event);
137 };
138
139
140 // Shortcut manager class
131 // Shortcut manager class
141
132
142 var ShortcutManager = function (delay) {
133 var ShortcutManager = function (delay) {
@@ -252,7 +243,7 b' IPython.keyboard = (function (IPython) {'
252 ShortcutManager.prototype.handles = function (event) {
243 ShortcutManager.prototype.handles = function (event) {
253 var shortcut = event_to_shortcut(event);
244 var shortcut = event_to_shortcut(event);
254 var data = this._shortcuts[shortcut];
245 var data = this._shortcuts[shortcut];
255 return !( data === undefined )
246 return !( data === undefined || data.handler === undefined )
256 }
247 }
257
248
258 return {
249 return {
@@ -262,8 +253,7 b' IPython.keyboard = (function (IPython) {'
262 normalize_key : normalize_key,
253 normalize_key : normalize_key,
263 normalize_shortcut : normalize_shortcut,
254 normalize_shortcut : normalize_shortcut,
264 shortcut_to_event : shortcut_to_event,
255 shortcut_to_event : shortcut_to_event,
265 event_to_shortcut : event_to_shortcut,
256 event_to_shortcut : event_to_shortcut
266 trigger_keydown : trigger_keydown
267 };
257 };
268
258
269 }(IPython));
259 }(IPython));
@@ -58,6 +58,9 b' var IPython = (function (IPython) {'
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 this.save_notebook = function() { // don't allow save until notebook_loaded
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
63 };
61 };
64 };
62
65
63 /**
66 /**
@@ -1723,7 +1726,8 b' var IPython = (function (IPython) {'
1723 };
1726 };
1724
1727
1725 /**
1728 /**
1726 * Save this notebook on the server.
1729 * Save this notebook on the server. This becomes a notebook instance's
1730 * .save_notebook method *after* the entire notebook has been loaded.
1727 *
1731 *
1728 * @method save_notebook
1732 * @method save_notebook
1729 */
1733 */
@@ -1829,7 +1833,7 b' var IPython = (function (IPython) {'
1829 " Selecting trust will immediately reload this notebook in a trusted state."
1833 " Selecting trust will immediately reload this notebook in a trusted state."
1830 ).append(
1834 ).append(
1831 " For more information, see the "
1835 " For more information, see the "
1832 ).append($("<a>").attr("href", "http://ipython.org/security.html")
1836 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1833 .text("IPython security documentation")
1837 .text("IPython security documentation")
1834 ).append(".")
1838 ).append(".")
1835 );
1839 );
@@ -2100,7 +2104,9 b' var IPython = (function (IPython) {'
2100 IPython.CellToolbar.global_show();
2104 IPython.CellToolbar.global_show();
2101 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2105 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2102 }
2106 }
2103
2107
2108 // now that we're fully loaded, it is safe to restore save functionality
2109 delete(this.save_notebook);
2104 $([IPython.events]).trigger('notebook_loaded.Notebook');
2110 $([IPython.events]).trigger('notebook_loaded.Notebook');
2105 };
2111 };
2106
2112
@@ -188,8 +188,8 b' var IPython = (function (IPython) {'
188 $([IPython.events]).on('notebook_saved.Notebook', function () {
188 $([IPython.events]).on('notebook_saved.Notebook', function () {
189 nnw.set_message("Notebook saved",2000);
189 nnw.set_message("Notebook saved",2000);
190 });
190 });
191 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
191 $([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message("Notebook save failed");
192 nnw.set_message(data || "Notebook save failed");
193 });
193 });
194
194
195 // Checkpoint events
195 // Checkpoint events
@@ -541,6 +541,10 b' var IPython = (function (IPython) {'
541 var container = element;
541 var container = element;
542 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
542 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
543 // end backward compat
543 // end backward compat
544
545 // Fix for ipython/issues/5293, make sure `element` is the area which
546 // output can be inserted into at the time of JS execution.
547 element = toinsert;
544 try {
548 try {
545 eval(js);
549 eval(js);
546 } catch(err) {
550 } catch(err) {
@@ -131,17 +131,13 b' var IPython = (function (IPython) {'
131 Tooltip.prototype.showInPager = function (cell) {
131 Tooltip.prototype.showInPager = function (cell) {
132 // reexecute last call in pager by appending ? to show back in pager
132 // reexecute last call in pager by appending ? to show back in pager
133 var that = this;
133 var that = this;
134 var empty = function () {};
134 var callbacks = {'shell' : {
135 cell.kernel.execute(
135 'payload' : {
136 that.name + '?', {
136 'page' : $.proxy(cell._open_with_pager, cell)
137 'execute_reply': empty,
137 }
138 'output': empty,
138 }
139 'clear_output': empty,
139 };
140 'cell': cell
140 cell.kernel.execute(that.name + '?', callbacks, {'silent': false, 'store_history': true});
141 }, {
142 'silent': false,
143 'store_history': true
144 });
145 this.remove_and_cancel_tooltip();
141 this.remove_and_cancel_tooltip();
146 };
142 };
147
143
@@ -33,6 +33,14 b' div.prompt {'
33 line-height: @code_line_height;
33 line-height: @code_line_height;
34 }
34 }
35
35
36 @media (max-width: 480px) {
37 // prompts are in the main column on small screens,
38 // so text should be left-aligned
39 div.prompt {
40 text-align: left;
41 }
42 }
43
36 div.inner_cell {
44 div.inner_cell {
37 .vbox();
45 .vbox();
38 .box-flex1();
46 .box-flex1();
@@ -10,13 +10,19 b' div.input {'
10 .hbox();
10 .hbox();
11 }
11 }
12
12
13 @media (max-width: 480px) {
14 // move prompts above code on small screens
15 div.input {
16 .vbox();
17 }
18 }
19
13 /* input_area and input_prompt must match in top border and margin for alignment */
20 /* input_area and input_prompt must match in top border and margin for alignment */
14 div.input_prompt {
21 div.input_prompt {
15 color: navy;
22 color: navy;
16 border-top: 1px solid transparent;
23 border-top: 1px solid transparent;
17 }
24 }
18
25
19
20 // The styles related to div.highlight are for nbconvert HTML output only. This works
26 // The styles related to div.highlight are for nbconvert HTML output only. This works
21 // because the .highlight div isn't present in the live notebook. We could put this into
27 // because the .highlight div isn't present in the live notebook. We could put this into
22 // nbconvert, but it easily falls out of sync, can't use our less variables and doesn't
28 // nbconvert, but it easily falls out of sync, can't use our less variables and doesn't
@@ -7,6 +7,14 b' body.notebook_app {'
7 overflow: hidden;
7 overflow: hidden;
8 }
8 }
9
9
10 @media (max-width: 767px) {
11 // remove bootstrap-responsive's body padding on small screens
12 body.notebook_app {
13 padding-left: 0px;
14 padding-right: 0px;
15 }
16 }
17
10 span#notebook_name {
18 span#notebook_name {
11 height: 1em;
19 height: 1em;
12 line-height: 1em;
20 line-height: 1em;
@@ -72,6 +72,13 b' div.output_area {'
72 .vbox();
72 .vbox();
73 }
73 }
74
74
75 @media (max-width: 480px) {
76 // move prompts above output on small screens
77 div.output_area {
78 .vbox();
79 }
80 }
81
75 div.output_area pre {
82 div.output_area pre {
76 margin: 0;
83 margin: 0;
77 padding: 0;
84 padding: 0;
@@ -2,6 +2,12 b' div.text_cell {'
2 padding: 5px 5px 5px 0px;
2 padding: 5px 5px 5px 0px;
3 .hbox();
3 .hbox();
4 }
4 }
5 @media (max-width: 480px) {
6 // remove prompt indentation on small screens
7 div.text_cell > div.prompt {
8 display: none;
9 }
10 }
5
11
6 div.text_cell_render {
12 div.text_cell_render {
7 /*font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;*/
13 /*font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;*/
@@ -73,11 +73,11 b' div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:ver'
73 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
73 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
74 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
74 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
75 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
75 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
76 div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
76 @media (max-width:480px){div.prompt{text-align:left}}div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
77 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
77 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
78 div.prompt:empty{padding-top:0;padding-bottom:0}
78 div.prompt:empty{padding-top:0;padding-bottom:0}
79 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
79 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
80 div.input_prompt{color:#000080;border-top:1px solid transparent}
80 @media (max-width:480px){div.input{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.input_prompt{color:#000080;border-top:1px solid transparent}
81 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
81 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
82 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
82 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
83 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
83 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
@@ -117,7 +117,7 b' div.output_area{padding:0;page-break-inside:avoid;display:-webkit-box;-webkit-bo'
117 div.output_area .rendered_html table{margin-left:0;margin-right:0}
117 div.output_area .rendered_html table{margin-left:0;margin-right:0}
118 div.output_area .rendered_html img{margin-left:0;margin-right:0}
118 div.output_area .rendered_html img{margin-left:0;margin-right:0}
119 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
119 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
120 div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
120 @media (max-width:480px){div.output_area{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
121 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
121 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
122 div.output_text{text-align:left;color:#000;line-height:1.21429em}
122 div.output_text{text-align:left;color:#000;line-height:1.21429em}
123 div.output_stderr{background:#fdd;}
123 div.output_stderr{background:#fdd;}
@@ -170,7 +170,7 b' p.p-space{margin-bottom:10px}'
170 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
170 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
171 .rendered_html *+img{margin-top:1em}
171 .rendered_html *+img{margin-top:1em}
172 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
172 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
173 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
173 @media (max-width:480px){div.text_cell>div.prompt{display:none}}div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
174 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
174 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
175 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
175 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
176 div.cell.text_cell.rendered{padding:0}
176 div.cell.text_cell.rendered{padding:0}
@@ -1350,11 +1350,11 b' div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:ver'
1350 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
1350 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
1351 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
1351 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
1352 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
1352 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
1353 div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1353 @media (max-width:480px){div.prompt{text-align:left}}div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1354 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
1354 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
1355 div.prompt:empty{padding-top:0;padding-bottom:0}
1355 div.prompt:empty{padding-top:0;padding-bottom:0}
1356 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1356 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1357 div.input_prompt{color:#000080;border-top:1px solid transparent}
1357 @media (max-width:480px){div.input{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.input_prompt{color:#000080;border-top:1px solid transparent}
1358 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
1358 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
1359 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
1359 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
1360 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
1360 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
@@ -1394,7 +1394,7 b' div.output_area{padding:0;page-break-inside:avoid;display:-webkit-box;-webkit-bo'
1394 div.output_area .rendered_html table{margin-left:0;margin-right:0}
1394 div.output_area .rendered_html table{margin-left:0;margin-right:0}
1395 div.output_area .rendered_html img{margin-left:0;margin-right:0}
1395 div.output_area .rendered_html img{margin-left:0;margin-right:0}
1396 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
1396 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
1397 div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
1397 @media (max-width:480px){div.output_area{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
1398 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1398 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1399 div.output_text{text-align:left;color:#000;line-height:1.21429em}
1399 div.output_text{text-align:left;color:#000;line-height:1.21429em}
1400 div.output_stderr{background:#fdd;}
1400 div.output_stderr{background:#fdd;}
@@ -1447,7 +1447,7 b' p.p-space{margin-bottom:10px}'
1447 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
1447 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
1448 .rendered_html *+img{margin-top:1em}
1448 .rendered_html *+img{margin-top:1em}
1449 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1449 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1450 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
1450 @media (max-width:480px){div.text_cell>div.prompt{display:none}}div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
1451 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
1451 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
1452 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
1452 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
1453 div.cell.text_cell.rendered{padding:0}
1453 div.cell.text_cell.rendered{padding:0}
@@ -1476,7 +1476,7 b' div.cell.text_cell.rendered{padding:0}'
1476 .docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important}
1476 .docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important}
1477 body{background-color:#fff}
1477 body{background-color:#fff}
1478 body.notebook_app{overflow:hidden}
1478 body.notebook_app{overflow:hidden}
1479 span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%}
1479 @media (max-width:767px){body.notebook_app{padding-left:0;padding-right:0}}span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%}
1480 div#notebook_panel{margin:0 0 0 0;padding:0;-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}
1480 div#notebook_panel{margin:0 0 0 0;padding:0;-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}
1481 div#notebook{font-size:14px;line-height:20px;overflow-y:scroll;overflow-x:auto;width:100%;padding:1em 0 1em 0;margin:0;border-top:1px solid #ababab;outline:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
1481 div#notebook{font-size:14px;line-height:20px;overflow-y:scroll;overflow-x:auto;width:100%;padding:1em 0 1em 0;margin:0;border-top:1px solid #ababab;outline:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
1482 div.ui-widget-content{border:1px solid #ababab;outline:none}
1482 div.ui-widget-content{border:1px solid #ababab;outline:none}
@@ -227,14 +227,14 b' class="notebook_app"'
227 (
227 (
228 ("http://ipython.org/documentation.html","IPython Help",True),
228 ("http://ipython.org/documentation.html","IPython Help",True),
229 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
229 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
230 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
230 ("http://ipython.org/ipython-doc/2/notebook/notebook.html","Notebook Help",True),
231 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
231 ("http://ipython.org/ipython-doc/2/notebook/cm_keyboard.html","Editor Shortcuts",True),
232 ),(
232 ),(
233 ("http://docs.python.org","Python",True),
233 ("http://docs.python.org","Python",True),
234 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
234 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
235 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
235 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
236 ("http://matplotlib.org/contents.html","Matplotlib",True),
236 ("http://matplotlib.org/contents.html","Matplotlib",True),
237 ("http://docs.sympy.org/dev/index.html","SymPy",True),
237 ("http://docs.sympy.org/latest/index.html","SymPy",True),
238 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
238 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
239 )
239 )
240 )
240 )
@@ -10,12 +10,12 b' casper.notebook_test(function () {'
10 for (i = 0; i < ncells; i++) {
10 for (i = 0; i < ncells; i++) {
11 IPython.notebook.delete_cell();
11 IPython.notebook.delete_cell();
12 }
12 }
13
13
14 // Simulate the "up arrow" and "down arrow" keys.
15 //
16 IPython.keyboard.trigger_keydown('up');
17 IPython.keyboard.trigger_keydown('down');
18 return true;
14 return true;
19 });
15 });
16
17 // Simulate the "up arrow" and "down arrow" keys.
18 this.trigger_keydown('up');
19 this.trigger_keydown('down');
20 this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
20 this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
21 });
21 });
@@ -22,7 +22,11 b' casper.notebook_test(function () {'
22 var cell = IPython.notebook.get_cell(0);
22 var cell = IPython.notebook.get_cell(0);
23 cell.set_text('a=11; print(a)');
23 cell.set_text('a=11; print(a)');
24 cell.clear_output();
24 cell.clear_output();
25 IPython.keyboard.trigger_keydown('shift-enter');
25 });
26
27 this.then(function(){
28
29 this.trigger_keydown('shift-enter');
26 });
30 });
27
31
28 this.wait_for_output(0);
32 this.wait_for_output(0);
@@ -41,7 +45,10 b' casper.notebook_test(function () {'
41 var cell = IPython.notebook.get_cell(0);
45 var cell = IPython.notebook.get_cell(0);
42 cell.set_text('a=12; print(a)');
46 cell.set_text('a=12; print(a)');
43 cell.clear_output();
47 cell.clear_output();
44 IPython.keyboard.trigger_keydown('ctrl-enter');
48 });
49
50 this.then(function(){
51 this.trigger_keydown('ctrl-enter');
45 });
52 });
46
53
47 this.wait_for_output(0);
54 this.wait_for_output(0);
@@ -31,8 +31,8 b' casper.notebook_test(function () {'
31 });
31 });
32
32
33 // interrupt using Ctrl-M I keyboard shortcut
33 // interrupt using Ctrl-M I keyboard shortcut
34 this.thenEvaluate( function() {
34 this.then(function(){
35 IPython.keyboard.trigger_keydown('i');
35 this.trigger_keydown('i');
36 });
36 });
37
37
38 this.wait_for_output(0);
38 this.wait_for_output(0);
@@ -2,37 +2,42 b''
2 // Test merging two notebook cells.
2 // Test merging two notebook cells.
3 //
3 //
4 casper.notebook_test(function() {
4 casper.notebook_test(function() {
5 var output = this.evaluate(function () {
5 var that = this;
6 // Fill in test data.
6 var set_cells_text = function () {
7 IPython.notebook.command_mode();
7 that.evaluate(function() {
8 var set_cell_text = function () {
9 var cell_one = IPython.notebook.get_selected_cell();
8 var cell_one = IPython.notebook.get_selected_cell();
10 cell_one.set_text('a = 5');
9 cell_one.set_text('a = 5');
11
10 });
12 IPython.keyboard.trigger_keydown('b');
11
12 that.trigger_keydown('b');
13
14 that.evaluate(function() {
13 var cell_two = IPython.notebook.get_selected_cell();
15 var cell_two = IPython.notebook.get_selected_cell();
14 cell_two.set_text('print(a)');
16 cell_two.set_text('print(a)');
15 };
17 });
18 };
19
20 this.evaluate(function () {
21 IPython.notebook.command_mode();
22 });
16
23
17 // merge_cell_above()
24 // merge_cell_above()
18 set_cell_text();
25 set_cells_text();
26 var output_above = this.evaluate(function () {
19 IPython.notebook.merge_cell_above();
27 IPython.notebook.merge_cell_above();
20 var merged_above = IPython.notebook.get_selected_cell();
28 return IPython.notebook.get_selected_cell().get_text();
29 });
21
30
22 // merge_cell_below()
31 // merge_cell_below()
23 set_cell_text();
32 set_cells_text();
33 var output_below = this.evaluate(function() {
24 IPython.notebook.select(0);
34 IPython.notebook.select(0);
25 IPython.notebook.merge_cell_below();
35 IPython.notebook.merge_cell_below();
26 var merged_below = IPython.notebook.get_selected_cell();
36 return IPython.notebook.get_selected_cell().get_text();
27
28 return {
29 above: merged_above.get_text(),
30 below: merged_below.get_text()
31 };
32 });
37 });
33
38
34 this.test.assertEquals(output.above, 'a = 5\nprint(a)',
39 this.test.assertEquals(output_above, 'a = 5\nprint(a)',
35 'Successful merge_cell_above().');
40 'Successful merge_cell_above().');
36 this.test.assertEquals(output.below, 'a = 5\nprint(a)',
41 this.test.assertEquals(output_below, 'a = 5\nprint(a)',
37 'Successful merge_cell_below().');
42 'Successful merge_cell_below().');
38 });
43 });
@@ -2,15 +2,15 b''
2 // Utility functions for the HTML notebook's CasperJS tests.
2 // Utility functions for the HTML notebook's CasperJS tests.
3 //
3 //
4
4
5 // Get the URL of a notebook server on which to run tests.
6 casper.get_notebook_server = function () {
5 casper.get_notebook_server = function () {
7 port = casper.cli.get("port")
6 // Get the URL of a notebook server on which to run tests.
7 port = casper.cli.get("port");
8 port = (typeof port === 'undefined') ? '8888' : port;
8 port = (typeof port === 'undefined') ? '8888' : port;
9 return 'http://127.0.0.1:' + port
9 return 'http://127.0.0.1:' + port;
10 };
10 };
11
11
12 // Create and open a new notebook.
13 casper.open_new_notebook = function () {
12 casper.open_new_notebook = function () {
13 // Create and open a new notebook.
14 var baseUrl = this.get_notebook_server();
14 var baseUrl = this.get_notebook_server();
15 this.start(baseUrl);
15 this.start(baseUrl);
16 this.thenClick('button#new_notebook');
16 this.thenClick('button#new_notebook');
@@ -34,15 +34,15 b' casper.open_new_notebook = function () {'
34 });
34 });
35 };
35 };
36
36
37 // Return whether or not the kernel is running.
38 casper.kernel_running = function kernel_running() {
37 casper.kernel_running = function kernel_running() {
38 // Return whether or not the kernel is running.
39 return this.evaluate(function kernel_running() {
39 return this.evaluate(function kernel_running() {
40 return IPython.notebook.kernel.running;
40 return IPython.notebook.kernel.running;
41 });
41 });
42 };
42 };
43
43
44 // Shut down the current notebook's kernel.
45 casper.shutdown_current_kernel = function () {
44 casper.shutdown_current_kernel = function () {
45 // Shut down the current notebook's kernel.
46 this.thenEvaluate(function() {
46 this.thenEvaluate(function() {
47 IPython.notebook.kernel.kill();
47 IPython.notebook.kernel.kill();
48 });
48 });
@@ -50,8 +50,9 b' casper.shutdown_current_kernel = function () {'
50 this.wait(1000);
50 this.wait(1000);
51 };
51 };
52
52
53 // Delete created notebook.
54 casper.delete_current_notebook = function () {
53 casper.delete_current_notebook = function () {
54 // Delete created notebook.
55
55 // For some unknown reason, this doesn't work?!?
56 // For some unknown reason, this doesn't work?!?
56 this.thenEvaluate(function() {
57 this.thenEvaluate(function() {
57 IPython.notebook.delete();
58 IPython.notebook.delete();
@@ -59,6 +60,7 b' casper.delete_current_notebook = function () {'
59 };
60 };
60
61
61 casper.wait_for_busy = function () {
62 casper.wait_for_busy = function () {
63 // Waits for the notebook to enter a busy state.
62 this.waitFor(function () {
64 this.waitFor(function () {
63 return this.evaluate(function () {
65 return this.evaluate(function () {
64 return IPython._status == 'busy';
66 return IPython._status == 'busy';
@@ -67,6 +69,7 b' casper.wait_for_busy = function () {'
67 };
69 };
68
70
69 casper.wait_for_idle = function () {
71 casper.wait_for_idle = function () {
72 // Waits for the notebook to idle.
70 this.waitFor(function () {
73 this.waitFor(function () {
71 return this.evaluate(function () {
74 return this.evaluate(function () {
72 return IPython._status == 'idle';
75 return IPython._status == 'idle';
@@ -74,8 +77,8 b' casper.wait_for_idle = function () {'
74 });
77 });
75 };
78 };
76
79
77 // wait for the nth output in a given cell
78 casper.wait_for_output = function (cell_num, out_num) {
80 casper.wait_for_output = function (cell_num, out_num) {
81 // wait for the nth output in a given cell
79 this.wait_for_idle();
82 this.wait_for_idle();
80 out_num = out_num || 0;
83 out_num = out_num || 0;
81 this.then(function() {
84 this.then(function() {
@@ -94,29 +97,29 b' casper.wait_for_output = function (cell_num, out_num) {'
94 });
97 });
95 };
98 };
96
99
97 // wait for a widget msg que to reach 0
98 //
99 // Parameters
100 // ----------
101 // widget_info : object
102 // Object which contains info related to the widget. The model_id property
103 // is used to identify the widget.
104 casper.wait_for_widget = function (widget_info) {
100 casper.wait_for_widget = function (widget_info) {
101 // wait for a widget msg que to reach 0
102 //
103 // Parameters
104 // ----------
105 // widget_info : object
106 // Object which contains info related to the widget. The model_id property
107 // is used to identify the widget.
105 this.waitFor(function () {
108 this.waitFor(function () {
106 var pending = this.evaluate(function (m) {
109 var pending = this.evaluate(function (m) {
107 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
110 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
108 }, {m: widget_info.model_id});
111 }, {m: widget_info.model_id});
109
112
110 if (pending == 0) {
113 if (pending === 0) {
111 return true;
114 return true;
112 } else {
115 } else {
113 return false;
116 return false;
114 }
117 }
115 });
118 });
116 }
119 };
117
120
118 // return an output of a given cell
119 casper.get_output_cell = function (cell_num, out_num) {
121 casper.get_output_cell = function (cell_num, out_num) {
122 // return an output of a given cell
120 out_num = out_num || 0;
123 out_num = out_num || 0;
121 var result = casper.evaluate(function (c, o) {
124 var result = casper.evaluate(function (c, o) {
122 var cell = IPython.notebook.get_cell(c);
125 var cell = IPython.notebook.get_cell(c);
@@ -137,25 +140,33 b' casper.get_output_cell = function (cell_num, out_num) {'
137 }
140 }
138 };
141 };
139
142
140 // return the number of cells in the notebook
141 casper.get_cells_length = function () {
143 casper.get_cells_length = function () {
144 // return the number of cells in the notebook
142 var result = casper.evaluate(function () {
145 var result = casper.evaluate(function () {
143 return IPython.notebook.get_cells().length;
146 return IPython.notebook.get_cells().length;
144 })
147 });
145 return result;
148 return result;
146 };
149 };
147
150
148 // Set the text content of a cell.
149 casper.set_cell_text = function(index, text){
151 casper.set_cell_text = function(index, text){
152 // Set the text content of a cell.
150 this.evaluate(function (index, text) {
153 this.evaluate(function (index, text) {
151 var cell = IPython.notebook.get_cell(index);
154 var cell = IPython.notebook.get_cell(index);
152 cell.set_text(text);
155 cell.set_text(text);
153 }, index, text);
156 }, index, text);
154 };
157 };
155
158
156 // Inserts a cell at the bottom of the notebook
159 casper.get_cell_text = function(index){
157 // Returns the new cell's index.
160 // Get the text content of a cell.
161 return this.evaluate(function (index) {
162 var cell = IPython.notebook.get_cell(index);
163 return cell.get_text();
164 }, index);
165 };
166
158 casper.insert_cell_at_bottom = function(cell_type){
167 casper.insert_cell_at_bottom = function(cell_type){
168 // Inserts a cell at the bottom of the notebook
169 // Returns the new cell's index.
159 cell_type = cell_type || 'code';
170 cell_type = cell_type || 'code';
160
171
161 return this.evaluate(function (cell_type) {
172 return this.evaluate(function (cell_type) {
@@ -164,9 +175,9 b' casper.insert_cell_at_bottom = function(cell_type){'
164 }, cell_type);
175 }, cell_type);
165 };
176 };
166
177
167 // Insert a cell at the bottom of the notebook and set the cells text.
168 // Returns the new cell's index.
169 casper.append_cell = function(text, cell_type) {
178 casper.append_cell = function(text, cell_type) {
179 // Insert a cell at the bottom of the notebook and set the cells text.
180 // Returns the new cell's index.
170 var index = this.insert_cell_at_bottom(cell_type);
181 var index = this.insert_cell_at_bottom(cell_type);
171 if (text !== undefined) {
182 if (text !== undefined) {
172 this.set_cell_text(index, text);
183 this.set_cell_text(index, text);
@@ -174,9 +185,9 b' casper.append_cell = function(text, cell_type) {'
174 return index;
185 return index;
175 };
186 };
176
187
177 // Asynchronously executes a cell by index.
178 // Returns the cell's index.
179 casper.execute_cell = function(index){
188 casper.execute_cell = function(index){
189 // Asynchronously executes a cell by index.
190 // Returns the cell's index.
180 var that = this;
191 var that = this;
181 this.then(function(){
192 this.then(function(){
182 that.evaluate(function (index) {
193 that.evaluate(function (index) {
@@ -187,11 +198,11 b' casper.execute_cell = function(index){'
187 return index;
198 return index;
188 };
199 };
189
200
190 // Synchronously executes a cell by index.
191 // Optionally accepts a then_callback parameter. then_callback will get called
192 // when the cell has finished executing.
193 // Returns the cell's index.
194 casper.execute_cell_then = function(index, then_callback) {
201 casper.execute_cell_then = function(index, then_callback) {
202 // Synchronously executes a cell by index.
203 // Optionally accepts a then_callback parameter. then_callback will get called
204 // when the cell has finished executing.
205 // Returns the cell's index.
195 var return_val = this.execute_cell(index);
206 var return_val = this.execute_cell(index);
196
207
197 this.wait_for_idle();
208 this.wait_for_idle();
@@ -206,18 +217,18 b' casper.execute_cell_then = function(index, then_callback) {'
206 return return_val;
217 return return_val;
207 };
218 };
208
219
209 // Utility function that allows us to easily check if an element exists
210 // within a cell. Uses JQuery selector to look for the element.
211 casper.cell_element_exists = function(index, selector){
220 casper.cell_element_exists = function(index, selector){
221 // Utility function that allows us to easily check if an element exists
222 // within a cell. Uses JQuery selector to look for the element.
212 return casper.evaluate(function (index, selector) {
223 return casper.evaluate(function (index, selector) {
213 var $cell = IPython.notebook.get_cell(index).element;
224 var $cell = IPython.notebook.get_cell(index).element;
214 return $cell.find(selector).length > 0;
225 return $cell.find(selector).length > 0;
215 }, index, selector);
226 }, index, selector);
216 };
227 };
217
228
218 // Utility function that allows us to execute a jQuery function on an
219 // element within a cell.
220 casper.cell_element_function = function(index, selector, function_name, function_args){
229 casper.cell_element_function = function(index, selector, function_name, function_args){
230 // Utility function that allows us to execute a jQuery function on an
231 // element within a cell.
221 return casper.evaluate(function (index, selector, function_name, function_args) {
232 return casper.evaluate(function (index, selector, function_name, function_args) {
222 var $cell = IPython.notebook.get_cell(index).element;
233 var $cell = IPython.notebook.get_cell(index).element;
223 var $el = $cell.find(selector);
234 var $el = $cell.find(selector);
@@ -225,8 +236,183 b' casper.cell_element_function = function(index, selector, function_name, function'
225 }, index, selector, function_name, function_args);
236 }, index, selector, function_name, function_args);
226 };
237 };
227
238
228 // Wrap a notebook test to reduce boilerplate.
239 casper.validate_notebook_state = function(message, mode, cell_index) {
240 // Validate the entire dual mode state of the notebook. Make sure no more than
241 // one cell is selected, focused, in edit mode, etc...
242
243 // General tests.
244 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
245 message + '; keyboard and notebook modes match');
246 // Is the selected cell the only cell that is selected?
247 if (cell_index!==undefined) {
248 this.test.assert(this.is_only_cell_selected(cell_index),
249 message + '; cell ' + cell_index + ' is the only cell selected');
250 }
251
252 // Mode specific tests.
253 if (mode==='command') {
254 // Are the notebook and keyboard manager in command mode?
255 this.test.assertEquals(this.get_keyboard_mode(), 'command',
256 message + '; in command mode');
257 // Make sure there isn't a single cell in edit mode.
258 this.test.assert(this.is_only_cell_edit(null),
259 message + '; all cells in command mode');
260 this.test.assert(this.is_cell_editor_focused(null),
261 message + '; no cell editors are focused while in command mode');
262
263 } else if (mode==='edit') {
264 // Are the notebook and keyboard manager in edit mode?
265 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
266 message + '; in edit mode');
267 if (cell_index!==undefined) {
268 // Is the specified cell the only cell in edit mode?
269 this.test.assert(this.is_only_cell_edit(cell_index),
270 message + '; cell ' + cell_index + ' is the only cell in edit mode');
271 // Is the specified cell the only cell with a focused code mirror?
272 this.test.assert(this.is_cell_editor_focused(cell_index),
273 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
274 }
275
276 } else {
277 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
278 }
279 };
280
281 casper.select_cell = function(index) {
282 // Select a cell in the notebook.
283 this.evaluate(function (i) {
284 IPython.notebook.select(i);
285 }, {i: index});
286 };
287
288 casper.click_cell_editor = function(index) {
289 // Emulate a click on a cell's editor.
290
291 // Code Mirror does not play nicely with emulated brower events.
292 // Instead of trying to emulate a click, here we run code similar to
293 // the code used in Code Mirror that handles the mousedown event on a
294 // region of codemirror that the user can focus.
295 this.evaluate(function (i) {
296 var cm = IPython.notebook.get_cell(i).code_mirror;
297 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
298 cm.display.input.focus();
299 }, {i: index});
300 };
301
302 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
303 // Set the Code Mirror instance cursor's location.
304 this.evaluate(function (i, l, c) {
305 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
306 }, {i: index, l: line_index, c: char_index});
307 };
308
309 casper.focus_notebook = function() {
310 // Focus the notebook div.
311 this.evaluate(function (){
312 $('#notebook').focus();
313 }, {});
314 };
315
316 casper.trigger_keydown = function() {
317 // Emulate a keydown in the notebook.
318 for (var i = 0; i < arguments.length; i++) {
319 this.evaluate(function (k) {
320 var element = $(document);
321 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
322 element.trigger(event);
323 }, {k: arguments[i]});
324 }
325 };
326
327 casper.get_keyboard_mode = function() {
328 // Get the mode of the keyboard manager.
329 return this.evaluate(function() {
330 return IPython.keyboard_manager.mode;
331 }, {});
332 };
333
334 casper.get_notebook_mode = function() {
335 // Get the mode of the notebook.
336 return this.evaluate(function() {
337 return IPython.notebook.mode;
338 }, {});
339 };
340
341 casper.get_cell = function(index) {
342 // Get a single cell.
343 //
344 // Note: Handles to DOM elements stored in the cell will be useless once in
345 // CasperJS context.
346 return this.evaluate(function(i) {
347 var cell = IPython.notebook.get_cell(i);
348 if (cell) {
349 return cell;
350 }
351 return null;
352 }, {i : index});
353 };
354
355 casper.is_cell_editor_focused = function(index) {
356 // Make sure a cell's editor is the only editor focused on the page.
357 return this.evaluate(function(i) {
358 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
359 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
360 if (i === null) {
361 return focused_textarea.length === 0;
362 } else {
363 var cell = IPython.notebook.get_cell(i);
364 if (cell) {
365 return cell.code_mirror.getInputField() == focused_textarea[0];
366 }
367 }
368 return false;
369 }, {i : index});
370 };
371
372 casper.is_only_cell_selected = function(index) {
373 // Check if a cell is the only cell selected.
374 // Pass null as the index to check if no cells are selected.
375 return this.is_only_cell_on(index, 'selected', 'unselected');
376 };
377
378 casper.is_only_cell_edit = function(index) {
379 // Check if a cell is the only cell in edit mode.
380 // Pass null as the index to check if all of the cells are in command mode.
381 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
382 };
383
384 casper.is_only_cell_on = function(i, on_class, off_class) {
385 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
386 // All of the other cells are checked for the `off_class` DOM class.
387 // Pass null as the index to check if all of the cells have the `off_class`.
388 var cells_length = this.get_cells_length();
389 for (var j = 0; j < cells_length; j++) {
390 if (j === i) {
391 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
392 return false;
393 }
394 } else {
395 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
396 return false;
397 }
398 }
399 }
400 return true;
401 };
402
403 casper.cell_has_class = function(index, classes) {
404 // Check if a cell has a class.
405 return this.evaluate(function(i, c) {
406 var cell = IPython.notebook.get_cell(i);
407 if (cell) {
408 return cell.element.hasClass(c);
409 }
410 return false;
411 }, {i : index, c: classes});
412 };
413
229 casper.notebook_test = function(test) {
414 casper.notebook_test = function(test) {
415 // Wrap a notebook test to reduce boilerplate.
230 this.open_new_notebook();
416 this.open_new_notebook();
231 this.then(test);
417 this.then(test);
232
418
@@ -253,14 +439,14 b' casper.notebook_test = function(test) {'
253 casper.wait_for_dashboard = function () {
439 casper.wait_for_dashboard = function () {
254 // Wait for the dashboard list to load.
440 // Wait for the dashboard list to load.
255 casper.waitForSelector('.list_item');
441 casper.waitForSelector('.list_item');
256 }
442 };
257
443
258 casper.open_dashboard = function () {
444 casper.open_dashboard = function () {
259 // Start casper by opening the dashboard page.
445 // Start casper by opening the dashboard page.
260 var baseUrl = this.get_notebook_server();
446 var baseUrl = this.get_notebook_server();
261 this.start(baseUrl);
447 this.start(baseUrl);
262 this.wait_for_dashboard();
448 this.wait_for_dashboard();
263 }
449 };
264
450
265 casper.dashboard_test = function (test) {
451 casper.dashboard_test = function (test) {
266 // Open the dashboard page and run a test.
452 // Open the dashboard page and run a test.
@@ -276,16 +462,16 b' casper.dashboard_test = function (test) {'
276 this.run(function() {
462 this.run(function() {
277 this.test.done();
463 this.test.done();
278 });
464 });
279 }
465 };
280
466
281 casper.options.waitTimeout=10000
467 casper.options.waitTimeout=10000;
282 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
468 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
283 this.echo("Timeout for " + casper.get_notebook_server());
469 this.echo("Timeout for " + casper.get_notebook_server());
284 this.echo("Is the notebook server running?");
470 this.echo("Is the notebook server running?");
285 });
471 });
286
472
287 // Pass `console.log` calls from page JS to casper.
473 casper.print_log = function () {
288 casper.printLog = function () {
474 // Pass `console.log` calls from page JS to casper.
289 this.on('remote.message', function(msg) {
475 this.on('remote.message', function(msg) {
290 this.echo('Remote message caught: ' + msg);
476 this.echo('Remote message caught: ' + msg);
291 });
477 });
@@ -12,6 +12,10 b''
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import io
16
17 from IPython.nbformat import current
18
15 from .base import ExportersTestsBase
19 from .base import ExportersTestsBase
16 from ..rst import RSTExporter
20 from ..rst import RSTExporter
17 from IPython.testing.decorators import onlyif_cmds_exist
21 from IPython.testing.decorators import onlyif_cmds_exist
@@ -40,3 +44,22 b' class TestRSTExporter(ExportersTestsBase):'
40 """
44 """
41 (output, resources) = RSTExporter().from_filename(self._get_notebook())
45 (output, resources) = RSTExporter().from_filename(self._get_notebook())
42 assert len(output) > 0
46 assert len(output) > 0
47
48 @onlyif_cmds_exist('pandoc')
49 def test_empty_code_cell(self):
50 """No empty code cells in rst"""
51 nbname = self._get_notebook()
52 with io.open(nbname, encoding='utf8') as f:
53 nb = current.read(f, 'json')
54
55 exporter = self.exporter_class()
56
57 (output, resources) = exporter.from_notebook_node(nb)
58 # add an empty code cell
59 nb.worksheets[0].cells.append(
60 current.new_code_cell(input="")
61 )
62 (output2, resources) = exporter.from_notebook_node(nb)
63 # adding an empty code cell shouldn't change output
64 self.assertEqual(output.strip(), output2.strip())
65
@@ -8,7 +8,7 b''
8 {% endblock output_prompt %}
8 {% endblock output_prompt %}
9
9
10 {% block input %}
10 {% block input %}
11 {%- if not cell.input.isspace() -%}
11 {%- if cell.input.strip() -%}
12 .. code:: python
12 .. code:: python
13
13
14 {{ cell.input | indent}}
14 {{ cell.input | indent}}
@@ -522,19 +522,23 b' class TestClient(ClusterTestCase):'
522 def test_spin_thread(self):
522 def test_spin_thread(self):
523 self.client.spin_thread(0.01)
523 self.client.spin_thread(0.01)
524 ar = self.client[-1].apply_async(lambda : 1)
524 ar = self.client[-1].apply_async(lambda : 1)
525 time.sleep(0.1)
525 md = self.client.metadata[ar.msg_ids[0]]
526 self.assertTrue(ar.wall_time < 0.1,
526 # 3s timeout, 100ms poll
527 "spin should have kept wall_time < 0.1, but got %f" % ar.wall_time
527 for i in range(30):
528 )
528 time.sleep(0.1)
529 if md['received'] is not None:
530 break
531 self.assertIsInstance(md['received'], datetime)
529
532
530 def test_stop_spin_thread(self):
533 def test_stop_spin_thread(self):
531 self.client.spin_thread(0.01)
534 self.client.spin_thread(0.01)
532 self.client.stop_spin_thread()
535 self.client.stop_spin_thread()
533 ar = self.client[-1].apply_async(lambda : 1)
536 ar = self.client[-1].apply_async(lambda : 1)
534 time.sleep(0.15)
537 md = self.client.metadata[ar.msg_ids[0]]
535 self.assertTrue(ar.wall_time > 0.1,
538 # 500ms timeout, 100ms poll
536 "Shouldn't be spinning, but got wall_time=%f" % ar.wall_time
539 for i in range(5):
537 )
540 time.sleep(0.1)
541 self.assertIsNone(md['received'], None)
538
542
539 def test_activate(self):
543 def test_activate(self):
540 ip = get_ipython()
544 ip = get_ipython()
@@ -82,7 +82,6 b' def pkg_info(pkg_path):'
82 return dict(
82 return dict(
83 ipython_version=release.version,
83 ipython_version=release.version,
84 ipython_path=pkg_path,
84 ipython_path=pkg_path,
85 codename=release.codename,
86 commit_source=src,
85 commit_source=src,
87 commit_hash=hsh,
86 commit_hash=hsh,
88 sys_version=sys.version,
87 sys_version=sys.version,
@@ -1,5 +1,5 b''
1 include README.rst
1 include README.rst
2 include COPYING.txt
2 include COPYING.rst
3 include setupbase.py
3 include setupbase.py
4 include setupegg.py
4 include setupegg.py
5
5
@@ -1,9 +1,9 b''
1 <html>
1 <html>
2 <head>
2 <head>
3 <meta http-equiv="Refresh" content="0; url=notebook.html" />
3 <meta http-equiv="Refresh" content="0; url=../notebook/index.html" />
4 <title>Notebook page has move</title>
4 <title>Notebook docs have moved</title>
5 </head>
5 </head>
6 <body>
6 <body>
7 <p>The notebook page has moved to <a href="notebook.html">this link</a>.</p>
7 <p>The notebook docs have moved <a href="../notebook/index.html">here</a>.</p>
8 </body>
8 </body>
9 </html>
9 </html>
@@ -95,8 +95,7 b' numpydoc_class_members_toctree = False'
95 # other places throughout the built documents.
95 # other places throughout the built documents.
96 #
96 #
97 # The full version, including alpha/beta/rc tags.
97 # The full version, including alpha/beta/rc tags.
98 codename = iprelease['codename']
98 release = "%s" % iprelease['version']
99 release = "%s: %s" % (iprelease['version'], codename)
100 # Just the X.Y.Z part, no '-dev'
99 # Just the X.Y.Z part, no '-dev'
101 version = iprelease['version'].split('-', 1)[0]
100 version = iprelease['version'].split('-', 1)[0]
102
101
@@ -164,7 +163,10 b" html_last_updated_fmt = '%b %d, %Y'"
164 # Additional templates that should be rendered to pages, maps page names to
163 # Additional templates that should be rendered to pages, maps page names to
165 # template names.
164 # template names.
166 html_additional_pages = {
165 html_additional_pages = {
167 'interactive/htmlnotebook': 'htmlnotebook.html',
166 'interactive/htmlnotebook': 'notebook_redirect.html',
167 'interactive/notebook': 'notebook_redirect.html',
168 'interactive/nbconvert': 'notebook_redirect.html',
169 'interactive/public_server': 'notebook_redirect.html',
168 }
170 }
169
171
170 # If false, no module index is generated.
172 # If false, no module index is generated.
@@ -4,4 +4,9 b''
4 octavemagic
4 octavemagic
5 ===========
5 ===========
6
6
7 .. note::
8
9 The octavemagic extension has been moved to `oct2py <http://blink1073.github.io/oct2py/docs/>`_
10 as :mod:`oct2py.ipython`.
11
7 .. automodule:: IPython.extensions.octavemagic
12 .. automodule:: IPython.extensions.octavemagic
@@ -4,4 +4,9 b''
4 rmagic
4 rmagic
5 ===========
5 ===========
6
6
7 .. note::
8
9 The rmagic extension has been moved to `rpy2 <http://rpy.sourceforge.net/rpy2.html>`_
10 as :mod:`rpy2.interactive.ipython`.
11
7 .. automodule:: IPython.extensions.rmagic
12 .. automodule:: IPython.extensions.rmagic
@@ -19,8 +19,6 b' on the IPython GitHub wiki.'
19 .. toctree::
19 .. toctree::
20 :maxdepth: 1
20 :maxdepth: 1
21
21
22
23 gitwash/index
24 messaging
22 messaging
25 parallel_messages
23 parallel_messages
26 parallel_connections
24 parallel_connections
@@ -25,6 +25,7 b' Contents'
25 whatsnew/index
25 whatsnew/index
26 install/index
26 install/index
27 interactive/index
27 interactive/index
28 notebook/index
28 parallel/index
29 parallel/index
29 config/index
30 config/index
30 development/index
31 development/index
@@ -10,9 +10,7 b' Using IPython for interactive work'
10 reference
10 reference
11 shell
11 shell
12 qtconsole
12 qtconsole
13 notebook
14 cm_keyboard
15 nbconvert
16 public_server
17
13
14 .. seealso::
18
15
16 :doc:`/notebook/index`
1 NO CONTENT: file renamed from docs/source/interactive/cm_keyboard.rst to docs/source/notebook/cm_keyboard.rst
NO CONTENT: file renamed from docs/source/interactive/cm_keyboard.rst to docs/source/notebook/cm_keyboard.rst
1 NO CONTENT: file renamed from docs/source/interactive/nbconvert.rst to docs/source/notebook/nbconvert.rst
NO CONTENT: file renamed from docs/source/interactive/nbconvert.rst to docs/source/notebook/nbconvert.rst
1 NO CONTENT: file renamed from docs/source/interactive/notebook.rst to docs/source/notebook/notebook.rst
NO CONTENT: file renamed from docs/source/interactive/notebook.rst to docs/source/notebook/notebook.rst
@@ -19,8 +19,8 b' a public interface <notebook_public_server>`.'
19
19
20 .. _notebook_security:
20 .. _notebook_security:
21
21
22 Notebook security
22 Securing a notebook server
23 -----------------
23 --------------------------
24
24
25 You can protect your notebook server with a simple single password by
25 You can protect your notebook server with a simple single password by
26 setting the :attr:`NotebookApp.password` configurable. You can prepare a
26 setting the :attr:`NotebookApp.password` configurable. You can prepare a
@@ -58,9 +58,9 b' The code to generate the simple DAG:'
58 .. sourcecode:: python
58 .. sourcecode:: python
59
59
60 import networkx as nx
60 import networkx as nx
61
61
62 G = nx.DiGraph()
62 G = nx.DiGraph()
63
63
64 # add 5 nodes, labeled 0-4:
64 # add 5 nodes, labeled 0-4:
65 map(G.add_node, range(5))
65 map(G.add_node, range(5))
66 # 1,2 depend on 0:
66 # 1,2 depend on 0:
@@ -71,7 +71,7 b' The code to generate the simple DAG:'
71 G.add_edge(2,3)
71 G.add_edge(2,3)
72 # 4 depends on 1
72 # 4 depends on 1
73 G.add_edge(1,4)
73 G.add_edge(1,4)
74
74
75 # now draw the graph:
75 # now draw the graph:
76 pos = { 0 : (0,0), 1 : (1,1), 2 : (-1,1),
76 pos = { 0 : (0,0), 1 : (1,1), 2 : (-1,1),
77 3 : (0,2), 4 : (2,2)}
77 3 : (0,2), 4 : (2,2)}
@@ -96,11 +96,11 b' Now, we need to build our dict of jobs corresponding to the nodes on the graph:'
96 .. sourcecode:: ipython
96 .. sourcecode:: ipython
97
97
98 In [3]: jobs = {}
98 In [3]: jobs = {}
99
99
100 # in reality, each job would presumably be different
100 # in reality, each job would presumably be different
101 # randomwait is just a function that sleeps for a random interval
101 # randomwait is just a function that sleeps for a random interval
102 In [4]: for node in G:
102 In [4]: for node in G:
103 ...: jobs[node] = randomwait
103 ...: jobs[node] = randomwait
104
104
105 Once we have a dict of jobs matching the nodes on the graph, we can start submitting jobs,
105 Once we have a dict of jobs matching the nodes on the graph, we can start submitting jobs,
106 and linking up the dependencies. Since we don't know a job's msg_id until it is submitted,
106 and linking up the dependencies. Since we don't know a job's msg_id until it is submitted,
@@ -114,10 +114,10 b' on which it depends:'
114
114
115 In [5]: rc = Client()
115 In [5]: rc = Client()
116 In [5]: view = rc.load_balanced_view()
116 In [5]: view = rc.load_balanced_view()
117
117
118 In [6]: results = {}
118 In [6]: results = {}
119
119
120 In [7]: for node in G.topological_sort():
120 In [7]: for node in nx.topological_sort(G):
121 ...: # get list of AsyncResult objects from nodes
121 ...: # get list of AsyncResult objects from nodes
122 ...: # leading into this one as dependencies
122 ...: # leading into this one as dependencies
123 ...: deps = [ results[n] for n in G.predecessors(node) ]
123 ...: deps = [ results[n] for n in G.predecessors(node) ]
@@ -152,18 +152,18 b' will be at the top, and quick, small tasks will be at the bottom.'
152 .. sourcecode:: ipython
152 .. sourcecode:: ipython
153
153
154 In [10]: from matplotlib.dates import date2num
154 In [10]: from matplotlib.dates import date2num
155
155
156 In [11]: from matplotlib.cm import gist_rainbow
156 In [11]: from matplotlib.cm import gist_rainbow
157
157
158 In [12]: pos = {}; colors = {}
158 In [12]: pos = {}; colors = {}
159
159
160 In [12]: for node in G:
160 In [12]: for node in G:
161 ....: md = results[node].metadata
161 ....: md = results[node].metadata
162 ....: start = date2num(md.started)
162 ....: start = date2num(md.started)
163 ....: runtime = date2num(md.completed) - start
163 ....: runtime = date2num(md.completed) - start
164 ....: pos[node] = (start, runtime)
164 ....: pos[node] = (start, runtime)
165 ....: colors[node] = md.engine_id
165 ....: colors[node] = md.engine_id
166
166
167 In [13]: nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(),
167 In [13]: nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(),
168 ....: cmap=gist_rainbow)
168 ....: cmap=gist_rainbow)
169
169
@@ -191,6 +191,13 b' Dashboard "Running" Tab'
191 The dashboard now has a "Running" tab which shows all of the running
191 The dashboard now has a "Running" tab which shows all of the running
192 notebooks.
192 notebooks.
193
193
194 Interactive Notebook Tour
195 -------------------------
196
197 Familiarize yourself with the updated notebook user interface, including an
198 explanation of Edit and Command modes, by going through the short guided tour
199 which can be started from the Help menu.
200
194 Other changes
201 Other changes
195 -------------
202 -------------
196
203
@@ -14,7 +14,7 b' requires utilities which are not available under Windows."""'
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.txt, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
@@ -161,6 +161,7 b' def find_package_data():'
161 pjoin(components, "jquery", "jquery.min.js"),
161 pjoin(components, "jquery", "jquery.min.js"),
162 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
162 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
163 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
163 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
164 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
164 pjoin(components, "marked", "lib", "marked.js"),
165 pjoin(components, "marked", "lib", "marked.js"),
165 pjoin(components, "requirejs", "require.js"),
166 pjoin(components, "requirejs", "require.js"),
166 pjoin(components, "underscore", "underscore-min.js"),
167 pjoin(components, "underscore", "underscore-min.js"),
@@ -142,7 +142,7 b' def get_pulls_list(project, auth=False, **params):'
142 headers = make_auth_header()
142 headers = make_auth_header()
143 else:
143 else:
144 headers = None
144 headers = None
145 pages = get_paged_request(url, headers=headers, params=params)
145 pages = get_paged_request(url, headers=headers, **params)
146 return pages
146 return pages
147
147
148 def get_issues_list(project, auth=False, **params):
148 def get_issues_list(project, auth=False, **params):
@@ -1,5 +1,9 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Simple tools to query github.com and gather stats about issues.
2 """Simple tools to query github.com and gather stats about issues.
3
4 To generate a report for IPython 2.0, run:
5
6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
3 """
7 """
4 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
5 # Imports
9 # Imports
@@ -7,14 +11,18 b''
7
11
8 from __future__ import print_function
12 from __future__ import print_function
9
13
14 import codecs
10 import json
15 import json
11 import re
16 import re
12 import sys
17 import sys
13
18
19 from argparse import ArgumentParser
14 from datetime import datetime, timedelta
20 from datetime import datetime, timedelta
15 from subprocess import check_output
21 from subprocess import check_output
16 from gh_api import get_paged_request, make_auth_header, get_pull_request, is_pull_request
22 from gh_api import (
17
23 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
24 get_milestone_id, get_issues_list,
25 )
18 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
19 # Globals
27 # Globals
20 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
@@ -26,12 +34,6 b' PER_PAGE = 100'
26 # Functions
34 # Functions
27 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
28
36
29 def get_issues(project="ipython/ipython", state="closed", pulls=False):
30 """Get a list of the issues from the Github API."""
31 which = 'pulls' if pulls else 'issues'
32 url = "https://api.github.com/repos/%s/%s?state=%s&per_page=%i" % (project, which, state, PER_PAGE)
33 return get_paged_request(url, headers=make_auth_header())
34
35 def round_hour(dt):
37 def round_hour(dt):
36 return dt.replace(minute=0,second=0,microsecond=0)
38 return dt.replace(minute=0,second=0,microsecond=0)
37
39
@@ -42,7 +44,6 b' def _parse_datetime(s):'
42 else:
44 else:
43 return datetime.fromtimestamp(0)
45 return datetime.fromtimestamp(0)
44
46
45
46 def issues2dict(issues):
47 def issues2dict(issues):
47 """Convert a list of issues to a dict, keyed by issue number."""
48 """Convert a list of issues to a dict, keyed by issue number."""
48 idict = {}
49 idict = {}
@@ -63,7 +64,6 b' def split_pulls(all_issues, project="ipython/ipython"):'
63 return issues, pulls
64 return issues, pulls
64
65
65
66
66
67 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
67 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
68 """Get all issues closed since a particular point in time. period
68 """Get all issues closed since a particular point in time. period
69 can either be a datetime object, or a timedelta object. In the
69 can either be a datetime object, or a timedelta object. In the
@@ -114,23 +114,31 b' def report(issues, show_urls=False):'
114
114
115 if __name__ == "__main__":
115 if __name__ == "__main__":
116 # deal with unicode
116 # deal with unicode
117 import codecs
118 sys.stdout = codecs.getwriter('utf8')(sys.stdout)
117 sys.stdout = codecs.getwriter('utf8')(sys.stdout)
119
118
120 # Whether to add reST urls for all issues in printout.
119 # Whether to add reST urls for all issues in printout.
121 show_urls = True
120 show_urls = True
122
123 # By default, search one month back
124 tag = None
125 if len(sys.argv) > 1:
126 try:
127 days = int(sys.argv[1])
128 except:
129 tag = sys.argv[1]
130 else:
131 tag = check_output(['git', 'describe', '--abbrev=0']).strip()
132
121
133 if tag:
122 parser = ArgumentParser()
123 parser.add_argument('--since-tag', type=str,
124 help="The git tag to use for the starting point (typically the last major release)."
125 )
126 parser.add_argument('--milestone', type=str,
127 help="The GitHub milestone to use for filtering issues [optional]."
128 )
129 parser.add_argument('--days', type=int,
130 help="The number of days of data to summarize (use this or --since-tag)."
131 )
132
133 opts = parser.parse_args()
134 tag = opts.since_tag
135
136 # set `since` from days or git tag
137 if opts.days:
138 since = datetime.utcnow() - timedelta(days=opts.days)
139 else:
140 if not tag:
141 tag = check_output(['git', 'describe', '--abbrev=0']).strip()
134 cmd = ['git', 'log', '-1', '--format=%ai', tag]
142 cmd = ['git', 'log', '-1', '--format=%ai', tag]
135 tagday, tz = check_output(cmd).strip().rsplit(' ', 1)
143 tagday, tz = check_output(cmd).strip().rsplit(' ', 1)
136 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
144 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
@@ -141,16 +149,23 b' if __name__ == "__main__":'
141 since += td
149 since += td
142 else:
150 else:
143 since -= td
151 since -= td
144 else:
145 since = datetime.utcnow() - timedelta(days=days)
146
152
147 since = round_hour(since)
153 since = round_hour(since)
148
154
149 print("fetching GitHub stats since %s (tag: %s)" % (since, tag), file=sys.stderr)
155 milestone = opts.milestone
150 # turn off to play interactively without redownloading, use %run -i
156
151 if 1:
157 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
158 if milestone:
159 milestone_id = get_milestone_id("ipython/ipython", milestone,
160 auth=True)
161 issues = get_issues_list("ipython/ipython",
162 milestone=milestone_id,
163 state='closed',
164 auth=True,
165 )
166 else:
152 issues = issues_closed_since(since, pulls=False)
167 issues = issues_closed_since(since, pulls=False)
153 pulls = issues_closed_since(since, pulls=True)
168 pulls = issues_closed_since(since, pulls=True)
154
169
155 # For regular reports, it's nice to show them in reverse chronological order
170 # For regular reports, it's nice to show them in reverse chronological order
156 issues = sorted_by_field(issues, reverse=True)
171 issues = sorted_by_field(issues, reverse=True)
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now