##// END OF EJS Templates
Merge branch 'master' of github.com:ipython/ipython
Brian E. Granger -
r16053:50c0a9b1 merge
parent child Browse files
Show More
@@ -0,0 +1,78 b''
1 // Test the notebook dual mode feature.
2
3 // Test
4 casper.notebook_test(function () {
5 var a = 'print("a")';
6 var index = this.append_cell(a);
7 this.execute_cell_then(index);
8
9 var b = 'print("b")';
10 index = this.append_cell(b);
11 this.execute_cell_then(index);
12
13 var c = 'print("c")';
14 index = this.append_cell(c);
15 this.execute_cell_then(index);
16
17 this.then(function () {
18 this.validate_notebook_state('initial state', 'edit', 0);
19 this.trigger_keydown('esc');
20 this.validate_notebook_state('esc', 'command', 0);
21 this.trigger_keydown('down');
22 this.validate_notebook_state('down', 'command', 1);
23 this.trigger_keydown('enter');
24 this.validate_notebook_state('enter', 'edit', 1);
25 this.trigger_keydown('j');
26 this.validate_notebook_state('j in edit mode', 'edit', 1);
27 this.trigger_keydown('esc');
28 this.validate_notebook_state('esc', 'command', 1);
29 this.trigger_keydown('j');
30 this.validate_notebook_state('j in command mode', 'command', 2);
31 this.click_cell_editor(0);
32 this.validate_notebook_state('click cell 0', 'edit', 0);
33 this.click_cell_editor(3);
34 this.validate_notebook_state('click cell 3', 'edit', 3);
35 this.trigger_keydown('esc');
36 this.validate_notebook_state('esc', 'command', 3);
37
38 // Open keyboard help
39 this.evaluate(function(){
40 $('#keyboard_shortcuts a').click();
41 }, {});
42
43 this.trigger_keydown('k');
44 this.validate_notebook_state('k in command mode while keyboard help is up', 'command', 3);
45
46 // Close keyboard help
47 this.evaluate(function(){
48 $('div.modal button.close').click();
49 }, {});
50
51 this.trigger_keydown('k');
52 this.validate_notebook_state('k in command mode', 'command', 2);
53 this.click_cell_editor(0);
54 this.validate_notebook_state('click cell 0', 'edit', 0);
55 this.focus_notebook();
56 this.validate_notebook_state('focus #notebook', 'command', 0);
57 this.click_cell_editor(0);
58 this.validate_notebook_state('click cell 0', 'edit', 0);
59 this.focus_notebook();
60 this.validate_notebook_state('focus #notebook', 'command', 0);
61 this.click_cell_editor(3);
62 this.validate_notebook_state('click cell 3', 'edit', 3);
63
64 // Cell deletion
65 this.trigger_keydown('esc', 'd', 'd');
66 this.test.assertEquals(this.get_cells_length(), 3, 'dd actually deletes a cell');
67 this.validate_notebook_state('dd', 'command', 2);
68
69 // Make sure that if the time between d presses is too long, nothing gets removed.
70 this.trigger_keydown('d');
71 });
72 this.wait(1000);
73 this.then(function () {
74 this.trigger_keydown('d');
75 this.test.assertEquals(this.get_cells_length(), 3, "d, 1 second wait, d doesn't delete a cell");
76 this.validate_notebook_state('d, 1 second wait, d', 'command', 2);
77 });
78 });
@@ -0,0 +1,51 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 var b = 'print("b")';
9 index = this.append_cell(b);
10 this.execute_cell_then(index);
11
12 var c = 'print("c")';
13 index = this.append_cell(c);
14 this.execute_cell_then(index);
15
16 this.then(function () {
17
18 // Up and down in command mode
19 this.select_cell(3);
20 this.trigger_keydown('j');
21 this.validate_notebook_state('j at end of notebook', 'command', 3);
22 this.trigger_keydown('down');
23 this.validate_notebook_state('down at end of notebook', 'command', 3);
24 this.trigger_keydown('up');
25 this.validate_notebook_state('up', 'command', 2);
26 this.select_cell(0);
27 this.validate_notebook_state('select 0', 'command', 0);
28 this.trigger_keydown('k');
29 this.validate_notebook_state('k at top of notebook', 'command', 0);
30 this.trigger_keydown('up');
31 this.validate_notebook_state('up at top of notebook', 'command', 0);
32 this.trigger_keydown('down');
33 this.validate_notebook_state('down', 'command', 1);
34
35 // Up and down in edit mode
36 this.click_cell_editor(3);
37 this.validate_notebook_state('click cell 3', 'edit', 3);
38 this.trigger_keydown('down');
39 this.validate_notebook_state('down at end of notebook', 'edit', 3);
40 this.set_cell_editor_cursor(3, 0, 0);
41 this.trigger_keydown('up');
42 this.validate_notebook_state('up', 'edit', 2);
43 this.click_cell_editor(0);
44 this.validate_notebook_state('click 0', 'edit', 0);
45 this.trigger_keydown('up');
46 this.validate_notebook_state('up at top of notebook', 'edit', 0);
47 this.set_cell_editor_cursor(0, 0, 10);
48 this.trigger_keydown('down');
49 this.validate_notebook_state('down', 'edit', 1);
50 });
51 });
@@ -0,0 +1,27 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 var b = 'print("b")';
9 index = this.append_cell(b);
10 this.execute_cell_then(index);
11
12 var c = 'print("c")';
13 index = this.append_cell(c);
14 this.execute_cell_then(index);
15
16 this.then(function () {
17 // Cell insertion
18 this.select_cell(2);
19 this.trigger_keydown('a'); // Creates one cell
20 this.test.assertEquals(this.get_cell_text(2), '', 'a; New cell 2 text is empty');
21 this.validate_notebook_state('a', 'command', 2);
22 this.trigger_keydown('b'); // Creates one cell
23 this.test.assertEquals(this.get_cell_text(2), '', 'b; Cell 2 text is still empty');
24 this.test.assertEquals(this.get_cell_text(3), '', 'b; New cell 3 text is empty');
25 this.validate_notebook_state('b', 'command', 3);
26 });
27 }); No newline at end of file
@@ -0,0 +1,28 b''
1 // Test keyboard shortcuts that change the cell's mode.
2
3 // Test
4 casper.notebook_test(function () {
5 this.then(function () {
6 // Cell mode change
7 this.select_cell(0);
8 this.trigger_keydown('esc','r');
9 this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw');
10 this.trigger_keydown('1');
11 this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading');
12 this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading');
13 this.trigger_keydown('2');
14 this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading');
15 this.trigger_keydown('3');
16 this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading');
17 this.trigger_keydown('4');
18 this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading');
19 this.trigger_keydown('5');
20 this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading');
21 this.trigger_keydown('6');
22 this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading');
23 this.trigger_keydown('m');
24 this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown');
25 this.trigger_keydown('y');
26 this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code');
27 });
28 }); No newline at end of file
@@ -0,0 +1,55 b''
1
2
3 // Test
4 casper.notebook_test(function () {
5 var a = 'print("a")';
6 var index = this.append_cell(a);
7 this.execute_cell_then(index);
8
9 var b = 'print("b")';
10 index = this.append_cell(b);
11 this.execute_cell_then(index);
12
13 var c = 'print("c")';
14 index = this.append_cell(c);
15 this.execute_cell_then(index);
16
17 this.then(function () {
18 // Copy/paste/cut
19 var num_cells = this.get_cells_length();
20 this.test.assertEquals(this.get_cell_text(1), a, 'Verify that cell 1 is a');
21 this.select_cell(1);
22 this.trigger_keydown('x'); // Cut
23 this.validate_notebook_state('x', 'command', 1);
24 this.test.assertEquals(this.get_cells_length(), num_cells-1, 'Verify that a cell was removed.');
25 this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 2 is now where cell 1 was.');
26 this.select_cell(2);
27 this.trigger_keydown('v'); // Paste
28 this.validate_notebook_state('v', 'command', 3); // Selection should move to pasted cell, below current cell.
29 this.test.assertEquals(this.get_cell_text(3), a, 'Verify that cell 3 has the cut contents.');
30 this.test.assertEquals(this.get_cells_length(), num_cells, 'Verify a the cell was added.');
31 this.trigger_keydown('v'); // Paste
32 this.validate_notebook_state('v', 'command', 4); // Selection should move to pasted cell, below current cell.
33 this.test.assertEquals(this.get_cell_text(4), a, 'Verify that cell 4 has the cut contents.');
34 this.test.assertEquals(this.get_cells_length(), num_cells+1, 'Verify a the cell was added.');
35 this.select_cell(1);
36 this.trigger_keydown('c'); // Copy
37 this.validate_notebook_state('c', 'command', 1);
38 this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 1 is b');
39 this.select_cell(2);
40 this.trigger_keydown('c'); // Copy
41 this.validate_notebook_state('c', 'command', 2);
42 this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 is c');
43 this.select_cell(4);
44 this.trigger_keydown('v'); // Paste
45 this.validate_notebook_state('v', 'command', 5);
46 this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 still has the copied contents.');
47 this.test.assertEquals(this.get_cell_text(5), c, 'Verify that cell 5 has the copied contents.');
48 this.test.assertEquals(this.get_cells_length(), num_cells+2, 'Verify a the cell was added.');
49 this.select_cell(0);
50 this.trigger_keydown('shift-v'); // Paste
51 this.validate_notebook_state('shift-v', 'command', 0);
52 this.test.assertEquals(this.get_cell_text(0), c, 'Verify that cell 0 has the copied contents.');
53 this.test.assertEquals(this.get_cells_length(), num_cells+3, 'Verify a the cell was added.');
54 });
55 }); No newline at end of file
@@ -0,0 +1,72 b''
1 // Test keyboard invoked execution.
2
3 // Test
4 casper.notebook_test(function () {
5 var a = 'print("a")';
6 var index = this.append_cell(a);
7 this.execute_cell_then(index);
8
9 var b = 'print("b")';
10 index = this.append_cell(b);
11 this.execute_cell_then(index);
12
13 var c = 'print("c")';
14 index = this.append_cell(c);
15 this.execute_cell_then(index);
16
17 this.then(function () {
18
19 // shift-enter
20 // last cell in notebook
21 var base_index = 3;
22 this.select_cell(base_index);
23 this.trigger_keydown('shift-enter'); // Creates one cell
24 this.validate_notebook_state('shift-enter (no cell below)', 'edit', base_index + 1);
25 // not last cell in notebook & starts in edit mode
26 this.click_cell_editor(base_index);
27 this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
28 this.trigger_keydown('shift-enter');
29 this.validate_notebook_state('shift-enter (cell exists below)', 'command', base_index + 1);
30 // starts in command mode
31 this.trigger_keydown('k');
32 this.validate_notebook_state('k in comand mode', 'command', base_index);
33 this.trigger_keydown('shift-enter');
34 this.validate_notebook_state('shift-enter (start in command mode)', 'command', base_index + 1);
35
36 // ctrl-enter
37 // last cell in notebook
38 base_index++;
39 this.trigger_keydown('ctrl-enter');
40 this.validate_notebook_state('ctrl-enter (no cell below)', 'command', base_index);
41 // not last cell in notebook & starts in edit mode
42 this.click_cell_editor(base_index-1);
43 this.validate_notebook_state('click cell ' + (base_index-1), 'edit', base_index-1);
44 this.trigger_keydown('ctrl-enter');
45 this.validate_notebook_state('ctrl-enter (cell exists below)', 'command', base_index-1);
46 // starts in command mode
47 this.trigger_keydown('j');
48 this.validate_notebook_state('j in comand mode', 'command', base_index);
49 this.trigger_keydown('ctrl-enter');
50 this.validate_notebook_state('ctrl-enter (start in command mode)', 'command', base_index);
51
52 // alt-enter
53 // last cell in notebook
54 this.trigger_keydown('alt-enter'); // Creates one cell
55 this.validate_notebook_state('alt-enter (no cell below)', 'edit', base_index + 1);
56 // not last cell in notebook & starts in edit mode
57 this.click_cell_editor(base_index);
58 this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
59 this.trigger_keydown('alt-enter'); // Creates one cell
60 this.validate_notebook_state('alt-enter (cell exists below)', 'edit', base_index + 1);
61 // starts in command mode
62 this.trigger_keydown('esc', 'k');
63 this.validate_notebook_state('k in comand mode', 'command', base_index);
64 this.trigger_keydown('alt-enter'); // Creates one cell
65 this.validate_notebook_state('alt-enter (start in command mode)', 'edit', base_index + 1);
66
67 // Notebook will now have 8 cells, the index of the last cell will be 7.
68 this.test.assertEquals(this.get_cells_length(), 8, '*-enter commands added cells where needed.');
69 this.select_cell(7);
70 this.validate_notebook_state('click cell ' + 7 + ' and esc', 'command', 7);
71 });
72 }); No newline at end of file
@@ -0,0 +1,39 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 this.then(function () {
9 // Markdown rendering / unredering
10 this.select_cell(1);
11 this.validate_notebook_state('select 1', 'command', 1);
12 this.trigger_keydown('m');
13 this.test.assertEquals(this.get_cell(1).cell_type, 'markdown', 'm; cell is markdown');
14 this.test.assertEquals(this.get_cell(1).rendered, false, 'm; cell is rendered');
15 this.trigger_keydown('enter');
16 this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
17 this.validate_notebook_state('enter', 'edit', 1);
18 this.trigger_keydown('ctrl-enter');
19 this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
20 this.validate_notebook_state('enter', 'command', 1);
21 this.trigger_keydown('enter');
22 this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
23 this.select_cell(0);
24 this.test.assertEquals(this.get_cell(1).rendered, false, 'select 0; cell 1 is still unrendered');
25 this.validate_notebook_state('select 0', 'command', 0);
26 this.select_cell(1);
27 this.validate_notebook_state('select 1', 'command', 1);
28 this.trigger_keydown('ctrl-enter');
29 this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
30 this.select_cell(0);
31 this.validate_notebook_state('select 0', 'command', 0);
32 this.trigger_keydown('shift-enter');
33 this.validate_notebook_state('shift-enter', 'command', 1);
34 this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
35 this.trigger_keydown('shift-enter'); // Creates one cell
36 this.validate_notebook_state('shift-enter', 'edit', 2);
37 this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
38 });
39 }); No newline at end of file
@@ -0,0 +1,21 b''
1
2 // Test
3 casper.notebook_test(function () {
4 this.then(function () {
5 // Split and merge cells
6 this.select_cell(0);
7 this.trigger_keydown('a', 'enter'); // Create cell above and enter edit mode.
8 this.validate_notebook_state('a, enter', 'edit', 0);
9 this.set_cell_text(0, 'abcd');
10 this.set_cell_editor_cursor(0, 0, 2);
11 this.test.assertEquals(this.get_cell_text(0), 'abcd', 'Verify that cell 0 has the new contents.');
12 this.trigger_keydown('ctrl-shift-subtract'); // Split
13 this.test.assertEquals(this.get_cell_text(0), 'ab', 'split; Verify that cell 0 has the first half.');
14 this.test.assertEquals(this.get_cell_text(1), 'cd', 'split; Verify that cell 1 has the second half.');
15 this.validate_notebook_state('split', 'edit', 1);
16 this.select_cell(0); // Move up to cell 0
17 this.trigger_keydown('shift-m'); // Merge
18 this.validate_notebook_state('merge', 'command', 0);
19 this.test.assertEquals(this.get_cell_text(0), 'ab\ncd', 'merge; Verify that cell 0 has the merged contents.');
20 });
21 }); No newline at end of file
@@ -0,0 +1,25 b''
1
2 // Test
3 casper.notebook_test(function () {
4 var a = 'print("a")';
5 var index = this.append_cell(a);
6 this.execute_cell_then(index);
7
8 var b = 'print("b")';
9 index = this.append_cell(b);
10 this.execute_cell_then(index);
11
12 this.then(function () {
13 // Cell movement ( ctrl-(k or j) )
14 this.select_cell(2);
15 this.test.assertEquals(this.get_cell_text(2), b, 'select 2; Cell 2 text is correct');
16 this.trigger_keydown('ctrl-k'); // Move cell 2 up one
17 this.test.assertEquals(this.get_cell_text(1), b, 'ctrl-k; Cell 1 text is correct');
18 this.test.assertEquals(this.get_cell_text(2), a, 'ctrl-k; Cell 2 text is correct');
19 this.validate_notebook_state('ctrl-k', 'command', 1);
20 this.trigger_keydown('ctrl-j'); // Move cell 1 down one
21 this.test.assertEquals(this.get_cell_text(1), a, 'ctrl-j; Cell 1 text is correct');
22 this.test.assertEquals(this.get_cell_text(2), b, 'ctrl-j; Cell 2 text is correct');
23 this.validate_notebook_state('ctrl-j', 'command', 2);
24 });
25 }); No newline at end of file
@@ -0,0 +1,13 b''
1 ====================
2 The IPython notebook
3 ====================
4
5 .. toctree::
6 :maxdepth: 2
7
8 notebook
9 cm_keyboard
10 nbconvert
11 public_server
12 security
13
@@ -0,0 +1,52 b''
1 -----BEGIN PGP PUBLIC KEY BLOCK-----
2 Version: GnuPG v2.0.22 (GNU/Linux)
3
4 mQINBFMx2LoBEAC9xU8JiKI1VlCJ4PT9zqhU5nChQZ06/bj1BBftiMJG07fdGVO0
5 ibOn4TrCoRYaeRlet0UpHzxT4zDa5h3/usJaJNTSRwtWePw2o7Lik8J+F3LionRf
6 8Jz81WpJ+81Klg4UWKErXjBHsu/50aoQm6ZNYG4S2nwOmMVEC4nc44IAA0bb+6kW
7 saFKKzEDsASGyuvyutdyUHiCfvvh5GOC2h9mXYvl4FaMW7K+d2UgCYERcXDNy7C1
8 Bw+uepQ9ELKdG4ZpvonO6BNr1BWLln3wk93AQfD5qhfsYRJIyj0hJlaRLtBU3i6c
9 xs+gQNF4mPmybpPSGuOyUr4FYC7NfoG7IUMLj+DYa6d8LcMJO+9px4IbdhQvzGtC
10 qz5av1TX7/+gnS4L8C9i1g8xgI+MtvogngPmPY4repOlK6y3l/WtxUPkGkyYkn3s
11 RzYyE/GJgTwuxFXzMQs91s+/iELFQq/QwmEJf+g/QYfSAuM+lVGajEDNBYVAQkxf
12 gau4s8Gm0GzTZmINilk+7TxpXtKbFc/Yr4A/fMIHmaQ7KmJB84zKwONsQdVv7Jjj
13 0dpwu8EIQdHxX3k7/Q+KKubEivgoSkVwuoQTG15X9xrOsDZNwfOVQh+JKazPvJtd
14 SNfep96r9t/8gnXv9JI95CGCQ8lNhXBUSBM3BDPTbudc4b6lFUyMXN0mKQARAQAB
15 tCxJUHl0aG9uIFNlY3VyaXR5IFRlYW0gPHNlY3VyaXR5QGlweXRob24ub3JnPokC
16 OAQTAQIAIgUCUzHYugIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQEwJc
17 LcmZYkjuXg//R/t6nMNQmf9W1h52IVfUbRAVmvZ5d063hQHKV2dssxtnA2dRm/x5
18 JZu8Wz7ZrEZpyqwRJO14sxN1/lC3v+zs9XzYXr2lBTZuKCPIBypYVGIynCuWJBQJ
19 rWnfG4+u1RHahnjqlTWTY1C/le6v7SjAvCb6GbdA6k4ZL2EJjQlRaHDmzw3rV/+l
20 LLx6/tYzIsotuflm/bFumyOMmpQQpJjnCkWIVjnRICZvuAn97jLgtTI0+0Rzf4Zb
21 k2BwmHwDRqWCTTcRI9QvTl8AzjW+dNImN22TpGOBPfYj8BCZ9twrpKUbf+jNqJ1K
22 THQzFtpdJ6SzqiFVm74xW4TKqCLkbCQ/HtVjTGMGGz/y7KTtaLpGutQ6XE8SSy6P
23 EffSb5u+kKlQOWaH7Mc3B0yAojz6T3j5RSI8ts6pFi6pZhDg9hBfPK2dT0v/7Mkv
24 E1Z7q2IdjZnhhtGWjDAMtDDn2NbY2wuGoa5jAWAR0WvIbEZ3kOxuLE5/ZOG1FyYm
25 noJRliBz7038nT92EoD5g1pdzuxgXtGCpYyyjRZwaLmmi4CvA+oThKmnqWNY5lyY
26 ricdNHDiyEXK0YafJL1oZgM86MSb0jKJMp5U11nUkUGzkroFfpGDmzBwAzEPgeiF
27 40+qgsKB9lqwb3G7PxvfSi3XwxfXgpm1cTyEaPSzsVzve3d1xeqb7Yq5Ag0EUzHY
28 ugEQALQ5FtLdNoxTxMsgvrRr1ejLiUeRNUfXtN1TYttOfvAhfBVnszjtkpIW8DCB
29 JF/bA7ETiH8OYYn/Fm6MPI5H64IHEncpzxjf57jgpXd9CA9U2OMk/P1nve5zYchP
30 QmP2fJxeAWr0aRH0Mse5JS5nCkh8Xv4nAjsBYeLTJEVOb1gPQFXOiFcVp3gaKAzX
31 GWOZ/mtG/uaNsabH/3TkcQQEgJefd11DWgMB7575GU+eME7c6hn3FPITA5TC5HUX
32 azvjv/PsWGTTVAJluJ3fUDvhpbGwYOh1uV0rB68lPpqVIro18IIJhNDnccM/xqko
33 4fpJdokdg4L1wih+B04OEXnwgjWG8OIphR/oL/+M37VV2U7Om/GE6LGefaYccC9c
34 tIaacRQJmZpG/8RsimFIY2wJ07z8xYBITmhMmOt0bLBv0mU0ym5KH9Dnru1m9QDO
35 AHwcKrDgL85f9MCn+YYw0d1lYxjOXjf+moaeW3izXCJ5brM+MqVtixY6aos3YO29
36 J7SzQ4aEDv3h/oKdDfZny21jcVPQxGDui8sqaZCi8usCcyqWsKvFHcr6vkwaufcm
37 3Knr2HKVotOUF5CDZybopIz1sJvY/5Dx9yfRmtivJtglrxoDKsLi1rQTlEQcFhCS
38 ACjf7txLtv03vWHxmp4YKQFkkOlbyhIcvfPVLTvqGerdT2FHABEBAAGJAh8EGAEC
39 AAkFAlMx2LoCGwwACgkQEwJcLcmZYkgK0BAAny0YUugpZldiHzYNf8I6p2OpiDWv
40 ZHaguTTPg2LJSKaTd+5UHZwRFIWjcSiFu+qTGLNtZAdcr0D5f991CPvyDSLYgOwb
41 Jm2p3GM2KxfECWzFbB/n/PjbZ5iky3+5sPlOdBR4TkfG4fcu5GwUgCkVe5u3USAk
42 C6W5lpeaspDz39HAPRSIOFEX70+xV+6FZ17B7nixFGN+giTpGYOEdGFxtUNmHmf+
43 waJoPECyImDwJvmlMTeP9jfahlB6Pzaxt6TBZYHetI/JR9FU69EmA+XfCSGt5S+0
44 Eoc330gpsSzo2VlxwRCVNrcuKmG7PsFFANok05ssFq1/Djv5rJ++3lYb88b8HSP2
45 3pQJPrM7cQNU8iPku9yLXkY5qsoZOH+3yAia554Dgc8WBhp6fWh58R0dIONQxbbo
46 apNdwvlI8hKFB7TiUL6PNShE1yL+XD201iNkGAJXbLMIC1ImGLirUfU267A3Cop5
47 hoGs179HGBcyj/sKA3uUIFdNtP+NndaP3v4iYhCitdVCvBJMm6K3tW88qkyRGzOk
48 4PW422oyWKwbAPeMk5PubvEFuFAIoBAFn1zecrcOg85RzRnEeXaiemmmH8GOe1Xu
49 Kh+7h8XXyG6RPFy8tCcLOTk+miTqX+4VWy+kVqoS2cQ5IV8WsJ3S7aeIy0H89Z8n
50 5vmLc+Ibz+eT+rM=
51 =XVDe
52 -----END PGP PUBLIC KEY BLOCK-----
@@ -0,0 +1,146 b''
1 Security in IPython notebooks
2 =============================
3
4 As IPython notebooks become more popular for sharing and collaboration,
5 the potential for malicious people to attempt to exploit the notebook
6 for their nefarious purposes increases. IPython 2.0 introduces a
7 security model to prevent execution of untrusted code without explicit
8 user input.
9
10 The problem
11 -----------
12
13 The whole point of IPython is arbitrary code execution. We have no
14 desire to limit what can be done with a notebook, which would negatively
15 impact its utility.
16
17 Unlike other programs, an IPython notebook document includes output.
18 Unlike other documents, that output exists in a context that can execute
19 code (via Javascript).
20
21 The security problem we need to solve is that no code should execute
22 just because a user has **opened** a notebook that **they did not
23 write**. Like any other program, once a user decides to execute code in
24 a notebook, it is considered trusted, and should be allowed to do
25 anything.
26
27 Our security model
28 ------------------
29
30 - Untrusted HTML is always sanitized
31 - Untrusted Javascript is never executed
32 - HTML and Javascript in Markdown cells are never trusted
33 - **Outputs** generated by the user are trusted
34 - Any other HTML or Javascript (in Markdown cells, output generated by
35 others) is never trusted
36 - The central question of trust is "Did the current user do this?"
37
38 The details of trust
39 --------------------
40
41 IPython notebooks store a signature in metadata, which is used to answer
42 the question "Did the current user do this?"
43
44 This signature is a digest of the notebooks contents plus a secret key,
45 known only to the user. The secret key is a user-only readable file in
46 the IPython profile's security directory. By default, this is::
47
48 ~/.ipython/profile_default/security/notebook_secret
49
50 When a notebook is opened by a user, the server computes a signature
51 with the user's key, and compares it with the signature stored in the
52 notebook's metadata. If the signature matches, HTML and Javascript
53 output in the notebook will be trusted at load, otherwise it will be
54 untrusted.
55
56 Any output generated during an interactive session is trusted.
57
58 Updating trust
59 **************
60
61 A notebook's trust is updated when the notebook is saved. If there are
62 any untrusted outputs still in the notebook, the notebook will not be
63 trusted, and no signature will be stored. If all untrusted outputs have
64 been removed (either via ``Clear Output`` or re-execution), then the
65 notebook will become trusted.
66
67 While trust is updated per output, this is only for the duration of a
68 single session. A notebook file on disk is either trusted or not in its
69 entirety.
70
71 Explicit trust
72 **************
73
74 Sometimes re-executing a notebook to generate trusted output is not an
75 option, either because dependencies are unavailable, or it would take a
76 long time. Users can explicitly trust a notebook in two ways:
77
78 - At the command-line, with::
79
80 ipython trust /path/to/notebook.ipynb
81
82 - After loading the untrusted notebook, with ``File / Trust Notebook``
83
84 These two methods simply load the notebook, compute a new signature with
85 the user's key, and then store the newly signed notebook.
86
87 Reporting security issues
88 -------------------------
89
90 If you find a security vulnerability in IPython, either a failure of the
91 code to properly implement the model described here, or a failure of the
92 model itself, please report it to security@ipython.org.
93
94 If you prefer to encrypt your security reports,
95 you can use :download:`this PGP public key <ipython_security.asc>`.
96
97 Affected use cases
98 ------------------
99
100 Some use cases that work in IPython 1.0 will become less convenient in
101 2.0 as a result of the security changes. We do our best to minimize
102 these annoyance, but security is always at odds with convenience.
103
104 Javascript and CSS in Markdown cells
105 ************************************
106
107 While never officially supported, it had become common practice to put
108 hidden Javascript or CSS styling in Markdown cells, so that they would
109 not be visible on the page. Since Markdown cells are now sanitized (by
110 `Google Caja <https://developers.google.com/caja>`__), all Javascript
111 (including click event handlers, etc.) and CSS will be stripped.
112
113 We plan to provide a mechanism for notebook themes, but in the meantime
114 styling the notebook can only be done via either ``custom.css`` or CSS
115 in HTML output. The latter only have an effect if the notebook is
116 trusted, because otherwise the output will be sanitized just like
117 Markdown.
118
119 Collaboration
120 *************
121
122 When collaborating on a notebook, people probably want to see the
123 outputs produced by their colleagues' most recent executions. Since each
124 collaborator's key will differ, this will result in each share starting
125 in an untrusted state. There are three basic approaches to this:
126
127 - re-run notebooks when you get them (not always viable)
128 - explicitly trust notebooks via ``ipython trust`` or the notebook menu
129 (annoying, but easy)
130 - share a notebook secret, and use an IPython profile dedicated to the
131 collaboration while working on the project.
132
133 Multiple profiles or machines
134 *****************************
135
136 Since the notebook secret is stored in a profile directory by default,
137 opening a notebook with a different profile or on a different machine
138 will result in a different key, and thus be untrusted. The only current
139 way to address this is by sharing the notebook secret. This can be
140 facilitated by setting the configurable:
141
142 .. sourcecode:: python
143
144 c.NotebookApp.secret_file = "/path/to/notebook_secret"
145
146 in each profile, and only sharing the secret once per machine.
@@ -3,12 +3,12 b''
3 3 =============================
4 4
5 5 IPython is licensed under the terms of the Modified BSD License (also known as
6 New or Revised BSD), as follows:
6 New or Revised or 3-Clause BSD), as follows:
7 7
8 Copyright (c) 2008-2010, IPython Development Team
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>
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 # Distributed under the terms of the Modified BSD License.
83 #
84 # The full license is in the file COPYING.txt, distributed with this software.
85 #-----------------------------------------------------------------------------
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=0):
258 """get info about a session
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. The current session is 0, and negative
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, (input, output)) if output is True.
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, (input, output)) if output is True.
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_input_ranges("~8/5-~7/4 2"))
769 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
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 bytes
99 """Print a figure to an image, and return the resulting file data
100
101 Returned data will be bytes unless ``fmt='svg'``,
102 in which case it will be unicode.
100 103
101 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 return bytes_io.getvalue()
131 data = bytes_io.getvalue()
132 if fmt == 'svg':
133 data = data.decode('utf-8')
134 return data
129 135
130 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(b'doctype svg', svg)
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 self.config.NotebookApp.notebook_dir = f
560 c.NotebookApp.notebook_dir = f
559 561 elif os.path.isfile(f):
560 self.config.NotebookApp.file_to_run = f
562 c.NotebookApp.file_to_run = f
563 self.update_config(c)
561 564
562 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/stable/interactive/notebook.html","Notebook Help",True),
231 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
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/dev/index.html","SymPy",True),
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.thenEvaluate( function() {
35 IPython.keyboard.trigger_keydown('i');
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 // merge_cell_above()
18 set_cell_text();
24 // merge_cell_above()
25 set_cells_text();
26 var output_above = this.evaluate(function () {
19 27 IPython.notebook.merge_cell_above();
20 var merged_above = IPython.notebook.get_selected_cell();
28 return IPython.notebook.get_selected_cell().get_text();
29 });
21 30
22 // merge_cell_below()
23 set_cell_text();
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 var merged_below = IPython.notebook.get_selected_cell();
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.above, 'a = 5\nprint(a)',
39 this.test.assertEquals(output_above, 'a = 5\nprint(a)',
35 40 'Successful merge_cell_above().');
36 this.test.assertEquals(output.below, 'a = 5\nprint(a)',
41 this.test.assertEquals(output_below, 'a = 5\nprint(a)',
37 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 not cell.input.isspace() -%}
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.txt
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 page has move</title>
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 page has moved to <a href="notebook.html">this link</a>.</p>
7 <p>The notebook docs have moved <a href="../notebook/index.html">here</a>.</p>
8 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 codename = iprelease['codename']
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 'interactive/htmlnotebook': 'htmlnotebook.html',
166 'interactive/htmlnotebook': 'notebook_redirect.html',
167 'interactive/notebook': 'notebook_redirect.html',
168 'interactive/nbconvert': 'notebook_redirect.html',
169 'interactive/public_server': 'notebook_redirect.html',
168 170 }
169 171
170 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 G.topological_sort():
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.txt, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
@@ -161,6 +161,7 b' def find_package_data():'
161 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, params=params)
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 pulls = issues_closed_since(since, pulls=True)
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