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 | 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-201 |
|
|
9 |
Copyright (c) 2001-2007, Fernando Perez |
|
|
10 | Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> | |
|
11 | Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> | |
|
8 | - Copyright (c) 2008-2014, IPython Development Team | |
|
9 | - Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu> | |
|
10 | - Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> | |
|
11 | - Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> | |
|
12 | 12 | |
|
13 | 13 | All rights reserved. |
|
14 | 14 | |
@@ -50,15 +50,7 b' details is kept in the documentation directory, in the file' | |||
|
50 | 50 | ``about/credits.txt``. |
|
51 | 51 | |
|
52 | 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: | |
|
54 | ||
|
55 | * Brian E. Granger | |
|
56 | * Jonathan March | |
|
57 | * Evan Patterson | |
|
58 | * Fernando Perez | |
|
59 | * Min Ragan-Kelley | |
|
60 | * Robert Kern | |
|
61 | ||
|
53 | https://github.com/ipython/. | |
|
62 | 54 | |
|
63 | 55 | Our Copyright Policy |
|
64 | 56 | -------------------- |
@@ -73,13 +65,10 b' changes/contributions they have specific copyright on, they should indicate' | |||
|
73 | 65 | their copyright in the commit message of the change, when they commit the |
|
74 | 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 | 69 | to indicate the copyright and license terms: |
|
78 | 70 | |
|
79 | #----------------------------------------------------------------------------- | |
|
80 | # Copyright (c) 2010, IPython Development Team. | |
|
81 | # | |
|
82 |
|
|
|
83 | # | |
|
84 | # The full license is in the file COPYING.txt, distributed with this software. | |
|
85 | #----------------------------------------------------------------------------- | |
|
71 | :: | |
|
72 | ||
|
73 | # Copyright (c) IPython Development Team. | |
|
74 | # Distributed under the terms of the Modified BSD License. |
@@ -29,6 +29,7 b' import threading' | |||
|
29 | 29 | # Our own packages |
|
30 | 30 | from IPython.config.configurable import Configurable |
|
31 | 31 | from IPython.external.decorator import decorator |
|
32 | from IPython.utils.decorators import undoc | |
|
32 | 33 | from IPython.utils.path import locate_profile |
|
33 | 34 | from IPython.utils import py3compat |
|
34 | 35 | from IPython.utils.traitlets import ( |
@@ -40,6 +41,7 b' from IPython.utils.warn import warn' | |||
|
40 | 41 | # Classes and functions |
|
41 | 42 | #----------------------------------------------------------------------------- |
|
42 | 43 | |
|
44 | @undoc | |
|
43 | 45 | class DummyDB(object): |
|
44 | 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 | 62 | @decorator |
|
61 | 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 | 65 | if sqlite3 is None or not self.enabled: |
|
64 | 66 | return [] |
|
65 | 67 | else: |
@@ -69,6 +71,7 b' def needs_sqlite(f, self, *a, **kw):' | |||
|
69 | 71 | if sqlite3 is not None: |
|
70 | 72 | DatabaseError = sqlite3.DatabaseError |
|
71 | 73 | else: |
|
74 | @undoc | |
|
72 | 75 | class DatabaseError(Exception): |
|
73 | 76 | "Dummy exception when sqlite could not be imported. Should never occur." |
|
74 | 77 | |
@@ -159,7 +162,7 b' class HistoryAccessor(Configurable):' | |||
|
159 | 162 | hist_file : str |
|
160 | 163 | Path to an SQLite history database stored by IPython. If specified, |
|
161 | 164 | hist_file overrides profile. |
|
162 | config : | |
|
165 | config : :class:`~IPython.config.loader.Config` | |
|
163 | 166 | Config object. hist_file can also be set through this. |
|
164 | 167 | """ |
|
165 | 168 | # We need a pointer back to the shell for various tasks. |
@@ -254,34 +257,43 b' class HistoryAccessor(Configurable):' | |||
|
254 | 257 | |
|
255 | 258 | @needs_sqlite |
|
256 | 259 | @catch_corrupt_db |
|
257 |
def get_session_info(self, session |
|
|
258 |
""" |
|
|
260 | def get_session_info(self, session): | |
|
261 | """Get info about a session. | |
|
259 | 262 | |
|
260 | 263 | Parameters |
|
261 | 264 | ---------- |
|
262 | 265 | |
|
263 | 266 | session : int |
|
264 |
Session number to retrieve. |
|
|
265 | numbers count back from current session, so -1 is previous session. | |
|
267 | Session number to retrieve. | |
|
266 | 268 | |
|
267 | 269 | Returns |
|
268 | 270 | ------- |
|
269 | ||
|
270 | (session_id [int], start [datetime], end [datetime], num_cmds [int], | |
|
271 | remark [unicode]) | |
|
272 | ||
|
273 | Sessions that are running or did not exit cleanly will have `end=None` | |
|
274 | and `num_cmds=None`. | |
|
275 | ||
|
271 | ||
|
272 | session_id : int | |
|
273 | Session ID number | |
|
274 | start : datetime | |
|
275 | Timestamp for the start of the session. | |
|
276 | end : datetime | |
|
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 | 283 | query = "SELECT * from sessions where session == ?" |
|
282 | 284 | return self.db.execute(query, (session,)).fetchone() |
|
283 | 285 | |
|
284 | 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 | 297 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): |
|
286 | 298 | """Get the last n lines from the history database. |
|
287 | 299 | |
@@ -374,9 +386,10 b' class HistoryAccessor(Configurable):' | |||
|
374 | 386 | |
|
375 | 387 | Returns |
|
376 | 388 | ------- |
|
377 | An iterator over the desired lines. Each line is a 3-tuple, either | |
|
378 | (session, line, input) if output is False, or | |
|
379 |
(session, line, |
|
|
389 | entries | |
|
390 | An iterator over the desired lines. Each line is a 3-tuple, either | |
|
391 | (session, line, input) if output is False, or | |
|
392 | (session, line, (input, output)) if output is True. | |
|
380 | 393 | """ |
|
381 | 394 | if stop: |
|
382 | 395 | lineclause = "line >= ? AND line < ?" |
@@ -535,6 +548,35 b' class HistoryManager(HistoryAccessor):' | |||
|
535 | 548 | # ------------------------------ |
|
536 | 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 | 580 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): |
|
539 | 581 | """Get input and output history from the current session. Called by |
|
540 | 582 | get_range, and takes similar parameters.""" |
@@ -578,9 +620,10 b' class HistoryManager(HistoryAccessor):' | |||
|
578 | 620 | |
|
579 | 621 | Returns |
|
580 | 622 | ------- |
|
581 | An iterator over the desired lines. Each line is a 3-tuple, either | |
|
582 | (session, line, input) if output is False, or | |
|
583 |
(session, line, |
|
|
623 | entries | |
|
624 | An iterator over the desired lines. Each line is a 3-tuple, either | |
|
625 | (session, line, input) if output is False, or | |
|
626 | (session, line, (input, output)) if output is True. | |
|
584 | 627 | """ |
|
585 | 628 | if session <= 0: |
|
586 | 629 | session += self.session_number |
@@ -594,7 +637,7 b' class HistoryManager(HistoryAccessor):' | |||
|
594 | 637 | ## ---------------------------- |
|
595 | 638 | def store_inputs(self, line_num, source, source_raw=None): |
|
596 | 639 | """Store source and raw input in history and create input cache |
|
597 | variables _i*. | |
|
640 | variables ``_i*``. | |
|
598 | 641 | |
|
599 | 642 | Parameters |
|
600 | 643 | ---------- |
@@ -765,8 +808,8 b' def extract_hist_ranges(ranges_str):' | |||
|
765 | 808 | |
|
766 | 809 | Examples |
|
767 | 810 | -------- |
|
768 |
list(extract_ |
|
|
769 |
[(-8, 5, None), (-7, 1, |
|
|
811 | >>> list(extract_hist_ranges("~8/5-~7/4 2")) | |
|
812 | [(-8, 5, None), (-7, 1, 5), (0, 2, 3)] | |
|
770 | 813 | """ |
|
771 | 814 | for range_str in ranges_str.split(): |
|
772 | 815 | rmatch = range_re.match(range_str) |
@@ -97,6 +97,7 b' class ScriptMagics(Magics):' | |||
|
97 | 97 | 'perl', |
|
98 | 98 | 'ruby', |
|
99 | 99 | 'python', |
|
100 | 'python2', | |
|
100 | 101 | 'python3', |
|
101 | 102 | 'pypy', |
|
102 | 103 | ] |
@@ -96,7 +96,10 b' def figsize(sizex, sizey):' | |||
|
96 | 96 | |
|
97 | 97 | |
|
98 | 98 | def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs): |
|
99 |
"""Print a figure to an image, and return the resulting |
|
|
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 | 104 | Any keyword args are passed to fig.canvas.print_figure, |
|
102 | 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 | 129 | bytes_io = BytesIO() |
|
127 | 130 | fig.canvas.print_figure(bytes_io, **kw) |
|
128 |
|
|
|
131 | data = bytes_io.getvalue() | |
|
132 | if fmt == 'svg': | |
|
133 | data = data.decode('utf-8') | |
|
134 | return data | |
|
129 | 135 | |
|
130 | 136 | def retina_figure(fig, **kwargs): |
|
131 | 137 | """format a figure as a pixel-doubled (retina) PNG""" |
@@ -26,7 +26,8 b" _version_extra = 'dev'" | |||
|
26 | 26 | # _version_extra = 'rc1' |
|
27 | 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 | 32 | # Construct full version string from these. |
|
32 | 33 | _ver = [_version_major, _version_minor, _version_patch] |
@@ -58,7 +58,7 b' def test_figure_to_svg():' | |||
|
58 | 58 | ax.plot([1,2,3]) |
|
59 | 59 | plt.draw() |
|
60 | 60 | svg = pt.print_figure(fig, 'svg')[:100].lower() |
|
61 |
nt.assert_in( |
|
|
61 | nt.assert_in(u'doctype svg', svg) | |
|
62 | 62 | |
|
63 | 63 | def _check_pil_jpeg_bytes(): |
|
64 | 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 | 74 | from .base.handlers import AuthenticatedFileHandler, FileFindHandler |
|
75 | 75 | |
|
76 | from IPython.config import Config | |
|
76 | 77 | from IPython.config.application import catch_config_error, boolean_flag |
|
77 | 78 | from IPython.core.application import BaseIPythonApplication |
|
78 | 79 | from IPython.core.profiledir import ProfileDir |
@@ -554,10 +555,12 b' class NotebookApp(BaseIPythonApplication):' | |||
|
554 | 555 | |
|
555 | 556 | # Use config here, to ensure that it takes higher priority than |
|
556 | 557 | # anything that comes from the profile. |
|
558 | c = Config() | |
|
557 | 559 | if os.path.isdir(f): |
|
558 |
|
|
|
560 | c.NotebookApp.notebook_dir = f | |
|
559 | 561 | elif os.path.isfile(f): |
|
560 |
|
|
|
562 | c.NotebookApp.file_to_run = f | |
|
563 | self.update_config(c) | |
|
561 | 564 | |
|
562 | 565 | def init_kernel_argv(self): |
|
563 | 566 | """construct the kernel arguments""" |
@@ -128,15 +128,6 b' IPython.keyboard = (function (IPython) {' | |||
|
128 | 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 | 131 | // Shortcut manager class |
|
141 | 132 | |
|
142 | 133 | var ShortcutManager = function (delay) { |
@@ -252,7 +243,7 b' IPython.keyboard = (function (IPython) {' | |||
|
252 | 243 | ShortcutManager.prototype.handles = function (event) { |
|
253 | 244 | var shortcut = event_to_shortcut(event); |
|
254 | 245 | var data = this._shortcuts[shortcut]; |
|
255 | return !( data === undefined ) | |
|
246 | return !( data === undefined || data.handler === undefined ) | |
|
256 | 247 | } |
|
257 | 248 | |
|
258 | 249 | return { |
@@ -262,8 +253,7 b' IPython.keyboard = (function (IPython) {' | |||
|
262 | 253 | normalize_key : normalize_key, |
|
263 | 254 | normalize_shortcut : normalize_shortcut, |
|
264 | 255 | shortcut_to_event : shortcut_to_event, |
|
265 |
event_to_shortcut : event_to_shortcut |
|
|
266 | trigger_keydown : trigger_keydown | |
|
256 | event_to_shortcut : event_to_shortcut | |
|
267 | 257 | }; |
|
268 | 258 | |
|
269 | 259 | }(IPython)); |
@@ -58,6 +58,9 b' var IPython = (function (IPython) {' | |||
|
58 | 58 | this.style(); |
|
59 | 59 | this.create_elements(); |
|
60 | 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 | 1732 | * @method save_notebook |
|
1729 | 1733 | */ |
@@ -1829,7 +1833,7 b' var IPython = (function (IPython) {' | |||
|
1829 | 1833 | " Selecting trust will immediately reload this notebook in a trusted state." |
|
1830 | 1834 | ).append( |
|
1831 | 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 | 1837 | .text("IPython security documentation") |
|
1834 | 1838 | ).append(".") |
|
1835 | 1839 | ); |
@@ -2100,7 +2104,9 b' var IPython = (function (IPython) {' | |||
|
2100 | 2104 | IPython.CellToolbar.global_show(); |
|
2101 | 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 | 2110 | $([IPython.events]).trigger('notebook_loaded.Notebook'); |
|
2105 | 2111 | }; |
|
2106 | 2112 |
@@ -188,8 +188,8 b' var IPython = (function (IPython) {' | |||
|
188 | 188 | $([IPython.events]).on('notebook_saved.Notebook', function () { |
|
189 | 189 | nnw.set_message("Notebook saved",2000); |
|
190 | 190 | }); |
|
191 | $([IPython.events]).on('notebook_save_failed.Notebook', function () { | |
|
192 | nnw.set_message("Notebook save failed"); | |
|
191 | $([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) { | |
|
192 | nnw.set_message(data || "Notebook save failed"); | |
|
193 | 193 | }); |
|
194 | 194 | |
|
195 | 195 | // Checkpoint events |
@@ -541,6 +541,10 b' var IPython = (function (IPython) {' | |||
|
541 | 541 | var container = element; |
|
542 | 542 | container.show = function(){console.log('Warning "container.show()" is deprecated.')}; |
|
543 | 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 | 548 | try { |
|
545 | 549 | eval(js); |
|
546 | 550 | } catch(err) { |
@@ -131,17 +131,13 b' var IPython = (function (IPython) {' | |||
|
131 | 131 | Tooltip.prototype.showInPager = function (cell) { |
|
132 | 132 | // reexecute last call in pager by appending ? to show back in pager |
|
133 | 133 | var that = this; |
|
134 | var empty = function () {}; | |
|
135 | cell.kernel.execute( | |
|
136 | that.name + '?', { | |
|
137 | 'execute_reply': empty, | |
|
138 | 'output': empty, | |
|
139 | 'clear_output': empty, | |
|
140 | 'cell': cell | |
|
141 | }, { | |
|
142 | 'silent': false, | |
|
143 | 'store_history': true | |
|
144 | }); | |
|
134 | var callbacks = {'shell' : { | |
|
135 | 'payload' : { | |
|
136 | 'page' : $.proxy(cell._open_with_pager, cell) | |
|
137 | } | |
|
138 | } | |
|
139 | }; | |
|
140 | cell.kernel.execute(that.name + '?', callbacks, {'silent': false, 'store_history': true}); | |
|
145 | 141 | this.remove_and_cancel_tooltip(); |
|
146 | 142 | }; |
|
147 | 143 |
@@ -33,6 +33,14 b' div.prompt {' | |||
|
33 | 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 | 44 | div.inner_cell { |
|
37 | 45 | .vbox(); |
|
38 | 46 | .box-flex1(); |
@@ -10,13 +10,19 b' div.input {' | |||
|
10 | 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 | 20 | /* input_area and input_prompt must match in top border and margin for alignment */ |
|
14 | 21 | div.input_prompt { |
|
15 | 22 | color: navy; |
|
16 | 23 | border-top: 1px solid transparent; |
|
17 | 24 | } |
|
18 | 25 | |
|
19 | ||
|
20 | 26 | // The styles related to div.highlight are for nbconvert HTML output only. This works |
|
21 | 27 | // because the .highlight div isn't present in the live notebook. We could put this into |
|
22 | 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 | 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 | 18 | span#notebook_name { |
|
11 | 19 | height: 1em; |
|
12 | 20 | line-height: 1em; |
@@ -72,6 +72,13 b' div.output_area {' | |||
|
72 | 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 | 82 | div.output_area pre { |
|
76 | 83 | margin: 0; |
|
77 | 84 | padding: 0; |
@@ -2,6 +2,12 b' div.text_cell {' | |||
|
2 | 2 | padding: 5px 5px 5px 0px; |
|
3 | 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 | 12 | div.text_cell_render { |
|
7 | 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 | 73 | div.cell.edit_mode{border-radius:4px;border:thin #008000 solid} |
|
74 | 74 | div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none} |
|
75 | 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 | 77 | div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7} |
|
78 | 78 | div.prompt:empty{padding-top:0;padding-bottom:0} |
|
79 | 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 | 81 | div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent} |
|
82 | 82 | div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em} |
|
83 | 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 | 117 | div.output_area .rendered_html table{margin-left:0;margin-right:0} |
|
118 | 118 | div.output_area .rendered_html img{margin-left:0;margin-right:0} |
|
119 | 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 | 121 | div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1} |
|
122 | 122 | div.output_text{text-align:left;color:#000;line-height:1.21429em} |
|
123 | 123 | div.output_stderr{background:#fdd;} |
@@ -170,7 +170,7 b' p.p-space{margin-bottom:10px}' | |||
|
170 | 170 | .rendered_html img{display:block;margin-left:auto;margin-right:auto} |
|
171 | 171 | .rendered_html *+img{margin-top:1em} |
|
172 | 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 | 174 | a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden} |
|
175 | 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 | 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 | 1350 | div.cell.edit_mode{border-radius:4px;border:thin #008000 solid} |
|
1351 | 1351 | div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none} |
|
1352 | 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 | 1354 | div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7} |
|
1355 | 1355 | div.prompt:empty{padding-top:0;padding-bottom:0} |
|
1356 | 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 | 1358 | div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent} |
|
1359 | 1359 | div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em} |
|
1360 | 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 | 1394 | div.output_area .rendered_html table{margin-left:0;margin-right:0} |
|
1395 | 1395 | div.output_area .rendered_html img{margin-left:0;margin-right:0} |
|
1396 | 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 | 1398 | div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1} |
|
1399 | 1399 | div.output_text{text-align:left;color:#000;line-height:1.21429em} |
|
1400 | 1400 | div.output_stderr{background:#fdd;} |
@@ -1447,7 +1447,7 b' p.p-space{margin-bottom:10px}' | |||
|
1447 | 1447 | .rendered_html img{display:block;margin-left:auto;margin-right:auto} |
|
1448 | 1448 | .rendered_html *+img{margin-top:1em} |
|
1449 | 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 | 1451 | a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden} |
|
1452 | 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 | 1453 | div.cell.text_cell.rendered{padding:0} |
@@ -1476,7 +1476,7 b' div.cell.text_cell.rendered{padding:0}' | |||
|
1476 | 1476 | .docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important} |
|
1477 | 1477 | body{background-color:#fff} |
|
1478 | 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 | 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 | 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 | 1482 | div.ui-widget-content{border:1px solid #ababab;outline:none} |
@@ -227,14 +227,14 b' class="notebook_app"' | |||
|
227 | 227 | ( |
|
228 | 228 | ("http://ipython.org/documentation.html","IPython Help",True), |
|
229 | 229 | ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True), |
|
230 |
("http://ipython.org/ipython-doc/ |
|
|
231 |
("http://ipython.org/ipython-doc/ |
|
|
230 | ("http://ipython.org/ipython-doc/2/notebook/notebook.html","Notebook Help",True), | |
|
231 | ("http://ipython.org/ipython-doc/2/notebook/cm_keyboard.html","Editor Shortcuts",True), | |
|
232 | 232 | ),( |
|
233 | 233 | ("http://docs.python.org","Python",True), |
|
234 | 234 | ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True), |
|
235 | 235 | ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True), |
|
236 | 236 | ("http://matplotlib.org/contents.html","Matplotlib",True), |
|
237 |
("http://docs.sympy.org/ |
|
|
237 | ("http://docs.sympy.org/latest/index.html","SymPy",True), | |
|
238 | 238 | ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True) |
|
239 | 239 | ) |
|
240 | 240 | ) |
@@ -10,12 +10,12 b' casper.notebook_test(function () {' | |||
|
10 | 10 | for (i = 0; i < ncells; i++) { |
|
11 | 11 | IPython.notebook.delete_cell(); |
|
12 | 12 | } |
|
13 | ||
|
14 | // Simulate the "up arrow" and "down arrow" keys. | |
|
15 | // | |
|
16 | IPython.keyboard.trigger_keydown('up'); | |
|
17 | IPython.keyboard.trigger_keydown('down'); | |
|
13 | ||
|
18 | 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 | 20 | this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.'); |
|
21 | 21 | }); |
@@ -22,7 +22,11 b' casper.notebook_test(function () {' | |||
|
22 | 22 | var cell = IPython.notebook.get_cell(0); |
|
23 | 23 | cell.set_text('a=11; print(a)'); |
|
24 | 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 | 32 | this.wait_for_output(0); |
@@ -41,7 +45,10 b' casper.notebook_test(function () {' | |||
|
41 | 45 | var cell = IPython.notebook.get_cell(0); |
|
42 | 46 | cell.set_text('a=12; print(a)'); |
|
43 | 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 | 54 | this.wait_for_output(0); |
@@ -31,8 +31,8 b' casper.notebook_test(function () {' | |||
|
31 | 31 | }); |
|
32 | 32 | |
|
33 | 33 | // interrupt using Ctrl-M I keyboard shortcut |
|
34 |
this.then |
|
|
35 |
|
|
|
34 | this.then(function(){ | |
|
35 | this.trigger_keydown('i'); | |
|
36 | 36 | }); |
|
37 | 37 | |
|
38 | 38 | this.wait_for_output(0); |
@@ -2,37 +2,42 b'' | |||
|
2 | 2 | // Test merging two notebook cells. |
|
3 | 3 | // |
|
4 | 4 | casper.notebook_test(function() { |
|
5 | var output = this.evaluate(function () { | |
|
6 | // Fill in test data. | |
|
7 | IPython.notebook.command_mode(); | |
|
8 | var set_cell_text = function () { | |
|
5 | var that = this; | |
|
6 | var set_cells_text = function () { | |
|
7 | that.evaluate(function() { | |
|
9 | 8 | var cell_one = IPython.notebook.get_selected_cell(); |
|
10 | 9 | cell_one.set_text('a = 5'); |
|
11 |
|
|
|
12 | IPython.keyboard.trigger_keydown('b'); | |
|
10 | }); | |
|
11 | ||
|
12 | that.trigger_keydown('b'); | |
|
13 | ||
|
14 | that.evaluate(function() { | |
|
13 | 15 | var cell_two = IPython.notebook.get_selected_cell(); |
|
14 | 16 | cell_two.set_text('print(a)'); |
|
15 | }; | |
|
17 | }); | |
|
18 | }; | |
|
19 | ||
|
20 | this.evaluate(function () { | |
|
21 | IPython.notebook.command_mode(); | |
|
22 | }); | |
|
16 | 23 | |
|
17 |
|
|
|
18 |
|
|
|
24 | // merge_cell_above() | |
|
25 | set_cells_text(); | |
|
26 | var output_above = this.evaluate(function () { | |
|
19 | 27 | IPython.notebook.merge_cell_above(); |
|
20 |
|
|
|
28 | return IPython.notebook.get_selected_cell().get_text(); | |
|
29 | }); | |
|
21 | 30 | |
|
22 |
|
|
|
23 |
|
|
|
31 | // merge_cell_below() | |
|
32 | set_cells_text(); | |
|
33 | var output_below = this.evaluate(function() { | |
|
24 | 34 | IPython.notebook.select(0); |
|
25 | 35 | IPython.notebook.merge_cell_below(); |
|
26 |
|
|
|
27 | ||
|
28 | return { | |
|
29 | above: merged_above.get_text(), | |
|
30 | below: merged_below.get_text() | |
|
31 | }; | |
|
36 | return IPython.notebook.get_selected_cell().get_text(); | |
|
32 | 37 | }); |
|
33 | 38 | |
|
34 |
this.test.assertEquals(output |
|
|
39 | this.test.assertEquals(output_above, 'a = 5\nprint(a)', | |
|
35 | 40 | 'Successful merge_cell_above().'); |
|
36 |
this.test.assertEquals(output |
|
|
41 | this.test.assertEquals(output_below, 'a = 5\nprint(a)', | |
|
37 | 42 | 'Successful merge_cell_below().'); |
|
38 | 43 | }); |
@@ -2,15 +2,15 b'' | |||
|
2 | 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 | 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 | 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 | 12 | casper.open_new_notebook = function () { |
|
13 | // Create and open a new notebook. | |
|
14 | 14 | var baseUrl = this.get_notebook_server(); |
|
15 | 15 | this.start(baseUrl); |
|
16 | 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 | 37 | casper.kernel_running = function kernel_running() { |
|
38 | // Return whether or not the kernel is running. | |
|
39 | 39 | return this.evaluate(function kernel_running() { |
|
40 | 40 | return IPython.notebook.kernel.running; |
|
41 | 41 | }); |
|
42 | 42 | }; |
|
43 | 43 | |
|
44 | // Shut down the current notebook's kernel. | |
|
45 | 44 | casper.shutdown_current_kernel = function () { |
|
45 | // Shut down the current notebook's kernel. | |
|
46 | 46 | this.thenEvaluate(function() { |
|
47 | 47 | IPython.notebook.kernel.kill(); |
|
48 | 48 | }); |
@@ -50,8 +50,9 b' casper.shutdown_current_kernel = function () {' | |||
|
50 | 50 | this.wait(1000); |
|
51 | 51 | }; |
|
52 | 52 | |
|
53 | // Delete created notebook. | |
|
54 | 53 | casper.delete_current_notebook = function () { |
|
54 | // Delete created notebook. | |
|
55 | ||
|
55 | 56 | // For some unknown reason, this doesn't work?!? |
|
56 | 57 | this.thenEvaluate(function() { |
|
57 | 58 | IPython.notebook.delete(); |
@@ -59,6 +60,7 b' casper.delete_current_notebook = function () {' | |||
|
59 | 60 | }; |
|
60 | 61 | |
|
61 | 62 | casper.wait_for_busy = function () { |
|
63 | // Waits for the notebook to enter a busy state. | |
|
62 | 64 | this.waitFor(function () { |
|
63 | 65 | return this.evaluate(function () { |
|
64 | 66 | return IPython._status == 'busy'; |
@@ -67,6 +69,7 b' casper.wait_for_busy = function () {' | |||
|
67 | 69 | }; |
|
68 | 70 | |
|
69 | 71 | casper.wait_for_idle = function () { |
|
72 | // Waits for the notebook to idle. | |
|
70 | 73 | this.waitFor(function () { |
|
71 | 74 | return this.evaluate(function () { |
|
72 | 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 | 80 | casper.wait_for_output = function (cell_num, out_num) { |
|
81 | // wait for the nth output in a given cell | |
|
79 | 82 | this.wait_for_idle(); |
|
80 | 83 | out_num = out_num || 0; |
|
81 | 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 | 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 | 108 | this.waitFor(function () { |
|
106 | 109 | var pending = this.evaluate(function (m) { |
|
107 | 110 | return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs; |
|
108 | 111 | }, {m: widget_info.model_id}); |
|
109 | 112 | |
|
110 | if (pending == 0) { | |
|
113 | if (pending === 0) { | |
|
111 | 114 | return true; |
|
112 | 115 | } else { |
|
113 | 116 | return false; |
|
114 | 117 | } |
|
115 | 118 | }); |
|
116 | } | |
|
119 | }; | |
|
117 | 120 | |
|
118 | // return an output of a given cell | |
|
119 | 121 | casper.get_output_cell = function (cell_num, out_num) { |
|
122 | // return an output of a given cell | |
|
120 | 123 | out_num = out_num || 0; |
|
121 | 124 | var result = casper.evaluate(function (c, o) { |
|
122 | 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 | 143 | casper.get_cells_length = function () { |
|
144 | // return the number of cells in the notebook | |
|
142 | 145 | var result = casper.evaluate(function () { |
|
143 | 146 | return IPython.notebook.get_cells().length; |
|
144 | }) | |
|
147 | }); | |
|
145 | 148 | return result; |
|
146 | 149 | }; |
|
147 | 150 | |
|
148 | // Set the text content of a cell. | |
|
149 | 151 | casper.set_cell_text = function(index, text){ |
|
152 | // Set the text content of a cell. | |
|
150 | 153 | this.evaluate(function (index, text) { |
|
151 | 154 | var cell = IPython.notebook.get_cell(index); |
|
152 | 155 | cell.set_text(text); |
|
153 | 156 | }, index, text); |
|
154 | 157 | }; |
|
155 | 158 | |
|
156 | // Inserts a cell at the bottom of the notebook | |
|
157 | // Returns the new cell's index. | |
|
159 | casper.get_cell_text = function(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 | 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 | 170 | cell_type = cell_type || 'code'; |
|
160 | 171 | |
|
161 | 172 | return this.evaluate(function (cell_type) { |
@@ -164,9 +175,9 b' casper.insert_cell_at_bottom = function(cell_type){' | |||
|
164 | 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 | 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 | 181 | var index = this.insert_cell_at_bottom(cell_type); |
|
171 | 182 | if (text !== undefined) { |
|
172 | 183 | this.set_cell_text(index, text); |
@@ -174,9 +185,9 b' casper.append_cell = function(text, cell_type) {' | |||
|
174 | 185 | return index; |
|
175 | 186 | }; |
|
176 | 187 | |
|
177 | // Asynchronously executes a cell by index. | |
|
178 | // Returns the cell's index. | |
|
179 | 188 | casper.execute_cell = function(index){ |
|
189 | // Asynchronously executes a cell by index. | |
|
190 | // Returns the cell's index. | |
|
180 | 191 | var that = this; |
|
181 | 192 | this.then(function(){ |
|
182 | 193 | that.evaluate(function (index) { |
@@ -187,11 +198,11 b' casper.execute_cell = function(index){' | |||
|
187 | 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 | 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 | 206 | var return_val = this.execute_cell(index); |
|
196 | 207 | |
|
197 | 208 | this.wait_for_idle(); |
@@ -206,18 +217,18 b' casper.execute_cell_then = function(index, then_callback) {' | |||
|
206 | 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 | 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 | 223 | return casper.evaluate(function (index, selector) { |
|
213 | 224 | var $cell = IPython.notebook.get_cell(index).element; |
|
214 | 225 | return $cell.find(selector).length > 0; |
|
215 | 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 | 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 | 232 | return casper.evaluate(function (index, selector, function_name, function_args) { |
|
222 | 233 | var $cell = IPython.notebook.get_cell(index).element; |
|
223 | 234 | var $el = $cell.find(selector); |
@@ -225,8 +236,183 b' casper.cell_element_function = function(index, selector, function_name, function' | |||
|
225 | 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 | 414 | casper.notebook_test = function(test) { |
|
415 | // Wrap a notebook test to reduce boilerplate. | |
|
230 | 416 | this.open_new_notebook(); |
|
231 | 417 | this.then(test); |
|
232 | 418 | |
@@ -253,14 +439,14 b' casper.notebook_test = function(test) {' | |||
|
253 | 439 | casper.wait_for_dashboard = function () { |
|
254 | 440 | // Wait for the dashboard list to load. |
|
255 | 441 | casper.waitForSelector('.list_item'); |
|
256 | } | |
|
442 | }; | |
|
257 | 443 | |
|
258 | 444 | casper.open_dashboard = function () { |
|
259 | 445 | // Start casper by opening the dashboard page. |
|
260 | 446 | var baseUrl = this.get_notebook_server(); |
|
261 | 447 | this.start(baseUrl); |
|
262 | 448 | this.wait_for_dashboard(); |
|
263 | } | |
|
449 | }; | |
|
264 | 450 | |
|
265 | 451 | casper.dashboard_test = function (test) { |
|
266 | 452 | // Open the dashboard page and run a test. |
@@ -276,16 +462,16 b' casper.dashboard_test = function (test) {' | |||
|
276 | 462 | this.run(function() { |
|
277 | 463 | this.test.done(); |
|
278 | 464 | }); |
|
279 | } | |
|
465 | }; | |
|
280 | 466 | |
|
281 | casper.options.waitTimeout=10000 | |
|
467 | casper.options.waitTimeout=10000; | |
|
282 | 468 | casper.on('waitFor.timeout', function onWaitForTimeout(timeout) { |
|
283 | 469 | this.echo("Timeout for " + casper.get_notebook_server()); |
|
284 | 470 | this.echo("Is the notebook server running?"); |
|
285 | 471 | }); |
|
286 | 472 | |
|
287 | // Pass `console.log` calls from page JS to casper. | |
|
288 | casper.printLog = function () { | |
|
473 | casper.print_log = function () { | |
|
474 | // Pass `console.log` calls from page JS to casper. | |
|
289 | 475 | this.on('remote.message', function(msg) { |
|
290 | 476 | this.echo('Remote message caught: ' + msg); |
|
291 | 477 | }); |
@@ -12,6 +12,10 b'' | |||
|
12 | 12 | # Imports |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | |
|
15 | import io | |
|
16 | ||
|
17 | from IPython.nbformat import current | |
|
18 | ||
|
15 | 19 | from .base import ExportersTestsBase |
|
16 | 20 | from ..rst import RSTExporter |
|
17 | 21 | from IPython.testing.decorators import onlyif_cmds_exist |
@@ -40,3 +44,22 b' class TestRSTExporter(ExportersTestsBase):' | |||
|
40 | 44 | """ |
|
41 | 45 | (output, resources) = RSTExporter().from_filename(self._get_notebook()) |
|
42 | 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 | 8 | {% endblock output_prompt %} |
|
9 | 9 | |
|
10 | 10 | {% block input %} |
|
11 |
{%- if |
|
|
11 | {%- if cell.input.strip() -%} | |
|
12 | 12 | .. code:: python |
|
13 | 13 | |
|
14 | 14 | {{ cell.input | indent}} |
@@ -522,19 +522,23 b' class TestClient(ClusterTestCase):' | |||
|
522 | 522 | def test_spin_thread(self): |
|
523 | 523 | self.client.spin_thread(0.01) |
|
524 | 524 | ar = self.client[-1].apply_async(lambda : 1) |
|
525 | time.sleep(0.1) | |
|
526 | self.assertTrue(ar.wall_time < 0.1, | |
|
527 | "spin should have kept wall_time < 0.1, but got %f" % ar.wall_time | |
|
528 | ) | |
|
525 | md = self.client.metadata[ar.msg_ids[0]] | |
|
526 | # 3s timeout, 100ms poll | |
|
527 | for i in range(30): | |
|
528 | time.sleep(0.1) | |
|
529 | if md['received'] is not None: | |
|
530 | break | |
|
531 | self.assertIsInstance(md['received'], datetime) | |
|
529 | 532 | |
|
530 | 533 | def test_stop_spin_thread(self): |
|
531 | 534 | self.client.spin_thread(0.01) |
|
532 | 535 | self.client.stop_spin_thread() |
|
533 | 536 | ar = self.client[-1].apply_async(lambda : 1) |
|
534 | time.sleep(0.15) | |
|
535 | self.assertTrue(ar.wall_time > 0.1, | |
|
536 | "Shouldn't be spinning, but got wall_time=%f" % ar.wall_time | |
|
537 | ) | |
|
537 | md = self.client.metadata[ar.msg_ids[0]] | |
|
538 | # 500ms timeout, 100ms poll | |
|
539 | for i in range(5): | |
|
540 | time.sleep(0.1) | |
|
541 | self.assertIsNone(md['received'], None) | |
|
538 | 542 | |
|
539 | 543 | def test_activate(self): |
|
540 | 544 | ip = get_ipython() |
@@ -82,7 +82,6 b' def pkg_info(pkg_path):' | |||
|
82 | 82 | return dict( |
|
83 | 83 | ipython_version=release.version, |
|
84 | 84 | ipython_path=pkg_path, |
|
85 | codename=release.codename, | |
|
86 | 85 | commit_source=src, |
|
87 | 86 | commit_hash=hsh, |
|
88 | 87 | sys_version=sys.version, |
@@ -1,5 +1,5 b'' | |||
|
1 | 1 | include README.rst |
|
2 |
include COPYING. |
|
|
2 | include COPYING.rst | |
|
3 | 3 | include setupbase.py |
|
4 | 4 | include setupegg.py |
|
5 | 5 |
@@ -1,9 +1,9 b'' | |||
|
1 | 1 | <html> |
|
2 | 2 | <head> |
|
3 | <meta http-equiv="Refresh" content="0; url=notebook.html" /> | |
|
4 |
<title>Notebook |
|
|
5 |
</head> |
|
|
3 | <meta http-equiv="Refresh" content="0; url=../notebook/index.html" /> | |
|
4 | <title>Notebook docs have moved</title> | |
|
5 | </head> | |
|
6 | 6 | <body> |
|
7 |
<p>The notebook |
|
|
7 | <p>The notebook docs have moved <a href="../notebook/index.html">here</a>.</p> | |
|
8 | 8 | </body> |
|
9 | 9 | </html> |
@@ -95,8 +95,7 b' numpydoc_class_members_toctree = False' | |||
|
95 | 95 | # other places throughout the built documents. |
|
96 | 96 | # |
|
97 | 97 | # The full version, including alpha/beta/rc tags. |
|
98 |
|
|
|
99 | release = "%s: %s" % (iprelease['version'], codename) | |
|
98 | release = "%s" % iprelease['version'] | |
|
100 | 99 | # Just the X.Y.Z part, no '-dev' |
|
101 | 100 | version = iprelease['version'].split('-', 1)[0] |
|
102 | 101 | |
@@ -164,7 +163,10 b" html_last_updated_fmt = '%b %d, %Y'" | |||
|
164 | 163 | # Additional templates that should be rendered to pages, maps page names to |
|
165 | 164 | # template names. |
|
166 | 165 | html_additional_pages = { |
|
167 |
|
|
|
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 | 172 | # If false, no module index is generated. |
@@ -4,4 +4,9 b'' | |||
|
4 | 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 | 12 | .. automodule:: IPython.extensions.octavemagic |
@@ -4,4 +4,9 b'' | |||
|
4 | 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 | 12 | .. automodule:: IPython.extensions.rmagic |
@@ -19,8 +19,6 b' on the IPython GitHub wiki.' | |||
|
19 | 19 | .. toctree:: |
|
20 | 20 | :maxdepth: 1 |
|
21 | 21 | |
|
22 | ||
|
23 | gitwash/index | |
|
24 | 22 | messaging |
|
25 | 23 | parallel_messages |
|
26 | 24 | parallel_connections |
@@ -25,6 +25,7 b' Contents' | |||
|
25 | 25 | whatsnew/index |
|
26 | 26 | install/index |
|
27 | 27 | interactive/index |
|
28 | notebook/index | |
|
28 | 29 | parallel/index |
|
29 | 30 | config/index |
|
30 | 31 | development/index |
@@ -10,9 +10,7 b' Using IPython for interactive work' | |||
|
10 | 10 | reference |
|
11 | 11 | shell |
|
12 | 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 |
|
1 | 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 |
@@ -19,8 +19,8 b' a public interface <notebook_public_server>`.' | |||
|
19 | 19 | |
|
20 | 20 | .. _notebook_security: |
|
21 | 21 | |
|
22 | Notebook security | |
|
23 | ----------------- | |
|
22 | Securing a notebook server | |
|
23 | -------------------------- | |
|
24 | 24 | |
|
25 | 25 | You can protect your notebook server with a simple single password by |
|
26 | 26 | setting the :attr:`NotebookApp.password` configurable. You can prepare a |
@@ -58,9 +58,9 b' The code to generate the simple DAG:' | |||
|
58 | 58 | .. sourcecode:: python |
|
59 | 59 | |
|
60 | 60 | import networkx as nx |
|
61 | ||
|
61 | ||
|
62 | 62 | G = nx.DiGraph() |
|
63 | ||
|
63 | ||
|
64 | 64 | # add 5 nodes, labeled 0-4: |
|
65 | 65 | map(G.add_node, range(5)) |
|
66 | 66 | # 1,2 depend on 0: |
@@ -71,7 +71,7 b' The code to generate the simple DAG:' | |||
|
71 | 71 | G.add_edge(2,3) |
|
72 | 72 | # 4 depends on 1 |
|
73 | 73 | G.add_edge(1,4) |
|
74 | ||
|
74 | ||
|
75 | 75 | # now draw the graph: |
|
76 | 76 | pos = { 0 : (0,0), 1 : (1,1), 2 : (-1,1), |
|
77 | 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 | 96 | .. sourcecode:: ipython |
|
97 | 97 | |
|
98 | 98 | In [3]: jobs = {} |
|
99 | ||
|
99 | ||
|
100 | 100 | # in reality, each job would presumably be different |
|
101 | 101 | # randomwait is just a function that sleeps for a random interval |
|
102 | 102 | In [4]: for node in G: |
|
103 |
...: jobs[node] = randomwait |
|
|
103 | ...: jobs[node] = randomwait | |
|
104 | 104 | |
|
105 | 105 | Once we have a dict of jobs matching the nodes on the graph, we can start submitting jobs, |
|
106 | 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 | 115 | In [5]: rc = Client() |
|
116 | 116 | In [5]: view = rc.load_balanced_view() |
|
117 | ||
|
117 | ||
|
118 | 118 | In [6]: results = {} |
|
119 | ||
|
120 |
In [7]: for node in |
|
|
119 | ||
|
120 | In [7]: for node in nx.topological_sort(G): | |
|
121 | 121 | ...: # get list of AsyncResult objects from nodes |
|
122 | 122 | ...: # leading into this one as dependencies |
|
123 | 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 | 152 | .. sourcecode:: ipython |
|
153 | 153 | |
|
154 | 154 | In [10]: from matplotlib.dates import date2num |
|
155 | ||
|
155 | ||
|
156 | 156 | In [11]: from matplotlib.cm import gist_rainbow |
|
157 | ||
|
157 | ||
|
158 | 158 | In [12]: pos = {}; colors = {} |
|
159 | ||
|
159 | ||
|
160 | 160 | In [12]: for node in G: |
|
161 | 161 | ....: md = results[node].metadata |
|
162 | 162 | ....: start = date2num(md.started) |
|
163 | 163 | ....: runtime = date2num(md.completed) - start |
|
164 | 164 | ....: pos[node] = (start, runtime) |
|
165 | 165 | ....: colors[node] = md.engine_id |
|
166 | ||
|
166 | ||
|
167 | 167 | In [13]: nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(), |
|
168 | 168 | ....: cmap=gist_rainbow) |
|
169 | 169 |
@@ -191,6 +191,13 b' Dashboard "Running" Tab' | |||
|
191 | 191 | The dashboard now has a "Running" tab which shows all of the running |
|
192 | 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 | 201 | Other changes |
|
195 | 202 | ------------- |
|
196 | 203 |
@@ -14,7 +14,7 b' requires utilities which are not available under Windows."""' | |||
|
14 | 14 | # |
|
15 | 15 | # Distributed under the terms of the Modified BSD License. |
|
16 | 16 | # |
|
17 |
# The full license is in the file COPYING. |
|
|
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 | 161 | pjoin(components, "jquery", "jquery.min.js"), |
|
162 | 162 | pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"), |
|
163 | 163 | pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"), |
|
164 | pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"), | |
|
164 | 165 | pjoin(components, "marked", "lib", "marked.js"), |
|
165 | 166 | pjoin(components, "requirejs", "require.js"), |
|
166 | 167 | pjoin(components, "underscore", "underscore-min.js"), |
@@ -142,7 +142,7 b' def get_pulls_list(project, auth=False, **params):' | |||
|
142 | 142 | headers = make_auth_header() |
|
143 | 143 | else: |
|
144 | 144 | headers = None |
|
145 |
pages = get_paged_request(url, headers=headers, |
|
|
145 | pages = get_paged_request(url, headers=headers, **params) | |
|
146 | 146 | return pages |
|
147 | 147 | |
|
148 | 148 | def get_issues_list(project, auth=False, **params): |
@@ -1,5 +1,9 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 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 | 9 | # Imports |
@@ -7,14 +11,18 b'' | |||
|
7 | 11 | |
|
8 | 12 | from __future__ import print_function |
|
9 | 13 | |
|
14 | import codecs | |
|
10 | 15 | import json |
|
11 | 16 | import re |
|
12 | 17 | import sys |
|
13 | 18 | |
|
19 | from argparse import ArgumentParser | |
|
14 | 20 | from datetime import datetime, timedelta |
|
15 | 21 | from subprocess import check_output |
|
16 | from gh_api import get_paged_request, make_auth_header, get_pull_request, is_pull_request | |
|
17 | ||
|
22 | from gh_api import ( | |
|
23 | get_paged_request, make_auth_header, get_pull_request, is_pull_request, | |
|
24 | get_milestone_id, get_issues_list, | |
|
25 | ) | |
|
18 | 26 | #----------------------------------------------------------------------------- |
|
19 | 27 | # Globals |
|
20 | 28 | #----------------------------------------------------------------------------- |
@@ -26,12 +34,6 b' PER_PAGE = 100' | |||
|
26 | 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 | 37 | def round_hour(dt): |
|
36 | 38 | return dt.replace(minute=0,second=0,microsecond=0) |
|
37 | 39 | |
@@ -42,7 +44,6 b' def _parse_datetime(s):' | |||
|
42 | 44 | else: |
|
43 | 45 | return datetime.fromtimestamp(0) |
|
44 | 46 | |
|
45 | ||
|
46 | 47 | def issues2dict(issues): |
|
47 | 48 | """Convert a list of issues to a dict, keyed by issue number.""" |
|
48 | 49 | idict = {} |
@@ -63,7 +64,6 b' def split_pulls(all_issues, project="ipython/ipython"):' | |||
|
63 | 64 | return issues, pulls |
|
64 | 65 | |
|
65 | 66 | |
|
66 | ||
|
67 | 67 | def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False): |
|
68 | 68 | """Get all issues closed since a particular point in time. period |
|
69 | 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 | 115 | if __name__ == "__main__": |
|
116 | 116 | # deal with unicode |
|
117 | import codecs | |
|
118 | 117 | sys.stdout = codecs.getwriter('utf8')(sys.stdout) |
|
119 | 118 | |
|
120 | 119 | # Whether to add reST urls for all issues in printout. |
|
121 | 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 | 142 | cmd = ['git', 'log', '-1', '--format=%ai', tag] |
|
135 | 143 | tagday, tz = check_output(cmd).strip().rsplit(' ', 1) |
|
136 | 144 | since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S") |
@@ -141,16 +149,23 b' if __name__ == "__main__":' | |||
|
141 | 149 | since += td |
|
142 | 150 | else: |
|
143 | 151 | since -= td |
|
144 | else: | |
|
145 | since = datetime.utcnow() - timedelta(days=days) | |
|
146 | 152 | |
|
147 | 153 | since = round_hour(since) |
|
148 | ||
|
149 | print("fetching GitHub stats since %s (tag: %s)" % (since, tag), file=sys.stderr) | |
|
150 | # turn off to play interactively without redownloading, use %run -i | |
|
151 | if 1: | |
|
154 | ||
|
155 | milestone = opts.milestone | |
|
156 | ||
|
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 | 167 | issues = issues_closed_since(since, pulls=False) |
|
153 |
|
|
|
168 | pulls = issues_closed_since(since, pulls=True) | |
|
154 | 169 | |
|
155 | 170 | # For regular reports, it's nice to show them in reverse chronological order |
|
156 | 171 | issues = sorted_by_field(issues, reverse=True) |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed, binary diff hidden |
|
1 | NO CONTENT: file was removed, binary diff hidden |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed, binary diff hidden |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed, binary diff hidden |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now