##// 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.
@@ -1,85 +1,74 b''
1 =============================
1 =============================
2 The IPython licensing terms
2 The IPython licensing terms
3 =============================
3 =============================
4
4
5 IPython is licensed under the terms of the Modified BSD License (also known as
5 IPython is licensed under the terms of the Modified BSD License (also known as
6 New or Revised BSD), as follows:
6 New or Revised or 3-Clause BSD), as follows:
7
7
8 Copyright (c) 2008-2010, IPython Development Team
8 - Copyright (c) 2008-2014, IPython Development Team
9 Copyright (c) 2001-2007, Fernando Perez. <fernando.perez@colorado.edu>
9 - Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
10 - Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
11 - Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12
12
13 All rights reserved.
13 All rights reserved.
14
14
15 Redistribution and use in source and binary forms, with or without
15 Redistribution and use in source and binary forms, with or without
16 modification, are permitted provided that the following conditions are met:
16 modification, are permitted provided that the following conditions are met:
17
17
18 Redistributions of source code must retain the above copyright notice, this
18 Redistributions of source code must retain the above copyright notice, this
19 list of conditions and the following disclaimer.
19 list of conditions and the following disclaimer.
20
20
21 Redistributions in binary form must reproduce the above copyright notice, this
21 Redistributions in binary form must reproduce the above copyright notice, this
22 list of conditions and the following disclaimer in the documentation and/or
22 list of conditions and the following disclaimer in the documentation and/or
23 other materials provided with the distribution.
23 other materials provided with the distribution.
24
24
25 Neither the name of the IPython Development Team nor the names of its
25 Neither the name of the IPython Development Team nor the names of its
26 contributors may be used to endorse or promote products derived from this
26 contributors may be used to endorse or promote products derived from this
27 software without specific prior written permission.
27 software without specific prior written permission.
28
28
29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
32 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
33 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
36 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
37 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
39
40 About the IPython Development Team
40 About the IPython Development Team
41 ----------------------------------
41 ----------------------------------
42
42
43 Fernando Perez began IPython in 2001 based on code from Janko Hauser
43 Fernando Perez began IPython in 2001 based on code from Janko Hauser
44 <jhauser@zscout.de> and Nathaniel Gray <n8gray@caltech.edu>. Fernando is still
44 <jhauser@zscout.de> and Nathaniel Gray <n8gray@caltech.edu>. Fernando is still
45 the project lead.
45 the project lead.
46
46
47 The IPython Development Team is the set of all contributors to the IPython
47 The IPython Development Team is the set of all contributors to the IPython
48 project. This includes all of the IPython subprojects. A full list with
48 project. This includes all of the IPython subprojects. A full list with
49 details is kept in the documentation directory, in the file
49 details is kept in the documentation directory, in the file
50 ``about/credits.txt``.
50 ``about/credits.txt``.
51
51
52 The core team that coordinates development on GitHub can be found here:
52 The core team that coordinates development on GitHub can be found here:
53 http://github.com/ipython. As of late 2010, it consists of:
53 https://github.com/ipython/.
54
55 * Brian E. Granger
56 * Jonathan March
57 * Evan Patterson
58 * Fernando Perez
59 * Min Ragan-Kelley
60 * Robert Kern
61
62
54
63 Our Copyright Policy
55 Our Copyright Policy
64 --------------------
56 --------------------
65
57
66 IPython uses a shared copyright model. Each contributor maintains copyright
58 IPython uses a shared copyright model. Each contributor maintains copyright
67 over their contributions to IPython. But, it is important to note that these
59 over their contributions to IPython. But, it is important to note that these
68 contributions are typically only changes to the repositories. Thus, the IPython
60 contributions are typically only changes to the repositories. Thus, the IPython
69 source code, in its entirety is not the copyright of any single person or
61 source code, in its entirety is not the copyright of any single person or
70 institution. Instead, it is the collective copyright of the entire IPython
62 institution. Instead, it is the collective copyright of the entire IPython
71 Development Team. If individual contributors want to maintain a record of what
63 Development Team. If individual contributors want to maintain a record of what
72 changes/contributions they have specific copyright on, they should indicate
64 changes/contributions they have specific copyright on, they should indicate
73 their copyright in the commit message of the change, when they commit the
65 their copyright in the commit message of the change, when they commit the
74 change to one of the IPython repositories.
66 change to one of the IPython repositories.
75
67
76 With this in mind, the following banner should be used in any source code file
68 With this in mind, the following banner should be used in any source code file
77 to indicate the copyright and license terms:
69 to indicate the copyright and license terms:
78
70
79 #-----------------------------------------------------------------------------
71 ::
80 # Copyright (c) 2010, IPython Development Team.
72
81 #
73 # Copyright (c) IPython Development Team.
82 # Distributed under the terms of the Modified BSD License.
74 # Distributed under the terms of the Modified BSD License.
83 #
84 # The full license is in the file COPYING.txt, distributed with this software.
85 #-----------------------------------------------------------------------------
@@ -1,811 +1,854 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010-2011 The IPython Development Team.
3 # Copyright (C) 2010-2011 The IPython Development Team.
4 #
4 #
5 # Distributed under the terms of the BSD License.
5 # Distributed under the terms of the BSD License.
6 #
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import atexit
16 import atexit
17 import datetime
17 import datetime
18 import os
18 import os
19 import re
19 import re
20 try:
20 try:
21 import sqlite3
21 import sqlite3
22 except ImportError:
22 except ImportError:
23 try:
23 try:
24 from pysqlite2 import dbapi2 as sqlite3
24 from pysqlite2 import dbapi2 as sqlite3
25 except ImportError:
25 except ImportError:
26 sqlite3 = None
26 sqlite3 = None
27 import threading
27 import threading
28
28
29 # Our own packages
29 # Our own packages
30 from IPython.config.configurable import Configurable
30 from IPython.config.configurable import Configurable
31 from IPython.external.decorator import decorator
31 from IPython.external.decorator import decorator
32 from IPython.utils.decorators import undoc
32 from IPython.utils.path import locate_profile
33 from IPython.utils.path import locate_profile
33 from IPython.utils import py3compat
34 from IPython.utils import py3compat
34 from IPython.utils.traitlets import (
35 from IPython.utils.traitlets import (
35 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
36 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
36 )
37 )
37 from IPython.utils.warn import warn
38 from IPython.utils.warn import warn
38
39
39 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
40 # Classes and functions
41 # Classes and functions
41 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
42
43
44 @undoc
43 class DummyDB(object):
45 class DummyDB(object):
44 """Dummy DB that will act as a black hole for history.
46 """Dummy DB that will act as a black hole for history.
45
47
46 Only used in the absence of sqlite"""
48 Only used in the absence of sqlite"""
47 def execute(*args, **kwargs):
49 def execute(*args, **kwargs):
48 return []
50 return []
49
51
50 def commit(self, *args, **kwargs):
52 def commit(self, *args, **kwargs):
51 pass
53 pass
52
54
53 def __enter__(self, *args, **kwargs):
55 def __enter__(self, *args, **kwargs):
54 pass
56 pass
55
57
56 def __exit__(self, *args, **kwargs):
58 def __exit__(self, *args, **kwargs):
57 pass
59 pass
58
60
59
61
60 @decorator
62 @decorator
61 def needs_sqlite(f, self, *a, **kw):
63 def needs_sqlite(f, self, *a, **kw):
62 """return an empty list in the absence of sqlite"""
64 """Decorator: return an empty list in the absence of sqlite."""
63 if sqlite3 is None or not self.enabled:
65 if sqlite3 is None or not self.enabled:
64 return []
66 return []
65 else:
67 else:
66 return f(self, *a, **kw)
68 return f(self, *a, **kw)
67
69
68
70
69 if sqlite3 is not None:
71 if sqlite3 is not None:
70 DatabaseError = sqlite3.DatabaseError
72 DatabaseError = sqlite3.DatabaseError
71 else:
73 else:
74 @undoc
72 class DatabaseError(Exception):
75 class DatabaseError(Exception):
73 "Dummy exception when sqlite could not be imported. Should never occur."
76 "Dummy exception when sqlite could not be imported. Should never occur."
74
77
75 @decorator
78 @decorator
76 def catch_corrupt_db(f, self, *a, **kw):
79 def catch_corrupt_db(f, self, *a, **kw):
77 """A decorator which wraps HistoryAccessor method calls to catch errors from
80 """A decorator which wraps HistoryAccessor method calls to catch errors from
78 a corrupt SQLite database, move the old database out of the way, and create
81 a corrupt SQLite database, move the old database out of the way, and create
79 a new one.
82 a new one.
80 """
83 """
81 try:
84 try:
82 return f(self, *a, **kw)
85 return f(self, *a, **kw)
83 except DatabaseError:
86 except DatabaseError:
84 if os.path.isfile(self.hist_file):
87 if os.path.isfile(self.hist_file):
85 # Try to move the file out of the way
88 # Try to move the file out of the way
86 base,ext = os.path.splitext(self.hist_file)
89 base,ext = os.path.splitext(self.hist_file)
87 newpath = base + '-corrupt' + ext
90 newpath = base + '-corrupt' + ext
88 os.rename(self.hist_file, newpath)
91 os.rename(self.hist_file, newpath)
89 self.init_db()
92 self.init_db()
90 print("ERROR! History file wasn't a valid SQLite database.",
93 print("ERROR! History file wasn't a valid SQLite database.",
91 "It was moved to %s" % newpath, "and a new file created.")
94 "It was moved to %s" % newpath, "and a new file created.")
92 return []
95 return []
93
96
94 else:
97 else:
95 # The hist_file is probably :memory: or something else.
98 # The hist_file is probably :memory: or something else.
96 raise
99 raise
97
100
98
101
99
102
100 class HistoryAccessor(Configurable):
103 class HistoryAccessor(Configurable):
101 """Access the history database without adding to it.
104 """Access the history database without adding to it.
102
105
103 This is intended for use by standalone history tools. IPython shells use
106 This is intended for use by standalone history tools. IPython shells use
104 HistoryManager, below, which is a subclass of this."""
107 HistoryManager, below, which is a subclass of this."""
105
108
106 # String holding the path to the history file
109 # String holding the path to the history file
107 hist_file = Unicode(config=True,
110 hist_file = Unicode(config=True,
108 help="""Path to file to use for SQLite history database.
111 help="""Path to file to use for SQLite history database.
109
112
110 By default, IPython will put the history database in the IPython
113 By default, IPython will put the history database in the IPython
111 profile directory. If you would rather share one history among
114 profile directory. If you would rather share one history among
112 profiles, you can set this value in each, so that they are consistent.
115 profiles, you can set this value in each, so that they are consistent.
113
116
114 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
117 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
115 mounts. If you see IPython hanging, try setting this to something on a
118 mounts. If you see IPython hanging, try setting this to something on a
116 local disk, e.g::
119 local disk, e.g::
117
120
118 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
121 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
119
122
120 """)
123 """)
121
124
122 enabled = Bool(True, config=True,
125 enabled = Bool(True, config=True,
123 help="""enable the SQLite history
126 help="""enable the SQLite history
124
127
125 set enabled=False to disable the SQLite history,
128 set enabled=False to disable the SQLite history,
126 in which case there will be no stored history, no SQLite connection,
129 in which case there will be no stored history, no SQLite connection,
127 and no background saving thread. This may be necessary in some
130 and no background saving thread. This may be necessary in some
128 threaded environments where IPython is embedded.
131 threaded environments where IPython is embedded.
129 """
132 """
130 )
133 )
131
134
132 connection_options = Dict(config=True,
135 connection_options = Dict(config=True,
133 help="""Options for configuring the SQLite connection
136 help="""Options for configuring the SQLite connection
134
137
135 These options are passed as keyword args to sqlite3.connect
138 These options are passed as keyword args to sqlite3.connect
136 when establishing database conenctions.
139 when establishing database conenctions.
137 """
140 """
138 )
141 )
139
142
140 # The SQLite database
143 # The SQLite database
141 db = Any()
144 db = Any()
142 def _db_changed(self, name, old, new):
145 def _db_changed(self, name, old, new):
143 """validate the db, since it can be an Instance of two different types"""
146 """validate the db, since it can be an Instance of two different types"""
144 connection_types = (DummyDB,)
147 connection_types = (DummyDB,)
145 if sqlite3 is not None:
148 if sqlite3 is not None:
146 connection_types = (DummyDB, sqlite3.Connection)
149 connection_types = (DummyDB, sqlite3.Connection)
147 if not isinstance(new, connection_types):
150 if not isinstance(new, connection_types):
148 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
151 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
149 (self.__class__.__name__, new)
152 (self.__class__.__name__, new)
150 raise TraitError(msg)
153 raise TraitError(msg)
151
154
152 def __init__(self, profile='default', hist_file=u'', **traits):
155 def __init__(self, profile='default', hist_file=u'', **traits):
153 """Create a new history accessor.
156 """Create a new history accessor.
154
157
155 Parameters
158 Parameters
156 ----------
159 ----------
157 profile : str
160 profile : str
158 The name of the profile from which to open history.
161 The name of the profile from which to open history.
159 hist_file : str
162 hist_file : str
160 Path to an SQLite history database stored by IPython. If specified,
163 Path to an SQLite history database stored by IPython. If specified,
161 hist_file overrides profile.
164 hist_file overrides profile.
162 config :
165 config : :class:`~IPython.config.loader.Config`
163 Config object. hist_file can also be set through this.
166 Config object. hist_file can also be set through this.
164 """
167 """
165 # We need a pointer back to the shell for various tasks.
168 # We need a pointer back to the shell for various tasks.
166 super(HistoryAccessor, self).__init__(**traits)
169 super(HistoryAccessor, self).__init__(**traits)
167 # defer setting hist_file from kwarg until after init,
170 # defer setting hist_file from kwarg until after init,
168 # otherwise the default kwarg value would clobber any value
171 # otherwise the default kwarg value would clobber any value
169 # set by config
172 # set by config
170 if hist_file:
173 if hist_file:
171 self.hist_file = hist_file
174 self.hist_file = hist_file
172
175
173 if self.hist_file == u'':
176 if self.hist_file == u'':
174 # No one has set the hist_file, yet.
177 # No one has set the hist_file, yet.
175 self.hist_file = self._get_hist_file_name(profile)
178 self.hist_file = self._get_hist_file_name(profile)
176
179
177 if sqlite3 is None and self.enabled:
180 if sqlite3 is None and self.enabled:
178 warn("IPython History requires SQLite, your history will not be saved")
181 warn("IPython History requires SQLite, your history will not be saved")
179 self.enabled = False
182 self.enabled = False
180
183
181 self.init_db()
184 self.init_db()
182
185
183 def _get_hist_file_name(self, profile='default'):
186 def _get_hist_file_name(self, profile='default'):
184 """Find the history file for the given profile name.
187 """Find the history file for the given profile name.
185
188
186 This is overridden by the HistoryManager subclass, to use the shell's
189 This is overridden by the HistoryManager subclass, to use the shell's
187 active profile.
190 active profile.
188
191
189 Parameters
192 Parameters
190 ----------
193 ----------
191 profile : str
194 profile : str
192 The name of a profile which has a history file.
195 The name of a profile which has a history file.
193 """
196 """
194 return os.path.join(locate_profile(profile), 'history.sqlite')
197 return os.path.join(locate_profile(profile), 'history.sqlite')
195
198
196 @catch_corrupt_db
199 @catch_corrupt_db
197 def init_db(self):
200 def init_db(self):
198 """Connect to the database, and create tables if necessary."""
201 """Connect to the database, and create tables if necessary."""
199 if not self.enabled:
202 if not self.enabled:
200 self.db = DummyDB()
203 self.db = DummyDB()
201 return
204 return
202
205
203 # use detect_types so that timestamps return datetime objects
206 # use detect_types so that timestamps return datetime objects
204 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
207 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
205 kwargs.update(self.connection_options)
208 kwargs.update(self.connection_options)
206 self.db = sqlite3.connect(self.hist_file, **kwargs)
209 self.db = sqlite3.connect(self.hist_file, **kwargs)
207 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
210 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
208 primary key autoincrement, start timestamp,
211 primary key autoincrement, start timestamp,
209 end timestamp, num_cmds integer, remark text)""")
212 end timestamp, num_cmds integer, remark text)""")
210 self.db.execute("""CREATE TABLE IF NOT EXISTS history
213 self.db.execute("""CREATE TABLE IF NOT EXISTS history
211 (session integer, line integer, source text, source_raw text,
214 (session integer, line integer, source text, source_raw text,
212 PRIMARY KEY (session, line))""")
215 PRIMARY KEY (session, line))""")
213 # Output history is optional, but ensure the table's there so it can be
216 # Output history is optional, but ensure the table's there so it can be
214 # enabled later.
217 # enabled later.
215 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
218 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
216 (session integer, line integer, output text,
219 (session integer, line integer, output text,
217 PRIMARY KEY (session, line))""")
220 PRIMARY KEY (session, line))""")
218 self.db.commit()
221 self.db.commit()
219
222
220 def writeout_cache(self):
223 def writeout_cache(self):
221 """Overridden by HistoryManager to dump the cache before certain
224 """Overridden by HistoryManager to dump the cache before certain
222 database lookups."""
225 database lookups."""
223 pass
226 pass
224
227
225 ## -------------------------------
228 ## -------------------------------
226 ## Methods for retrieving history:
229 ## Methods for retrieving history:
227 ## -------------------------------
230 ## -------------------------------
228 def _run_sql(self, sql, params, raw=True, output=False):
231 def _run_sql(self, sql, params, raw=True, output=False):
229 """Prepares and runs an SQL query for the history database.
232 """Prepares and runs an SQL query for the history database.
230
233
231 Parameters
234 Parameters
232 ----------
235 ----------
233 sql : str
236 sql : str
234 Any filtering expressions to go after SELECT ... FROM ...
237 Any filtering expressions to go after SELECT ... FROM ...
235 params : tuple
238 params : tuple
236 Parameters passed to the SQL query (to replace "?")
239 Parameters passed to the SQL query (to replace "?")
237 raw, output : bool
240 raw, output : bool
238 See :meth:`get_range`
241 See :meth:`get_range`
239
242
240 Returns
243 Returns
241 -------
244 -------
242 Tuples as :meth:`get_range`
245 Tuples as :meth:`get_range`
243 """
246 """
244 toget = 'source_raw' if raw else 'source'
247 toget = 'source_raw' if raw else 'source'
245 sqlfrom = "history"
248 sqlfrom = "history"
246 if output:
249 if output:
247 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
250 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
248 toget = "history.%s, output_history.output" % toget
251 toget = "history.%s, output_history.output" % toget
249 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
252 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
250 (toget, sqlfrom) + sql, params)
253 (toget, sqlfrom) + sql, params)
251 if output: # Regroup into 3-tuples, and parse JSON
254 if output: # Regroup into 3-tuples, and parse JSON
252 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
255 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
253 return cur
256 return cur
254
257
255 @needs_sqlite
258 @needs_sqlite
256 @catch_corrupt_db
259 @catch_corrupt_db
257 def get_session_info(self, session=0):
260 def get_session_info(self, session):
258 """get info about a session
261 """Get info about a session.
259
262
260 Parameters
263 Parameters
261 ----------
264 ----------
262
265
263 session : int
266 session : int
264 Session number to retrieve. The current session is 0, and negative
267 Session number to retrieve.
265 numbers count back from current session, so -1 is previous session.
266
268
267 Returns
269 Returns
268 -------
270 -------
269
271
270 (session_id [int], start [datetime], end [datetime], num_cmds [int],
272 session_id : int
271 remark [unicode])
273 Session ID number
272
274 start : datetime
273 Sessions that are running or did not exit cleanly will have `end=None`
275 Timestamp for the start of the session.
274 and `num_cmds=None`.
276 end : datetime
275
277 Timestamp for the end of the session, or None if IPython crashed.
278 num_cmds : int
279 Number of commands run, or None if IPython crashed.
280 remark : unicode
281 A manually set description.
276 """
282 """
277
278 if session <= 0:
279 session += self.session_number
280
281 query = "SELECT * from sessions where session == ?"
283 query = "SELECT * from sessions where session == ?"
282 return self.db.execute(query, (session,)).fetchone()
284 return self.db.execute(query, (session,)).fetchone()
283
285
284 @catch_corrupt_db
286 @catch_corrupt_db
287 def get_last_session_id(self):
288 """Get the last session ID currently in the database.
289
290 Within IPython, this should be the same as the value stored in
291 :attr:`HistoryManager.session_number`.
292 """
293 for record in self.get_tail(n=1, include_latest=True):
294 return record[0]
295
296 @catch_corrupt_db
285 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
297 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
286 """Get the last n lines from the history database.
298 """Get the last n lines from the history database.
287
299
288 Parameters
300 Parameters
289 ----------
301 ----------
290 n : int
302 n : int
291 The number of lines to get
303 The number of lines to get
292 raw, output : bool
304 raw, output : bool
293 See :meth:`get_range`
305 See :meth:`get_range`
294 include_latest : bool
306 include_latest : bool
295 If False (default), n+1 lines are fetched, and the latest one
307 If False (default), n+1 lines are fetched, and the latest one
296 is discarded. This is intended to be used where the function
308 is discarded. This is intended to be used where the function
297 is called by a user command, which it should not return.
309 is called by a user command, which it should not return.
298
310
299 Returns
311 Returns
300 -------
312 -------
301 Tuples as :meth:`get_range`
313 Tuples as :meth:`get_range`
302 """
314 """
303 self.writeout_cache()
315 self.writeout_cache()
304 if not include_latest:
316 if not include_latest:
305 n += 1
317 n += 1
306 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
318 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
307 (n,), raw=raw, output=output)
319 (n,), raw=raw, output=output)
308 if not include_latest:
320 if not include_latest:
309 return reversed(list(cur)[1:])
321 return reversed(list(cur)[1:])
310 return reversed(list(cur))
322 return reversed(list(cur))
311
323
312 @catch_corrupt_db
324 @catch_corrupt_db
313 def search(self, pattern="*", raw=True, search_raw=True,
325 def search(self, pattern="*", raw=True, search_raw=True,
314 output=False, n=None, unique=False):
326 output=False, n=None, unique=False):
315 """Search the database using unix glob-style matching (wildcards
327 """Search the database using unix glob-style matching (wildcards
316 * and ?).
328 * and ?).
317
329
318 Parameters
330 Parameters
319 ----------
331 ----------
320 pattern : str
332 pattern : str
321 The wildcarded pattern to match when searching
333 The wildcarded pattern to match when searching
322 search_raw : bool
334 search_raw : bool
323 If True, search the raw input, otherwise, the parsed input
335 If True, search the raw input, otherwise, the parsed input
324 raw, output : bool
336 raw, output : bool
325 See :meth:`get_range`
337 See :meth:`get_range`
326 n : None or int
338 n : None or int
327 If an integer is given, it defines the limit of
339 If an integer is given, it defines the limit of
328 returned entries.
340 returned entries.
329 unique : bool
341 unique : bool
330 When it is true, return only unique entries.
342 When it is true, return only unique entries.
331
343
332 Returns
344 Returns
333 -------
345 -------
334 Tuples as :meth:`get_range`
346 Tuples as :meth:`get_range`
335 """
347 """
336 tosearch = "source_raw" if search_raw else "source"
348 tosearch = "source_raw" if search_raw else "source"
337 if output:
349 if output:
338 tosearch = "history." + tosearch
350 tosearch = "history." + tosearch
339 self.writeout_cache()
351 self.writeout_cache()
340 sqlform = "WHERE %s GLOB ?" % tosearch
352 sqlform = "WHERE %s GLOB ?" % tosearch
341 params = (pattern,)
353 params = (pattern,)
342 if unique:
354 if unique:
343 sqlform += ' GROUP BY {0}'.format(tosearch)
355 sqlform += ' GROUP BY {0}'.format(tosearch)
344 if n is not None:
356 if n is not None:
345 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
357 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
346 params += (n,)
358 params += (n,)
347 elif unique:
359 elif unique:
348 sqlform += " ORDER BY session, line"
360 sqlform += " ORDER BY session, line"
349 cur = self._run_sql(sqlform, params, raw=raw, output=output)
361 cur = self._run_sql(sqlform, params, raw=raw, output=output)
350 if n is not None:
362 if n is not None:
351 return reversed(list(cur))
363 return reversed(list(cur))
352 return cur
364 return cur
353
365
354 @catch_corrupt_db
366 @catch_corrupt_db
355 def get_range(self, session, start=1, stop=None, raw=True,output=False):
367 def get_range(self, session, start=1, stop=None, raw=True,output=False):
356 """Retrieve input by session.
368 """Retrieve input by session.
357
369
358 Parameters
370 Parameters
359 ----------
371 ----------
360 session : int
372 session : int
361 Session number to retrieve.
373 Session number to retrieve.
362 start : int
374 start : int
363 First line to retrieve.
375 First line to retrieve.
364 stop : int
376 stop : int
365 End of line range (excluded from output itself). If None, retrieve
377 End of line range (excluded from output itself). If None, retrieve
366 to the end of the session.
378 to the end of the session.
367 raw : bool
379 raw : bool
368 If True, return untranslated input
380 If True, return untranslated input
369 output : bool
381 output : bool
370 If True, attempt to include output. This will be 'real' Python
382 If True, attempt to include output. This will be 'real' Python
371 objects for the current session, or text reprs from previous
383 objects for the current session, or text reprs from previous
372 sessions if db_log_output was enabled at the time. Where no output
384 sessions if db_log_output was enabled at the time. Where no output
373 is found, None is used.
385 is found, None is used.
374
386
375 Returns
387 Returns
376 -------
388 -------
377 An iterator over the desired lines. Each line is a 3-tuple, either
389 entries
378 (session, line, input) if output is False, or
390 An iterator over the desired lines. Each line is a 3-tuple, either
379 (session, line, (input, output)) if output is True.
391 (session, line, input) if output is False, or
392 (session, line, (input, output)) if output is True.
380 """
393 """
381 if stop:
394 if stop:
382 lineclause = "line >= ? AND line < ?"
395 lineclause = "line >= ? AND line < ?"
383 params = (session, start, stop)
396 params = (session, start, stop)
384 else:
397 else:
385 lineclause = "line>=?"
398 lineclause = "line>=?"
386 params = (session, start)
399 params = (session, start)
387
400
388 return self._run_sql("WHERE session==? AND %s" % lineclause,
401 return self._run_sql("WHERE session==? AND %s" % lineclause,
389 params, raw=raw, output=output)
402 params, raw=raw, output=output)
390
403
391 def get_range_by_str(self, rangestr, raw=True, output=False):
404 def get_range_by_str(self, rangestr, raw=True, output=False):
392 """Get lines of history from a string of ranges, as used by magic
405 """Get lines of history from a string of ranges, as used by magic
393 commands %hist, %save, %macro, etc.
406 commands %hist, %save, %macro, etc.
394
407
395 Parameters
408 Parameters
396 ----------
409 ----------
397 rangestr : str
410 rangestr : str
398 A string specifying ranges, e.g. "5 ~2/1-4". See
411 A string specifying ranges, e.g. "5 ~2/1-4". See
399 :func:`magic_history` for full details.
412 :func:`magic_history` for full details.
400 raw, output : bool
413 raw, output : bool
401 As :meth:`get_range`
414 As :meth:`get_range`
402
415
403 Returns
416 Returns
404 -------
417 -------
405 Tuples as :meth:`get_range`
418 Tuples as :meth:`get_range`
406 """
419 """
407 for sess, s, e in extract_hist_ranges(rangestr):
420 for sess, s, e in extract_hist_ranges(rangestr):
408 for line in self.get_range(sess, s, e, raw=raw, output=output):
421 for line in self.get_range(sess, s, e, raw=raw, output=output):
409 yield line
422 yield line
410
423
411
424
412 class HistoryManager(HistoryAccessor):
425 class HistoryManager(HistoryAccessor):
413 """A class to organize all history-related functionality in one place.
426 """A class to organize all history-related functionality in one place.
414 """
427 """
415 # Public interface
428 # Public interface
416
429
417 # An instance of the IPython shell we are attached to
430 # An instance of the IPython shell we are attached to
418 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
431 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
419 # Lists to hold processed and raw history. These start with a blank entry
432 # Lists to hold processed and raw history. These start with a blank entry
420 # so that we can index them starting from 1
433 # so that we can index them starting from 1
421 input_hist_parsed = List([""])
434 input_hist_parsed = List([""])
422 input_hist_raw = List([""])
435 input_hist_raw = List([""])
423 # A list of directories visited during session
436 # A list of directories visited during session
424 dir_hist = List()
437 dir_hist = List()
425 def _dir_hist_default(self):
438 def _dir_hist_default(self):
426 try:
439 try:
427 return [py3compat.getcwd()]
440 return [py3compat.getcwd()]
428 except OSError:
441 except OSError:
429 return []
442 return []
430
443
431 # A dict of output history, keyed with ints from the shell's
444 # A dict of output history, keyed with ints from the shell's
432 # execution count.
445 # execution count.
433 output_hist = Dict()
446 output_hist = Dict()
434 # The text/plain repr of outputs.
447 # The text/plain repr of outputs.
435 output_hist_reprs = Dict()
448 output_hist_reprs = Dict()
436
449
437 # The number of the current session in the history database
450 # The number of the current session in the history database
438 session_number = Integer()
451 session_number = Integer()
439
452
440 db_log_output = Bool(False, config=True,
453 db_log_output = Bool(False, config=True,
441 help="Should the history database include output? (default: no)"
454 help="Should the history database include output? (default: no)"
442 )
455 )
443 db_cache_size = Integer(0, config=True,
456 db_cache_size = Integer(0, config=True,
444 help="Write to database every x commands (higher values save disk access & power).\n"
457 help="Write to database every x commands (higher values save disk access & power).\n"
445 "Values of 1 or less effectively disable caching."
458 "Values of 1 or less effectively disable caching."
446 )
459 )
447 # The input and output caches
460 # The input and output caches
448 db_input_cache = List()
461 db_input_cache = List()
449 db_output_cache = List()
462 db_output_cache = List()
450
463
451 # History saving in separate thread
464 # History saving in separate thread
452 save_thread = Instance('IPython.core.history.HistorySavingThread')
465 save_thread = Instance('IPython.core.history.HistorySavingThread')
453 try: # Event is a function returning an instance of _Event...
466 try: # Event is a function returning an instance of _Event...
454 save_flag = Instance(threading._Event)
467 save_flag = Instance(threading._Event)
455 except AttributeError: # ...until Python 3.3, when it's a class.
468 except AttributeError: # ...until Python 3.3, when it's a class.
456 save_flag = Instance(threading.Event)
469 save_flag = Instance(threading.Event)
457
470
458 # Private interface
471 # Private interface
459 # Variables used to store the three last inputs from the user. On each new
472 # Variables used to store the three last inputs from the user. On each new
460 # history update, we populate the user's namespace with these, shifted as
473 # history update, we populate the user's namespace with these, shifted as
461 # necessary.
474 # necessary.
462 _i00 = Unicode(u'')
475 _i00 = Unicode(u'')
463 _i = Unicode(u'')
476 _i = Unicode(u'')
464 _ii = Unicode(u'')
477 _ii = Unicode(u'')
465 _iii = Unicode(u'')
478 _iii = Unicode(u'')
466
479
467 # A regex matching all forms of the exit command, so that we don't store
480 # A regex matching all forms of the exit command, so that we don't store
468 # them in the history (it's annoying to rewind the first entry and land on
481 # them in the history (it's annoying to rewind the first entry and land on
469 # an exit call).
482 # an exit call).
470 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
483 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
471
484
472 def __init__(self, shell=None, config=None, **traits):
485 def __init__(self, shell=None, config=None, **traits):
473 """Create a new history manager associated with a shell instance.
486 """Create a new history manager associated with a shell instance.
474 """
487 """
475 # We need a pointer back to the shell for various tasks.
488 # We need a pointer back to the shell for various tasks.
476 super(HistoryManager, self).__init__(shell=shell, config=config,
489 super(HistoryManager, self).__init__(shell=shell, config=config,
477 **traits)
490 **traits)
478 self.save_flag = threading.Event()
491 self.save_flag = threading.Event()
479 self.db_input_cache_lock = threading.Lock()
492 self.db_input_cache_lock = threading.Lock()
480 self.db_output_cache_lock = threading.Lock()
493 self.db_output_cache_lock = threading.Lock()
481 if self.enabled and self.hist_file != ':memory:':
494 if self.enabled and self.hist_file != ':memory:':
482 self.save_thread = HistorySavingThread(self)
495 self.save_thread = HistorySavingThread(self)
483 self.save_thread.start()
496 self.save_thread.start()
484
497
485 self.new_session()
498 self.new_session()
486
499
487 def _get_hist_file_name(self, profile=None):
500 def _get_hist_file_name(self, profile=None):
488 """Get default history file name based on the Shell's profile.
501 """Get default history file name based on the Shell's profile.
489
502
490 The profile parameter is ignored, but must exist for compatibility with
503 The profile parameter is ignored, but must exist for compatibility with
491 the parent class."""
504 the parent class."""
492 profile_dir = self.shell.profile_dir.location
505 profile_dir = self.shell.profile_dir.location
493 return os.path.join(profile_dir, 'history.sqlite')
506 return os.path.join(profile_dir, 'history.sqlite')
494
507
495 @needs_sqlite
508 @needs_sqlite
496 def new_session(self, conn=None):
509 def new_session(self, conn=None):
497 """Get a new session number."""
510 """Get a new session number."""
498 if conn is None:
511 if conn is None:
499 conn = self.db
512 conn = self.db
500
513
501 with conn:
514 with conn:
502 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
515 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
503 NULL, "") """, (datetime.datetime.now(),))
516 NULL, "") """, (datetime.datetime.now(),))
504 self.session_number = cur.lastrowid
517 self.session_number = cur.lastrowid
505
518
506 def end_session(self):
519 def end_session(self):
507 """Close the database session, filling in the end time and line count."""
520 """Close the database session, filling in the end time and line count."""
508 self.writeout_cache()
521 self.writeout_cache()
509 with self.db:
522 with self.db:
510 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
523 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
511 session==?""", (datetime.datetime.now(),
524 session==?""", (datetime.datetime.now(),
512 len(self.input_hist_parsed)-1, self.session_number))
525 len(self.input_hist_parsed)-1, self.session_number))
513 self.session_number = 0
526 self.session_number = 0
514
527
515 def name_session(self, name):
528 def name_session(self, name):
516 """Give the current session a name in the history database."""
529 """Give the current session a name in the history database."""
517 with self.db:
530 with self.db:
518 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
531 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
519 (name, self.session_number))
532 (name, self.session_number))
520
533
521 def reset(self, new_session=True):
534 def reset(self, new_session=True):
522 """Clear the session history, releasing all object references, and
535 """Clear the session history, releasing all object references, and
523 optionally open a new session."""
536 optionally open a new session."""
524 self.output_hist.clear()
537 self.output_hist.clear()
525 # The directory history can't be completely empty
538 # The directory history can't be completely empty
526 self.dir_hist[:] = [py3compat.getcwd()]
539 self.dir_hist[:] = [py3compat.getcwd()]
527
540
528 if new_session:
541 if new_session:
529 if self.session_number:
542 if self.session_number:
530 self.end_session()
543 self.end_session()
531 self.input_hist_parsed[:] = [""]
544 self.input_hist_parsed[:] = [""]
532 self.input_hist_raw[:] = [""]
545 self.input_hist_raw[:] = [""]
533 self.new_session()
546 self.new_session()
534
547
535 # ------------------------------
548 # ------------------------------
536 # Methods for retrieving history
549 # Methods for retrieving history
537 # ------------------------------
550 # ------------------------------
551 def get_session_info(self, session=0):
552 """Get info about a session.
553
554 Parameters
555 ----------
556
557 session : int
558 Session number to retrieve. The current session is 0, and negative
559 numbers count back from current session, so -1 is the previous session.
560
561 Returns
562 -------
563
564 session_id : int
565 Session ID number
566 start : datetime
567 Timestamp for the start of the session.
568 end : datetime
569 Timestamp for the end of the session, or None if IPython crashed.
570 num_cmds : int
571 Number of commands run, or None if IPython crashed.
572 remark : unicode
573 A manually set description.
574 """
575 if session <= 0:
576 session += self.session_number
577
578 return super(HistoryManager, self).get_session_info(session=session)
579
538 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
580 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
539 """Get input and output history from the current session. Called by
581 """Get input and output history from the current session. Called by
540 get_range, and takes similar parameters."""
582 get_range, and takes similar parameters."""
541 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
583 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
542
584
543 n = len(input_hist)
585 n = len(input_hist)
544 if start < 0:
586 if start < 0:
545 start += n
587 start += n
546 if not stop or (stop > n):
588 if not stop or (stop > n):
547 stop = n
589 stop = n
548 elif stop < 0:
590 elif stop < 0:
549 stop += n
591 stop += n
550
592
551 for i in range(start, stop):
593 for i in range(start, stop):
552 if output:
594 if output:
553 line = (input_hist[i], self.output_hist_reprs.get(i))
595 line = (input_hist[i], self.output_hist_reprs.get(i))
554 else:
596 else:
555 line = input_hist[i]
597 line = input_hist[i]
556 yield (0, i, line)
598 yield (0, i, line)
557
599
558 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
600 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
559 """Retrieve input by session.
601 """Retrieve input by session.
560
602
561 Parameters
603 Parameters
562 ----------
604 ----------
563 session : int
605 session : int
564 Session number to retrieve. The current session is 0, and negative
606 Session number to retrieve. The current session is 0, and negative
565 numbers count back from current session, so -1 is previous session.
607 numbers count back from current session, so -1 is previous session.
566 start : int
608 start : int
567 First line to retrieve.
609 First line to retrieve.
568 stop : int
610 stop : int
569 End of line range (excluded from output itself). If None, retrieve
611 End of line range (excluded from output itself). If None, retrieve
570 to the end of the session.
612 to the end of the session.
571 raw : bool
613 raw : bool
572 If True, return untranslated input
614 If True, return untranslated input
573 output : bool
615 output : bool
574 If True, attempt to include output. This will be 'real' Python
616 If True, attempt to include output. This will be 'real' Python
575 objects for the current session, or text reprs from previous
617 objects for the current session, or text reprs from previous
576 sessions if db_log_output was enabled at the time. Where no output
618 sessions if db_log_output was enabled at the time. Where no output
577 is found, None is used.
619 is found, None is used.
578
620
579 Returns
621 Returns
580 -------
622 -------
581 An iterator over the desired lines. Each line is a 3-tuple, either
623 entries
582 (session, line, input) if output is False, or
624 An iterator over the desired lines. Each line is a 3-tuple, either
583 (session, line, (input, output)) if output is True.
625 (session, line, input) if output is False, or
626 (session, line, (input, output)) if output is True.
584 """
627 """
585 if session <= 0:
628 if session <= 0:
586 session += self.session_number
629 session += self.session_number
587 if session==self.session_number: # Current session
630 if session==self.session_number: # Current session
588 return self._get_range_session(start, stop, raw, output)
631 return self._get_range_session(start, stop, raw, output)
589 return super(HistoryManager, self).get_range(session, start, stop, raw,
632 return super(HistoryManager, self).get_range(session, start, stop, raw,
590 output)
633 output)
591
634
592 ## ----------------------------
635 ## ----------------------------
593 ## Methods for storing history:
636 ## Methods for storing history:
594 ## ----------------------------
637 ## ----------------------------
595 def store_inputs(self, line_num, source, source_raw=None):
638 def store_inputs(self, line_num, source, source_raw=None):
596 """Store source and raw input in history and create input cache
639 """Store source and raw input in history and create input cache
597 variables _i*.
640 variables ``_i*``.
598
641
599 Parameters
642 Parameters
600 ----------
643 ----------
601 line_num : int
644 line_num : int
602 The prompt number of this input.
645 The prompt number of this input.
603
646
604 source : str
647 source : str
605 Python input.
648 Python input.
606
649
607 source_raw : str, optional
650 source_raw : str, optional
608 If given, this is the raw input without any IPython transformations
651 If given, this is the raw input without any IPython transformations
609 applied to it. If not given, ``source`` is used.
652 applied to it. If not given, ``source`` is used.
610 """
653 """
611 if source_raw is None:
654 if source_raw is None:
612 source_raw = source
655 source_raw = source
613 source = source.rstrip('\n')
656 source = source.rstrip('\n')
614 source_raw = source_raw.rstrip('\n')
657 source_raw = source_raw.rstrip('\n')
615
658
616 # do not store exit/quit commands
659 # do not store exit/quit commands
617 if self._exit_re.match(source_raw.strip()):
660 if self._exit_re.match(source_raw.strip()):
618 return
661 return
619
662
620 self.input_hist_parsed.append(source)
663 self.input_hist_parsed.append(source)
621 self.input_hist_raw.append(source_raw)
664 self.input_hist_raw.append(source_raw)
622
665
623 with self.db_input_cache_lock:
666 with self.db_input_cache_lock:
624 self.db_input_cache.append((line_num, source, source_raw))
667 self.db_input_cache.append((line_num, source, source_raw))
625 # Trigger to flush cache and write to DB.
668 # Trigger to flush cache and write to DB.
626 if len(self.db_input_cache) >= self.db_cache_size:
669 if len(self.db_input_cache) >= self.db_cache_size:
627 self.save_flag.set()
670 self.save_flag.set()
628
671
629 # update the auto _i variables
672 # update the auto _i variables
630 self._iii = self._ii
673 self._iii = self._ii
631 self._ii = self._i
674 self._ii = self._i
632 self._i = self._i00
675 self._i = self._i00
633 self._i00 = source_raw
676 self._i00 = source_raw
634
677
635 # hackish access to user namespace to create _i1,_i2... dynamically
678 # hackish access to user namespace to create _i1,_i2... dynamically
636 new_i = '_i%s' % line_num
679 new_i = '_i%s' % line_num
637 to_main = {'_i': self._i,
680 to_main = {'_i': self._i,
638 '_ii': self._ii,
681 '_ii': self._ii,
639 '_iii': self._iii,
682 '_iii': self._iii,
640 new_i : self._i00 }
683 new_i : self._i00 }
641
684
642 if self.shell is not None:
685 if self.shell is not None:
643 self.shell.push(to_main, interactive=False)
686 self.shell.push(to_main, interactive=False)
644
687
645 def store_output(self, line_num):
688 def store_output(self, line_num):
646 """If database output logging is enabled, this saves all the
689 """If database output logging is enabled, this saves all the
647 outputs from the indicated prompt number to the database. It's
690 outputs from the indicated prompt number to the database. It's
648 called by run_cell after code has been executed.
691 called by run_cell after code has been executed.
649
692
650 Parameters
693 Parameters
651 ----------
694 ----------
652 line_num : int
695 line_num : int
653 The line number from which to save outputs
696 The line number from which to save outputs
654 """
697 """
655 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
698 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
656 return
699 return
657 output = self.output_hist_reprs[line_num]
700 output = self.output_hist_reprs[line_num]
658
701
659 with self.db_output_cache_lock:
702 with self.db_output_cache_lock:
660 self.db_output_cache.append((line_num, output))
703 self.db_output_cache.append((line_num, output))
661 if self.db_cache_size <= 1:
704 if self.db_cache_size <= 1:
662 self.save_flag.set()
705 self.save_flag.set()
663
706
664 def _writeout_input_cache(self, conn):
707 def _writeout_input_cache(self, conn):
665 with conn:
708 with conn:
666 for line in self.db_input_cache:
709 for line in self.db_input_cache:
667 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
710 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
668 (self.session_number,)+line)
711 (self.session_number,)+line)
669
712
670 def _writeout_output_cache(self, conn):
713 def _writeout_output_cache(self, conn):
671 with conn:
714 with conn:
672 for line in self.db_output_cache:
715 for line in self.db_output_cache:
673 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
716 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
674 (self.session_number,)+line)
717 (self.session_number,)+line)
675
718
676 @needs_sqlite
719 @needs_sqlite
677 def writeout_cache(self, conn=None):
720 def writeout_cache(self, conn=None):
678 """Write any entries in the cache to the database."""
721 """Write any entries in the cache to the database."""
679 if conn is None:
722 if conn is None:
680 conn = self.db
723 conn = self.db
681
724
682 with self.db_input_cache_lock:
725 with self.db_input_cache_lock:
683 try:
726 try:
684 self._writeout_input_cache(conn)
727 self._writeout_input_cache(conn)
685 except sqlite3.IntegrityError:
728 except sqlite3.IntegrityError:
686 self.new_session(conn)
729 self.new_session(conn)
687 print("ERROR! Session/line number was not unique in",
730 print("ERROR! Session/line number was not unique in",
688 "database. History logging moved to new session",
731 "database. History logging moved to new session",
689 self.session_number)
732 self.session_number)
690 try:
733 try:
691 # Try writing to the new session. If this fails, don't
734 # Try writing to the new session. If this fails, don't
692 # recurse
735 # recurse
693 self._writeout_input_cache(conn)
736 self._writeout_input_cache(conn)
694 except sqlite3.IntegrityError:
737 except sqlite3.IntegrityError:
695 pass
738 pass
696 finally:
739 finally:
697 self.db_input_cache = []
740 self.db_input_cache = []
698
741
699 with self.db_output_cache_lock:
742 with self.db_output_cache_lock:
700 try:
743 try:
701 self._writeout_output_cache(conn)
744 self._writeout_output_cache(conn)
702 except sqlite3.IntegrityError:
745 except sqlite3.IntegrityError:
703 print("!! Session/line number for output was not unique",
746 print("!! Session/line number for output was not unique",
704 "in database. Output will not be stored.")
747 "in database. Output will not be stored.")
705 finally:
748 finally:
706 self.db_output_cache = []
749 self.db_output_cache = []
707
750
708
751
709 class HistorySavingThread(threading.Thread):
752 class HistorySavingThread(threading.Thread):
710 """This thread takes care of writing history to the database, so that
753 """This thread takes care of writing history to the database, so that
711 the UI isn't held up while that happens.
754 the UI isn't held up while that happens.
712
755
713 It waits for the HistoryManager's save_flag to be set, then writes out
756 It waits for the HistoryManager's save_flag to be set, then writes out
714 the history cache. The main thread is responsible for setting the flag when
757 the history cache. The main thread is responsible for setting the flag when
715 the cache size reaches a defined threshold."""
758 the cache size reaches a defined threshold."""
716 daemon = True
759 daemon = True
717 stop_now = False
760 stop_now = False
718 enabled = True
761 enabled = True
719 def __init__(self, history_manager):
762 def __init__(self, history_manager):
720 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
763 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
721 self.history_manager = history_manager
764 self.history_manager = history_manager
722 self.enabled = history_manager.enabled
765 self.enabled = history_manager.enabled
723 atexit.register(self.stop)
766 atexit.register(self.stop)
724
767
725 @needs_sqlite
768 @needs_sqlite
726 def run(self):
769 def run(self):
727 # We need a separate db connection per thread:
770 # We need a separate db connection per thread:
728 try:
771 try:
729 self.db = sqlite3.connect(self.history_manager.hist_file,
772 self.db = sqlite3.connect(self.history_manager.hist_file,
730 **self.history_manager.connection_options
773 **self.history_manager.connection_options
731 )
774 )
732 while True:
775 while True:
733 self.history_manager.save_flag.wait()
776 self.history_manager.save_flag.wait()
734 if self.stop_now:
777 if self.stop_now:
735 return
778 return
736 self.history_manager.save_flag.clear()
779 self.history_manager.save_flag.clear()
737 self.history_manager.writeout_cache(self.db)
780 self.history_manager.writeout_cache(self.db)
738 except Exception as e:
781 except Exception as e:
739 print(("The history saving thread hit an unexpected error (%s)."
782 print(("The history saving thread hit an unexpected error (%s)."
740 "History will not be written to the database.") % repr(e))
783 "History will not be written to the database.") % repr(e))
741
784
742 def stop(self):
785 def stop(self):
743 """This can be called from the main thread to safely stop this thread.
786 """This can be called from the main thread to safely stop this thread.
744
787
745 Note that it does not attempt to write out remaining history before
788 Note that it does not attempt to write out remaining history before
746 exiting. That should be done by calling the HistoryManager's
789 exiting. That should be done by calling the HistoryManager's
747 end_session method."""
790 end_session method."""
748 self.stop_now = True
791 self.stop_now = True
749 self.history_manager.save_flag.set()
792 self.history_manager.save_flag.set()
750 self.join()
793 self.join()
751
794
752
795
753 # To match, e.g. ~5/8-~2/3
796 # To match, e.g. ~5/8-~2/3
754 range_re = re.compile(r"""
797 range_re = re.compile(r"""
755 ((?P<startsess>~?\d+)/)?
798 ((?P<startsess>~?\d+)/)?
756 (?P<start>\d+)?
799 (?P<start>\d+)?
757 ((?P<sep>[\-:])
800 ((?P<sep>[\-:])
758 ((?P<endsess>~?\d+)/)?
801 ((?P<endsess>~?\d+)/)?
759 (?P<end>\d+))?
802 (?P<end>\d+))?
760 $""", re.VERBOSE)
803 $""", re.VERBOSE)
761
804
762
805
763 def extract_hist_ranges(ranges_str):
806 def extract_hist_ranges(ranges_str):
764 """Turn a string of history ranges into 3-tuples of (session, start, stop).
807 """Turn a string of history ranges into 3-tuples of (session, start, stop).
765
808
766 Examples
809 Examples
767 --------
810 --------
768 list(extract_input_ranges("~8/5-~7/4 2"))
811 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
769 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
812 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
770 """
813 """
771 for range_str in ranges_str.split():
814 for range_str in ranges_str.split():
772 rmatch = range_re.match(range_str)
815 rmatch = range_re.match(range_str)
773 if not rmatch:
816 if not rmatch:
774 continue
817 continue
775 start = rmatch.group("start")
818 start = rmatch.group("start")
776 if start:
819 if start:
777 start = int(start)
820 start = int(start)
778 end = rmatch.group("end")
821 end = rmatch.group("end")
779 # If no end specified, get (a, a + 1)
822 # If no end specified, get (a, a + 1)
780 end = int(end) if end else start + 1
823 end = int(end) if end else start + 1
781 else: # start not specified
824 else: # start not specified
782 if not rmatch.group('startsess'): # no startsess
825 if not rmatch.group('startsess'): # no startsess
783 continue
826 continue
784 start = 1
827 start = 1
785 end = None # provide the entire session hist
828 end = None # provide the entire session hist
786
829
787 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
830 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
788 end += 1
831 end += 1
789 startsess = rmatch.group("startsess") or "0"
832 startsess = rmatch.group("startsess") or "0"
790 endsess = rmatch.group("endsess") or startsess
833 endsess = rmatch.group("endsess") or startsess
791 startsess = int(startsess.replace("~","-"))
834 startsess = int(startsess.replace("~","-"))
792 endsess = int(endsess.replace("~","-"))
835 endsess = int(endsess.replace("~","-"))
793 assert endsess >= startsess, "start session must be earlier than end session"
836 assert endsess >= startsess, "start session must be earlier than end session"
794
837
795 if endsess == startsess:
838 if endsess == startsess:
796 yield (startsess, start, end)
839 yield (startsess, start, end)
797 continue
840 continue
798 # Multiple sessions in one range:
841 # Multiple sessions in one range:
799 yield (startsess, start, None)
842 yield (startsess, start, None)
800 for sess in range(startsess+1, endsess):
843 for sess in range(startsess+1, endsess):
801 yield (sess, 1, None)
844 yield (sess, 1, None)
802 yield (endsess, 1, end)
845 yield (endsess, 1, end)
803
846
804
847
805 def _format_lineno(session, line):
848 def _format_lineno(session, line):
806 """Helper function to format line numbers properly."""
849 """Helper function to format line numbers properly."""
807 if session == 0:
850 if session == 0:
808 return str(line)
851 return str(line)
809 return "%s#%s" % (session, line)
852 return "%s#%s" % (session, line)
810
853
811
854
@@ -1,280 +1,281 b''
1 """Magic functions for running cells in various scripts."""
1 """Magic functions for running cells in various scripts."""
2 from __future__ import print_function
2 from __future__ import print_function
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2012 The IPython Development Team.
4 # Copyright (c) 2012 The IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # Stdlib
15 # Stdlib
16 import errno
16 import errno
17 import os
17 import os
18 import sys
18 import sys
19 import signal
19 import signal
20 import time
20 import time
21 from subprocess import Popen, PIPE
21 from subprocess import Popen, PIPE
22 import atexit
22 import atexit
23
23
24 # Our own packages
24 # Our own packages
25 from IPython.config.configurable import Configurable
25 from IPython.config.configurable import Configurable
26 from IPython.core import magic_arguments
26 from IPython.core import magic_arguments
27 from IPython.core.magic import (
27 from IPython.core.magic import (
28 Magics, magics_class, line_magic, cell_magic
28 Magics, magics_class, line_magic, cell_magic
29 )
29 )
30 from IPython.lib.backgroundjobs import BackgroundJobManager
30 from IPython.lib.backgroundjobs import BackgroundJobManager
31 from IPython.utils import py3compat
31 from IPython.utils import py3compat
32 from IPython.utils.process import arg_split
32 from IPython.utils.process import arg_split
33 from IPython.utils.traitlets import List, Dict
33 from IPython.utils.traitlets import List, Dict
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Magic implementation classes
36 # Magic implementation classes
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 def script_args(f):
39 def script_args(f):
40 """single decorator for adding script args"""
40 """single decorator for adding script args"""
41 args = [
41 args = [
42 magic_arguments.argument(
42 magic_arguments.argument(
43 '--out', type=str,
43 '--out', type=str,
44 help="""The variable in which to store stdout from the script.
44 help="""The variable in which to store stdout from the script.
45 If the script is backgrounded, this will be the stdout *pipe*,
45 If the script is backgrounded, this will be the stdout *pipe*,
46 instead of the stderr text itself.
46 instead of the stderr text itself.
47 """
47 """
48 ),
48 ),
49 magic_arguments.argument(
49 magic_arguments.argument(
50 '--err', type=str,
50 '--err', type=str,
51 help="""The variable in which to store stderr from the script.
51 help="""The variable in which to store stderr from the script.
52 If the script is backgrounded, this will be the stderr *pipe*,
52 If the script is backgrounded, this will be the stderr *pipe*,
53 instead of the stderr text itself.
53 instead of the stderr text itself.
54 """
54 """
55 ),
55 ),
56 magic_arguments.argument(
56 magic_arguments.argument(
57 '--bg', action="store_true",
57 '--bg', action="store_true",
58 help="""Whether to run the script in the background.
58 help="""Whether to run the script in the background.
59 If given, the only way to see the output of the command is
59 If given, the only way to see the output of the command is
60 with --out/err.
60 with --out/err.
61 """
61 """
62 ),
62 ),
63 magic_arguments.argument(
63 magic_arguments.argument(
64 '--proc', type=str,
64 '--proc', type=str,
65 help="""The variable in which to store Popen instance.
65 help="""The variable in which to store Popen instance.
66 This is used only when --bg option is given.
66 This is used only when --bg option is given.
67 """
67 """
68 ),
68 ),
69 ]
69 ]
70 for arg in args:
70 for arg in args:
71 f = arg(f)
71 f = arg(f)
72 return f
72 return f
73
73
74 @magics_class
74 @magics_class
75 class ScriptMagics(Magics):
75 class ScriptMagics(Magics):
76 """Magics for talking to scripts
76 """Magics for talking to scripts
77
77
78 This defines a base `%%script` cell magic for running a cell
78 This defines a base `%%script` cell magic for running a cell
79 with a program in a subprocess, and registers a few top-level
79 with a program in a subprocess, and registers a few top-level
80 magics that call %%script with common interpreters.
80 magics that call %%script with common interpreters.
81 """
81 """
82 script_magics = List(config=True,
82 script_magics = List(config=True,
83 help="""Extra script cell magics to define
83 help="""Extra script cell magics to define
84
84
85 This generates simple wrappers of `%%script foo` as `%%foo`.
85 This generates simple wrappers of `%%script foo` as `%%foo`.
86
86
87 If you want to add script magics that aren't on your path,
87 If you want to add script magics that aren't on your path,
88 specify them in script_paths
88 specify them in script_paths
89 """,
89 """,
90 )
90 )
91 def _script_magics_default(self):
91 def _script_magics_default(self):
92 """default to a common list of programs"""
92 """default to a common list of programs"""
93
93
94 defaults = [
94 defaults = [
95 'sh',
95 'sh',
96 'bash',
96 'bash',
97 'perl',
97 'perl',
98 'ruby',
98 'ruby',
99 'python',
99 'python',
100 'python2',
100 'python3',
101 'python3',
101 'pypy',
102 'pypy',
102 ]
103 ]
103 if os.name == 'nt':
104 if os.name == 'nt':
104 defaults.extend([
105 defaults.extend([
105 'cmd',
106 'cmd',
106 'powershell',
107 'powershell',
107 ])
108 ])
108
109
109 return defaults
110 return defaults
110
111
111 script_paths = Dict(config=True,
112 script_paths = Dict(config=True,
112 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
113 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
113
114
114 Only necessary for items in script_magics where the default path will not
115 Only necessary for items in script_magics where the default path will not
115 find the right interpreter.
116 find the right interpreter.
116 """
117 """
117 )
118 )
118
119
119 def __init__(self, shell=None):
120 def __init__(self, shell=None):
120 super(ScriptMagics, self).__init__(shell=shell)
121 super(ScriptMagics, self).__init__(shell=shell)
121 self._generate_script_magics()
122 self._generate_script_magics()
122 self.job_manager = BackgroundJobManager()
123 self.job_manager = BackgroundJobManager()
123 self.bg_processes = []
124 self.bg_processes = []
124 atexit.register(self.kill_bg_processes)
125 atexit.register(self.kill_bg_processes)
125
126
126 def __del__(self):
127 def __del__(self):
127 self.kill_bg_processes()
128 self.kill_bg_processes()
128
129
129 def _generate_script_magics(self):
130 def _generate_script_magics(self):
130 cell_magics = self.magics['cell']
131 cell_magics = self.magics['cell']
131 for name in self.script_magics:
132 for name in self.script_magics:
132 cell_magics[name] = self._make_script_magic(name)
133 cell_magics[name] = self._make_script_magic(name)
133
134
134 def _make_script_magic(self, name):
135 def _make_script_magic(self, name):
135 """make a named magic, that calls %%script with a particular program"""
136 """make a named magic, that calls %%script with a particular program"""
136 # expand to explicit path if necessary:
137 # expand to explicit path if necessary:
137 script = self.script_paths.get(name, name)
138 script = self.script_paths.get(name, name)
138
139
139 @magic_arguments.magic_arguments()
140 @magic_arguments.magic_arguments()
140 @script_args
141 @script_args
141 def named_script_magic(line, cell):
142 def named_script_magic(line, cell):
142 # if line, add it as cl-flags
143 # if line, add it as cl-flags
143 if line:
144 if line:
144 line = "%s %s" % (script, line)
145 line = "%s %s" % (script, line)
145 else:
146 else:
146 line = script
147 line = script
147 return self.shebang(line, cell)
148 return self.shebang(line, cell)
148
149
149 # write a basic docstring:
150 # write a basic docstring:
150 named_script_magic.__doc__ = \
151 named_script_magic.__doc__ = \
151 """%%{name} script magic
152 """%%{name} script magic
152
153
153 Run cells with {script} in a subprocess.
154 Run cells with {script} in a subprocess.
154
155
155 This is a shortcut for `%%script {script}`
156 This is a shortcut for `%%script {script}`
156 """.format(**locals())
157 """.format(**locals())
157
158
158 return named_script_magic
159 return named_script_magic
159
160
160 @magic_arguments.magic_arguments()
161 @magic_arguments.magic_arguments()
161 @script_args
162 @script_args
162 @cell_magic("script")
163 @cell_magic("script")
163 def shebang(self, line, cell):
164 def shebang(self, line, cell):
164 """Run a cell via a shell command
165 """Run a cell via a shell command
165
166
166 The `%%script` line is like the #! line of script,
167 The `%%script` line is like the #! line of script,
167 specifying a program (bash, perl, ruby, etc.) with which to run.
168 specifying a program (bash, perl, ruby, etc.) with which to run.
168
169
169 The rest of the cell is run by that program.
170 The rest of the cell is run by that program.
170
171
171 Examples
172 Examples
172 --------
173 --------
173 ::
174 ::
174
175
175 In [1]: %%script bash
176 In [1]: %%script bash
176 ...: for i in 1 2 3; do
177 ...: for i in 1 2 3; do
177 ...: echo $i
178 ...: echo $i
178 ...: done
179 ...: done
179 1
180 1
180 2
181 2
181 3
182 3
182 """
183 """
183 argv = arg_split(line, posix = not sys.platform.startswith('win'))
184 argv = arg_split(line, posix = not sys.platform.startswith('win'))
184 args, cmd = self.shebang.parser.parse_known_args(argv)
185 args, cmd = self.shebang.parser.parse_known_args(argv)
185
186
186 try:
187 try:
187 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
188 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
188 except OSError as e:
189 except OSError as e:
189 if e.errno == errno.ENOENT:
190 if e.errno == errno.ENOENT:
190 print("Couldn't find program: %r" % cmd[0])
191 print("Couldn't find program: %r" % cmd[0])
191 return
192 return
192 else:
193 else:
193 raise
194 raise
194
195
195 cell = cell.encode('utf8', 'replace')
196 cell = cell.encode('utf8', 'replace')
196 if args.bg:
197 if args.bg:
197 self.bg_processes.append(p)
198 self.bg_processes.append(p)
198 self._gc_bg_processes()
199 self._gc_bg_processes()
199 if args.out:
200 if args.out:
200 self.shell.user_ns[args.out] = p.stdout
201 self.shell.user_ns[args.out] = p.stdout
201 if args.err:
202 if args.err:
202 self.shell.user_ns[args.err] = p.stderr
203 self.shell.user_ns[args.err] = p.stderr
203 self.job_manager.new(self._run_script, p, cell, daemon=True)
204 self.job_manager.new(self._run_script, p, cell, daemon=True)
204 if args.proc:
205 if args.proc:
205 self.shell.user_ns[args.proc] = p
206 self.shell.user_ns[args.proc] = p
206 return
207 return
207
208
208 try:
209 try:
209 out, err = p.communicate(cell)
210 out, err = p.communicate(cell)
210 except KeyboardInterrupt:
211 except KeyboardInterrupt:
211 try:
212 try:
212 p.send_signal(signal.SIGINT)
213 p.send_signal(signal.SIGINT)
213 time.sleep(0.1)
214 time.sleep(0.1)
214 if p.poll() is not None:
215 if p.poll() is not None:
215 print("Process is interrupted.")
216 print("Process is interrupted.")
216 return
217 return
217 p.terminate()
218 p.terminate()
218 time.sleep(0.1)
219 time.sleep(0.1)
219 if p.poll() is not None:
220 if p.poll() is not None:
220 print("Process is terminated.")
221 print("Process is terminated.")
221 return
222 return
222 p.kill()
223 p.kill()
223 print("Process is killed.")
224 print("Process is killed.")
224 except OSError:
225 except OSError:
225 pass
226 pass
226 except Exception as e:
227 except Exception as e:
227 print("Error while terminating subprocess (pid=%i): %s" \
228 print("Error while terminating subprocess (pid=%i): %s" \
228 % (p.pid, e))
229 % (p.pid, e))
229 return
230 return
230 out = py3compat.bytes_to_str(out)
231 out = py3compat.bytes_to_str(out)
231 err = py3compat.bytes_to_str(err)
232 err = py3compat.bytes_to_str(err)
232 if args.out:
233 if args.out:
233 self.shell.user_ns[args.out] = out
234 self.shell.user_ns[args.out] = out
234 else:
235 else:
235 sys.stdout.write(out)
236 sys.stdout.write(out)
236 sys.stdout.flush()
237 sys.stdout.flush()
237 if args.err:
238 if args.err:
238 self.shell.user_ns[args.err] = err
239 self.shell.user_ns[args.err] = err
239 else:
240 else:
240 sys.stderr.write(err)
241 sys.stderr.write(err)
241 sys.stderr.flush()
242 sys.stderr.flush()
242
243
243 def _run_script(self, p, cell):
244 def _run_script(self, p, cell):
244 """callback for running the script in the background"""
245 """callback for running the script in the background"""
245 p.stdin.write(cell)
246 p.stdin.write(cell)
246 p.stdin.close()
247 p.stdin.close()
247 p.wait()
248 p.wait()
248
249
249 @line_magic("killbgscripts")
250 @line_magic("killbgscripts")
250 def killbgscripts(self, _nouse_=''):
251 def killbgscripts(self, _nouse_=''):
251 """Kill all BG processes started by %%script and its family."""
252 """Kill all BG processes started by %%script and its family."""
252 self.kill_bg_processes()
253 self.kill_bg_processes()
253 print("All background processes were killed.")
254 print("All background processes were killed.")
254
255
255 def kill_bg_processes(self):
256 def kill_bg_processes(self):
256 """Kill all BG processes which are still running."""
257 """Kill all BG processes which are still running."""
257 for p in self.bg_processes:
258 for p in self.bg_processes:
258 if p.poll() is None:
259 if p.poll() is None:
259 try:
260 try:
260 p.send_signal(signal.SIGINT)
261 p.send_signal(signal.SIGINT)
261 except:
262 except:
262 pass
263 pass
263 time.sleep(0.1)
264 time.sleep(0.1)
264 for p in self.bg_processes:
265 for p in self.bg_processes:
265 if p.poll() is None:
266 if p.poll() is None:
266 try:
267 try:
267 p.terminate()
268 p.terminate()
268 except:
269 except:
269 pass
270 pass
270 time.sleep(0.1)
271 time.sleep(0.1)
271 for p in self.bg_processes:
272 for p in self.bg_processes:
272 if p.poll() is None:
273 if p.poll() is None:
273 try:
274 try:
274 p.kill()
275 p.kill()
275 except:
276 except:
276 pass
277 pass
277 self._gc_bg_processes()
278 self._gc_bg_processes()
278
279
279 def _gc_bg_processes(self):
280 def _gc_bg_processes(self):
280 self.bg_processes = [p for p in self.bg_processes if p.poll() is None]
281 self.bg_processes = [p for p in self.bg_processes if p.poll() is None]
@@ -1,376 +1,382 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities.
2 """Pylab (matplotlib) support utilities.
3
3
4 Authors
4 Authors
5 -------
5 -------
6
6
7 * Fernando Perez.
7 * Fernando Perez.
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2009 The IPython Development Team
13 # Copyright (C) 2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import sys
23 import sys
24 from io import BytesIO
24 from io import BytesIO
25
25
26 from IPython.core.display import _pngxy
26 from IPython.core.display import _pngxy
27 from IPython.utils.decorators import flag_calls
27 from IPython.utils.decorators import flag_calls
28 from IPython.utils import py3compat
28 from IPython.utils import py3compat
29
29
30 # If user specifies a GUI, that dictates the backend, otherwise we read the
30 # If user specifies a GUI, that dictates the backend, otherwise we read the
31 # user's mpl default from the mpl rc structure
31 # user's mpl default from the mpl rc structure
32 backends = {'tk': 'TkAgg',
32 backends = {'tk': 'TkAgg',
33 'gtk': 'GTKAgg',
33 'gtk': 'GTKAgg',
34 'gtk3': 'GTK3Agg',
34 'gtk3': 'GTK3Agg',
35 'wx': 'WXAgg',
35 'wx': 'WXAgg',
36 'qt': 'Qt4Agg', # qt3 not supported
36 'qt': 'Qt4Agg', # qt3 not supported
37 'qt4': 'Qt4Agg',
37 'qt4': 'Qt4Agg',
38 'osx': 'MacOSX',
38 'osx': 'MacOSX',
39 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
39 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
40
40
41 # We also need a reverse backends2guis mapping that will properly choose which
41 # We also need a reverse backends2guis mapping that will properly choose which
42 # GUI support to activate based on the desired matplotlib backend. For the
42 # GUI support to activate based on the desired matplotlib backend. For the
43 # most part it's just a reverse of the above dict, but we also need to add a
43 # most part it's just a reverse of the above dict, but we also need to add a
44 # few others that map to the same GUI manually:
44 # few others that map to the same GUI manually:
45 backend2gui = dict(zip(backends.values(), backends.keys()))
45 backend2gui = dict(zip(backends.values(), backends.keys()))
46 # Our tests expect backend2gui to just return 'qt'
46 # Our tests expect backend2gui to just return 'qt'
47 backend2gui['Qt4Agg'] = 'qt'
47 backend2gui['Qt4Agg'] = 'qt'
48 # In the reverse mapping, there are a few extra valid matplotlib backends that
48 # In the reverse mapping, there are a few extra valid matplotlib backends that
49 # map to the same GUI support
49 # map to the same GUI support
50 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
50 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
51 backend2gui['GTK3Cairo'] = 'gtk3'
51 backend2gui['GTK3Cairo'] = 'gtk3'
52 backend2gui['WX'] = 'wx'
52 backend2gui['WX'] = 'wx'
53 backend2gui['CocoaAgg'] = 'osx'
53 backend2gui['CocoaAgg'] = 'osx'
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Matplotlib utilities
56 # Matplotlib utilities
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59
59
60 def getfigs(*fig_nums):
60 def getfigs(*fig_nums):
61 """Get a list of matplotlib figures by figure numbers.
61 """Get a list of matplotlib figures by figure numbers.
62
62
63 If no arguments are given, all available figures are returned. If the
63 If no arguments are given, all available figures are returned. If the
64 argument list contains references to invalid figures, a warning is printed
64 argument list contains references to invalid figures, a warning is printed
65 but the function continues pasting further figures.
65 but the function continues pasting further figures.
66
66
67 Parameters
67 Parameters
68 ----------
68 ----------
69 figs : tuple
69 figs : tuple
70 A tuple of ints giving the figure numbers of the figures to return.
70 A tuple of ints giving the figure numbers of the figures to return.
71 """
71 """
72 from matplotlib._pylab_helpers import Gcf
72 from matplotlib._pylab_helpers import Gcf
73 if not fig_nums:
73 if not fig_nums:
74 fig_managers = Gcf.get_all_fig_managers()
74 fig_managers = Gcf.get_all_fig_managers()
75 return [fm.canvas.figure for fm in fig_managers]
75 return [fm.canvas.figure for fm in fig_managers]
76 else:
76 else:
77 figs = []
77 figs = []
78 for num in fig_nums:
78 for num in fig_nums:
79 f = Gcf.figs.get(num)
79 f = Gcf.figs.get(num)
80 if f is None:
80 if f is None:
81 print('Warning: figure %s not available.' % num)
81 print('Warning: figure %s not available.' % num)
82 else:
82 else:
83 figs.append(f.canvas.figure)
83 figs.append(f.canvas.figure)
84 return figs
84 return figs
85
85
86
86
87 def figsize(sizex, sizey):
87 def figsize(sizex, sizey):
88 """Set the default figure size to be [sizex, sizey].
88 """Set the default figure size to be [sizex, sizey].
89
89
90 This is just an easy to remember, convenience wrapper that sets::
90 This is just an easy to remember, convenience wrapper that sets::
91
91
92 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
93 """
93 """
94 import matplotlib
94 import matplotlib
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
96
96
97
97
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
99 """Print a figure to an image, and return the resulting bytes
99 """Print a figure to an image, and return the resulting file data
100
101 Returned data will be bytes unless ``fmt='svg'``,
102 in which case it will be unicode.
100
103
101 Any keyword args are passed to fig.canvas.print_figure,
104 Any keyword args are passed to fig.canvas.print_figure,
102 such as ``quality`` or ``bbox_inches``.
105 such as ``quality`` or ``bbox_inches``.
103 """
106 """
104 from matplotlib import rcParams
107 from matplotlib import rcParams
105 # When there's an empty figure, we shouldn't return anything, otherwise we
108 # When there's an empty figure, we shouldn't return anything, otherwise we
106 # get big blank areas in the qt console.
109 # get big blank areas in the qt console.
107 if not fig.axes and not fig.lines:
110 if not fig.axes and not fig.lines:
108 return
111 return
109
112
110 dpi = rcParams['savefig.dpi']
113 dpi = rcParams['savefig.dpi']
111 if fmt == 'retina':
114 if fmt == 'retina':
112 dpi = dpi * 2
115 dpi = dpi * 2
113 fmt = 'png'
116 fmt = 'png'
114
117
115 # build keyword args
118 # build keyword args
116 kw = dict(
119 kw = dict(
117 format=fmt,
120 format=fmt,
118 fc=fig.get_facecolor(),
121 fc=fig.get_facecolor(),
119 ec=fig.get_edgecolor(),
122 ec=fig.get_edgecolor(),
120 dpi=dpi,
123 dpi=dpi,
121 bbox_inches=bbox_inches,
124 bbox_inches=bbox_inches,
122 )
125 )
123 # **kwargs get higher priority
126 # **kwargs get higher priority
124 kw.update(kwargs)
127 kw.update(kwargs)
125
128
126 bytes_io = BytesIO()
129 bytes_io = BytesIO()
127 fig.canvas.print_figure(bytes_io, **kw)
130 fig.canvas.print_figure(bytes_io, **kw)
128 return bytes_io.getvalue()
131 data = bytes_io.getvalue()
132 if fmt == 'svg':
133 data = data.decode('utf-8')
134 return data
129
135
130 def retina_figure(fig, **kwargs):
136 def retina_figure(fig, **kwargs):
131 """format a figure as a pixel-doubled (retina) PNG"""
137 """format a figure as a pixel-doubled (retina) PNG"""
132 pngdata = print_figure(fig, fmt='retina', **kwargs)
138 pngdata = print_figure(fig, fmt='retina', **kwargs)
133 w, h = _pngxy(pngdata)
139 w, h = _pngxy(pngdata)
134 metadata = dict(width=w//2, height=h//2)
140 metadata = dict(width=w//2, height=h//2)
135 return pngdata, metadata
141 return pngdata, metadata
136
142
137 # We need a little factory function here to create the closure where
143 # We need a little factory function here to create the closure where
138 # safe_execfile can live.
144 # safe_execfile can live.
139 def mpl_runner(safe_execfile):
145 def mpl_runner(safe_execfile):
140 """Factory to return a matplotlib-enabled runner for %run.
146 """Factory to return a matplotlib-enabled runner for %run.
141
147
142 Parameters
148 Parameters
143 ----------
149 ----------
144 safe_execfile : function
150 safe_execfile : function
145 This must be a function with the same interface as the
151 This must be a function with the same interface as the
146 :meth:`safe_execfile` method of IPython.
152 :meth:`safe_execfile` method of IPython.
147
153
148 Returns
154 Returns
149 -------
155 -------
150 A function suitable for use as the ``runner`` argument of the %run magic
156 A function suitable for use as the ``runner`` argument of the %run magic
151 function.
157 function.
152 """
158 """
153
159
154 def mpl_execfile(fname,*where,**kw):
160 def mpl_execfile(fname,*where,**kw):
155 """matplotlib-aware wrapper around safe_execfile.
161 """matplotlib-aware wrapper around safe_execfile.
156
162
157 Its interface is identical to that of the :func:`execfile` builtin.
163 Its interface is identical to that of the :func:`execfile` builtin.
158
164
159 This is ultimately a call to execfile(), but wrapped in safeties to
165 This is ultimately a call to execfile(), but wrapped in safeties to
160 properly handle interactive rendering."""
166 properly handle interactive rendering."""
161
167
162 import matplotlib
168 import matplotlib
163 import matplotlib.pylab as pylab
169 import matplotlib.pylab as pylab
164
170
165 #print '*** Matplotlib runner ***' # dbg
171 #print '*** Matplotlib runner ***' # dbg
166 # turn off rendering until end of script
172 # turn off rendering until end of script
167 is_interactive = matplotlib.rcParams['interactive']
173 is_interactive = matplotlib.rcParams['interactive']
168 matplotlib.interactive(False)
174 matplotlib.interactive(False)
169 safe_execfile(fname,*where,**kw)
175 safe_execfile(fname,*where,**kw)
170 matplotlib.interactive(is_interactive)
176 matplotlib.interactive(is_interactive)
171 # make rendering call now, if the user tried to do it
177 # make rendering call now, if the user tried to do it
172 if pylab.draw_if_interactive.called:
178 if pylab.draw_if_interactive.called:
173 pylab.draw()
179 pylab.draw()
174 pylab.draw_if_interactive.called = False
180 pylab.draw_if_interactive.called = False
175
181
176 return mpl_execfile
182 return mpl_execfile
177
183
178
184
179 def select_figure_formats(shell, formats, **kwargs):
185 def select_figure_formats(shell, formats, **kwargs):
180 """Select figure formats for the inline backend.
186 """Select figure formats for the inline backend.
181
187
182 Parameters
188 Parameters
183 ==========
189 ==========
184 shell : InteractiveShell
190 shell : InteractiveShell
185 The main IPython instance.
191 The main IPython instance.
186 formats : str or set
192 formats : str or set
187 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
193 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
188 **kwargs : any
194 **kwargs : any
189 Extra keyword arguments to be passed to fig.canvas.print_figure.
195 Extra keyword arguments to be passed to fig.canvas.print_figure.
190 """
196 """
191 from matplotlib.figure import Figure
197 from matplotlib.figure import Figure
192 from IPython.kernel.zmq.pylab import backend_inline
198 from IPython.kernel.zmq.pylab import backend_inline
193
199
194 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
200 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
195 png_formatter = shell.display_formatter.formatters['image/png']
201 png_formatter = shell.display_formatter.formatters['image/png']
196 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
202 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
197 pdf_formatter = shell.display_formatter.formatters['application/pdf']
203 pdf_formatter = shell.display_formatter.formatters['application/pdf']
198
204
199 if isinstance(formats, py3compat.string_types):
205 if isinstance(formats, py3compat.string_types):
200 formats = {formats}
206 formats = {formats}
201 # cast in case of list / tuple
207 # cast in case of list / tuple
202 formats = set(formats)
208 formats = set(formats)
203
209
204 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
210 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
205
211
206 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
212 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
207 bad = formats.difference(supported)
213 bad = formats.difference(supported)
208 if bad:
214 if bad:
209 bs = "%s" % ','.join([repr(f) for f in bad])
215 bs = "%s" % ','.join([repr(f) for f in bad])
210 gs = "%s" % ','.join([repr(f) for f in supported])
216 gs = "%s" % ','.join([repr(f) for f in supported])
211 raise ValueError("supported formats are: %s not %s" % (gs, bs))
217 raise ValueError("supported formats are: %s not %s" % (gs, bs))
212
218
213 if 'png' in formats:
219 if 'png' in formats:
214 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
220 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
215 if 'retina' in formats or 'png2x' in formats:
221 if 'retina' in formats or 'png2x' in formats:
216 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
222 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
217 if 'jpg' in formats or 'jpeg' in formats:
223 if 'jpg' in formats or 'jpeg' in formats:
218 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
224 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
219 if 'svg' in formats:
225 if 'svg' in formats:
220 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
226 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
221 if 'pdf' in formats:
227 if 'pdf' in formats:
222 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
228 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
223
229
224 #-----------------------------------------------------------------------------
230 #-----------------------------------------------------------------------------
225 # Code for initializing matplotlib and importing pylab
231 # Code for initializing matplotlib and importing pylab
226 #-----------------------------------------------------------------------------
232 #-----------------------------------------------------------------------------
227
233
228
234
229 def find_gui_and_backend(gui=None, gui_select=None):
235 def find_gui_and_backend(gui=None, gui_select=None):
230 """Given a gui string return the gui and mpl backend.
236 """Given a gui string return the gui and mpl backend.
231
237
232 Parameters
238 Parameters
233 ----------
239 ----------
234 gui : str
240 gui : str
235 Can be one of ('tk','gtk','wx','qt','qt4','inline').
241 Can be one of ('tk','gtk','wx','qt','qt4','inline').
236 gui_select : str
242 gui_select : str
237 Can be one of ('tk','gtk','wx','qt','qt4','inline').
243 Can be one of ('tk','gtk','wx','qt','qt4','inline').
238 This is any gui already selected by the shell.
244 This is any gui already selected by the shell.
239
245
240 Returns
246 Returns
241 -------
247 -------
242 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
248 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
243 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
249 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
244 """
250 """
245
251
246 import matplotlib
252 import matplotlib
247
253
248 if gui and gui != 'auto':
254 if gui and gui != 'auto':
249 # select backend based on requested gui
255 # select backend based on requested gui
250 backend = backends[gui]
256 backend = backends[gui]
251 else:
257 else:
252 # We need to read the backend from the original data structure, *not*
258 # We need to read the backend from the original data structure, *not*
253 # from mpl.rcParams, since a prior invocation of %matplotlib may have
259 # from mpl.rcParams, since a prior invocation of %matplotlib may have
254 # overwritten that.
260 # overwritten that.
255 # WARNING: this assumes matplotlib 1.1 or newer!!
261 # WARNING: this assumes matplotlib 1.1 or newer!!
256 backend = matplotlib.rcParamsOrig['backend']
262 backend = matplotlib.rcParamsOrig['backend']
257 # In this case, we need to find what the appropriate gui selection call
263 # In this case, we need to find what the appropriate gui selection call
258 # should be for IPython, so we can activate inputhook accordingly
264 # should be for IPython, so we can activate inputhook accordingly
259 gui = backend2gui.get(backend, None)
265 gui = backend2gui.get(backend, None)
260
266
261 # If we have already had a gui active, we need it and inline are the
267 # If we have already had a gui active, we need it and inline are the
262 # ones allowed.
268 # ones allowed.
263 if gui_select and gui != gui_select:
269 if gui_select and gui != gui_select:
264 gui = gui_select
270 gui = gui_select
265 backend = backends[gui]
271 backend = backends[gui]
266
272
267 return gui, backend
273 return gui, backend
268
274
269
275
270 def activate_matplotlib(backend):
276 def activate_matplotlib(backend):
271 """Activate the given backend and set interactive to True."""
277 """Activate the given backend and set interactive to True."""
272
278
273 import matplotlib
279 import matplotlib
274 matplotlib.interactive(True)
280 matplotlib.interactive(True)
275
281
276 # Matplotlib had a bug where even switch_backend could not force
282 # Matplotlib had a bug where even switch_backend could not force
277 # the rcParam to update. This needs to be set *before* the module
283 # the rcParam to update. This needs to be set *before* the module
278 # magic of switch_backend().
284 # magic of switch_backend().
279 matplotlib.rcParams['backend'] = backend
285 matplotlib.rcParams['backend'] = backend
280
286
281 import matplotlib.pyplot
287 import matplotlib.pyplot
282 matplotlib.pyplot.switch_backend(backend)
288 matplotlib.pyplot.switch_backend(backend)
283
289
284 # This must be imported last in the matplotlib series, after
290 # This must be imported last in the matplotlib series, after
285 # backend/interactivity choices have been made
291 # backend/interactivity choices have been made
286 import matplotlib.pylab as pylab
292 import matplotlib.pylab as pylab
287
293
288 pylab.show._needmain = False
294 pylab.show._needmain = False
289 # We need to detect at runtime whether show() is called by the user.
295 # We need to detect at runtime whether show() is called by the user.
290 # For this, we wrap it into a decorator which adds a 'called' flag.
296 # For this, we wrap it into a decorator which adds a 'called' flag.
291 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
297 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
292
298
293
299
294 def import_pylab(user_ns, import_all=True):
300 def import_pylab(user_ns, import_all=True):
295 """Populate the namespace with pylab-related values.
301 """Populate the namespace with pylab-related values.
296
302
297 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
303 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
298
304
299 Also imports a few names from IPython (figsize, display, getfigs)
305 Also imports a few names from IPython (figsize, display, getfigs)
300
306
301 """
307 """
302
308
303 # Import numpy as np/pyplot as plt are conventions we're trying to
309 # Import numpy as np/pyplot as plt are conventions we're trying to
304 # somewhat standardize on. Making them available to users by default
310 # somewhat standardize on. Making them available to users by default
305 # will greatly help this.
311 # will greatly help this.
306 s = ("import numpy\n"
312 s = ("import numpy\n"
307 "import matplotlib\n"
313 "import matplotlib\n"
308 "from matplotlib import pylab, mlab, pyplot\n"
314 "from matplotlib import pylab, mlab, pyplot\n"
309 "np = numpy\n"
315 "np = numpy\n"
310 "plt = pyplot\n"
316 "plt = pyplot\n"
311 )
317 )
312 exec(s, user_ns)
318 exec(s, user_ns)
313
319
314 if import_all:
320 if import_all:
315 s = ("from matplotlib.pylab import *\n"
321 s = ("from matplotlib.pylab import *\n"
316 "from numpy import *\n")
322 "from numpy import *\n")
317 exec(s, user_ns)
323 exec(s, user_ns)
318
324
319 # IPython symbols to add
325 # IPython symbols to add
320 user_ns['figsize'] = figsize
326 user_ns['figsize'] = figsize
321 from IPython.core.display import display
327 from IPython.core.display import display
322 # Add display and getfigs to the user's namespace
328 # Add display and getfigs to the user's namespace
323 user_ns['display'] = display
329 user_ns['display'] = display
324 user_ns['getfigs'] = getfigs
330 user_ns['getfigs'] = getfigs
325
331
326
332
327 def configure_inline_support(shell, backend):
333 def configure_inline_support(shell, backend):
328 """Configure an IPython shell object for matplotlib use.
334 """Configure an IPython shell object for matplotlib use.
329
335
330 Parameters
336 Parameters
331 ----------
337 ----------
332 shell : InteractiveShell instance
338 shell : InteractiveShell instance
333
339
334 backend : matplotlib backend
340 backend : matplotlib backend
335 """
341 """
336 # If using our svg payload backend, register the post-execution
342 # If using our svg payload backend, register the post-execution
337 # function that will pick up the results for display. This can only be
343 # function that will pick up the results for display. This can only be
338 # done with access to the real shell object.
344 # done with access to the real shell object.
339
345
340 # Note: if we can't load the inline backend, then there's no point
346 # Note: if we can't load the inline backend, then there's no point
341 # continuing (such as in terminal-only shells in environments without
347 # continuing (such as in terminal-only shells in environments without
342 # zeromq available).
348 # zeromq available).
343 try:
349 try:
344 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
350 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
345 except ImportError:
351 except ImportError:
346 return
352 return
347 from matplotlib import pyplot
353 from matplotlib import pyplot
348
354
349 cfg = InlineBackend.instance(parent=shell)
355 cfg = InlineBackend.instance(parent=shell)
350 cfg.shell = shell
356 cfg.shell = shell
351 if cfg not in shell.configurables:
357 if cfg not in shell.configurables:
352 shell.configurables.append(cfg)
358 shell.configurables.append(cfg)
353
359
354 if backend == backends['inline']:
360 if backend == backends['inline']:
355 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
361 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
356 shell.events.register('post_execute', flush_figures)
362 shell.events.register('post_execute', flush_figures)
357
363
358 # Save rcParams that will be overwrittern
364 # Save rcParams that will be overwrittern
359 shell._saved_rcParams = dict()
365 shell._saved_rcParams = dict()
360 for k in cfg.rc:
366 for k in cfg.rc:
361 shell._saved_rcParams[k] = pyplot.rcParams[k]
367 shell._saved_rcParams[k] = pyplot.rcParams[k]
362 # load inline_rc
368 # load inline_rc
363 pyplot.rcParams.update(cfg.rc)
369 pyplot.rcParams.update(cfg.rc)
364 else:
370 else:
365 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
371 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
366 try:
372 try:
367 shell.events.unregister('post_execute', flush_figures)
373 shell.events.unregister('post_execute', flush_figures)
368 except ValueError:
374 except ValueError:
369 pass
375 pass
370 if hasattr(shell, '_saved_rcParams'):
376 if hasattr(shell, '_saved_rcParams'):
371 pyplot.rcParams.update(shell._saved_rcParams)
377 pyplot.rcParams.update(shell._saved_rcParams)
372 del shell._saved_rcParams
378 del shell._saved_rcParams
373
379
374 # Setup the default figure format
380 # Setup the default figure format
375 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
381 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
376
382
@@ -1,148 +1,149 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Release data for the IPython project."""
2 """Release data for the IPython project."""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2008, IPython Development Team.
5 # Copyright (c) 2008, IPython Development Team.
6 # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
6 # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
7 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
7 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
8 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
8 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # Name of the package for release purposes. This is the name which labels
15 # Name of the package for release purposes. This is the name which labels
16 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
16 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
17 name = 'ipython'
17 name = 'ipython'
18
18
19 # IPython version information. An empty _version_extra corresponds to a full
19 # IPython version information. An empty _version_extra corresponds to a full
20 # release. 'dev' as a _version_extra string means this is a development
20 # release. 'dev' as a _version_extra string means this is a development
21 # version
21 # version
22 _version_major = 2
22 _version_major = 2
23 _version_minor = 0
23 _version_minor = 0
24 _version_patch = 0
24 _version_patch = 0
25 _version_extra = 'dev'
25 _version_extra = 'dev'
26 # _version_extra = 'rc1'
26 # _version_extra = 'rc1'
27 # _version_extra = '' # Uncomment this for full releases
27 # _version_extra = '' # Uncomment this for full releases
28
28
29 codename = 'Work in Progress'
29 # release.codename is deprecated in 2.0, will be removed in 3.0
30 codename = ''
30
31
31 # Construct full version string from these.
32 # Construct full version string from these.
32 _ver = [_version_major, _version_minor, _version_patch]
33 _ver = [_version_major, _version_minor, _version_patch]
33
34
34 __version__ = '.'.join(map(str, _ver))
35 __version__ = '.'.join(map(str, _ver))
35 if _version_extra:
36 if _version_extra:
36 __version__ = __version__ + '-' + _version_extra
37 __version__ = __version__ + '-' + _version_extra
37
38
38 version = __version__ # backwards compatibility name
39 version = __version__ # backwards compatibility name
39 version_info = (_version_major, _version_minor, _version_patch, _version_extra)
40 version_info = (_version_major, _version_minor, _version_patch, _version_extra)
40
41
41 # Change this when incrementing the kernel protocol version
42 # Change this when incrementing the kernel protocol version
42 kernel_protocol_version_info = (4, 1)
43 kernel_protocol_version_info = (4, 1)
43
44
44 description = "IPython: Productive Interactive Computing"
45 description = "IPython: Productive Interactive Computing"
45
46
46 long_description = \
47 long_description = \
47 """
48 """
48 IPython provides a rich toolkit to help you make the most out of using Python
49 IPython provides a rich toolkit to help you make the most out of using Python
49 interactively. Its main components are:
50 interactively. Its main components are:
50
51
51 * Powerful interactive Python shells (terminal- and Qt-based).
52 * Powerful interactive Python shells (terminal- and Qt-based).
52 * A web-based interactive notebook environment with all shell features plus
53 * A web-based interactive notebook environment with all shell features plus
53 support for embedded figures, animations and rich media.
54 support for embedded figures, animations and rich media.
54 * Support for interactive data visualization and use of GUI toolkits.
55 * Support for interactive data visualization and use of GUI toolkits.
55 * Flexible, embeddable interpreters to load into your own projects.
56 * Flexible, embeddable interpreters to load into your own projects.
56 * A high-performance library for high level and interactive parallel computing
57 * A high-performance library for high level and interactive parallel computing
57 that works in multicore systems, clusters, supercomputing and cloud scenarios.
58 that works in multicore systems, clusters, supercomputing and cloud scenarios.
58
59
59 The enhanced interactive Python shells have the following main features:
60 The enhanced interactive Python shells have the following main features:
60
61
61 * Comprehensive object introspection.
62 * Comprehensive object introspection.
62
63
63 * Input history, persistent across sessions.
64 * Input history, persistent across sessions.
64
65
65 * Caching of output results during a session with automatically generated
66 * Caching of output results during a session with automatically generated
66 references.
67 references.
67
68
68 * Extensible tab completion, with support by default for completion of python
69 * Extensible tab completion, with support by default for completion of python
69 variables and keywords, filenames and function keywords.
70 variables and keywords, filenames and function keywords.
70
71
71 * Extensible system of 'magic' commands for controlling the environment and
72 * Extensible system of 'magic' commands for controlling the environment and
72 performing many tasks related either to IPython or the operating system.
73 performing many tasks related either to IPython or the operating system.
73
74
74 * A rich configuration system with easy switching between different setups
75 * A rich configuration system with easy switching between different setups
75 (simpler than changing $PYTHONSTARTUP environment variables every time).
76 (simpler than changing $PYTHONSTARTUP environment variables every time).
76
77
77 * Session logging and reloading.
78 * Session logging and reloading.
78
79
79 * Extensible syntax processing for special purpose situations.
80 * Extensible syntax processing for special purpose situations.
80
81
81 * Access to the system shell with user-extensible alias system.
82 * Access to the system shell with user-extensible alias system.
82
83
83 * Easily embeddable in other Python programs and GUIs.
84 * Easily embeddable in other Python programs and GUIs.
84
85
85 * Integrated access to the pdb debugger and the Python profiler.
86 * Integrated access to the pdb debugger and the Python profiler.
86
87
87 The parallel computing architecture has the following main features:
88 The parallel computing architecture has the following main features:
88
89
89 * Quickly parallelize Python code from an interactive Python/IPython session.
90 * Quickly parallelize Python code from an interactive Python/IPython session.
90
91
91 * A flexible and dynamic process model that be deployed on anything from
92 * A flexible and dynamic process model that be deployed on anything from
92 multicore workstations to supercomputers.
93 multicore workstations to supercomputers.
93
94
94 * An architecture that supports many different styles of parallelism, from
95 * An architecture that supports many different styles of parallelism, from
95 message passing to task farming.
96 message passing to task farming.
96
97
97 * Both blocking and fully asynchronous interfaces.
98 * Both blocking and fully asynchronous interfaces.
98
99
99 * High level APIs that enable many things to be parallelized in a few lines
100 * High level APIs that enable many things to be parallelized in a few lines
100 of code.
101 of code.
101
102
102 * Share live parallel jobs with other users securely.
103 * Share live parallel jobs with other users securely.
103
104
104 * Dynamically load balanced task farming system.
105 * Dynamically load balanced task farming system.
105
106
106 * Robust error handling in parallel code.
107 * Robust error handling in parallel code.
107
108
108 The latest development version is always available from IPython's `GitHub
109 The latest development version is always available from IPython's `GitHub
109 site <http://github.com/ipython>`_.
110 site <http://github.com/ipython>`_.
110 """
111 """
111
112
112 license = 'BSD'
113 license = 'BSD'
113
114
114 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
115 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
115 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
116 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
116 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
117 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
117 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
118 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
118 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
119 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
119 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'),
120 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'),
120 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'),
121 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'),
121 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'),
122 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'),
122 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'),
123 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'),
123 }
124 }
124
125
125 author = 'The IPython Development Team'
126 author = 'The IPython Development Team'
126
127
127 author_email = 'ipython-dev@scipy.org'
128 author_email = 'ipython-dev@scipy.org'
128
129
129 url = 'http://ipython.org'
130 url = 'http://ipython.org'
130
131
131 download_url = 'https://github.com/ipython/ipython/downloads'
132 download_url = 'https://github.com/ipython/ipython/downloads'
132
133
133 platforms = ['Linux','Mac OSX','Windows XP/Vista/7/8']
134 platforms = ['Linux','Mac OSX','Windows XP/Vista/7/8']
134
135
135 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed',
136 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed',
136 'Web-based computing', 'Qt console', 'Embedding']
137 'Web-based computing', 'Qt console', 'Embedding']
137
138
138 classifiers = [
139 classifiers = [
139 'Intended Audience :: Developers',
140 'Intended Audience :: Developers',
140 'Intended Audience :: Science/Research',
141 'Intended Audience :: Science/Research',
141 'License :: OSI Approved :: BSD License',
142 'License :: OSI Approved :: BSD License',
142 'Programming Language :: Python',
143 'Programming Language :: Python',
143 'Programming Language :: Python :: 2',
144 'Programming Language :: Python :: 2',
144 'Programming Language :: Python :: 2.7',
145 'Programming Language :: Python :: 2.7',
145 'Programming Language :: Python :: 3',
146 'Programming Language :: Python :: 3',
146 'Topic :: System :: Distributed Computing',
147 'Topic :: System :: Distributed Computing',
147 'Topic :: System :: Shells'
148 'Topic :: System :: Shells'
148 ]
149 ]
@@ -1,241 +1,241 b''
1 """Tests for pylab tools module.
1 """Tests for pylab tools module.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2011, the IPython Development Team.
4 # Copyright (c) 2011, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 from __future__ import print_function
14 from __future__ import print_function
15
15
16 from io import UnsupportedOperation, BytesIO
16 from io import UnsupportedOperation, BytesIO
17
17
18 import matplotlib
18 import matplotlib
19 matplotlib.use('Agg')
19 matplotlib.use('Agg')
20 from matplotlib.figure import Figure
20 from matplotlib.figure import Figure
21
21
22 from nose import SkipTest
22 from nose import SkipTest
23 import nose.tools as nt
23 import nose.tools as nt
24
24
25 from matplotlib import pyplot as plt
25 from matplotlib import pyplot as plt
26 import numpy as np
26 import numpy as np
27
27
28 # Our own imports
28 # Our own imports
29 from IPython.core.getipython import get_ipython
29 from IPython.core.getipython import get_ipython
30 from IPython.core.interactiveshell import InteractiveShell
30 from IPython.core.interactiveshell import InteractiveShell
31 from IPython.core.display import _PNG, _JPEG
31 from IPython.core.display import _PNG, _JPEG
32 from .. import pylabtools as pt
32 from .. import pylabtools as pt
33
33
34 from IPython.testing import decorators as dec
34 from IPython.testing import decorators as dec
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Globals and constants
37 # Globals and constants
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Local utilities
41 # Local utilities
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Classes and functions
45 # Classes and functions
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 def test_figure_to_svg():
48 def test_figure_to_svg():
49 # simple empty-figure test
49 # simple empty-figure test
50 fig = plt.figure()
50 fig = plt.figure()
51 nt.assert_equal(pt.print_figure(fig, 'svg'), None)
51 nt.assert_equal(pt.print_figure(fig, 'svg'), None)
52
52
53 plt.close('all')
53 plt.close('all')
54
54
55 # simple check for at least svg-looking output
55 # simple check for at least svg-looking output
56 fig = plt.figure()
56 fig = plt.figure()
57 ax = fig.add_subplot(1,1,1)
57 ax = fig.add_subplot(1,1,1)
58 ax.plot([1,2,3])
58 ax.plot([1,2,3])
59 plt.draw()
59 plt.draw()
60 svg = pt.print_figure(fig, 'svg')[:100].lower()
60 svg = pt.print_figure(fig, 'svg')[:100].lower()
61 nt.assert_in(b'doctype svg', svg)
61 nt.assert_in(u'doctype svg', svg)
62
62
63 def _check_pil_jpeg_bytes():
63 def _check_pil_jpeg_bytes():
64 """Skip if PIL can't write JPEGs to BytesIO objects"""
64 """Skip if PIL can't write JPEGs to BytesIO objects"""
65 # PIL's JPEG plugin can't write to BytesIO objects
65 # PIL's JPEG plugin can't write to BytesIO objects
66 # Pillow fixes this
66 # Pillow fixes this
67 from PIL import Image
67 from PIL import Image
68 buf = BytesIO()
68 buf = BytesIO()
69 img = Image.new("RGB", (4,4))
69 img = Image.new("RGB", (4,4))
70 try:
70 try:
71 img.save(buf, 'jpeg')
71 img.save(buf, 'jpeg')
72 except Exception as e:
72 except Exception as e:
73 ename = e.__class__.__name__
73 ename = e.__class__.__name__
74 raise SkipTest("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e))
74 raise SkipTest("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e))
75
75
76 @dec.skip_without("PIL.Image")
76 @dec.skip_without("PIL.Image")
77 def test_figure_to_jpeg():
77 def test_figure_to_jpeg():
78 _check_pil_jpeg_bytes()
78 _check_pil_jpeg_bytes()
79 # simple check for at least jpeg-looking output
79 # simple check for at least jpeg-looking output
80 fig = plt.figure()
80 fig = plt.figure()
81 ax = fig.add_subplot(1,1,1)
81 ax = fig.add_subplot(1,1,1)
82 ax.plot([1,2,3])
82 ax.plot([1,2,3])
83 plt.draw()
83 plt.draw()
84 jpeg = pt.print_figure(fig, 'jpeg', quality=50)[:100].lower()
84 jpeg = pt.print_figure(fig, 'jpeg', quality=50)[:100].lower()
85 assert jpeg.startswith(_JPEG)
85 assert jpeg.startswith(_JPEG)
86
86
87 def test_retina_figure():
87 def test_retina_figure():
88 fig = plt.figure()
88 fig = plt.figure()
89 ax = fig.add_subplot(1,1,1)
89 ax = fig.add_subplot(1,1,1)
90 ax.plot([1,2,3])
90 ax.plot([1,2,3])
91 plt.draw()
91 plt.draw()
92 png, md = pt.retina_figure(fig)
92 png, md = pt.retina_figure(fig)
93 assert png.startswith(_PNG)
93 assert png.startswith(_PNG)
94 nt.assert_in('width', md)
94 nt.assert_in('width', md)
95 nt.assert_in('height', md)
95 nt.assert_in('height', md)
96
96
97 _fmt_mime_map = {
97 _fmt_mime_map = {
98 'png': 'image/png',
98 'png': 'image/png',
99 'jpeg': 'image/jpeg',
99 'jpeg': 'image/jpeg',
100 'pdf': 'application/pdf',
100 'pdf': 'application/pdf',
101 'retina': 'image/png',
101 'retina': 'image/png',
102 'svg': 'image/svg+xml',
102 'svg': 'image/svg+xml',
103 }
103 }
104
104
105 def test_select_figure_formats_str():
105 def test_select_figure_formats_str():
106 ip = get_ipython()
106 ip = get_ipython()
107 for fmt, active_mime in _fmt_mime_map.items():
107 for fmt, active_mime in _fmt_mime_map.items():
108 pt.select_figure_formats(ip, fmt)
108 pt.select_figure_formats(ip, fmt)
109 for mime, f in ip.display_formatter.formatters.items():
109 for mime, f in ip.display_formatter.formatters.items():
110 if mime == active_mime:
110 if mime == active_mime:
111 nt.assert_in(Figure, f)
111 nt.assert_in(Figure, f)
112 else:
112 else:
113 nt.assert_not_in(Figure, f)
113 nt.assert_not_in(Figure, f)
114
114
115 def test_select_figure_formats_kwargs():
115 def test_select_figure_formats_kwargs():
116 ip = get_ipython()
116 ip = get_ipython()
117 kwargs = dict(quality=10, bbox_inches='tight')
117 kwargs = dict(quality=10, bbox_inches='tight')
118 pt.select_figure_formats(ip, 'png', **kwargs)
118 pt.select_figure_formats(ip, 'png', **kwargs)
119 formatter = ip.display_formatter.formatters['image/png']
119 formatter = ip.display_formatter.formatters['image/png']
120 f = formatter.lookup_by_type(Figure)
120 f = formatter.lookup_by_type(Figure)
121 cell = f.__closure__[0].cell_contents
121 cell = f.__closure__[0].cell_contents
122 nt.assert_equal(cell, kwargs)
122 nt.assert_equal(cell, kwargs)
123
123
124 # check that the formatter doesn't raise
124 # check that the formatter doesn't raise
125 fig = plt.figure()
125 fig = plt.figure()
126 ax = fig.add_subplot(1,1,1)
126 ax = fig.add_subplot(1,1,1)
127 ax.plot([1,2,3])
127 ax.plot([1,2,3])
128 plt.draw()
128 plt.draw()
129 formatter.enabled = True
129 formatter.enabled = True
130 png = formatter(fig)
130 png = formatter(fig)
131 assert png.startswith(_PNG)
131 assert png.startswith(_PNG)
132
132
133 def test_select_figure_formats_set():
133 def test_select_figure_formats_set():
134 ip = get_ipython()
134 ip = get_ipython()
135 for fmts in [
135 for fmts in [
136 {'png', 'svg'},
136 {'png', 'svg'},
137 ['png'],
137 ['png'],
138 ('jpeg', 'pdf', 'retina'),
138 ('jpeg', 'pdf', 'retina'),
139 {'svg'},
139 {'svg'},
140 ]:
140 ]:
141 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
141 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
142 pt.select_figure_formats(ip, fmts)
142 pt.select_figure_formats(ip, fmts)
143 for mime, f in ip.display_formatter.formatters.items():
143 for mime, f in ip.display_formatter.formatters.items():
144 if mime in active_mimes:
144 if mime in active_mimes:
145 nt.assert_in(Figure, f)
145 nt.assert_in(Figure, f)
146 else:
146 else:
147 nt.assert_not_in(Figure, f)
147 nt.assert_not_in(Figure, f)
148
148
149 def test_select_figure_formats_bad():
149 def test_select_figure_formats_bad():
150 ip = get_ipython()
150 ip = get_ipython()
151 with nt.assert_raises(ValueError):
151 with nt.assert_raises(ValueError):
152 pt.select_figure_formats(ip, 'foo')
152 pt.select_figure_formats(ip, 'foo')
153 with nt.assert_raises(ValueError):
153 with nt.assert_raises(ValueError):
154 pt.select_figure_formats(ip, {'png', 'foo'})
154 pt.select_figure_formats(ip, {'png', 'foo'})
155 with nt.assert_raises(ValueError):
155 with nt.assert_raises(ValueError):
156 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
156 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
157
157
158 def test_import_pylab():
158 def test_import_pylab():
159 ns = {}
159 ns = {}
160 pt.import_pylab(ns, import_all=False)
160 pt.import_pylab(ns, import_all=False)
161 nt.assert_true('plt' in ns)
161 nt.assert_true('plt' in ns)
162 nt.assert_equal(ns['np'], np)
162 nt.assert_equal(ns['np'], np)
163
163
164 class TestPylabSwitch(object):
164 class TestPylabSwitch(object):
165 class Shell(InteractiveShell):
165 class Shell(InteractiveShell):
166 def enable_gui(self, gui):
166 def enable_gui(self, gui):
167 pass
167 pass
168
168
169 def setup(self):
169 def setup(self):
170 import matplotlib
170 import matplotlib
171 def act_mpl(backend):
171 def act_mpl(backend):
172 matplotlib.rcParams['backend'] = backend
172 matplotlib.rcParams['backend'] = backend
173
173
174 # Save rcParams since they get modified
174 # Save rcParams since they get modified
175 self._saved_rcParams = matplotlib.rcParams
175 self._saved_rcParams = matplotlib.rcParams
176 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
176 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
177 matplotlib.rcParams = dict(backend='Qt4Agg')
177 matplotlib.rcParams = dict(backend='Qt4Agg')
178 matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
178 matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
179
179
180 # Mock out functions
180 # Mock out functions
181 self._save_am = pt.activate_matplotlib
181 self._save_am = pt.activate_matplotlib
182 pt.activate_matplotlib = act_mpl
182 pt.activate_matplotlib = act_mpl
183 self._save_ip = pt.import_pylab
183 self._save_ip = pt.import_pylab
184 pt.import_pylab = lambda *a,**kw:None
184 pt.import_pylab = lambda *a,**kw:None
185 self._save_cis = pt.configure_inline_support
185 self._save_cis = pt.configure_inline_support
186 pt.configure_inline_support = lambda *a,**kw:None
186 pt.configure_inline_support = lambda *a,**kw:None
187
187
188 def teardown(self):
188 def teardown(self):
189 pt.activate_matplotlib = self._save_am
189 pt.activate_matplotlib = self._save_am
190 pt.import_pylab = self._save_ip
190 pt.import_pylab = self._save_ip
191 pt.configure_inline_support = self._save_cis
191 pt.configure_inline_support = self._save_cis
192 import matplotlib
192 import matplotlib
193 matplotlib.rcParams = self._saved_rcParams
193 matplotlib.rcParams = self._saved_rcParams
194 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
194 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
195
195
196 def test_qt(self):
196 def test_qt(self):
197 s = self.Shell()
197 s = self.Shell()
198 gui, backend = s.enable_matplotlib(None)
198 gui, backend = s.enable_matplotlib(None)
199 nt.assert_equal(gui, 'qt')
199 nt.assert_equal(gui, 'qt')
200 nt.assert_equal(s.pylab_gui_select, 'qt')
200 nt.assert_equal(s.pylab_gui_select, 'qt')
201
201
202 gui, backend = s.enable_matplotlib('inline')
202 gui, backend = s.enable_matplotlib('inline')
203 nt.assert_equal(gui, 'inline')
203 nt.assert_equal(gui, 'inline')
204 nt.assert_equal(s.pylab_gui_select, 'qt')
204 nt.assert_equal(s.pylab_gui_select, 'qt')
205
205
206 gui, backend = s.enable_matplotlib('qt')
206 gui, backend = s.enable_matplotlib('qt')
207 nt.assert_equal(gui, 'qt')
207 nt.assert_equal(gui, 'qt')
208 nt.assert_equal(s.pylab_gui_select, 'qt')
208 nt.assert_equal(s.pylab_gui_select, 'qt')
209
209
210 gui, backend = s.enable_matplotlib('inline')
210 gui, backend = s.enable_matplotlib('inline')
211 nt.assert_equal(gui, 'inline')
211 nt.assert_equal(gui, 'inline')
212 nt.assert_equal(s.pylab_gui_select, 'qt')
212 nt.assert_equal(s.pylab_gui_select, 'qt')
213
213
214 gui, backend = s.enable_matplotlib()
214 gui, backend = s.enable_matplotlib()
215 nt.assert_equal(gui, 'qt')
215 nt.assert_equal(gui, 'qt')
216 nt.assert_equal(s.pylab_gui_select, 'qt')
216 nt.assert_equal(s.pylab_gui_select, 'qt')
217
217
218 def test_inline(self):
218 def test_inline(self):
219 s = self.Shell()
219 s = self.Shell()
220 gui, backend = s.enable_matplotlib('inline')
220 gui, backend = s.enable_matplotlib('inline')
221 nt.assert_equal(gui, 'inline')
221 nt.assert_equal(gui, 'inline')
222 nt.assert_equal(s.pylab_gui_select, None)
222 nt.assert_equal(s.pylab_gui_select, None)
223
223
224 gui, backend = s.enable_matplotlib('inline')
224 gui, backend = s.enable_matplotlib('inline')
225 nt.assert_equal(gui, 'inline')
225 nt.assert_equal(gui, 'inline')
226 nt.assert_equal(s.pylab_gui_select, None)
226 nt.assert_equal(s.pylab_gui_select, None)
227
227
228 gui, backend = s.enable_matplotlib('qt')
228 gui, backend = s.enable_matplotlib('qt')
229 nt.assert_equal(gui, 'qt')
229 nt.assert_equal(gui, 'qt')
230 nt.assert_equal(s.pylab_gui_select, 'qt')
230 nt.assert_equal(s.pylab_gui_select, 'qt')
231
231
232 def test_qt_gtk(self):
232 def test_qt_gtk(self):
233 s = self.Shell()
233 s = self.Shell()
234 gui, backend = s.enable_matplotlib('qt')
234 gui, backend = s.enable_matplotlib('qt')
235 nt.assert_equal(gui, 'qt')
235 nt.assert_equal(gui, 'qt')
236 nt.assert_equal(s.pylab_gui_select, 'qt')
236 nt.assert_equal(s.pylab_gui_select, 'qt')
237
237
238 gui, backend = s.enable_matplotlib('gtk')
238 gui, backend = s.enable_matplotlib('gtk')
239 nt.assert_equal(gui, 'qt')
239 nt.assert_equal(gui, 'qt')
240 nt.assert_equal(s.pylab_gui_select, 'qt')
240 nt.assert_equal(s.pylab_gui_select, 'qt')
241
241
@@ -1,851 +1,854 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 from __future__ import print_function
8 from __future__ import print_function
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # stdlib
20 # stdlib
21 import errno
21 import errno
22 import io
22 import io
23 import json
23 import json
24 import logging
24 import logging
25 import os
25 import os
26 import random
26 import random
27 import select
27 import select
28 import signal
28 import signal
29 import socket
29 import socket
30 import sys
30 import sys
31 import threading
31 import threading
32 import time
32 import time
33 import webbrowser
33 import webbrowser
34
34
35
35
36 # Third party
36 # Third party
37 # check for pyzmq 2.1.11
37 # check for pyzmq 2.1.11
38 from IPython.utils.zmqrelated import check_for_zmq
38 from IPython.utils.zmqrelated import check_for_zmq
39 check_for_zmq('2.1.11', 'IPython.html')
39 check_for_zmq('2.1.11', 'IPython.html')
40
40
41 from jinja2 import Environment, FileSystemLoader
41 from jinja2 import Environment, FileSystemLoader
42
42
43 # Install the pyzmq ioloop. This has to be done before anything else from
43 # Install the pyzmq ioloop. This has to be done before anything else from
44 # tornado is imported.
44 # tornado is imported.
45 from zmq.eventloop import ioloop
45 from zmq.eventloop import ioloop
46 ioloop.install()
46 ioloop.install()
47
47
48 # check for tornado 3.1.0
48 # check for tornado 3.1.0
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
50 try:
50 try:
51 import tornado
51 import tornado
52 except ImportError:
52 except ImportError:
53 raise ImportError(msg)
53 raise ImportError(msg)
54 try:
54 try:
55 version_info = tornado.version_info
55 version_info = tornado.version_info
56 except AttributeError:
56 except AttributeError:
57 raise ImportError(msg + ", but you have < 1.1.0")
57 raise ImportError(msg + ", but you have < 1.1.0")
58 if version_info < (3,1,0):
58 if version_info < (3,1,0):
59 raise ImportError(msg + ", but you have %s" % tornado.version)
59 raise ImportError(msg + ", but you have %s" % tornado.version)
60
60
61 from tornado import httpserver
61 from tornado import httpserver
62 from tornado import web
62 from tornado import web
63
63
64 # Our own libraries
64 # Our own libraries
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
66 from .base.handlers import Template404
66 from .base.handlers import Template404
67 from .log import log_request
67 from .log import log_request
68 from .services.kernels.kernelmanager import MappingKernelManager
68 from .services.kernels.kernelmanager import MappingKernelManager
69 from .services.notebooks.nbmanager import NotebookManager
69 from .services.notebooks.nbmanager import NotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
71 from .services.clusters.clustermanager import ClusterManager
71 from .services.clusters.clustermanager import ClusterManager
72 from .services.sessions.sessionmanager import SessionManager
72 from .services.sessions.sessionmanager import SessionManager
73
73
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
75
75
76 from IPython.config import Config
76 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.profiledir import ProfileDir
79 from IPython.core.profiledir import ProfileDir
79 from IPython.consoleapp import IPythonConsoleApp
80 from IPython.consoleapp import IPythonConsoleApp
80 from IPython.kernel import swallow_argv
81 from IPython.kernel import swallow_argv
81 from IPython.kernel.zmq.session import default_secure
82 from IPython.kernel.zmq.session import default_secure
82 from IPython.kernel.zmq.kernelapp import (
83 from IPython.kernel.zmq.kernelapp import (
83 kernel_flags,
84 kernel_flags,
84 kernel_aliases,
85 kernel_aliases,
85 )
86 )
86 from IPython.nbformat.sign import NotebookNotary
87 from IPython.nbformat.sign import NotebookNotary
87 from IPython.utils.importstring import import_item
88 from IPython.utils.importstring import import_item
88 from IPython.utils.localinterfaces import localhost
89 from IPython.utils.localinterfaces import localhost
89 from IPython.utils import submodule
90 from IPython.utils import submodule
90 from IPython.utils.traitlets import (
91 from IPython.utils.traitlets import (
91 Dict, Unicode, Integer, List, Bool, Bytes,
92 Dict, Unicode, Integer, List, Bool, Bytes,
92 DottedObjectName, TraitError,
93 DottedObjectName, TraitError,
93 )
94 )
94 from IPython.utils import py3compat
95 from IPython.utils import py3compat
95 from IPython.utils.path import filefind, get_ipython_dir
96 from IPython.utils.path import filefind, get_ipython_dir
96
97
97 from .utils import url_path_join
98 from .utils import url_path_join
98
99
99 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
100 # Module globals
101 # Module globals
101 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
102
103
103 _examples = """
104 _examples = """
104 ipython notebook # start the notebook
105 ipython notebook # start the notebook
105 ipython notebook --profile=sympy # use the sympy profile
106 ipython notebook --profile=sympy # use the sympy profile
106 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
107 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
107 """
108 """
108
109
109 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
110 # Helper functions
111 # Helper functions
111 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
112
113
113 def random_ports(port, n):
114 def random_ports(port, n):
114 """Generate a list of n random ports near the given port.
115 """Generate a list of n random ports near the given port.
115
116
116 The first 5 ports will be sequential, and the remaining n-5 will be
117 The first 5 ports will be sequential, and the remaining n-5 will be
117 randomly selected in the range [port-2*n, port+2*n].
118 randomly selected in the range [port-2*n, port+2*n].
118 """
119 """
119 for i in range(min(5, n)):
120 for i in range(min(5, n)):
120 yield port + i
121 yield port + i
121 for i in range(n-5):
122 for i in range(n-5):
122 yield max(1, port + random.randint(-2*n, 2*n))
123 yield max(1, port + random.randint(-2*n, 2*n))
123
124
124 def load_handlers(name):
125 def load_handlers(name):
125 """Load the (URL pattern, handler) tuples for each component."""
126 """Load the (URL pattern, handler) tuples for each component."""
126 name = 'IPython.html.' + name
127 name = 'IPython.html.' + name
127 mod = __import__(name, fromlist=['default_handlers'])
128 mod = __import__(name, fromlist=['default_handlers'])
128 return mod.default_handlers
129 return mod.default_handlers
129
130
130 #-----------------------------------------------------------------------------
131 #-----------------------------------------------------------------------------
131 # The Tornado web application
132 # The Tornado web application
132 #-----------------------------------------------------------------------------
133 #-----------------------------------------------------------------------------
133
134
134 class NotebookWebApplication(web.Application):
135 class NotebookWebApplication(web.Application):
135
136
136 def __init__(self, ipython_app, kernel_manager, notebook_manager,
137 def __init__(self, ipython_app, kernel_manager, notebook_manager,
137 cluster_manager, session_manager, log, base_url,
138 cluster_manager, session_manager, log, base_url,
138 settings_overrides, jinja_env_options):
139 settings_overrides, jinja_env_options):
139
140
140 settings = self.init_settings(
141 settings = self.init_settings(
141 ipython_app, kernel_manager, notebook_manager, cluster_manager,
142 ipython_app, kernel_manager, notebook_manager, cluster_manager,
142 session_manager, log, base_url, settings_overrides, jinja_env_options)
143 session_manager, log, base_url, settings_overrides, jinja_env_options)
143 handlers = self.init_handlers(settings)
144 handlers = self.init_handlers(settings)
144
145
145 super(NotebookWebApplication, self).__init__(handlers, **settings)
146 super(NotebookWebApplication, self).__init__(handlers, **settings)
146
147
147 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
148 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
148 cluster_manager, session_manager, log, base_url,
149 cluster_manager, session_manager, log, base_url,
149 settings_overrides, jinja_env_options=None):
150 settings_overrides, jinja_env_options=None):
150 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
151 # base_url will always be unicode, which will in turn
152 # base_url will always be unicode, which will in turn
152 # make the patterns unicode, and ultimately result in unicode
153 # make the patterns unicode, and ultimately result in unicode
153 # keys in kwargs to handler._execute(**kwargs) in tornado.
154 # keys in kwargs to handler._execute(**kwargs) in tornado.
154 # This enforces that base_url be ascii in that situation.
155 # This enforces that base_url be ascii in that situation.
155 #
156 #
156 # Note that the URLs these patterns check against are escaped,
157 # Note that the URLs these patterns check against are escaped,
157 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
158 base_url = py3compat.unicode_to_str(base_url, 'ascii')
159 base_url = py3compat.unicode_to_str(base_url, 'ascii')
159 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
160 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 jenv_opt = jinja_env_options if jinja_env_options else {}
161 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
162 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
162 settings = dict(
163 settings = dict(
163 # basics
164 # basics
164 log_function=log_request,
165 log_function=log_request,
165 base_url=base_url,
166 base_url=base_url,
166 template_path=template_path,
167 template_path=template_path,
167 static_path=ipython_app.static_file_path,
168 static_path=ipython_app.static_file_path,
168 static_handler_class = FileFindHandler,
169 static_handler_class = FileFindHandler,
169 static_url_prefix = url_path_join(base_url,'/static/'),
170 static_url_prefix = url_path_join(base_url,'/static/'),
170
171
171 # authentication
172 # authentication
172 cookie_secret=ipython_app.cookie_secret,
173 cookie_secret=ipython_app.cookie_secret,
173 login_url=url_path_join(base_url,'/login'),
174 login_url=url_path_join(base_url,'/login'),
174 password=ipython_app.password,
175 password=ipython_app.password,
175
176
176 # managers
177 # managers
177 kernel_manager=kernel_manager,
178 kernel_manager=kernel_manager,
178 notebook_manager=notebook_manager,
179 notebook_manager=notebook_manager,
179 cluster_manager=cluster_manager,
180 cluster_manager=cluster_manager,
180 session_manager=session_manager,
181 session_manager=session_manager,
181
182
182 # IPython stuff
183 # IPython stuff
183 nbextensions_path = ipython_app.nbextensions_path,
184 nbextensions_path = ipython_app.nbextensions_path,
184 mathjax_url=ipython_app.mathjax_url,
185 mathjax_url=ipython_app.mathjax_url,
185 config=ipython_app.config,
186 config=ipython_app.config,
186 jinja2_env=env,
187 jinja2_env=env,
187 )
188 )
188
189
189 # allow custom overrides for the tornado web app.
190 # allow custom overrides for the tornado web app.
190 settings.update(settings_overrides)
191 settings.update(settings_overrides)
191 return settings
192 return settings
192
193
193 def init_handlers(self, settings):
194 def init_handlers(self, settings):
194 # Load the (URL pattern, handler) tuples for each component.
195 # Load the (URL pattern, handler) tuples for each component.
195 handlers = []
196 handlers = []
196 handlers.extend(load_handlers('base.handlers'))
197 handlers.extend(load_handlers('base.handlers'))
197 handlers.extend(load_handlers('tree.handlers'))
198 handlers.extend(load_handlers('tree.handlers'))
198 handlers.extend(load_handlers('auth.login'))
199 handlers.extend(load_handlers('auth.login'))
199 handlers.extend(load_handlers('auth.logout'))
200 handlers.extend(load_handlers('auth.logout'))
200 handlers.extend(load_handlers('notebook.handlers'))
201 handlers.extend(load_handlers('notebook.handlers'))
201 handlers.extend(load_handlers('nbconvert.handlers'))
202 handlers.extend(load_handlers('nbconvert.handlers'))
202 handlers.extend(load_handlers('services.kernels.handlers'))
203 handlers.extend(load_handlers('services.kernels.handlers'))
203 handlers.extend(load_handlers('services.notebooks.handlers'))
204 handlers.extend(load_handlers('services.notebooks.handlers'))
204 handlers.extend(load_handlers('services.clusters.handlers'))
205 handlers.extend(load_handlers('services.clusters.handlers'))
205 handlers.extend(load_handlers('services.sessions.handlers'))
206 handlers.extend(load_handlers('services.sessions.handlers'))
206 handlers.extend(load_handlers('services.nbconvert.handlers'))
207 handlers.extend(load_handlers('services.nbconvert.handlers'))
207 # FIXME: /files/ should be handled by the Contents service when it exists
208 # FIXME: /files/ should be handled by the Contents service when it exists
208 nbm = settings['notebook_manager']
209 nbm = settings['notebook_manager']
209 if hasattr(nbm, 'notebook_dir'):
210 if hasattr(nbm, 'notebook_dir'):
210 handlers.extend([
211 handlers.extend([
211 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
212 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
212 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
213 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
213 ])
214 ])
214 # prepend base_url onto the patterns that we match
215 # prepend base_url onto the patterns that we match
215 new_handlers = []
216 new_handlers = []
216 for handler in handlers:
217 for handler in handlers:
217 pattern = url_path_join(settings['base_url'], handler[0])
218 pattern = url_path_join(settings['base_url'], handler[0])
218 new_handler = tuple([pattern] + list(handler[1:]))
219 new_handler = tuple([pattern] + list(handler[1:]))
219 new_handlers.append(new_handler)
220 new_handlers.append(new_handler)
220 # add 404 on the end, which will catch everything that falls through
221 # add 404 on the end, which will catch everything that falls through
221 new_handlers.append((r'(.*)', Template404))
222 new_handlers.append((r'(.*)', Template404))
222 return new_handlers
223 return new_handlers
223
224
224
225
225 class NbserverListApp(BaseIPythonApplication):
226 class NbserverListApp(BaseIPythonApplication):
226
227
227 description="List currently running notebook servers in this profile."
228 description="List currently running notebook servers in this profile."
228
229
229 flags = dict(
230 flags = dict(
230 json=({'NbserverListApp': {'json': True}},
231 json=({'NbserverListApp': {'json': True}},
231 "Produce machine-readable JSON output."),
232 "Produce machine-readable JSON output."),
232 )
233 )
233
234
234 json = Bool(False, config=True,
235 json = Bool(False, config=True,
235 help="If True, each line of output will be a JSON object with the "
236 help="If True, each line of output will be a JSON object with the "
236 "details from the server info file.")
237 "details from the server info file.")
237
238
238 def start(self):
239 def start(self):
239 if not self.json:
240 if not self.json:
240 print("Currently running servers:")
241 print("Currently running servers:")
241 for serverinfo in list_running_servers(self.profile):
242 for serverinfo in list_running_servers(self.profile):
242 if self.json:
243 if self.json:
243 print(json.dumps(serverinfo))
244 print(json.dumps(serverinfo))
244 else:
245 else:
245 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
246 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
246
247
247 #-----------------------------------------------------------------------------
248 #-----------------------------------------------------------------------------
248 # Aliases and Flags
249 # Aliases and Flags
249 #-----------------------------------------------------------------------------
250 #-----------------------------------------------------------------------------
250
251
251 flags = dict(kernel_flags)
252 flags = dict(kernel_flags)
252 flags['no-browser']=(
253 flags['no-browser']=(
253 {'NotebookApp' : {'open_browser' : False}},
254 {'NotebookApp' : {'open_browser' : False}},
254 "Don't open the notebook in a browser after startup."
255 "Don't open the notebook in a browser after startup."
255 )
256 )
256 flags['no-mathjax']=(
257 flags['no-mathjax']=(
257 {'NotebookApp' : {'enable_mathjax' : False}},
258 {'NotebookApp' : {'enable_mathjax' : False}},
258 """Disable MathJax
259 """Disable MathJax
259
260
260 MathJax is the javascript library IPython uses to render math/LaTeX. It is
261 MathJax is the javascript library IPython uses to render math/LaTeX. It is
261 very large, so you may want to disable it if you have a slow internet
262 very large, so you may want to disable it if you have a slow internet
262 connection, or for offline use of the notebook.
263 connection, or for offline use of the notebook.
263
264
264 When disabled, equations etc. will appear as their untransformed TeX source.
265 When disabled, equations etc. will appear as their untransformed TeX source.
265 """
266 """
266 )
267 )
267
268
268 # Add notebook manager flags
269 # Add notebook manager flags
269 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
270 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
270 'Auto-save a .py script everytime the .ipynb notebook is saved',
271 'Auto-save a .py script everytime the .ipynb notebook is saved',
271 'Do not auto-save .py scripts for every notebook'))
272 'Do not auto-save .py scripts for every notebook'))
272
273
273 # the flags that are specific to the frontend
274 # the flags that are specific to the frontend
274 # these must be scrubbed before being passed to the kernel,
275 # these must be scrubbed before being passed to the kernel,
275 # or it will raise an error on unrecognized flags
276 # or it will raise an error on unrecognized flags
276 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
277 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
277
278
278 aliases = dict(kernel_aliases)
279 aliases = dict(kernel_aliases)
279
280
280 aliases.update({
281 aliases.update({
281 'ip': 'NotebookApp.ip',
282 'ip': 'NotebookApp.ip',
282 'port': 'NotebookApp.port',
283 'port': 'NotebookApp.port',
283 'port-retries': 'NotebookApp.port_retries',
284 'port-retries': 'NotebookApp.port_retries',
284 'transport': 'KernelManager.transport',
285 'transport': 'KernelManager.transport',
285 'keyfile': 'NotebookApp.keyfile',
286 'keyfile': 'NotebookApp.keyfile',
286 'certfile': 'NotebookApp.certfile',
287 'certfile': 'NotebookApp.certfile',
287 'notebook-dir': 'NotebookApp.notebook_dir',
288 'notebook-dir': 'NotebookApp.notebook_dir',
288 'browser': 'NotebookApp.browser',
289 'browser': 'NotebookApp.browser',
289 })
290 })
290
291
291 # remove ipkernel flags that are singletons, and don't make sense in
292 # remove ipkernel flags that are singletons, and don't make sense in
292 # multi-kernel evironment:
293 # multi-kernel evironment:
293 aliases.pop('f', None)
294 aliases.pop('f', None)
294
295
295 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
296 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
296 u'notebook-dir', u'profile', u'profile-dir']
297 u'notebook-dir', u'profile', u'profile-dir']
297
298
298 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
299 # NotebookApp
300 # NotebookApp
300 #-----------------------------------------------------------------------------
301 #-----------------------------------------------------------------------------
301
302
302 class NotebookApp(BaseIPythonApplication):
303 class NotebookApp(BaseIPythonApplication):
303
304
304 name = 'ipython-notebook'
305 name = 'ipython-notebook'
305
306
306 description = """
307 description = """
307 The IPython HTML Notebook.
308 The IPython HTML Notebook.
308
309
309 This launches a Tornado based HTML Notebook Server that serves up an
310 This launches a Tornado based HTML Notebook Server that serves up an
310 HTML5/Javascript Notebook client.
311 HTML5/Javascript Notebook client.
311 """
312 """
312 examples = _examples
313 examples = _examples
313
314
314 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
315 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
315 FileNotebookManager, NotebookNotary]
316 FileNotebookManager, NotebookNotary]
316 flags = Dict(flags)
317 flags = Dict(flags)
317 aliases = Dict(aliases)
318 aliases = Dict(aliases)
318
319
319 subcommands = dict(
320 subcommands = dict(
320 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
321 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
321 )
322 )
322
323
323 kernel_argv = List(Unicode)
324 kernel_argv = List(Unicode)
324
325
325 def _log_level_default(self):
326 def _log_level_default(self):
326 return logging.INFO
327 return logging.INFO
327
328
328 def _log_format_default(self):
329 def _log_format_default(self):
329 """override default log format to include time"""
330 """override default log format to include time"""
330 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
331 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
331
332
332 # create requested profiles by default, if they don't exist:
333 # create requested profiles by default, if they don't exist:
333 auto_create = Bool(True)
334 auto_create = Bool(True)
334
335
335 # file to be opened in the notebook server
336 # file to be opened in the notebook server
336 file_to_run = Unicode('', config=True)
337 file_to_run = Unicode('', config=True)
337 def _file_to_run_changed(self, name, old, new):
338 def _file_to_run_changed(self, name, old, new):
338 path, base = os.path.split(new)
339 path, base = os.path.split(new)
339 if path:
340 if path:
340 self.file_to_run = base
341 self.file_to_run = base
341 self.notebook_dir = path
342 self.notebook_dir = path
342
343
343 # Network related information.
344 # Network related information.
344
345
345 ip = Unicode(config=True,
346 ip = Unicode(config=True,
346 help="The IP address the notebook server will listen on."
347 help="The IP address the notebook server will listen on."
347 )
348 )
348 def _ip_default(self):
349 def _ip_default(self):
349 return localhost()
350 return localhost()
350
351
351 def _ip_changed(self, name, old, new):
352 def _ip_changed(self, name, old, new):
352 if new == u'*': self.ip = u''
353 if new == u'*': self.ip = u''
353
354
354 port = Integer(8888, config=True,
355 port = Integer(8888, config=True,
355 help="The port the notebook server will listen on."
356 help="The port the notebook server will listen on."
356 )
357 )
357 port_retries = Integer(50, config=True,
358 port_retries = Integer(50, config=True,
358 help="The number of additional ports to try if the specified port is not available."
359 help="The number of additional ports to try if the specified port is not available."
359 )
360 )
360
361
361 certfile = Unicode(u'', config=True,
362 certfile = Unicode(u'', config=True,
362 help="""The full path to an SSL/TLS certificate file."""
363 help="""The full path to an SSL/TLS certificate file."""
363 )
364 )
364
365
365 keyfile = Unicode(u'', config=True,
366 keyfile = Unicode(u'', config=True,
366 help="""The full path to a private key file for usage with SSL/TLS."""
367 help="""The full path to a private key file for usage with SSL/TLS."""
367 )
368 )
368
369
369 cookie_secret = Bytes(b'', config=True,
370 cookie_secret = Bytes(b'', config=True,
370 help="""The random bytes used to secure cookies.
371 help="""The random bytes used to secure cookies.
371 By default this is a new random number every time you start the Notebook.
372 By default this is a new random number every time you start the Notebook.
372 Set it to a value in a config file to enable logins to persist across server sessions.
373 Set it to a value in a config file to enable logins to persist across server sessions.
373
374
374 Note: Cookie secrets should be kept private, do not share config files with
375 Note: Cookie secrets should be kept private, do not share config files with
375 cookie_secret stored in plaintext (you can read the value from a file).
376 cookie_secret stored in plaintext (you can read the value from a file).
376 """
377 """
377 )
378 )
378 def _cookie_secret_default(self):
379 def _cookie_secret_default(self):
379 return os.urandom(1024)
380 return os.urandom(1024)
380
381
381 password = Unicode(u'', config=True,
382 password = Unicode(u'', config=True,
382 help="""Hashed password to use for web authentication.
383 help="""Hashed password to use for web authentication.
383
384
384 To generate, type in a python/IPython shell:
385 To generate, type in a python/IPython shell:
385
386
386 from IPython.lib import passwd; passwd()
387 from IPython.lib import passwd; passwd()
387
388
388 The string should be of the form type:salt:hashed-password.
389 The string should be of the form type:salt:hashed-password.
389 """
390 """
390 )
391 )
391
392
392 open_browser = Bool(True, config=True,
393 open_browser = Bool(True, config=True,
393 help="""Whether to open in a browser after starting.
394 help="""Whether to open in a browser after starting.
394 The specific browser used is platform dependent and
395 The specific browser used is platform dependent and
395 determined by the python standard library `webbrowser`
396 determined by the python standard library `webbrowser`
396 module, unless it is overridden using the --browser
397 module, unless it is overridden using the --browser
397 (NotebookApp.browser) configuration option.
398 (NotebookApp.browser) configuration option.
398 """)
399 """)
399
400
400 browser = Unicode(u'', config=True,
401 browser = Unicode(u'', config=True,
401 help="""Specify what command to use to invoke a web
402 help="""Specify what command to use to invoke a web
402 browser when opening the notebook. If not specified, the
403 browser when opening the notebook. If not specified, the
403 default browser will be determined by the `webbrowser`
404 default browser will be determined by the `webbrowser`
404 standard library module, which allows setting of the
405 standard library module, which allows setting of the
405 BROWSER environment variable to override it.
406 BROWSER environment variable to override it.
406 """)
407 """)
407
408
408 webapp_settings = Dict(config=True,
409 webapp_settings = Dict(config=True,
409 help="Supply overrides for the tornado.web.Application that the "
410 help="Supply overrides for the tornado.web.Application that the "
410 "IPython notebook uses.")
411 "IPython notebook uses.")
411
412
412 jinja_environment_options = Dict(config=True,
413 jinja_environment_options = Dict(config=True,
413 help="Supply extra arguments that will be passed to Jinja environment.")
414 help="Supply extra arguments that will be passed to Jinja environment.")
414
415
415
416
416 enable_mathjax = Bool(True, config=True,
417 enable_mathjax = Bool(True, config=True,
417 help="""Whether to enable MathJax for typesetting math/TeX
418 help="""Whether to enable MathJax for typesetting math/TeX
418
419
419 MathJax is the javascript library IPython uses to render math/LaTeX. It is
420 MathJax is the javascript library IPython uses to render math/LaTeX. It is
420 very large, so you may want to disable it if you have a slow internet
421 very large, so you may want to disable it if you have a slow internet
421 connection, or for offline use of the notebook.
422 connection, or for offline use of the notebook.
422
423
423 When disabled, equations etc. will appear as their untransformed TeX source.
424 When disabled, equations etc. will appear as their untransformed TeX source.
424 """
425 """
425 )
426 )
426 def _enable_mathjax_changed(self, name, old, new):
427 def _enable_mathjax_changed(self, name, old, new):
427 """set mathjax url to empty if mathjax is disabled"""
428 """set mathjax url to empty if mathjax is disabled"""
428 if not new:
429 if not new:
429 self.mathjax_url = u''
430 self.mathjax_url = u''
430
431
431 base_url = Unicode('/', config=True,
432 base_url = Unicode('/', config=True,
432 help='''The base URL for the notebook server.
433 help='''The base URL for the notebook server.
433
434
434 Leading and trailing slashes can be omitted,
435 Leading and trailing slashes can be omitted,
435 and will automatically be added.
436 and will automatically be added.
436 ''')
437 ''')
437 def _base_url_changed(self, name, old, new):
438 def _base_url_changed(self, name, old, new):
438 if not new.startswith('/'):
439 if not new.startswith('/'):
439 self.base_url = '/'+new
440 self.base_url = '/'+new
440 elif not new.endswith('/'):
441 elif not new.endswith('/'):
441 self.base_url = new+'/'
442 self.base_url = new+'/'
442
443
443 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
444 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
444 def _base_project_url_changed(self, name, old, new):
445 def _base_project_url_changed(self, name, old, new):
445 self.log.warn("base_project_url is deprecated, use base_url")
446 self.log.warn("base_project_url is deprecated, use base_url")
446 self.base_url = new
447 self.base_url = new
447
448
448 extra_static_paths = List(Unicode, config=True,
449 extra_static_paths = List(Unicode, config=True,
449 help="""Extra paths to search for serving static files.
450 help="""Extra paths to search for serving static files.
450
451
451 This allows adding javascript/css to be available from the notebook server machine,
452 This allows adding javascript/css to be available from the notebook server machine,
452 or overriding individual files in the IPython"""
453 or overriding individual files in the IPython"""
453 )
454 )
454 def _extra_static_paths_default(self):
455 def _extra_static_paths_default(self):
455 return [os.path.join(self.profile_dir.location, 'static')]
456 return [os.path.join(self.profile_dir.location, 'static')]
456
457
457 @property
458 @property
458 def static_file_path(self):
459 def static_file_path(self):
459 """return extra paths + the default location"""
460 """return extra paths + the default location"""
460 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
461 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
461
462
462 nbextensions_path = List(Unicode, config=True,
463 nbextensions_path = List(Unicode, config=True,
463 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
464 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
464 )
465 )
465 def _nbextensions_path_default(self):
466 def _nbextensions_path_default(self):
466 return [os.path.join(get_ipython_dir(), 'nbextensions')]
467 return [os.path.join(get_ipython_dir(), 'nbextensions')]
467
468
468 mathjax_url = Unicode("", config=True,
469 mathjax_url = Unicode("", config=True,
469 help="""The url for MathJax.js."""
470 help="""The url for MathJax.js."""
470 )
471 )
471 def _mathjax_url_default(self):
472 def _mathjax_url_default(self):
472 if not self.enable_mathjax:
473 if not self.enable_mathjax:
473 return u''
474 return u''
474 static_url_prefix = self.webapp_settings.get("static_url_prefix",
475 static_url_prefix = self.webapp_settings.get("static_url_prefix",
475 url_path_join(self.base_url, "static")
476 url_path_join(self.base_url, "static")
476 )
477 )
477
478
478 # try local mathjax, either in nbextensions/mathjax or static/mathjax
479 # try local mathjax, either in nbextensions/mathjax or static/mathjax
479 for (url_prefix, search_path) in [
480 for (url_prefix, search_path) in [
480 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
481 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
481 (static_url_prefix, self.static_file_path),
482 (static_url_prefix, self.static_file_path),
482 ]:
483 ]:
483 self.log.debug("searching for local mathjax in %s", search_path)
484 self.log.debug("searching for local mathjax in %s", search_path)
484 try:
485 try:
485 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
486 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
486 except IOError:
487 except IOError:
487 continue
488 continue
488 else:
489 else:
489 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
490 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
490 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
491 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
491 return url
492 return url
492
493
493 # no local mathjax, serve from CDN
494 # no local mathjax, serve from CDN
494 if self.certfile:
495 if self.certfile:
495 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
496 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
496 host = u"https://c328740.ssl.cf1.rackcdn.com"
497 host = u"https://c328740.ssl.cf1.rackcdn.com"
497 else:
498 else:
498 host = u"http://cdn.mathjax.org"
499 host = u"http://cdn.mathjax.org"
499
500
500 url = host + u"/mathjax/latest/MathJax.js"
501 url = host + u"/mathjax/latest/MathJax.js"
501 self.log.info("Using MathJax from CDN: %s", url)
502 self.log.info("Using MathJax from CDN: %s", url)
502 return url
503 return url
503
504
504 def _mathjax_url_changed(self, name, old, new):
505 def _mathjax_url_changed(self, name, old, new):
505 if new and not self.enable_mathjax:
506 if new and not self.enable_mathjax:
506 # enable_mathjax=False overrides mathjax_url
507 # enable_mathjax=False overrides mathjax_url
507 self.mathjax_url = u''
508 self.mathjax_url = u''
508 else:
509 else:
509 self.log.info("Using MathJax: %s", new)
510 self.log.info("Using MathJax: %s", new)
510
511
511 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
512 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
512 config=True,
513 config=True,
513 help='The notebook manager class to use.')
514 help='The notebook manager class to use.')
514
515
515 trust_xheaders = Bool(False, config=True,
516 trust_xheaders = Bool(False, config=True,
516 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
517 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
517 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
518 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
518 )
519 )
519
520
520 info_file = Unicode()
521 info_file = Unicode()
521
522
522 def _info_file_default(self):
523 def _info_file_default(self):
523 info_file = "nbserver-%s.json"%os.getpid()
524 info_file = "nbserver-%s.json"%os.getpid()
524 return os.path.join(self.profile_dir.security_dir, info_file)
525 return os.path.join(self.profile_dir.security_dir, info_file)
525
526
526 notebook_dir = Unicode(py3compat.getcwd(), config=True,
527 notebook_dir = Unicode(py3compat.getcwd(), config=True,
527 help="The directory to use for notebooks and kernels."
528 help="The directory to use for notebooks and kernels."
528 )
529 )
529
530
530 def _notebook_dir_changed(self, name, old, new):
531 def _notebook_dir_changed(self, name, old, new):
531 """Do a bit of validation of the notebook dir."""
532 """Do a bit of validation of the notebook dir."""
532 if not os.path.isabs(new):
533 if not os.path.isabs(new):
533 # If we receive a non-absolute path, make it absolute.
534 # If we receive a non-absolute path, make it absolute.
534 self.notebook_dir = os.path.abspath(new)
535 self.notebook_dir = os.path.abspath(new)
535 return
536 return
536 if not os.path.isdir(new):
537 if not os.path.isdir(new):
537 raise TraitError("No such notebook dir: %r" % new)
538 raise TraitError("No such notebook dir: %r" % new)
538
539
539 # setting App.notebook_dir implies setting notebook and kernel dirs as well
540 # setting App.notebook_dir implies setting notebook and kernel dirs as well
540 self.config.FileNotebookManager.notebook_dir = new
541 self.config.FileNotebookManager.notebook_dir = new
541 self.config.MappingKernelManager.root_dir = new
542 self.config.MappingKernelManager.root_dir = new
542
543
543
544
544 def parse_command_line(self, argv=None):
545 def parse_command_line(self, argv=None):
545 super(NotebookApp, self).parse_command_line(argv)
546 super(NotebookApp, self).parse_command_line(argv)
546
547
547 if self.extra_args:
548 if self.extra_args:
548 arg0 = self.extra_args[0]
549 arg0 = self.extra_args[0]
549 f = os.path.abspath(arg0)
550 f = os.path.abspath(arg0)
550 self.argv.remove(arg0)
551 self.argv.remove(arg0)
551 if not os.path.exists(f):
552 if not os.path.exists(f):
552 self.log.critical("No such file or directory: %s", f)
553 self.log.critical("No such file or directory: %s", f)
553 self.exit(1)
554 self.exit(1)
554
555
555 # Use config here, to ensure that it takes higher priority than
556 # Use config here, to ensure that it takes higher priority than
556 # anything that comes from the profile.
557 # anything that comes from the profile.
558 c = Config()
557 if os.path.isdir(f):
559 if os.path.isdir(f):
558 self.config.NotebookApp.notebook_dir = f
560 c.NotebookApp.notebook_dir = f
559 elif os.path.isfile(f):
561 elif os.path.isfile(f):
560 self.config.NotebookApp.file_to_run = f
562 c.NotebookApp.file_to_run = f
563 self.update_config(c)
561
564
562 def init_kernel_argv(self):
565 def init_kernel_argv(self):
563 """construct the kernel arguments"""
566 """construct the kernel arguments"""
564 # Scrub frontend-specific flags
567 # Scrub frontend-specific flags
565 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
568 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
566 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
569 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
567 self.log.warn('\n '.join([
570 self.log.warn('\n '.join([
568 "Starting all kernels in pylab mode is not recommended,",
571 "Starting all kernels in pylab mode is not recommended,",
569 "and will be disabled in a future release.",
572 "and will be disabled in a future release.",
570 "Please use the %matplotlib magic to enable matplotlib instead.",
573 "Please use the %matplotlib magic to enable matplotlib instead.",
571 "pylab implies many imports, which can have confusing side effects",
574 "pylab implies many imports, which can have confusing side effects",
572 "and harm the reproducibility of your notebooks.",
575 "and harm the reproducibility of your notebooks.",
573 ]))
576 ]))
574 # Kernel should inherit default config file from frontend
577 # Kernel should inherit default config file from frontend
575 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
578 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
576 # Kernel should get *absolute* path to profile directory
579 # Kernel should get *absolute* path to profile directory
577 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
580 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
578
581
579 def init_configurables(self):
582 def init_configurables(self):
580 # force Session default to be secure
583 # force Session default to be secure
581 default_secure(self.config)
584 default_secure(self.config)
582 self.kernel_manager = MappingKernelManager(
585 self.kernel_manager = MappingKernelManager(
583 parent=self, log=self.log, kernel_argv=self.kernel_argv,
586 parent=self, log=self.log, kernel_argv=self.kernel_argv,
584 connection_dir = self.profile_dir.security_dir,
587 connection_dir = self.profile_dir.security_dir,
585 )
588 )
586 kls = import_item(self.notebook_manager_class)
589 kls = import_item(self.notebook_manager_class)
587 self.notebook_manager = kls(parent=self, log=self.log)
590 self.notebook_manager = kls(parent=self, log=self.log)
588 self.session_manager = SessionManager(parent=self, log=self.log)
591 self.session_manager = SessionManager(parent=self, log=self.log)
589 self.cluster_manager = ClusterManager(parent=self, log=self.log)
592 self.cluster_manager = ClusterManager(parent=self, log=self.log)
590 self.cluster_manager.update_profiles()
593 self.cluster_manager.update_profiles()
591
594
592 def init_logging(self):
595 def init_logging(self):
593 # This prevents double log messages because tornado use a root logger that
596 # This prevents double log messages because tornado use a root logger that
594 # self.log is a child of. The logging module dipatches log messages to a log
597 # self.log is a child of. The logging module dipatches log messages to a log
595 # and all of its ancenstors until propagate is set to False.
598 # and all of its ancenstors until propagate is set to False.
596 self.log.propagate = False
599 self.log.propagate = False
597
600
598 # hook up tornado 3's loggers to our app handlers
601 # hook up tornado 3's loggers to our app handlers
599 for name in ('access', 'application', 'general'):
602 for name in ('access', 'application', 'general'):
600 logger = logging.getLogger('tornado.%s' % name)
603 logger = logging.getLogger('tornado.%s' % name)
601 logger.parent = self.log
604 logger.parent = self.log
602 logger.setLevel(self.log.level)
605 logger.setLevel(self.log.level)
603
606
604 def init_webapp(self):
607 def init_webapp(self):
605 """initialize tornado webapp and httpserver"""
608 """initialize tornado webapp and httpserver"""
606 self.web_app = NotebookWebApplication(
609 self.web_app = NotebookWebApplication(
607 self, self.kernel_manager, self.notebook_manager,
610 self, self.kernel_manager, self.notebook_manager,
608 self.cluster_manager, self.session_manager,
611 self.cluster_manager, self.session_manager,
609 self.log, self.base_url, self.webapp_settings,
612 self.log, self.base_url, self.webapp_settings,
610 self.jinja_environment_options
613 self.jinja_environment_options
611 )
614 )
612 if self.certfile:
615 if self.certfile:
613 ssl_options = dict(certfile=self.certfile)
616 ssl_options = dict(certfile=self.certfile)
614 if self.keyfile:
617 if self.keyfile:
615 ssl_options['keyfile'] = self.keyfile
618 ssl_options['keyfile'] = self.keyfile
616 else:
619 else:
617 ssl_options = None
620 ssl_options = None
618 self.web_app.password = self.password
621 self.web_app.password = self.password
619 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
622 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
620 xheaders=self.trust_xheaders)
623 xheaders=self.trust_xheaders)
621 if not self.ip:
624 if not self.ip:
622 warning = "WARNING: The notebook server is listening on all IP addresses"
625 warning = "WARNING: The notebook server is listening on all IP addresses"
623 if ssl_options is None:
626 if ssl_options is None:
624 self.log.critical(warning + " and not using encryption. This "
627 self.log.critical(warning + " and not using encryption. This "
625 "is not recommended.")
628 "is not recommended.")
626 if not self.password:
629 if not self.password:
627 self.log.critical(warning + " and not using authentication. "
630 self.log.critical(warning + " and not using authentication. "
628 "This is highly insecure and not recommended.")
631 "This is highly insecure and not recommended.")
629 success = None
632 success = None
630 for port in random_ports(self.port, self.port_retries+1):
633 for port in random_ports(self.port, self.port_retries+1):
631 try:
634 try:
632 self.http_server.listen(port, self.ip)
635 self.http_server.listen(port, self.ip)
633 except socket.error as e:
636 except socket.error as e:
634 if e.errno == errno.EADDRINUSE:
637 if e.errno == errno.EADDRINUSE:
635 self.log.info('The port %i is already in use, trying another random port.' % port)
638 self.log.info('The port %i is already in use, trying another random port.' % port)
636 continue
639 continue
637 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
640 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
638 self.log.warn("Permission to listen on port %i denied" % port)
641 self.log.warn("Permission to listen on port %i denied" % port)
639 continue
642 continue
640 else:
643 else:
641 raise
644 raise
642 else:
645 else:
643 self.port = port
646 self.port = port
644 success = True
647 success = True
645 break
648 break
646 if not success:
649 if not success:
647 self.log.critical('ERROR: the notebook server could not be started because '
650 self.log.critical('ERROR: the notebook server could not be started because '
648 'no available port could be found.')
651 'no available port could be found.')
649 self.exit(1)
652 self.exit(1)
650
653
651 @property
654 @property
652 def display_url(self):
655 def display_url(self):
653 ip = self.ip if self.ip else '[all ip addresses on your system]'
656 ip = self.ip if self.ip else '[all ip addresses on your system]'
654 return self._url(ip)
657 return self._url(ip)
655
658
656 @property
659 @property
657 def connection_url(self):
660 def connection_url(self):
658 ip = self.ip if self.ip else localhost()
661 ip = self.ip if self.ip else localhost()
659 return self._url(ip)
662 return self._url(ip)
660
663
661 def _url(self, ip):
664 def _url(self, ip):
662 proto = 'https' if self.certfile else 'http'
665 proto = 'https' if self.certfile else 'http'
663 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
666 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
664
667
665 def init_signal(self):
668 def init_signal(self):
666 if not sys.platform.startswith('win'):
669 if not sys.platform.startswith('win'):
667 signal.signal(signal.SIGINT, self._handle_sigint)
670 signal.signal(signal.SIGINT, self._handle_sigint)
668 signal.signal(signal.SIGTERM, self._signal_stop)
671 signal.signal(signal.SIGTERM, self._signal_stop)
669 if hasattr(signal, 'SIGUSR1'):
672 if hasattr(signal, 'SIGUSR1'):
670 # Windows doesn't support SIGUSR1
673 # Windows doesn't support SIGUSR1
671 signal.signal(signal.SIGUSR1, self._signal_info)
674 signal.signal(signal.SIGUSR1, self._signal_info)
672 if hasattr(signal, 'SIGINFO'):
675 if hasattr(signal, 'SIGINFO'):
673 # only on BSD-based systems
676 # only on BSD-based systems
674 signal.signal(signal.SIGINFO, self._signal_info)
677 signal.signal(signal.SIGINFO, self._signal_info)
675
678
676 def _handle_sigint(self, sig, frame):
679 def _handle_sigint(self, sig, frame):
677 """SIGINT handler spawns confirmation dialog"""
680 """SIGINT handler spawns confirmation dialog"""
678 # register more forceful signal handler for ^C^C case
681 # register more forceful signal handler for ^C^C case
679 signal.signal(signal.SIGINT, self._signal_stop)
682 signal.signal(signal.SIGINT, self._signal_stop)
680 # request confirmation dialog in bg thread, to avoid
683 # request confirmation dialog in bg thread, to avoid
681 # blocking the App
684 # blocking the App
682 thread = threading.Thread(target=self._confirm_exit)
685 thread = threading.Thread(target=self._confirm_exit)
683 thread.daemon = True
686 thread.daemon = True
684 thread.start()
687 thread.start()
685
688
686 def _restore_sigint_handler(self):
689 def _restore_sigint_handler(self):
687 """callback for restoring original SIGINT handler"""
690 """callback for restoring original SIGINT handler"""
688 signal.signal(signal.SIGINT, self._handle_sigint)
691 signal.signal(signal.SIGINT, self._handle_sigint)
689
692
690 def _confirm_exit(self):
693 def _confirm_exit(self):
691 """confirm shutdown on ^C
694 """confirm shutdown on ^C
692
695
693 A second ^C, or answering 'y' within 5s will cause shutdown,
696 A second ^C, or answering 'y' within 5s will cause shutdown,
694 otherwise original SIGINT handler will be restored.
697 otherwise original SIGINT handler will be restored.
695
698
696 This doesn't work on Windows.
699 This doesn't work on Windows.
697 """
700 """
698 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
701 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
699 time.sleep(0.1)
702 time.sleep(0.1)
700 info = self.log.info
703 info = self.log.info
701 info('interrupted')
704 info('interrupted')
702 print(self.notebook_info())
705 print(self.notebook_info())
703 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
706 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
704 sys.stdout.flush()
707 sys.stdout.flush()
705 r,w,x = select.select([sys.stdin], [], [], 5)
708 r,w,x = select.select([sys.stdin], [], [], 5)
706 if r:
709 if r:
707 line = sys.stdin.readline()
710 line = sys.stdin.readline()
708 if line.lower().startswith('y'):
711 if line.lower().startswith('y'):
709 self.log.critical("Shutdown confirmed")
712 self.log.critical("Shutdown confirmed")
710 ioloop.IOLoop.instance().stop()
713 ioloop.IOLoop.instance().stop()
711 return
714 return
712 else:
715 else:
713 print("No answer for 5s:", end=' ')
716 print("No answer for 5s:", end=' ')
714 print("resuming operation...")
717 print("resuming operation...")
715 # no answer, or answer is no:
718 # no answer, or answer is no:
716 # set it back to original SIGINT handler
719 # set it back to original SIGINT handler
717 # use IOLoop.add_callback because signal.signal must be called
720 # use IOLoop.add_callback because signal.signal must be called
718 # from main thread
721 # from main thread
719 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
722 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
720
723
721 def _signal_stop(self, sig, frame):
724 def _signal_stop(self, sig, frame):
722 self.log.critical("received signal %s, stopping", sig)
725 self.log.critical("received signal %s, stopping", sig)
723 ioloop.IOLoop.instance().stop()
726 ioloop.IOLoop.instance().stop()
724
727
725 def _signal_info(self, sig, frame):
728 def _signal_info(self, sig, frame):
726 print(self.notebook_info())
729 print(self.notebook_info())
727
730
728 def init_components(self):
731 def init_components(self):
729 """Check the components submodule, and warn if it's unclean"""
732 """Check the components submodule, and warn if it's unclean"""
730 status = submodule.check_submodule_status()
733 status = submodule.check_submodule_status()
731 if status == 'missing':
734 if status == 'missing':
732 self.log.warn("components submodule missing, running `git submodule update`")
735 self.log.warn("components submodule missing, running `git submodule update`")
733 submodule.update_submodules(submodule.ipython_parent())
736 submodule.update_submodules(submodule.ipython_parent())
734 elif status == 'unclean':
737 elif status == 'unclean':
735 self.log.warn("components submodule unclean, you may see 404s on static/components")
738 self.log.warn("components submodule unclean, you may see 404s on static/components")
736 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
739 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
737
740
738 @catch_config_error
741 @catch_config_error
739 def initialize(self, argv=None):
742 def initialize(self, argv=None):
740 super(NotebookApp, self).initialize(argv)
743 super(NotebookApp, self).initialize(argv)
741 self.init_logging()
744 self.init_logging()
742 self.init_kernel_argv()
745 self.init_kernel_argv()
743 self.init_configurables()
746 self.init_configurables()
744 self.init_components()
747 self.init_components()
745 self.init_webapp()
748 self.init_webapp()
746 self.init_signal()
749 self.init_signal()
747
750
748 def cleanup_kernels(self):
751 def cleanup_kernels(self):
749 """Shutdown all kernels.
752 """Shutdown all kernels.
750
753
751 The kernels will shutdown themselves when this process no longer exists,
754 The kernels will shutdown themselves when this process no longer exists,
752 but explicit shutdown allows the KernelManagers to cleanup the connection files.
755 but explicit shutdown allows the KernelManagers to cleanup the connection files.
753 """
756 """
754 self.log.info('Shutting down kernels')
757 self.log.info('Shutting down kernels')
755 self.kernel_manager.shutdown_all()
758 self.kernel_manager.shutdown_all()
756
759
757 def notebook_info(self):
760 def notebook_info(self):
758 "Return the current working directory and the server url information"
761 "Return the current working directory and the server url information"
759 info = self.notebook_manager.info_string() + "\n"
762 info = self.notebook_manager.info_string() + "\n"
760 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
763 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
761 return info + "The IPython Notebook is running at: %s" % self.display_url
764 return info + "The IPython Notebook is running at: %s" % self.display_url
762
765
763 def server_info(self):
766 def server_info(self):
764 """Return a JSONable dict of information about this server."""
767 """Return a JSONable dict of information about this server."""
765 return {'url': self.connection_url,
768 return {'url': self.connection_url,
766 'hostname': self.ip if self.ip else 'localhost',
769 'hostname': self.ip if self.ip else 'localhost',
767 'port': self.port,
770 'port': self.port,
768 'secure': bool(self.certfile),
771 'secure': bool(self.certfile),
769 'base_url': self.base_url,
772 'base_url': self.base_url,
770 'notebook_dir': os.path.abspath(self.notebook_dir),
773 'notebook_dir': os.path.abspath(self.notebook_dir),
771 }
774 }
772
775
773 def write_server_info_file(self):
776 def write_server_info_file(self):
774 """Write the result of server_info() to the JSON file info_file."""
777 """Write the result of server_info() to the JSON file info_file."""
775 with open(self.info_file, 'w') as f:
778 with open(self.info_file, 'w') as f:
776 json.dump(self.server_info(), f, indent=2)
779 json.dump(self.server_info(), f, indent=2)
777
780
778 def remove_server_info_file(self):
781 def remove_server_info_file(self):
779 """Remove the nbserver-<pid>.json file created for this server.
782 """Remove the nbserver-<pid>.json file created for this server.
780
783
781 Ignores the error raised when the file has already been removed.
784 Ignores the error raised when the file has already been removed.
782 """
785 """
783 try:
786 try:
784 os.unlink(self.info_file)
787 os.unlink(self.info_file)
785 except OSError as e:
788 except OSError as e:
786 if e.errno != errno.ENOENT:
789 if e.errno != errno.ENOENT:
787 raise
790 raise
788
791
789 def start(self):
792 def start(self):
790 """ Start the IPython Notebook server app, after initialization
793 """ Start the IPython Notebook server app, after initialization
791
794
792 This method takes no arguments so all configuration and initialization
795 This method takes no arguments so all configuration and initialization
793 must be done prior to calling this method."""
796 must be done prior to calling this method."""
794 if self.subapp is not None:
797 if self.subapp is not None:
795 return self.subapp.start()
798 return self.subapp.start()
796
799
797 info = self.log.info
800 info = self.log.info
798 for line in self.notebook_info().split("\n"):
801 for line in self.notebook_info().split("\n"):
799 info(line)
802 info(line)
800 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
803 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
801
804
802 self.write_server_info_file()
805 self.write_server_info_file()
803
806
804 if self.open_browser or self.file_to_run:
807 if self.open_browser or self.file_to_run:
805 try:
808 try:
806 browser = webbrowser.get(self.browser or None)
809 browser = webbrowser.get(self.browser or None)
807 except webbrowser.Error as e:
810 except webbrowser.Error as e:
808 self.log.warn('No web browser found: %s.' % e)
811 self.log.warn('No web browser found: %s.' % e)
809 browser = None
812 browser = None
810
813
811 if self.file_to_run:
814 if self.file_to_run:
812 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
815 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
813 if not os.path.exists(fullpath):
816 if not os.path.exists(fullpath):
814 self.log.critical("%s does not exist" % fullpath)
817 self.log.critical("%s does not exist" % fullpath)
815 self.exit(1)
818 self.exit(1)
816
819
817 uri = url_path_join('notebooks', self.file_to_run)
820 uri = url_path_join('notebooks', self.file_to_run)
818 else:
821 else:
819 uri = 'tree'
822 uri = 'tree'
820 if browser:
823 if browser:
821 b = lambda : browser.open(url_path_join(self.connection_url, uri),
824 b = lambda : browser.open(url_path_join(self.connection_url, uri),
822 new=2)
825 new=2)
823 threading.Thread(target=b).start()
826 threading.Thread(target=b).start()
824 try:
827 try:
825 ioloop.IOLoop.instance().start()
828 ioloop.IOLoop.instance().start()
826 except KeyboardInterrupt:
829 except KeyboardInterrupt:
827 info("Interrupted...")
830 info("Interrupted...")
828 finally:
831 finally:
829 self.cleanup_kernels()
832 self.cleanup_kernels()
830 self.remove_server_info_file()
833 self.remove_server_info_file()
831
834
832
835
833 def list_running_servers(profile='default'):
836 def list_running_servers(profile='default'):
834 """Iterate over the server info files of running notebook servers.
837 """Iterate over the server info files of running notebook servers.
835
838
836 Given a profile name, find nbserver-* files in the security directory of
839 Given a profile name, find nbserver-* files in the security directory of
837 that profile, and yield dicts of their information, each one pertaining to
840 that profile, and yield dicts of their information, each one pertaining to
838 a currently running notebook server instance.
841 a currently running notebook server instance.
839 """
842 """
840 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
843 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
841 for file in os.listdir(pd.security_dir):
844 for file in os.listdir(pd.security_dir):
842 if file.startswith('nbserver-'):
845 if file.startswith('nbserver-'):
843 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
846 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
844 yield json.load(f)
847 yield json.load(f)
845
848
846 #-----------------------------------------------------------------------------
849 #-----------------------------------------------------------------------------
847 # Main entry point
850 # Main entry point
848 #-----------------------------------------------------------------------------
851 #-----------------------------------------------------------------------------
849
852
850 launch_new_instance = NotebookApp.launch_instance
853 launch_new_instance = NotebookApp.launch_instance
851
854
@@ -1,269 +1,259 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 IPython.namespace('IPython.keyboard');
12 IPython.namespace('IPython.keyboard');
13
13
14 IPython.keyboard = (function (IPython) {
14 IPython.keyboard = (function (IPython) {
15 "use strict";
15 "use strict";
16
16
17 // Setup global keycodes and inverse keycodes.
17 // Setup global keycodes and inverse keycodes.
18
18
19 // See http://unixpapa.com/js/key.html for a complete description. The short of
19 // See http://unixpapa.com/js/key.html for a complete description. The short of
20 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
20 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
21 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
21 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
22 // but have minor differences.
22 // but have minor differences.
23
23
24 // These apply to Firefox, (Webkit and IE)
24 // These apply to Firefox, (Webkit and IE)
25 var _keycodes = {
25 var _keycodes = {
26 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
26 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
27 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
27 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
28 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
28 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
29 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
29 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
30 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
30 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
31 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
31 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
32 '\\ |': 220, '\' "': 222,
32 '\\ |': 220, '\' "': 222,
33 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
33 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
34 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
34 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
35 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
35 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
36 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
36 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
37 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
37 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
38 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
38 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
39 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
39 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
40 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
40 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
41 'insert': 45, 'delete': 46, 'numlock': 144,
41 'insert': 45, 'delete': 46, 'numlock': 144,
42 };
42 };
43
43
44 // These apply to Firefox and Opera
44 // These apply to Firefox and Opera
45 var _mozilla_keycodes = {
45 var _mozilla_keycodes = {
46 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
46 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
47 };
47 };
48
48
49 // This apply to Webkit and IE
49 // This apply to Webkit and IE
50 var _ie_keycodes = {
50 var _ie_keycodes = {
51 '; :': 186, '= +': 187, '- _': 189
51 '; :': 186, '= +': 187, '- _': 189
52 };
52 };
53
53
54 var browser = IPython.utils.browser[0];
54 var browser = IPython.utils.browser[0];
55 var platform = IPython.utils.platform;
55 var platform = IPython.utils.platform;
56
56
57 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
57 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
58 $.extend(_keycodes, _mozilla_keycodes);
58 $.extend(_keycodes, _mozilla_keycodes);
59 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
59 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
60 $.extend(_keycodes, _ie_keycodes);
60 $.extend(_keycodes, _ie_keycodes);
61 }
61 }
62
62
63 var keycodes = {};
63 var keycodes = {};
64 var inv_keycodes = {};
64 var inv_keycodes = {};
65 for (var name in _keycodes) {
65 for (var name in _keycodes) {
66 var names = name.split(' ');
66 var names = name.split(' ');
67 if (names.length === 1) {
67 if (names.length === 1) {
68 var n = names[0];
68 var n = names[0];
69 keycodes[n] = _keycodes[n];
69 keycodes[n] = _keycodes[n];
70 inv_keycodes[_keycodes[n]] = n;
70 inv_keycodes[_keycodes[n]] = n;
71 } else {
71 } else {
72 var primary = names[0];
72 var primary = names[0];
73 var secondary = names[1];
73 var secondary = names[1];
74 keycodes[primary] = _keycodes[name];
74 keycodes[primary] = _keycodes[name];
75 keycodes[secondary] = _keycodes[name];
75 keycodes[secondary] = _keycodes[name];
76 inv_keycodes[_keycodes[name]] = primary;
76 inv_keycodes[_keycodes[name]] = primary;
77 }
77 }
78 }
78 }
79
79
80 var normalize_key = function (key) {
80 var normalize_key = function (key) {
81 return inv_keycodes[keycodes[key]];
81 return inv_keycodes[keycodes[key]];
82 };
82 };
83
83
84 var normalize_shortcut = function (shortcut) {
84 var normalize_shortcut = function (shortcut) {
85 // Put a shortcut into normalized form:
85 // Put a shortcut into normalized form:
86 // 1. Make lowercase
86 // 1. Make lowercase
87 // 2. Replace cmd by meta
87 // 2. Replace cmd by meta
88 // 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
88 // 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
89 // 4. Normalize keys
89 // 4. Normalize keys
90 shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
90 shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
91 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
91 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
92 var values = shortcut.split("-");
92 var values = shortcut.split("-");
93 if (values.length === 1) {
93 if (values.length === 1) {
94 return normalize_key(values[0]);
94 return normalize_key(values[0]);
95 } else {
95 } else {
96 var modifiers = values.slice(0,-1);
96 var modifiers = values.slice(0,-1);
97 var key = normalize_key(values[values.length-1]);
97 var key = normalize_key(values[values.length-1]);
98 modifiers.sort();
98 modifiers.sort();
99 return modifiers.join('-') + '-' + key;
99 return modifiers.join('-') + '-' + key;
100 }
100 }
101 };
101 };
102
102
103 var shortcut_to_event = function (shortcut, type) {
103 var shortcut_to_event = function (shortcut, type) {
104 // Convert a shortcut (shift-r) to a jQuery Event object
104 // Convert a shortcut (shift-r) to a jQuery Event object
105 type = type || 'keydown';
105 type = type || 'keydown';
106 shortcut = normalize_shortcut(shortcut);
106 shortcut = normalize_shortcut(shortcut);
107 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
107 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
108 var values = shortcut.split("-");
108 var values = shortcut.split("-");
109 var modifiers = values.slice(0,-1);
109 var modifiers = values.slice(0,-1);
110 var key = values[values.length-1];
110 var key = values[values.length-1];
111 var opts = {which: keycodes[key]};
111 var opts = {which: keycodes[key]};
112 if (modifiers.indexOf('alt') !== -1) {opts.altKey = true;}
112 if (modifiers.indexOf('alt') !== -1) {opts.altKey = true;}
113 if (modifiers.indexOf('ctrl') !== -1) {opts.ctrlKey = true;}
113 if (modifiers.indexOf('ctrl') !== -1) {opts.ctrlKey = true;}
114 if (modifiers.indexOf('meta') !== -1) {opts.metaKey = true;}
114 if (modifiers.indexOf('meta') !== -1) {opts.metaKey = true;}
115 if (modifiers.indexOf('shift') !== -1) {opts.shiftKey = true;}
115 if (modifiers.indexOf('shift') !== -1) {opts.shiftKey = true;}
116 return $.Event(type, opts);
116 return $.Event(type, opts);
117 };
117 };
118
118
119 var event_to_shortcut = function (event) {
119 var event_to_shortcut = function (event) {
120 // Convert a jQuery Event object to a shortcut (shift-r)
120 // Convert a jQuery Event object to a shortcut (shift-r)
121 var shortcut = '';
121 var shortcut = '';
122 var key = inv_keycodes[event.which];
122 var key = inv_keycodes[event.which];
123 if (event.altKey && key !== 'alt') {shortcut += 'alt-';}
123 if (event.altKey && key !== 'alt') {shortcut += 'alt-';}
124 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl-';}
124 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl-';}
125 if (event.metaKey && key !== 'meta') {shortcut += 'meta-';}
125 if (event.metaKey && key !== 'meta') {shortcut += 'meta-';}
126 if (event.shiftKey && key !== 'shift') {shortcut += 'shift-';}
126 if (event.shiftKey && key !== 'shift') {shortcut += 'shift-';}
127 shortcut += key;
127 shortcut += key;
128 return shortcut;
128 return shortcut;
129 };
129 };
130
130
131 var trigger_keydown = function (shortcut, element) {
132 // Trigger shortcut keydown on an element
133 element = element || document;
134 element = $(element);
135 var event = shortcut_to_event(shortcut, 'keydown');
136 element.trigger(event);
137 };
138
139
140 // Shortcut manager class
131 // Shortcut manager class
141
132
142 var ShortcutManager = function (delay) {
133 var ShortcutManager = function (delay) {
143 this._shortcuts = {};
134 this._shortcuts = {};
144 this._counts = {};
135 this._counts = {};
145 this._timers = {};
136 this._timers = {};
146 this.delay = delay || 800; // delay in milliseconds
137 this.delay = delay || 800; // delay in milliseconds
147 };
138 };
148
139
149 ShortcutManager.prototype.help = function () {
140 ShortcutManager.prototype.help = function () {
150 var help = [];
141 var help = [];
151 for (var shortcut in this._shortcuts) {
142 for (var shortcut in this._shortcuts) {
152 var help_string = this._shortcuts[shortcut]['help'];
143 var help_string = this._shortcuts[shortcut]['help'];
153 var help_index = this._shortcuts[shortcut]['help_index'];
144 var help_index = this._shortcuts[shortcut]['help_index'];
154 if (help_string) {
145 if (help_string) {
155 if (platform === 'MacOS') {
146 if (platform === 'MacOS') {
156 shortcut = shortcut.replace('meta', 'cmd');
147 shortcut = shortcut.replace('meta', 'cmd');
157 }
148 }
158 help.push({
149 help.push({
159 shortcut: shortcut,
150 shortcut: shortcut,
160 help: help_string,
151 help: help_string,
161 help_index: help_index}
152 help_index: help_index}
162 );
153 );
163 }
154 }
164 }
155 }
165 help.sort(function (a, b) {
156 help.sort(function (a, b) {
166 if (a.help_index > b.help_index)
157 if (a.help_index > b.help_index)
167 return 1;
158 return 1;
168 if (a.help_index < b.help_index)
159 if (a.help_index < b.help_index)
169 return -1;
160 return -1;
170 return 0;
161 return 0;
171 });
162 });
172 return help;
163 return help;
173 };
164 };
174
165
175 ShortcutManager.prototype.clear_shortcuts = function () {
166 ShortcutManager.prototype.clear_shortcuts = function () {
176 this._shortcuts = {};
167 this._shortcuts = {};
177 };
168 };
178
169
179 ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
170 ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
180 if (typeof(data) === 'function') {
171 if (typeof(data) === 'function') {
181 data = {help: '', help_index: '', handler: data};
172 data = {help: '', help_index: '', handler: data};
182 }
173 }
183 data.help_index = data.help_index || '';
174 data.help_index = data.help_index || '';
184 data.help = data.help || '';
175 data.help = data.help || '';
185 data.count = data.count || 1;
176 data.count = data.count || 1;
186 if (data.help_index === '') {
177 if (data.help_index === '') {
187 data.help_index = 'zz';
178 data.help_index = 'zz';
188 }
179 }
189 shortcut = normalize_shortcut(shortcut);
180 shortcut = normalize_shortcut(shortcut);
190 this._counts[shortcut] = 0;
181 this._counts[shortcut] = 0;
191 this._shortcuts[shortcut] = data;
182 this._shortcuts[shortcut] = data;
192 if (!suppress_help_update) {
183 if (!suppress_help_update) {
193 // update the keyboard shortcuts notebook help
184 // update the keyboard shortcuts notebook help
194 $([IPython.events]).trigger('rebuild.QuickHelp');
185 $([IPython.events]).trigger('rebuild.QuickHelp');
195 }
186 }
196 };
187 };
197
188
198 ShortcutManager.prototype.add_shortcuts = function (data) {
189 ShortcutManager.prototype.add_shortcuts = function (data) {
199 for (var shortcut in data) {
190 for (var shortcut in data) {
200 this.add_shortcut(shortcut, data[shortcut], true);
191 this.add_shortcut(shortcut, data[shortcut], true);
201 }
192 }
202 // update the keyboard shortcuts notebook help
193 // update the keyboard shortcuts notebook help
203 $([IPython.events]).trigger('rebuild.QuickHelp');
194 $([IPython.events]).trigger('rebuild.QuickHelp');
204 };
195 };
205
196
206 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
197 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
207 shortcut = normalize_shortcut(shortcut);
198 shortcut = normalize_shortcut(shortcut);
208 delete this._counts[shortcut];
199 delete this._counts[shortcut];
209 delete this._shortcuts[shortcut];
200 delete this._shortcuts[shortcut];
210 if (!suppress_help_update) {
201 if (!suppress_help_update) {
211 // update the keyboard shortcuts notebook help
202 // update the keyboard shortcuts notebook help
212 $([IPython.events]).trigger('rebuild.QuickHelp');
203 $([IPython.events]).trigger('rebuild.QuickHelp');
213 }
204 }
214 };
205 };
215
206
216 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
207 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
217 var that = this;
208 var that = this;
218 var c = this._counts;
209 var c = this._counts;
219 var t = this._timers;
210 var t = this._timers;
220 var timer = null;
211 var timer = null;
221 if (c[shortcut] === data.count-1) {
212 if (c[shortcut] === data.count-1) {
222 c[shortcut] = 0;
213 c[shortcut] = 0;
223 var timer = t[shortcut];
214 var timer = t[shortcut];
224 if (timer) {clearTimeout(timer); delete t[shortcut];}
215 if (timer) {clearTimeout(timer); delete t[shortcut];}
225 return data.handler(event);
216 return data.handler(event);
226 } else {
217 } else {
227 c[shortcut] = c[shortcut] + 1;
218 c[shortcut] = c[shortcut] + 1;
228 timer = setTimeout(function () {
219 timer = setTimeout(function () {
229 c[shortcut] = 0;
220 c[shortcut] = 0;
230 }, that.delay);
221 }, that.delay);
231 t[shortcut] = timer;
222 t[shortcut] = timer;
232 }
223 }
233 return false;
224 return false;
234 };
225 };
235
226
236 ShortcutManager.prototype.call_handler = function (event) {
227 ShortcutManager.prototype.call_handler = function (event) {
237 var shortcut = event_to_shortcut(event);
228 var shortcut = event_to_shortcut(event);
238 var data = this._shortcuts[shortcut];
229 var data = this._shortcuts[shortcut];
239 if (data) {
230 if (data) {
240 var handler = data['handler'];
231 var handler = data['handler'];
241 if (handler) {
232 if (handler) {
242 if (data.count === 1) {
233 if (data.count === 1) {
243 return handler(event);
234 return handler(event);
244 } else if (data.count > 1) {
235 } else if (data.count > 1) {
245 return this.count_handler(shortcut, event, data);
236 return this.count_handler(shortcut, event, data);
246 }
237 }
247 }
238 }
248 }
239 }
249 return true;
240 return true;
250 };
241 };
251
242
252 ShortcutManager.prototype.handles = function (event) {
243 ShortcutManager.prototype.handles = function (event) {
253 var shortcut = event_to_shortcut(event);
244 var shortcut = event_to_shortcut(event);
254 var data = this._shortcuts[shortcut];
245 var data = this._shortcuts[shortcut];
255 return !( data === undefined )
246 return !( data === undefined || data.handler === undefined )
256 }
247 }
257
248
258 return {
249 return {
259 keycodes : keycodes,
250 keycodes : keycodes,
260 inv_keycodes : inv_keycodes,
251 inv_keycodes : inv_keycodes,
261 ShortcutManager : ShortcutManager,
252 ShortcutManager : ShortcutManager,
262 normalize_key : normalize_key,
253 normalize_key : normalize_key,
263 normalize_shortcut : normalize_shortcut,
254 normalize_shortcut : normalize_shortcut,
264 shortcut_to_event : shortcut_to_event,
255 shortcut_to_event : shortcut_to_event,
265 event_to_shortcut : event_to_shortcut,
256 event_to_shortcut : event_to_shortcut
266 trigger_keydown : trigger_keydown
267 };
257 };
268
258
269 }(IPython));
259 }(IPython));
@@ -1,2407 +1,2413 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 this.options = options = options || {};
26 this.options = options = options || {};
27 this.base_url = options.base_url;
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
29 this.notebook_name = options.notebook_name;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 this.save_notebook = function() { // don't allow save until notebook_loaded
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
63 };
61 };
64 };
62
65
63 /**
66 /**
64 * Tweak the notebook's CSS style.
67 * Tweak the notebook's CSS style.
65 *
68 *
66 * @method style
69 * @method style
67 */
70 */
68 Notebook.prototype.style = function () {
71 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
72 $('div#notebook').addClass('border-box-sizing');
70 };
73 };
71
74
72 /**
75 /**
73 * Create an HTML and CSS representation of the notebook.
76 * Create an HTML and CSS representation of the notebook.
74 *
77 *
75 * @method create_elements
78 * @method create_elements
76 */
79 */
77 Notebook.prototype.create_elements = function () {
80 Notebook.prototype.create_elements = function () {
78 var that = this;
81 var that = this;
79 this.element.attr('tabindex','-1');
82 this.element.attr('tabindex','-1');
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
83 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
81 // We add this end_space div to the end of the notebook div to:
84 // We add this end_space div to the end of the notebook div to:
82 // i) provide a margin between the last cell and the end of the notebook
85 // i) provide a margin between the last cell and the end of the notebook
83 // ii) to prevent the div from scrolling up when the last cell is being
86 // ii) to prevent the div from scrolling up when the last cell is being
84 // edited, but is too low on the page, which browsers will do automatically.
87 // edited, but is too low on the page, which browsers will do automatically.
85 var end_space = $('<div/>').addClass('end_space');
88 var end_space = $('<div/>').addClass('end_space');
86 end_space.dblclick(function (e) {
89 end_space.dblclick(function (e) {
87 var ncells = that.ncells();
90 var ncells = that.ncells();
88 that.insert_cell_below('code',ncells-1);
91 that.insert_cell_below('code',ncells-1);
89 });
92 });
90 this.element.append(this.container);
93 this.element.append(this.container);
91 this.container.append(end_space);
94 this.container.append(end_space);
92 };
95 };
93
96
94 /**
97 /**
95 * Bind JavaScript events: key presses and custom IPython events.
98 * Bind JavaScript events: key presses and custom IPython events.
96 *
99 *
97 * @method bind_events
100 * @method bind_events
98 */
101 */
99 Notebook.prototype.bind_events = function () {
102 Notebook.prototype.bind_events = function () {
100 var that = this;
103 var that = this;
101
104
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
105 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
103 var index = that.find_cell_index(data.cell);
106 var index = that.find_cell_index(data.cell);
104 var new_cell = that.insert_cell_below('code',index);
107 var new_cell = that.insert_cell_below('code',index);
105 new_cell.set_text(data.text);
108 new_cell.set_text(data.text);
106 that.dirty = true;
109 that.dirty = true;
107 });
110 });
108
111
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
112 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
110 that.dirty = data.value;
113 that.dirty = data.value;
111 });
114 });
112
115
113 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
116 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
114 that.trusted = data.value;
117 that.trusted = data.value;
115 });
118 });
116
119
117 $([IPython.events]).on('select.Cell', function (event, data) {
120 $([IPython.events]).on('select.Cell', function (event, data) {
118 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
119 that.select(index);
122 that.select(index);
120 });
123 });
121
124
122 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
125 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
123 that.handle_edit_mode(data.cell);
126 that.handle_edit_mode(data.cell);
124 });
127 });
125
128
126 $([IPython.events]).on('command_mode.Cell', function (event, data) {
129 $([IPython.events]).on('command_mode.Cell', function (event, data) {
127 that.handle_command_mode(data.cell);
130 that.handle_command_mode(data.cell);
128 });
131 });
129
132
130 $([IPython.events]).on('status_autorestarting.Kernel', function () {
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
131 IPython.dialog.modal({
134 IPython.dialog.modal({
132 title: "Kernel Restarting",
135 title: "Kernel Restarting",
133 body: "The kernel appears to have died. It will restart automatically.",
136 body: "The kernel appears to have died. It will restart automatically.",
134 buttons: {
137 buttons: {
135 OK : {
138 OK : {
136 class : "btn-primary"
139 class : "btn-primary"
137 }
140 }
138 }
141 }
139 });
142 });
140 });
143 });
141
144
142 var collapse_time = function (time) {
145 var collapse_time = function (time) {
143 var app_height = $('#ipython-main-app').height(); // content height
146 var app_height = $('#ipython-main-app').height(); // content height
144 var splitter_height = $('div#pager_splitter').outerHeight(true);
147 var splitter_height = $('div#pager_splitter').outerHeight(true);
145 var new_height = app_height - splitter_height;
148 var new_height = app_height - splitter_height;
146 that.element.animate({height : new_height + 'px'}, time);
149 that.element.animate({height : new_height + 'px'}, time);
147 };
150 };
148
151
149 this.element.bind('collapse_pager', function (event, extrap) {
152 this.element.bind('collapse_pager', function (event, extrap) {
150 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
153 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
151 collapse_time(time);
154 collapse_time(time);
152 });
155 });
153
156
154 var expand_time = function (time) {
157 var expand_time = function (time) {
155 var app_height = $('#ipython-main-app').height(); // content height
158 var app_height = $('#ipython-main-app').height(); // content height
156 var splitter_height = $('div#pager_splitter').outerHeight(true);
159 var splitter_height = $('div#pager_splitter').outerHeight(true);
157 var pager_height = $('div#pager').outerHeight(true);
160 var pager_height = $('div#pager').outerHeight(true);
158 var new_height = app_height - pager_height - splitter_height;
161 var new_height = app_height - pager_height - splitter_height;
159 that.element.animate({height : new_height + 'px'}, time);
162 that.element.animate({height : new_height + 'px'}, time);
160 };
163 };
161
164
162 this.element.bind('expand_pager', function (event, extrap) {
165 this.element.bind('expand_pager', function (event, extrap) {
163 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
166 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
164 expand_time(time);
167 expand_time(time);
165 });
168 });
166
169
167 // Firefox 22 broke $(window).on("beforeunload")
170 // Firefox 22 broke $(window).on("beforeunload")
168 // I'm not sure why or how.
171 // I'm not sure why or how.
169 window.onbeforeunload = function (e) {
172 window.onbeforeunload = function (e) {
170 // TODO: Make killing the kernel configurable.
173 // TODO: Make killing the kernel configurable.
171 var kill_kernel = false;
174 var kill_kernel = false;
172 if (kill_kernel) {
175 if (kill_kernel) {
173 that.session.kill_kernel();
176 that.session.kill_kernel();
174 }
177 }
175 // if we are autosaving, trigger an autosave on nav-away.
178 // if we are autosaving, trigger an autosave on nav-away.
176 // still warn, because if we don't the autosave may fail.
179 // still warn, because if we don't the autosave may fail.
177 if (that.dirty) {
180 if (that.dirty) {
178 if ( that.autosave_interval ) {
181 if ( that.autosave_interval ) {
179 // schedule autosave in a timeout
182 // schedule autosave in a timeout
180 // this gives you a chance to forcefully discard changes
183 // this gives you a chance to forcefully discard changes
181 // by reloading the page if you *really* want to.
184 // by reloading the page if you *really* want to.
182 // the timer doesn't start until you *dismiss* the dialog.
185 // the timer doesn't start until you *dismiss* the dialog.
183 setTimeout(function () {
186 setTimeout(function () {
184 if (that.dirty) {
187 if (that.dirty) {
185 that.save_notebook();
188 that.save_notebook();
186 }
189 }
187 }, 1000);
190 }, 1000);
188 return "Autosave in progress, latest changes may be lost.";
191 return "Autosave in progress, latest changes may be lost.";
189 } else {
192 } else {
190 return "Unsaved changes will be lost.";
193 return "Unsaved changes will be lost.";
191 }
194 }
192 }
195 }
193 // Null is the *only* return value that will make the browser not
196 // Null is the *only* return value that will make the browser not
194 // pop up the "don't leave" dialog.
197 // pop up the "don't leave" dialog.
195 return null;
198 return null;
196 };
199 };
197 };
200 };
198
201
199 /**
202 /**
200 * Set the dirty flag, and trigger the set_dirty.Notebook event
203 * Set the dirty flag, and trigger the set_dirty.Notebook event
201 *
204 *
202 * @method set_dirty
205 * @method set_dirty
203 */
206 */
204 Notebook.prototype.set_dirty = function (value) {
207 Notebook.prototype.set_dirty = function (value) {
205 if (value === undefined) {
208 if (value === undefined) {
206 value = true;
209 value = true;
207 }
210 }
208 if (this.dirty == value) {
211 if (this.dirty == value) {
209 return;
212 return;
210 }
213 }
211 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
214 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
212 };
215 };
213
216
214 /**
217 /**
215 * Scroll the top of the page to a given cell.
218 * Scroll the top of the page to a given cell.
216 *
219 *
217 * @method scroll_to_cell
220 * @method scroll_to_cell
218 * @param {Number} cell_number An index of the cell to view
221 * @param {Number} cell_number An index of the cell to view
219 * @param {Number} time Animation time in milliseconds
222 * @param {Number} time Animation time in milliseconds
220 * @return {Number} Pixel offset from the top of the container
223 * @return {Number} Pixel offset from the top of the container
221 */
224 */
222 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
225 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
223 var cells = this.get_cells();
226 var cells = this.get_cells();
224 time = time || 0;
227 time = time || 0;
225 cell_number = Math.min(cells.length-1,cell_number);
228 cell_number = Math.min(cells.length-1,cell_number);
226 cell_number = Math.max(0 ,cell_number);
229 cell_number = Math.max(0 ,cell_number);
227 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
230 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
228 this.element.animate({scrollTop:scroll_value}, time);
231 this.element.animate({scrollTop:scroll_value}, time);
229 return scroll_value;
232 return scroll_value;
230 };
233 };
231
234
232 /**
235 /**
233 * Scroll to the bottom of the page.
236 * Scroll to the bottom of the page.
234 *
237 *
235 * @method scroll_to_bottom
238 * @method scroll_to_bottom
236 */
239 */
237 Notebook.prototype.scroll_to_bottom = function () {
240 Notebook.prototype.scroll_to_bottom = function () {
238 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
241 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
239 };
242 };
240
243
241 /**
244 /**
242 * Scroll to the top of the page.
245 * Scroll to the top of the page.
243 *
246 *
244 * @method scroll_to_top
247 * @method scroll_to_top
245 */
248 */
246 Notebook.prototype.scroll_to_top = function () {
249 Notebook.prototype.scroll_to_top = function () {
247 this.element.animate({scrollTop:0}, 0);
250 this.element.animate({scrollTop:0}, 0);
248 };
251 };
249
252
250 // Edit Notebook metadata
253 // Edit Notebook metadata
251
254
252 Notebook.prototype.edit_metadata = function () {
255 Notebook.prototype.edit_metadata = function () {
253 var that = this;
256 var that = this;
254 IPython.dialog.edit_metadata(this.metadata, function (md) {
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
255 that.metadata = md;
258 that.metadata = md;
256 }, 'Notebook');
259 }, 'Notebook');
257 };
260 };
258
261
259 // Cell indexing, retrieval, etc.
262 // Cell indexing, retrieval, etc.
260
263
261 /**
264 /**
262 * Get all cell elements in the notebook.
265 * Get all cell elements in the notebook.
263 *
266 *
264 * @method get_cell_elements
267 * @method get_cell_elements
265 * @return {jQuery} A selector of all cell elements
268 * @return {jQuery} A selector of all cell elements
266 */
269 */
267 Notebook.prototype.get_cell_elements = function () {
270 Notebook.prototype.get_cell_elements = function () {
268 return this.container.children("div.cell");
271 return this.container.children("div.cell");
269 };
272 };
270
273
271 /**
274 /**
272 * Get a particular cell element.
275 * Get a particular cell element.
273 *
276 *
274 * @method get_cell_element
277 * @method get_cell_element
275 * @param {Number} index An index of a cell to select
278 * @param {Number} index An index of a cell to select
276 * @return {jQuery} A selector of the given cell.
279 * @return {jQuery} A selector of the given cell.
277 */
280 */
278 Notebook.prototype.get_cell_element = function (index) {
281 Notebook.prototype.get_cell_element = function (index) {
279 var result = null;
282 var result = null;
280 var e = this.get_cell_elements().eq(index);
283 var e = this.get_cell_elements().eq(index);
281 if (e.length !== 0) {
284 if (e.length !== 0) {
282 result = e;
285 result = e;
283 }
286 }
284 return result;
287 return result;
285 };
288 };
286
289
287 /**
290 /**
288 * Try to get a particular cell by msg_id.
291 * Try to get a particular cell by msg_id.
289 *
292 *
290 * @method get_msg_cell
293 * @method get_msg_cell
291 * @param {String} msg_id A message UUID
294 * @param {String} msg_id A message UUID
292 * @return {Cell} Cell or null if no cell was found.
295 * @return {Cell} Cell or null if no cell was found.
293 */
296 */
294 Notebook.prototype.get_msg_cell = function (msg_id) {
297 Notebook.prototype.get_msg_cell = function (msg_id) {
295 return IPython.CodeCell.msg_cells[msg_id] || null;
298 return IPython.CodeCell.msg_cells[msg_id] || null;
296 };
299 };
297
300
298 /**
301 /**
299 * Count the cells in this notebook.
302 * Count the cells in this notebook.
300 *
303 *
301 * @method ncells
304 * @method ncells
302 * @return {Number} The number of cells in this notebook
305 * @return {Number} The number of cells in this notebook
303 */
306 */
304 Notebook.prototype.ncells = function () {
307 Notebook.prototype.ncells = function () {
305 return this.get_cell_elements().length;
308 return this.get_cell_elements().length;
306 };
309 };
307
310
308 /**
311 /**
309 * Get all Cell objects in this notebook.
312 * Get all Cell objects in this notebook.
310 *
313 *
311 * @method get_cells
314 * @method get_cells
312 * @return {Array} This notebook's Cell objects
315 * @return {Array} This notebook's Cell objects
313 */
316 */
314 // TODO: we are often calling cells as cells()[i], which we should optimize
317 // TODO: we are often calling cells as cells()[i], which we should optimize
315 // to cells(i) or a new method.
318 // to cells(i) or a new method.
316 Notebook.prototype.get_cells = function () {
319 Notebook.prototype.get_cells = function () {
317 return this.get_cell_elements().toArray().map(function (e) {
320 return this.get_cell_elements().toArray().map(function (e) {
318 return $(e).data("cell");
321 return $(e).data("cell");
319 });
322 });
320 };
323 };
321
324
322 /**
325 /**
323 * Get a Cell object from this notebook.
326 * Get a Cell object from this notebook.
324 *
327 *
325 * @method get_cell
328 * @method get_cell
326 * @param {Number} index An index of a cell to retrieve
329 * @param {Number} index An index of a cell to retrieve
327 * @return {Cell} A particular cell
330 * @return {Cell} A particular cell
328 */
331 */
329 Notebook.prototype.get_cell = function (index) {
332 Notebook.prototype.get_cell = function (index) {
330 var result = null;
333 var result = null;
331 var ce = this.get_cell_element(index);
334 var ce = this.get_cell_element(index);
332 if (ce !== null) {
335 if (ce !== null) {
333 result = ce.data('cell');
336 result = ce.data('cell');
334 }
337 }
335 return result;
338 return result;
336 };
339 };
337
340
338 /**
341 /**
339 * Get the cell below a given cell.
342 * Get the cell below a given cell.
340 *
343 *
341 * @method get_next_cell
344 * @method get_next_cell
342 * @param {Cell} cell The provided cell
345 * @param {Cell} cell The provided cell
343 * @return {Cell} The next cell
346 * @return {Cell} The next cell
344 */
347 */
345 Notebook.prototype.get_next_cell = function (cell) {
348 Notebook.prototype.get_next_cell = function (cell) {
346 var result = null;
349 var result = null;
347 var index = this.find_cell_index(cell);
350 var index = this.find_cell_index(cell);
348 if (this.is_valid_cell_index(index+1)) {
351 if (this.is_valid_cell_index(index+1)) {
349 result = this.get_cell(index+1);
352 result = this.get_cell(index+1);
350 }
353 }
351 return result;
354 return result;
352 };
355 };
353
356
354 /**
357 /**
355 * Get the cell above a given cell.
358 * Get the cell above a given cell.
356 *
359 *
357 * @method get_prev_cell
360 * @method get_prev_cell
358 * @param {Cell} cell The provided cell
361 * @param {Cell} cell The provided cell
359 * @return {Cell} The previous cell
362 * @return {Cell} The previous cell
360 */
363 */
361 Notebook.prototype.get_prev_cell = function (cell) {
364 Notebook.prototype.get_prev_cell = function (cell) {
362 // TODO: off-by-one
365 // TODO: off-by-one
363 // nb.get_prev_cell(nb.get_cell(1)) is null
366 // nb.get_prev_cell(nb.get_cell(1)) is null
364 var result = null;
367 var result = null;
365 var index = this.find_cell_index(cell);
368 var index = this.find_cell_index(cell);
366 if (index !== null && index > 1) {
369 if (index !== null && index > 1) {
367 result = this.get_cell(index-1);
370 result = this.get_cell(index-1);
368 }
371 }
369 return result;
372 return result;
370 };
373 };
371
374
372 /**
375 /**
373 * Get the numeric index of a given cell.
376 * Get the numeric index of a given cell.
374 *
377 *
375 * @method find_cell_index
378 * @method find_cell_index
376 * @param {Cell} cell The provided cell
379 * @param {Cell} cell The provided cell
377 * @return {Number} The cell's numeric index
380 * @return {Number} The cell's numeric index
378 */
381 */
379 Notebook.prototype.find_cell_index = function (cell) {
382 Notebook.prototype.find_cell_index = function (cell) {
380 var result = null;
383 var result = null;
381 this.get_cell_elements().filter(function (index) {
384 this.get_cell_elements().filter(function (index) {
382 if ($(this).data("cell") === cell) {
385 if ($(this).data("cell") === cell) {
383 result = index;
386 result = index;
384 }
387 }
385 });
388 });
386 return result;
389 return result;
387 };
390 };
388
391
389 /**
392 /**
390 * Get a given index , or the selected index if none is provided.
393 * Get a given index , or the selected index if none is provided.
391 *
394 *
392 * @method index_or_selected
395 * @method index_or_selected
393 * @param {Number} index A cell's index
396 * @param {Number} index A cell's index
394 * @return {Number} The given index, or selected index if none is provided.
397 * @return {Number} The given index, or selected index if none is provided.
395 */
398 */
396 Notebook.prototype.index_or_selected = function (index) {
399 Notebook.prototype.index_or_selected = function (index) {
397 var i;
400 var i;
398 if (index === undefined || index === null) {
401 if (index === undefined || index === null) {
399 i = this.get_selected_index();
402 i = this.get_selected_index();
400 if (i === null) {
403 if (i === null) {
401 i = 0;
404 i = 0;
402 }
405 }
403 } else {
406 } else {
404 i = index;
407 i = index;
405 }
408 }
406 return i;
409 return i;
407 };
410 };
408
411
409 /**
412 /**
410 * Get the currently selected cell.
413 * Get the currently selected cell.
411 * @method get_selected_cell
414 * @method get_selected_cell
412 * @return {Cell} The selected cell
415 * @return {Cell} The selected cell
413 */
416 */
414 Notebook.prototype.get_selected_cell = function () {
417 Notebook.prototype.get_selected_cell = function () {
415 var index = this.get_selected_index();
418 var index = this.get_selected_index();
416 return this.get_cell(index);
419 return this.get_cell(index);
417 };
420 };
418
421
419 /**
422 /**
420 * Check whether a cell index is valid.
423 * Check whether a cell index is valid.
421 *
424 *
422 * @method is_valid_cell_index
425 * @method is_valid_cell_index
423 * @param {Number} index A cell index
426 * @param {Number} index A cell index
424 * @return True if the index is valid, false otherwise
427 * @return True if the index is valid, false otherwise
425 */
428 */
426 Notebook.prototype.is_valid_cell_index = function (index) {
429 Notebook.prototype.is_valid_cell_index = function (index) {
427 if (index !== null && index >= 0 && index < this.ncells()) {
430 if (index !== null && index >= 0 && index < this.ncells()) {
428 return true;
431 return true;
429 } else {
432 } else {
430 return false;
433 return false;
431 }
434 }
432 };
435 };
433
436
434 /**
437 /**
435 * Get the index of the currently selected cell.
438 * Get the index of the currently selected cell.
436
439
437 * @method get_selected_index
440 * @method get_selected_index
438 * @return {Number} The selected cell's numeric index
441 * @return {Number} The selected cell's numeric index
439 */
442 */
440 Notebook.prototype.get_selected_index = function () {
443 Notebook.prototype.get_selected_index = function () {
441 var result = null;
444 var result = null;
442 this.get_cell_elements().filter(function (index) {
445 this.get_cell_elements().filter(function (index) {
443 if ($(this).data("cell").selected === true) {
446 if ($(this).data("cell").selected === true) {
444 result = index;
447 result = index;
445 }
448 }
446 });
449 });
447 return result;
450 return result;
448 };
451 };
449
452
450
453
451 // Cell selection.
454 // Cell selection.
452
455
453 /**
456 /**
454 * Programmatically select a cell.
457 * Programmatically select a cell.
455 *
458 *
456 * @method select
459 * @method select
457 * @param {Number} index A cell's index
460 * @param {Number} index A cell's index
458 * @return {Notebook} This notebook
461 * @return {Notebook} This notebook
459 */
462 */
460 Notebook.prototype.select = function (index) {
463 Notebook.prototype.select = function (index) {
461 if (this.is_valid_cell_index(index)) {
464 if (this.is_valid_cell_index(index)) {
462 var sindex = this.get_selected_index();
465 var sindex = this.get_selected_index();
463 if (sindex !== null && index !== sindex) {
466 if (sindex !== null && index !== sindex) {
464 // If we are about to select a different cell, make sure we are
467 // If we are about to select a different cell, make sure we are
465 // first in command mode.
468 // first in command mode.
466 if (this.mode !== 'command') {
469 if (this.mode !== 'command') {
467 this.command_mode();
470 this.command_mode();
468 }
471 }
469 this.get_cell(sindex).unselect();
472 this.get_cell(sindex).unselect();
470 }
473 }
471 var cell = this.get_cell(index);
474 var cell = this.get_cell(index);
472 cell.select();
475 cell.select();
473 if (cell.cell_type === 'heading') {
476 if (cell.cell_type === 'heading') {
474 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
477 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
475 {'cell_type':cell.cell_type,level:cell.level}
478 {'cell_type':cell.cell_type,level:cell.level}
476 );
479 );
477 } else {
480 } else {
478 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
479 {'cell_type':cell.cell_type}
482 {'cell_type':cell.cell_type}
480 );
483 );
481 }
484 }
482 }
485 }
483 return this;
486 return this;
484 };
487 };
485
488
486 /**
489 /**
487 * Programmatically select the next cell.
490 * Programmatically select the next cell.
488 *
491 *
489 * @method select_next
492 * @method select_next
490 * @return {Notebook} This notebook
493 * @return {Notebook} This notebook
491 */
494 */
492 Notebook.prototype.select_next = function () {
495 Notebook.prototype.select_next = function () {
493 var index = this.get_selected_index();
496 var index = this.get_selected_index();
494 this.select(index+1);
497 this.select(index+1);
495 return this;
498 return this;
496 };
499 };
497
500
498 /**
501 /**
499 * Programmatically select the previous cell.
502 * Programmatically select the previous cell.
500 *
503 *
501 * @method select_prev
504 * @method select_prev
502 * @return {Notebook} This notebook
505 * @return {Notebook} This notebook
503 */
506 */
504 Notebook.prototype.select_prev = function () {
507 Notebook.prototype.select_prev = function () {
505 var index = this.get_selected_index();
508 var index = this.get_selected_index();
506 this.select(index-1);
509 this.select(index-1);
507 return this;
510 return this;
508 };
511 };
509
512
510
513
511 // Edit/Command mode
514 // Edit/Command mode
512
515
513 /**
516 /**
514 * Gets the index of the cell that is in edit mode.
517 * Gets the index of the cell that is in edit mode.
515 *
518 *
516 * @method get_edit_index
519 * @method get_edit_index
517 *
520 *
518 * @return index {int}
521 * @return index {int}
519 **/
522 **/
520 Notebook.prototype.get_edit_index = function () {
523 Notebook.prototype.get_edit_index = function () {
521 var result = null;
524 var result = null;
522 this.get_cell_elements().filter(function (index) {
525 this.get_cell_elements().filter(function (index) {
523 if ($(this).data("cell").mode === 'edit') {
526 if ($(this).data("cell").mode === 'edit') {
524 result = index;
527 result = index;
525 }
528 }
526 });
529 });
527 return result;
530 return result;
528 };
531 };
529
532
530 /**
533 /**
531 * Handle when a a cell blurs and the notebook should enter command mode.
534 * Handle when a a cell blurs and the notebook should enter command mode.
532 *
535 *
533 * @method handle_command_mode
536 * @method handle_command_mode
534 * @param [cell] {Cell} Cell to enter command mode on.
537 * @param [cell] {Cell} Cell to enter command mode on.
535 **/
538 **/
536 Notebook.prototype.handle_command_mode = function (cell) {
539 Notebook.prototype.handle_command_mode = function (cell) {
537 if (this.mode !== 'command') {
540 if (this.mode !== 'command') {
538 cell.command_mode();
541 cell.command_mode();
539 this.mode = 'command';
542 this.mode = 'command';
540 $([IPython.events]).trigger('command_mode.Notebook');
543 $([IPython.events]).trigger('command_mode.Notebook');
541 IPython.keyboard_manager.command_mode();
544 IPython.keyboard_manager.command_mode();
542 }
545 }
543 };
546 };
544
547
545 /**
548 /**
546 * Make the notebook enter command mode.
549 * Make the notebook enter command mode.
547 *
550 *
548 * @method command_mode
551 * @method command_mode
549 **/
552 **/
550 Notebook.prototype.command_mode = function () {
553 Notebook.prototype.command_mode = function () {
551 var cell = this.get_cell(this.get_edit_index());
554 var cell = this.get_cell(this.get_edit_index());
552 if (cell && this.mode !== 'command') {
555 if (cell && this.mode !== 'command') {
553 // We don't call cell.command_mode, but rather call cell.focus_cell()
556 // We don't call cell.command_mode, but rather call cell.focus_cell()
554 // which will blur and CM editor and trigger the call to
557 // which will blur and CM editor and trigger the call to
555 // handle_command_mode.
558 // handle_command_mode.
556 cell.focus_cell();
559 cell.focus_cell();
557 }
560 }
558 };
561 };
559
562
560 /**
563 /**
561 * Handle when a cell fires it's edit_mode event.
564 * Handle when a cell fires it's edit_mode event.
562 *
565 *
563 * @method handle_edit_mode
566 * @method handle_edit_mode
564 * @param [cell] {Cell} Cell to enter edit mode on.
567 * @param [cell] {Cell} Cell to enter edit mode on.
565 **/
568 **/
566 Notebook.prototype.handle_edit_mode = function (cell) {
569 Notebook.prototype.handle_edit_mode = function (cell) {
567 if (cell && this.mode !== 'edit') {
570 if (cell && this.mode !== 'edit') {
568 cell.edit_mode();
571 cell.edit_mode();
569 this.mode = 'edit';
572 this.mode = 'edit';
570 $([IPython.events]).trigger('edit_mode.Notebook');
573 $([IPython.events]).trigger('edit_mode.Notebook');
571 IPython.keyboard_manager.edit_mode();
574 IPython.keyboard_manager.edit_mode();
572 }
575 }
573 };
576 };
574
577
575 /**
578 /**
576 * Make a cell enter edit mode.
579 * Make a cell enter edit mode.
577 *
580 *
578 * @method edit_mode
581 * @method edit_mode
579 **/
582 **/
580 Notebook.prototype.edit_mode = function () {
583 Notebook.prototype.edit_mode = function () {
581 var cell = this.get_selected_cell();
584 var cell = this.get_selected_cell();
582 if (cell && this.mode !== 'edit') {
585 if (cell && this.mode !== 'edit') {
583 cell.unrender();
586 cell.unrender();
584 cell.focus_editor();
587 cell.focus_editor();
585 }
588 }
586 };
589 };
587
590
588 /**
591 /**
589 * Focus the currently selected cell.
592 * Focus the currently selected cell.
590 *
593 *
591 * @method focus_cell
594 * @method focus_cell
592 **/
595 **/
593 Notebook.prototype.focus_cell = function () {
596 Notebook.prototype.focus_cell = function () {
594 var cell = this.get_selected_cell();
597 var cell = this.get_selected_cell();
595 if (cell === null) {return;} // No cell is selected
598 if (cell === null) {return;} // No cell is selected
596 cell.focus_cell();
599 cell.focus_cell();
597 };
600 };
598
601
599 // Cell movement
602 // Cell movement
600
603
601 /**
604 /**
602 * Move given (or selected) cell up and select it.
605 * Move given (or selected) cell up and select it.
603 *
606 *
604 * @method move_cell_up
607 * @method move_cell_up
605 * @param [index] {integer} cell index
608 * @param [index] {integer} cell index
606 * @return {Notebook} This notebook
609 * @return {Notebook} This notebook
607 **/
610 **/
608 Notebook.prototype.move_cell_up = function (index) {
611 Notebook.prototype.move_cell_up = function (index) {
609 var i = this.index_or_selected(index);
612 var i = this.index_or_selected(index);
610 if (this.is_valid_cell_index(i) && i > 0) {
613 if (this.is_valid_cell_index(i) && i > 0) {
611 var pivot = this.get_cell_element(i-1);
614 var pivot = this.get_cell_element(i-1);
612 var tomove = this.get_cell_element(i);
615 var tomove = this.get_cell_element(i);
613 if (pivot !== null && tomove !== null) {
616 if (pivot !== null && tomove !== null) {
614 tomove.detach();
617 tomove.detach();
615 pivot.before(tomove);
618 pivot.before(tomove);
616 this.select(i-1);
619 this.select(i-1);
617 var cell = this.get_selected_cell();
620 var cell = this.get_selected_cell();
618 cell.focus_cell();
621 cell.focus_cell();
619 }
622 }
620 this.set_dirty(true);
623 this.set_dirty(true);
621 }
624 }
622 return this;
625 return this;
623 };
626 };
624
627
625
628
626 /**
629 /**
627 * Move given (or selected) cell down and select it
630 * Move given (or selected) cell down and select it
628 *
631 *
629 * @method move_cell_down
632 * @method move_cell_down
630 * @param [index] {integer} cell index
633 * @param [index] {integer} cell index
631 * @return {Notebook} This notebook
634 * @return {Notebook} This notebook
632 **/
635 **/
633 Notebook.prototype.move_cell_down = function (index) {
636 Notebook.prototype.move_cell_down = function (index) {
634 var i = this.index_or_selected(index);
637 var i = this.index_or_selected(index);
635 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
638 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
636 var pivot = this.get_cell_element(i+1);
639 var pivot = this.get_cell_element(i+1);
637 var tomove = this.get_cell_element(i);
640 var tomove = this.get_cell_element(i);
638 if (pivot !== null && tomove !== null) {
641 if (pivot !== null && tomove !== null) {
639 tomove.detach();
642 tomove.detach();
640 pivot.after(tomove);
643 pivot.after(tomove);
641 this.select(i+1);
644 this.select(i+1);
642 var cell = this.get_selected_cell();
645 var cell = this.get_selected_cell();
643 cell.focus_cell();
646 cell.focus_cell();
644 }
647 }
645 }
648 }
646 this.set_dirty();
649 this.set_dirty();
647 return this;
650 return this;
648 };
651 };
649
652
650
653
651 // Insertion, deletion.
654 // Insertion, deletion.
652
655
653 /**
656 /**
654 * Delete a cell from the notebook.
657 * Delete a cell from the notebook.
655 *
658 *
656 * @method delete_cell
659 * @method delete_cell
657 * @param [index] A cell's numeric index
660 * @param [index] A cell's numeric index
658 * @return {Notebook} This notebook
661 * @return {Notebook} This notebook
659 */
662 */
660 Notebook.prototype.delete_cell = function (index) {
663 Notebook.prototype.delete_cell = function (index) {
661 var i = this.index_or_selected(index);
664 var i = this.index_or_selected(index);
662 var cell = this.get_selected_cell();
665 var cell = this.get_selected_cell();
663 this.undelete_backup = cell.toJSON();
666 this.undelete_backup = cell.toJSON();
664 $('#undelete_cell').removeClass('disabled');
667 $('#undelete_cell').removeClass('disabled');
665 if (this.is_valid_cell_index(i)) {
668 if (this.is_valid_cell_index(i)) {
666 var old_ncells = this.ncells();
669 var old_ncells = this.ncells();
667 var ce = this.get_cell_element(i);
670 var ce = this.get_cell_element(i);
668 ce.remove();
671 ce.remove();
669 if (i === 0) {
672 if (i === 0) {
670 // Always make sure we have at least one cell.
673 // Always make sure we have at least one cell.
671 if (old_ncells === 1) {
674 if (old_ncells === 1) {
672 this.insert_cell_below('code');
675 this.insert_cell_below('code');
673 }
676 }
674 this.select(0);
677 this.select(0);
675 this.undelete_index = 0;
678 this.undelete_index = 0;
676 this.undelete_below = false;
679 this.undelete_below = false;
677 } else if (i === old_ncells-1 && i !== 0) {
680 } else if (i === old_ncells-1 && i !== 0) {
678 this.select(i-1);
681 this.select(i-1);
679 this.undelete_index = i - 1;
682 this.undelete_index = i - 1;
680 this.undelete_below = true;
683 this.undelete_below = true;
681 } else {
684 } else {
682 this.select(i);
685 this.select(i);
683 this.undelete_index = i;
686 this.undelete_index = i;
684 this.undelete_below = false;
687 this.undelete_below = false;
685 }
688 }
686 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
689 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
687 this.set_dirty(true);
690 this.set_dirty(true);
688 }
691 }
689 return this;
692 return this;
690 };
693 };
691
694
692 /**
695 /**
693 * Restore the most recently deleted cell.
696 * Restore the most recently deleted cell.
694 *
697 *
695 * @method undelete
698 * @method undelete
696 */
699 */
697 Notebook.prototype.undelete_cell = function() {
700 Notebook.prototype.undelete_cell = function() {
698 if (this.undelete_backup !== null && this.undelete_index !== null) {
701 if (this.undelete_backup !== null && this.undelete_index !== null) {
699 var current_index = this.get_selected_index();
702 var current_index = this.get_selected_index();
700 if (this.undelete_index < current_index) {
703 if (this.undelete_index < current_index) {
701 current_index = current_index + 1;
704 current_index = current_index + 1;
702 }
705 }
703 if (this.undelete_index >= this.ncells()) {
706 if (this.undelete_index >= this.ncells()) {
704 this.select(this.ncells() - 1);
707 this.select(this.ncells() - 1);
705 }
708 }
706 else {
709 else {
707 this.select(this.undelete_index);
710 this.select(this.undelete_index);
708 }
711 }
709 var cell_data = this.undelete_backup;
712 var cell_data = this.undelete_backup;
710 var new_cell = null;
713 var new_cell = null;
711 if (this.undelete_below) {
714 if (this.undelete_below) {
712 new_cell = this.insert_cell_below(cell_data.cell_type);
715 new_cell = this.insert_cell_below(cell_data.cell_type);
713 } else {
716 } else {
714 new_cell = this.insert_cell_above(cell_data.cell_type);
717 new_cell = this.insert_cell_above(cell_data.cell_type);
715 }
718 }
716 new_cell.fromJSON(cell_data);
719 new_cell.fromJSON(cell_data);
717 if (this.undelete_below) {
720 if (this.undelete_below) {
718 this.select(current_index+1);
721 this.select(current_index+1);
719 } else {
722 } else {
720 this.select(current_index);
723 this.select(current_index);
721 }
724 }
722 this.undelete_backup = null;
725 this.undelete_backup = null;
723 this.undelete_index = null;
726 this.undelete_index = null;
724 }
727 }
725 $('#undelete_cell').addClass('disabled');
728 $('#undelete_cell').addClass('disabled');
726 };
729 };
727
730
728 /**
731 /**
729 * Insert a cell so that after insertion the cell is at given index.
732 * Insert a cell so that after insertion the cell is at given index.
730 *
733 *
731 * Similar to insert_above, but index parameter is mandatory
734 * Similar to insert_above, but index parameter is mandatory
732 *
735 *
733 * Index will be brought back into the accissible range [0,n]
736 * Index will be brought back into the accissible range [0,n]
734 *
737 *
735 * @method insert_cell_at_index
738 * @method insert_cell_at_index
736 * @param type {string} in ['code','markdown','heading']
739 * @param type {string} in ['code','markdown','heading']
737 * @param [index] {int} a valid index where to inser cell
740 * @param [index] {int} a valid index where to inser cell
738 *
741 *
739 * @return cell {cell|null} created cell or null
742 * @return cell {cell|null} created cell or null
740 **/
743 **/
741 Notebook.prototype.insert_cell_at_index = function(type, index){
744 Notebook.prototype.insert_cell_at_index = function(type, index){
742
745
743 var ncells = this.ncells();
746 var ncells = this.ncells();
744 index = Math.min(index,ncells);
747 index = Math.min(index,ncells);
745 index = Math.max(index,0);
748 index = Math.max(index,0);
746 var cell = null;
749 var cell = null;
747
750
748 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
751 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
749 if (type === 'code') {
752 if (type === 'code') {
750 cell = new IPython.CodeCell(this.kernel);
753 cell = new IPython.CodeCell(this.kernel);
751 cell.set_input_prompt();
754 cell.set_input_prompt();
752 } else if (type === 'markdown') {
755 } else if (type === 'markdown') {
753 cell = new IPython.MarkdownCell();
756 cell = new IPython.MarkdownCell();
754 } else if (type === 'raw') {
757 } else if (type === 'raw') {
755 cell = new IPython.RawCell();
758 cell = new IPython.RawCell();
756 } else if (type === 'heading') {
759 } else if (type === 'heading') {
757 cell = new IPython.HeadingCell();
760 cell = new IPython.HeadingCell();
758 }
761 }
759
762
760 if(this._insert_element_at_index(cell.element,index)) {
763 if(this._insert_element_at_index(cell.element,index)) {
761 cell.render();
764 cell.render();
762 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
765 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
763 cell.refresh();
766 cell.refresh();
764 // We used to select the cell after we refresh it, but there
767 // We used to select the cell after we refresh it, but there
765 // are now cases were this method is called where select is
768 // are now cases were this method is called where select is
766 // not appropriate. The selection logic should be handled by the
769 // not appropriate. The selection logic should be handled by the
767 // caller of the the top level insert_cell methods.
770 // caller of the the top level insert_cell methods.
768 this.set_dirty(true);
771 this.set_dirty(true);
769 }
772 }
770 }
773 }
771 return cell;
774 return cell;
772
775
773 };
776 };
774
777
775 /**
778 /**
776 * Insert an element at given cell index.
779 * Insert an element at given cell index.
777 *
780 *
778 * @method _insert_element_at_index
781 * @method _insert_element_at_index
779 * @param element {dom element} a cell element
782 * @param element {dom element} a cell element
780 * @param [index] {int} a valid index where to inser cell
783 * @param [index] {int} a valid index where to inser cell
781 * @private
784 * @private
782 *
785 *
783 * return true if everything whent fine.
786 * return true if everything whent fine.
784 **/
787 **/
785 Notebook.prototype._insert_element_at_index = function(element, index){
788 Notebook.prototype._insert_element_at_index = function(element, index){
786 if (element === undefined){
789 if (element === undefined){
787 return false;
790 return false;
788 }
791 }
789
792
790 var ncells = this.ncells();
793 var ncells = this.ncells();
791
794
792 if (ncells === 0) {
795 if (ncells === 0) {
793 // special case append if empty
796 // special case append if empty
794 this.element.find('div.end_space').before(element);
797 this.element.find('div.end_space').before(element);
795 } else if ( ncells === index ) {
798 } else if ( ncells === index ) {
796 // special case append it the end, but not empty
799 // special case append it the end, but not empty
797 this.get_cell_element(index-1).after(element);
800 this.get_cell_element(index-1).after(element);
798 } else if (this.is_valid_cell_index(index)) {
801 } else if (this.is_valid_cell_index(index)) {
799 // otherwise always somewhere to append to
802 // otherwise always somewhere to append to
800 this.get_cell_element(index).before(element);
803 this.get_cell_element(index).before(element);
801 } else {
804 } else {
802 return false;
805 return false;
803 }
806 }
804
807
805 if (this.undelete_index !== null && index <= this.undelete_index) {
808 if (this.undelete_index !== null && index <= this.undelete_index) {
806 this.undelete_index = this.undelete_index + 1;
809 this.undelete_index = this.undelete_index + 1;
807 this.set_dirty(true);
810 this.set_dirty(true);
808 }
811 }
809 return true;
812 return true;
810 };
813 };
811
814
812 /**
815 /**
813 * Insert a cell of given type above given index, or at top
816 * Insert a cell of given type above given index, or at top
814 * of notebook if index smaller than 0.
817 * of notebook if index smaller than 0.
815 *
818 *
816 * default index value is the one of currently selected cell
819 * default index value is the one of currently selected cell
817 *
820 *
818 * @method insert_cell_above
821 * @method insert_cell_above
819 * @param type {string} cell type
822 * @param type {string} cell type
820 * @param [index] {integer}
823 * @param [index] {integer}
821 *
824 *
822 * @return handle to created cell or null
825 * @return handle to created cell or null
823 **/
826 **/
824 Notebook.prototype.insert_cell_above = function (type, index) {
827 Notebook.prototype.insert_cell_above = function (type, index) {
825 index = this.index_or_selected(index);
828 index = this.index_or_selected(index);
826 return this.insert_cell_at_index(type, index);
829 return this.insert_cell_at_index(type, index);
827 };
830 };
828
831
829 /**
832 /**
830 * Insert a cell of given type below given index, or at bottom
833 * Insert a cell of given type below given index, or at bottom
831 * of notebook if index greater thatn number of cell
834 * of notebook if index greater thatn number of cell
832 *
835 *
833 * default index value is the one of currently selected cell
836 * default index value is the one of currently selected cell
834 *
837 *
835 * @method insert_cell_below
838 * @method insert_cell_below
836 * @param type {string} cell type
839 * @param type {string} cell type
837 * @param [index] {integer}
840 * @param [index] {integer}
838 *
841 *
839 * @return handle to created cell or null
842 * @return handle to created cell or null
840 *
843 *
841 **/
844 **/
842 Notebook.prototype.insert_cell_below = function (type, index) {
845 Notebook.prototype.insert_cell_below = function (type, index) {
843 index = this.index_or_selected(index);
846 index = this.index_or_selected(index);
844 return this.insert_cell_at_index(type, index+1);
847 return this.insert_cell_at_index(type, index+1);
845 };
848 };
846
849
847
850
848 /**
851 /**
849 * Insert cell at end of notebook
852 * Insert cell at end of notebook
850 *
853 *
851 * @method insert_cell_at_bottom
854 * @method insert_cell_at_bottom
852 * @param {String} type cell type
855 * @param {String} type cell type
853 *
856 *
854 * @return the added cell; or null
857 * @return the added cell; or null
855 **/
858 **/
856 Notebook.prototype.insert_cell_at_bottom = function (type){
859 Notebook.prototype.insert_cell_at_bottom = function (type){
857 var len = this.ncells();
860 var len = this.ncells();
858 return this.insert_cell_below(type,len-1);
861 return this.insert_cell_below(type,len-1);
859 };
862 };
860
863
861 /**
864 /**
862 * Turn a cell into a code cell.
865 * Turn a cell into a code cell.
863 *
866 *
864 * @method to_code
867 * @method to_code
865 * @param {Number} [index] A cell's index
868 * @param {Number} [index] A cell's index
866 */
869 */
867 Notebook.prototype.to_code = function (index) {
870 Notebook.prototype.to_code = function (index) {
868 var i = this.index_or_selected(index);
871 var i = this.index_or_selected(index);
869 if (this.is_valid_cell_index(i)) {
872 if (this.is_valid_cell_index(i)) {
870 var source_element = this.get_cell_element(i);
873 var source_element = this.get_cell_element(i);
871 var source_cell = source_element.data("cell");
874 var source_cell = source_element.data("cell");
872 if (!(source_cell instanceof IPython.CodeCell)) {
875 if (!(source_cell instanceof IPython.CodeCell)) {
873 var target_cell = this.insert_cell_below('code',i);
876 var target_cell = this.insert_cell_below('code',i);
874 var text = source_cell.get_text();
877 var text = source_cell.get_text();
875 if (text === source_cell.placeholder) {
878 if (text === source_cell.placeholder) {
876 text = '';
879 text = '';
877 }
880 }
878 target_cell.set_text(text);
881 target_cell.set_text(text);
879 // make this value the starting point, so that we can only undo
882 // make this value the starting point, so that we can only undo
880 // to this state, instead of a blank cell
883 // to this state, instead of a blank cell
881 target_cell.code_mirror.clearHistory();
884 target_cell.code_mirror.clearHistory();
882 source_element.remove();
885 source_element.remove();
883 this.select(i);
886 this.select(i);
884 this.set_dirty(true);
887 this.set_dirty(true);
885 }
888 }
886 }
889 }
887 };
890 };
888
891
889 /**
892 /**
890 * Turn a cell into a Markdown cell.
893 * Turn a cell into a Markdown cell.
891 *
894 *
892 * @method to_markdown
895 * @method to_markdown
893 * @param {Number} [index] A cell's index
896 * @param {Number} [index] A cell's index
894 */
897 */
895 Notebook.prototype.to_markdown = function (index) {
898 Notebook.prototype.to_markdown = function (index) {
896 var i = this.index_or_selected(index);
899 var i = this.index_or_selected(index);
897 if (this.is_valid_cell_index(i)) {
900 if (this.is_valid_cell_index(i)) {
898 var source_element = this.get_cell_element(i);
901 var source_element = this.get_cell_element(i);
899 var source_cell = source_element.data("cell");
902 var source_cell = source_element.data("cell");
900 if (!(source_cell instanceof IPython.MarkdownCell)) {
903 if (!(source_cell instanceof IPython.MarkdownCell)) {
901 var target_cell = this.insert_cell_below('markdown',i);
904 var target_cell = this.insert_cell_below('markdown',i);
902 var text = source_cell.get_text();
905 var text = source_cell.get_text();
903 if (text === source_cell.placeholder) {
906 if (text === source_cell.placeholder) {
904 text = '';
907 text = '';
905 }
908 }
906 // We must show the editor before setting its contents
909 // We must show the editor before setting its contents
907 target_cell.unrender();
910 target_cell.unrender();
908 target_cell.set_text(text);
911 target_cell.set_text(text);
909 // make this value the starting point, so that we can only undo
912 // make this value the starting point, so that we can only undo
910 // to this state, instead of a blank cell
913 // to this state, instead of a blank cell
911 target_cell.code_mirror.clearHistory();
914 target_cell.code_mirror.clearHistory();
912 source_element.remove();
915 source_element.remove();
913 this.select(i);
916 this.select(i);
914 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
917 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
915 target_cell.render();
918 target_cell.render();
916 }
919 }
917 this.set_dirty(true);
920 this.set_dirty(true);
918 }
921 }
919 }
922 }
920 };
923 };
921
924
922 /**
925 /**
923 * Turn a cell into a raw text cell.
926 * Turn a cell into a raw text cell.
924 *
927 *
925 * @method to_raw
928 * @method to_raw
926 * @param {Number} [index] A cell's index
929 * @param {Number} [index] A cell's index
927 */
930 */
928 Notebook.prototype.to_raw = function (index) {
931 Notebook.prototype.to_raw = function (index) {
929 var i = this.index_or_selected(index);
932 var i = this.index_or_selected(index);
930 if (this.is_valid_cell_index(i)) {
933 if (this.is_valid_cell_index(i)) {
931 var source_element = this.get_cell_element(i);
934 var source_element = this.get_cell_element(i);
932 var source_cell = source_element.data("cell");
935 var source_cell = source_element.data("cell");
933 var target_cell = null;
936 var target_cell = null;
934 if (!(source_cell instanceof IPython.RawCell)) {
937 if (!(source_cell instanceof IPython.RawCell)) {
935 target_cell = this.insert_cell_below('raw',i);
938 target_cell = this.insert_cell_below('raw',i);
936 var text = source_cell.get_text();
939 var text = source_cell.get_text();
937 if (text === source_cell.placeholder) {
940 if (text === source_cell.placeholder) {
938 text = '';
941 text = '';
939 }
942 }
940 // We must show the editor before setting its contents
943 // We must show the editor before setting its contents
941 target_cell.unrender();
944 target_cell.unrender();
942 target_cell.set_text(text);
945 target_cell.set_text(text);
943 // make this value the starting point, so that we can only undo
946 // make this value the starting point, so that we can only undo
944 // to this state, instead of a blank cell
947 // to this state, instead of a blank cell
945 target_cell.code_mirror.clearHistory();
948 target_cell.code_mirror.clearHistory();
946 source_element.remove();
949 source_element.remove();
947 this.select(i);
950 this.select(i);
948 this.set_dirty(true);
951 this.set_dirty(true);
949 }
952 }
950 }
953 }
951 };
954 };
952
955
953 /**
956 /**
954 * Turn a cell into a heading cell.
957 * Turn a cell into a heading cell.
955 *
958 *
956 * @method to_heading
959 * @method to_heading
957 * @param {Number} [index] A cell's index
960 * @param {Number} [index] A cell's index
958 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
961 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
959 */
962 */
960 Notebook.prototype.to_heading = function (index, level) {
963 Notebook.prototype.to_heading = function (index, level) {
961 level = level || 1;
964 level = level || 1;
962 var i = this.index_or_selected(index);
965 var i = this.index_or_selected(index);
963 if (this.is_valid_cell_index(i)) {
966 if (this.is_valid_cell_index(i)) {
964 var source_element = this.get_cell_element(i);
967 var source_element = this.get_cell_element(i);
965 var source_cell = source_element.data("cell");
968 var source_cell = source_element.data("cell");
966 var target_cell = null;
969 var target_cell = null;
967 if (source_cell instanceof IPython.HeadingCell) {
970 if (source_cell instanceof IPython.HeadingCell) {
968 source_cell.set_level(level);
971 source_cell.set_level(level);
969 } else {
972 } else {
970 target_cell = this.insert_cell_below('heading',i);
973 target_cell = this.insert_cell_below('heading',i);
971 var text = source_cell.get_text();
974 var text = source_cell.get_text();
972 if (text === source_cell.placeholder) {
975 if (text === source_cell.placeholder) {
973 text = '';
976 text = '';
974 }
977 }
975 // We must show the editor before setting its contents
978 // We must show the editor before setting its contents
976 target_cell.set_level(level);
979 target_cell.set_level(level);
977 target_cell.unrender();
980 target_cell.unrender();
978 target_cell.set_text(text);
981 target_cell.set_text(text);
979 // make this value the starting point, so that we can only undo
982 // make this value the starting point, so that we can only undo
980 // to this state, instead of a blank cell
983 // to this state, instead of a blank cell
981 target_cell.code_mirror.clearHistory();
984 target_cell.code_mirror.clearHistory();
982 source_element.remove();
985 source_element.remove();
983 this.select(i);
986 this.select(i);
984 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
987 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
985 target_cell.render();
988 target_cell.render();
986 }
989 }
987 }
990 }
988 this.set_dirty(true);
991 this.set_dirty(true);
989 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
992 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
990 {'cell_type':'heading',level:level}
993 {'cell_type':'heading',level:level}
991 );
994 );
992 }
995 }
993 };
996 };
994
997
995
998
996 // Cut/Copy/Paste
999 // Cut/Copy/Paste
997
1000
998 /**
1001 /**
999 * Enable UI elements for pasting cells.
1002 * Enable UI elements for pasting cells.
1000 *
1003 *
1001 * @method enable_paste
1004 * @method enable_paste
1002 */
1005 */
1003 Notebook.prototype.enable_paste = function () {
1006 Notebook.prototype.enable_paste = function () {
1004 var that = this;
1007 var that = this;
1005 if (!this.paste_enabled) {
1008 if (!this.paste_enabled) {
1006 $('#paste_cell_replace').removeClass('disabled')
1009 $('#paste_cell_replace').removeClass('disabled')
1007 .on('click', function () {that.paste_cell_replace();});
1010 .on('click', function () {that.paste_cell_replace();});
1008 $('#paste_cell_above').removeClass('disabled')
1011 $('#paste_cell_above').removeClass('disabled')
1009 .on('click', function () {that.paste_cell_above();});
1012 .on('click', function () {that.paste_cell_above();});
1010 $('#paste_cell_below').removeClass('disabled')
1013 $('#paste_cell_below').removeClass('disabled')
1011 .on('click', function () {that.paste_cell_below();});
1014 .on('click', function () {that.paste_cell_below();});
1012 this.paste_enabled = true;
1015 this.paste_enabled = true;
1013 }
1016 }
1014 };
1017 };
1015
1018
1016 /**
1019 /**
1017 * Disable UI elements for pasting cells.
1020 * Disable UI elements for pasting cells.
1018 *
1021 *
1019 * @method disable_paste
1022 * @method disable_paste
1020 */
1023 */
1021 Notebook.prototype.disable_paste = function () {
1024 Notebook.prototype.disable_paste = function () {
1022 if (this.paste_enabled) {
1025 if (this.paste_enabled) {
1023 $('#paste_cell_replace').addClass('disabled').off('click');
1026 $('#paste_cell_replace').addClass('disabled').off('click');
1024 $('#paste_cell_above').addClass('disabled').off('click');
1027 $('#paste_cell_above').addClass('disabled').off('click');
1025 $('#paste_cell_below').addClass('disabled').off('click');
1028 $('#paste_cell_below').addClass('disabled').off('click');
1026 this.paste_enabled = false;
1029 this.paste_enabled = false;
1027 }
1030 }
1028 };
1031 };
1029
1032
1030 /**
1033 /**
1031 * Cut a cell.
1034 * Cut a cell.
1032 *
1035 *
1033 * @method cut_cell
1036 * @method cut_cell
1034 */
1037 */
1035 Notebook.prototype.cut_cell = function () {
1038 Notebook.prototype.cut_cell = function () {
1036 this.copy_cell();
1039 this.copy_cell();
1037 this.delete_cell();
1040 this.delete_cell();
1038 };
1041 };
1039
1042
1040 /**
1043 /**
1041 * Copy a cell.
1044 * Copy a cell.
1042 *
1045 *
1043 * @method copy_cell
1046 * @method copy_cell
1044 */
1047 */
1045 Notebook.prototype.copy_cell = function () {
1048 Notebook.prototype.copy_cell = function () {
1046 var cell = this.get_selected_cell();
1049 var cell = this.get_selected_cell();
1047 this.clipboard = cell.toJSON();
1050 this.clipboard = cell.toJSON();
1048 this.enable_paste();
1051 this.enable_paste();
1049 };
1052 };
1050
1053
1051 /**
1054 /**
1052 * Replace the selected cell with a cell in the clipboard.
1055 * Replace the selected cell with a cell in the clipboard.
1053 *
1056 *
1054 * @method paste_cell_replace
1057 * @method paste_cell_replace
1055 */
1058 */
1056 Notebook.prototype.paste_cell_replace = function () {
1059 Notebook.prototype.paste_cell_replace = function () {
1057 if (this.clipboard !== null && this.paste_enabled) {
1060 if (this.clipboard !== null && this.paste_enabled) {
1058 var cell_data = this.clipboard;
1061 var cell_data = this.clipboard;
1059 var new_cell = this.insert_cell_above(cell_data.cell_type);
1062 var new_cell = this.insert_cell_above(cell_data.cell_type);
1060 new_cell.fromJSON(cell_data);
1063 new_cell.fromJSON(cell_data);
1061 var old_cell = this.get_next_cell(new_cell);
1064 var old_cell = this.get_next_cell(new_cell);
1062 this.delete_cell(this.find_cell_index(old_cell));
1065 this.delete_cell(this.find_cell_index(old_cell));
1063 this.select(this.find_cell_index(new_cell));
1066 this.select(this.find_cell_index(new_cell));
1064 }
1067 }
1065 };
1068 };
1066
1069
1067 /**
1070 /**
1068 * Paste a cell from the clipboard above the selected cell.
1071 * Paste a cell from the clipboard above the selected cell.
1069 *
1072 *
1070 * @method paste_cell_above
1073 * @method paste_cell_above
1071 */
1074 */
1072 Notebook.prototype.paste_cell_above = function () {
1075 Notebook.prototype.paste_cell_above = function () {
1073 if (this.clipboard !== null && this.paste_enabled) {
1076 if (this.clipboard !== null && this.paste_enabled) {
1074 var cell_data = this.clipboard;
1077 var cell_data = this.clipboard;
1075 var new_cell = this.insert_cell_above(cell_data.cell_type);
1078 var new_cell = this.insert_cell_above(cell_data.cell_type);
1076 new_cell.fromJSON(cell_data);
1079 new_cell.fromJSON(cell_data);
1077 new_cell.focus_cell();
1080 new_cell.focus_cell();
1078 }
1081 }
1079 };
1082 };
1080
1083
1081 /**
1084 /**
1082 * Paste a cell from the clipboard below the selected cell.
1085 * Paste a cell from the clipboard below the selected cell.
1083 *
1086 *
1084 * @method paste_cell_below
1087 * @method paste_cell_below
1085 */
1088 */
1086 Notebook.prototype.paste_cell_below = function () {
1089 Notebook.prototype.paste_cell_below = function () {
1087 if (this.clipboard !== null && this.paste_enabled) {
1090 if (this.clipboard !== null && this.paste_enabled) {
1088 var cell_data = this.clipboard;
1091 var cell_data = this.clipboard;
1089 var new_cell = this.insert_cell_below(cell_data.cell_type);
1092 var new_cell = this.insert_cell_below(cell_data.cell_type);
1090 new_cell.fromJSON(cell_data);
1093 new_cell.fromJSON(cell_data);
1091 new_cell.focus_cell();
1094 new_cell.focus_cell();
1092 }
1095 }
1093 };
1096 };
1094
1097
1095 // Split/merge
1098 // Split/merge
1096
1099
1097 /**
1100 /**
1098 * Split the selected cell into two, at the cursor.
1101 * Split the selected cell into two, at the cursor.
1099 *
1102 *
1100 * @method split_cell
1103 * @method split_cell
1101 */
1104 */
1102 Notebook.prototype.split_cell = function () {
1105 Notebook.prototype.split_cell = function () {
1103 var mdc = IPython.MarkdownCell;
1106 var mdc = IPython.MarkdownCell;
1104 var rc = IPython.RawCell;
1107 var rc = IPython.RawCell;
1105 var cell = this.get_selected_cell();
1108 var cell = this.get_selected_cell();
1106 if (cell.is_splittable()) {
1109 if (cell.is_splittable()) {
1107 var texta = cell.get_pre_cursor();
1110 var texta = cell.get_pre_cursor();
1108 var textb = cell.get_post_cursor();
1111 var textb = cell.get_post_cursor();
1109 if (cell instanceof IPython.CodeCell) {
1112 if (cell instanceof IPython.CodeCell) {
1110 // In this case the operations keep the notebook in its existing mode
1113 // In this case the operations keep the notebook in its existing mode
1111 // so we don't need to do any post-op mode changes.
1114 // so we don't need to do any post-op mode changes.
1112 cell.set_text(textb);
1115 cell.set_text(textb);
1113 var new_cell = this.insert_cell_above('code');
1116 var new_cell = this.insert_cell_above('code');
1114 new_cell.set_text(texta);
1117 new_cell.set_text(texta);
1115 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1118 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1116 // We know cell is !rendered so we can use set_text.
1119 // We know cell is !rendered so we can use set_text.
1117 cell.set_text(textb);
1120 cell.set_text(textb);
1118 var new_cell = this.insert_cell_above(cell.cell_type);
1121 var new_cell = this.insert_cell_above(cell.cell_type);
1119 // Unrender the new cell so we can call set_text.
1122 // Unrender the new cell so we can call set_text.
1120 new_cell.unrender();
1123 new_cell.unrender();
1121 new_cell.set_text(texta);
1124 new_cell.set_text(texta);
1122 }
1125 }
1123 }
1126 }
1124 };
1127 };
1125
1128
1126 /**
1129 /**
1127 * Combine the selected cell into the cell above it.
1130 * Combine the selected cell into the cell above it.
1128 *
1131 *
1129 * @method merge_cell_above
1132 * @method merge_cell_above
1130 */
1133 */
1131 Notebook.prototype.merge_cell_above = function () {
1134 Notebook.prototype.merge_cell_above = function () {
1132 var mdc = IPython.MarkdownCell;
1135 var mdc = IPython.MarkdownCell;
1133 var rc = IPython.RawCell;
1136 var rc = IPython.RawCell;
1134 var index = this.get_selected_index();
1137 var index = this.get_selected_index();
1135 var cell = this.get_cell(index);
1138 var cell = this.get_cell(index);
1136 var render = cell.rendered;
1139 var render = cell.rendered;
1137 if (!cell.is_mergeable()) {
1140 if (!cell.is_mergeable()) {
1138 return;
1141 return;
1139 }
1142 }
1140 if (index > 0) {
1143 if (index > 0) {
1141 var upper_cell = this.get_cell(index-1);
1144 var upper_cell = this.get_cell(index-1);
1142 if (!upper_cell.is_mergeable()) {
1145 if (!upper_cell.is_mergeable()) {
1143 return;
1146 return;
1144 }
1147 }
1145 var upper_text = upper_cell.get_text();
1148 var upper_text = upper_cell.get_text();
1146 var text = cell.get_text();
1149 var text = cell.get_text();
1147 if (cell instanceof IPython.CodeCell) {
1150 if (cell instanceof IPython.CodeCell) {
1148 cell.set_text(upper_text+'\n'+text);
1151 cell.set_text(upper_text+'\n'+text);
1149 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1152 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1150 cell.unrender(); // Must unrender before we set_text.
1153 cell.unrender(); // Must unrender before we set_text.
1151 cell.set_text(upper_text+'\n\n'+text);
1154 cell.set_text(upper_text+'\n\n'+text);
1152 if (render) {
1155 if (render) {
1153 // The rendered state of the final cell should match
1156 // The rendered state of the final cell should match
1154 // that of the original selected cell;
1157 // that of the original selected cell;
1155 cell.render();
1158 cell.render();
1156 }
1159 }
1157 }
1160 }
1158 this.delete_cell(index-1);
1161 this.delete_cell(index-1);
1159 this.select(this.find_cell_index(cell));
1162 this.select(this.find_cell_index(cell));
1160 }
1163 }
1161 };
1164 };
1162
1165
1163 /**
1166 /**
1164 * Combine the selected cell into the cell below it.
1167 * Combine the selected cell into the cell below it.
1165 *
1168 *
1166 * @method merge_cell_below
1169 * @method merge_cell_below
1167 */
1170 */
1168 Notebook.prototype.merge_cell_below = function () {
1171 Notebook.prototype.merge_cell_below = function () {
1169 var mdc = IPython.MarkdownCell;
1172 var mdc = IPython.MarkdownCell;
1170 var rc = IPython.RawCell;
1173 var rc = IPython.RawCell;
1171 var index = this.get_selected_index();
1174 var index = this.get_selected_index();
1172 var cell = this.get_cell(index);
1175 var cell = this.get_cell(index);
1173 var render = cell.rendered;
1176 var render = cell.rendered;
1174 if (!cell.is_mergeable()) {
1177 if (!cell.is_mergeable()) {
1175 return;
1178 return;
1176 }
1179 }
1177 if (index < this.ncells()-1) {
1180 if (index < this.ncells()-1) {
1178 var lower_cell = this.get_cell(index+1);
1181 var lower_cell = this.get_cell(index+1);
1179 if (!lower_cell.is_mergeable()) {
1182 if (!lower_cell.is_mergeable()) {
1180 return;
1183 return;
1181 }
1184 }
1182 var lower_text = lower_cell.get_text();
1185 var lower_text = lower_cell.get_text();
1183 var text = cell.get_text();
1186 var text = cell.get_text();
1184 if (cell instanceof IPython.CodeCell) {
1187 if (cell instanceof IPython.CodeCell) {
1185 cell.set_text(text+'\n'+lower_text);
1188 cell.set_text(text+'\n'+lower_text);
1186 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1189 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1187 cell.unrender(); // Must unrender before we set_text.
1190 cell.unrender(); // Must unrender before we set_text.
1188 cell.set_text(text+'\n\n'+lower_text);
1191 cell.set_text(text+'\n\n'+lower_text);
1189 if (render) {
1192 if (render) {
1190 // The rendered state of the final cell should match
1193 // The rendered state of the final cell should match
1191 // that of the original selected cell;
1194 // that of the original selected cell;
1192 cell.render();
1195 cell.render();
1193 }
1196 }
1194 }
1197 }
1195 this.delete_cell(index+1);
1198 this.delete_cell(index+1);
1196 this.select(this.find_cell_index(cell));
1199 this.select(this.find_cell_index(cell));
1197 }
1200 }
1198 };
1201 };
1199
1202
1200
1203
1201 // Cell collapsing and output clearing
1204 // Cell collapsing and output clearing
1202
1205
1203 /**
1206 /**
1204 * Hide a cell's output.
1207 * Hide a cell's output.
1205 *
1208 *
1206 * @method collapse_output
1209 * @method collapse_output
1207 * @param {Number} index A cell's numeric index
1210 * @param {Number} index A cell's numeric index
1208 */
1211 */
1209 Notebook.prototype.collapse_output = function (index) {
1212 Notebook.prototype.collapse_output = function (index) {
1210 var i = this.index_or_selected(index);
1213 var i = this.index_or_selected(index);
1211 var cell = this.get_cell(i);
1214 var cell = this.get_cell(i);
1212 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1215 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1213 cell.collapse_output();
1216 cell.collapse_output();
1214 this.set_dirty(true);
1217 this.set_dirty(true);
1215 }
1218 }
1216 };
1219 };
1217
1220
1218 /**
1221 /**
1219 * Hide each code cell's output area.
1222 * Hide each code cell's output area.
1220 *
1223 *
1221 * @method collapse_all_output
1224 * @method collapse_all_output
1222 */
1225 */
1223 Notebook.prototype.collapse_all_output = function () {
1226 Notebook.prototype.collapse_all_output = function () {
1224 $.map(this.get_cells(), function (cell, i) {
1227 $.map(this.get_cells(), function (cell, i) {
1225 if (cell instanceof IPython.CodeCell) {
1228 if (cell instanceof IPython.CodeCell) {
1226 cell.collapse_output();
1229 cell.collapse_output();
1227 }
1230 }
1228 });
1231 });
1229 // this should not be set if the `collapse` key is removed from nbformat
1232 // this should not be set if the `collapse` key is removed from nbformat
1230 this.set_dirty(true);
1233 this.set_dirty(true);
1231 };
1234 };
1232
1235
1233 /**
1236 /**
1234 * Show a cell's output.
1237 * Show a cell's output.
1235 *
1238 *
1236 * @method expand_output
1239 * @method expand_output
1237 * @param {Number} index A cell's numeric index
1240 * @param {Number} index A cell's numeric index
1238 */
1241 */
1239 Notebook.prototype.expand_output = function (index) {
1242 Notebook.prototype.expand_output = function (index) {
1240 var i = this.index_or_selected(index);
1243 var i = this.index_or_selected(index);
1241 var cell = this.get_cell(i);
1244 var cell = this.get_cell(i);
1242 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1245 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1243 cell.expand_output();
1246 cell.expand_output();
1244 this.set_dirty(true);
1247 this.set_dirty(true);
1245 }
1248 }
1246 };
1249 };
1247
1250
1248 /**
1251 /**
1249 * Expand each code cell's output area, and remove scrollbars.
1252 * Expand each code cell's output area, and remove scrollbars.
1250 *
1253 *
1251 * @method expand_all_output
1254 * @method expand_all_output
1252 */
1255 */
1253 Notebook.prototype.expand_all_output = function () {
1256 Notebook.prototype.expand_all_output = function () {
1254 $.map(this.get_cells(), function (cell, i) {
1257 $.map(this.get_cells(), function (cell, i) {
1255 if (cell instanceof IPython.CodeCell) {
1258 if (cell instanceof IPython.CodeCell) {
1256 cell.expand_output();
1259 cell.expand_output();
1257 }
1260 }
1258 });
1261 });
1259 // this should not be set if the `collapse` key is removed from nbformat
1262 // this should not be set if the `collapse` key is removed from nbformat
1260 this.set_dirty(true);
1263 this.set_dirty(true);
1261 };
1264 };
1262
1265
1263 /**
1266 /**
1264 * Clear the selected CodeCell's output area.
1267 * Clear the selected CodeCell's output area.
1265 *
1268 *
1266 * @method clear_output
1269 * @method clear_output
1267 * @param {Number} index A cell's numeric index
1270 * @param {Number} index A cell's numeric index
1268 */
1271 */
1269 Notebook.prototype.clear_output = function (index) {
1272 Notebook.prototype.clear_output = function (index) {
1270 var i = this.index_or_selected(index);
1273 var i = this.index_or_selected(index);
1271 var cell = this.get_cell(i);
1274 var cell = this.get_cell(i);
1272 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1275 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1273 cell.clear_output();
1276 cell.clear_output();
1274 this.set_dirty(true);
1277 this.set_dirty(true);
1275 }
1278 }
1276 };
1279 };
1277
1280
1278 /**
1281 /**
1279 * Clear each code cell's output area.
1282 * Clear each code cell's output area.
1280 *
1283 *
1281 * @method clear_all_output
1284 * @method clear_all_output
1282 */
1285 */
1283 Notebook.prototype.clear_all_output = function () {
1286 Notebook.prototype.clear_all_output = function () {
1284 $.map(this.get_cells(), function (cell, i) {
1287 $.map(this.get_cells(), function (cell, i) {
1285 if (cell instanceof IPython.CodeCell) {
1288 if (cell instanceof IPython.CodeCell) {
1286 cell.clear_output();
1289 cell.clear_output();
1287 }
1290 }
1288 });
1291 });
1289 this.set_dirty(true);
1292 this.set_dirty(true);
1290 };
1293 };
1291
1294
1292 /**
1295 /**
1293 * Scroll the selected CodeCell's output area.
1296 * Scroll the selected CodeCell's output area.
1294 *
1297 *
1295 * @method scroll_output
1298 * @method scroll_output
1296 * @param {Number} index A cell's numeric index
1299 * @param {Number} index A cell's numeric index
1297 */
1300 */
1298 Notebook.prototype.scroll_output = function (index) {
1301 Notebook.prototype.scroll_output = function (index) {
1299 var i = this.index_or_selected(index);
1302 var i = this.index_or_selected(index);
1300 var cell = this.get_cell(i);
1303 var cell = this.get_cell(i);
1301 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1304 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1302 cell.scroll_output();
1305 cell.scroll_output();
1303 this.set_dirty(true);
1306 this.set_dirty(true);
1304 }
1307 }
1305 };
1308 };
1306
1309
1307 /**
1310 /**
1308 * Expand each code cell's output area, and add a scrollbar for long output.
1311 * Expand each code cell's output area, and add a scrollbar for long output.
1309 *
1312 *
1310 * @method scroll_all_output
1313 * @method scroll_all_output
1311 */
1314 */
1312 Notebook.prototype.scroll_all_output = function () {
1315 Notebook.prototype.scroll_all_output = function () {
1313 $.map(this.get_cells(), function (cell, i) {
1316 $.map(this.get_cells(), function (cell, i) {
1314 if (cell instanceof IPython.CodeCell) {
1317 if (cell instanceof IPython.CodeCell) {
1315 cell.scroll_output();
1318 cell.scroll_output();
1316 }
1319 }
1317 });
1320 });
1318 // this should not be set if the `collapse` key is removed from nbformat
1321 // this should not be set if the `collapse` key is removed from nbformat
1319 this.set_dirty(true);
1322 this.set_dirty(true);
1320 };
1323 };
1321
1324
1322 /** Toggle whether a cell's output is collapsed or expanded.
1325 /** Toggle whether a cell's output is collapsed or expanded.
1323 *
1326 *
1324 * @method toggle_output
1327 * @method toggle_output
1325 * @param {Number} index A cell's numeric index
1328 * @param {Number} index A cell's numeric index
1326 */
1329 */
1327 Notebook.prototype.toggle_output = function (index) {
1330 Notebook.prototype.toggle_output = function (index) {
1328 var i = this.index_or_selected(index);
1331 var i = this.index_or_selected(index);
1329 var cell = this.get_cell(i);
1332 var cell = this.get_cell(i);
1330 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1333 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1331 cell.toggle_output();
1334 cell.toggle_output();
1332 this.set_dirty(true);
1335 this.set_dirty(true);
1333 }
1336 }
1334 };
1337 };
1335
1338
1336 /**
1339 /**
1337 * Hide/show the output of all cells.
1340 * Hide/show the output of all cells.
1338 *
1341 *
1339 * @method toggle_all_output
1342 * @method toggle_all_output
1340 */
1343 */
1341 Notebook.prototype.toggle_all_output = function () {
1344 Notebook.prototype.toggle_all_output = function () {
1342 $.map(this.get_cells(), function (cell, i) {
1345 $.map(this.get_cells(), function (cell, i) {
1343 if (cell instanceof IPython.CodeCell) {
1346 if (cell instanceof IPython.CodeCell) {
1344 cell.toggle_output();
1347 cell.toggle_output();
1345 }
1348 }
1346 });
1349 });
1347 // this should not be set if the `collapse` key is removed from nbformat
1350 // this should not be set if the `collapse` key is removed from nbformat
1348 this.set_dirty(true);
1351 this.set_dirty(true);
1349 };
1352 };
1350
1353
1351 /**
1354 /**
1352 * Toggle a scrollbar for long cell outputs.
1355 * Toggle a scrollbar for long cell outputs.
1353 *
1356 *
1354 * @method toggle_output_scroll
1357 * @method toggle_output_scroll
1355 * @param {Number} index A cell's numeric index
1358 * @param {Number} index A cell's numeric index
1356 */
1359 */
1357 Notebook.prototype.toggle_output_scroll = function (index) {
1360 Notebook.prototype.toggle_output_scroll = function (index) {
1358 var i = this.index_or_selected(index);
1361 var i = this.index_or_selected(index);
1359 var cell = this.get_cell(i);
1362 var cell = this.get_cell(i);
1360 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1363 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1361 cell.toggle_output_scroll();
1364 cell.toggle_output_scroll();
1362 this.set_dirty(true);
1365 this.set_dirty(true);
1363 }
1366 }
1364 };
1367 };
1365
1368
1366 /**
1369 /**
1367 * Toggle the scrolling of long output on all cells.
1370 * Toggle the scrolling of long output on all cells.
1368 *
1371 *
1369 * @method toggle_all_output_scrolling
1372 * @method toggle_all_output_scrolling
1370 */
1373 */
1371 Notebook.prototype.toggle_all_output_scroll = function () {
1374 Notebook.prototype.toggle_all_output_scroll = function () {
1372 $.map(this.get_cells(), function (cell, i) {
1375 $.map(this.get_cells(), function (cell, i) {
1373 if (cell instanceof IPython.CodeCell) {
1376 if (cell instanceof IPython.CodeCell) {
1374 cell.toggle_output_scroll();
1377 cell.toggle_output_scroll();
1375 }
1378 }
1376 });
1379 });
1377 // this should not be set if the `collapse` key is removed from nbformat
1380 // this should not be set if the `collapse` key is removed from nbformat
1378 this.set_dirty(true);
1381 this.set_dirty(true);
1379 };
1382 };
1380
1383
1381 // Other cell functions: line numbers, ...
1384 // Other cell functions: line numbers, ...
1382
1385
1383 /**
1386 /**
1384 * Toggle line numbers in the selected cell's input area.
1387 * Toggle line numbers in the selected cell's input area.
1385 *
1388 *
1386 * @method cell_toggle_line_numbers
1389 * @method cell_toggle_line_numbers
1387 */
1390 */
1388 Notebook.prototype.cell_toggle_line_numbers = function() {
1391 Notebook.prototype.cell_toggle_line_numbers = function() {
1389 this.get_selected_cell().toggle_line_numbers();
1392 this.get_selected_cell().toggle_line_numbers();
1390 };
1393 };
1391
1394
1392 // Session related things
1395 // Session related things
1393
1396
1394 /**
1397 /**
1395 * Start a new session and set it on each code cell.
1398 * Start a new session and set it on each code cell.
1396 *
1399 *
1397 * @method start_session
1400 * @method start_session
1398 */
1401 */
1399 Notebook.prototype.start_session = function () {
1402 Notebook.prototype.start_session = function () {
1400 this.session = new IPython.Session(this, this.options);
1403 this.session = new IPython.Session(this, this.options);
1401 this.session.start($.proxy(this._session_started, this));
1404 this.session.start($.proxy(this._session_started, this));
1402 };
1405 };
1403
1406
1404
1407
1405 /**
1408 /**
1406 * Once a session is started, link the code cells to the kernel and pass the
1409 * Once a session is started, link the code cells to the kernel and pass the
1407 * comm manager to the widget manager
1410 * comm manager to the widget manager
1408 *
1411 *
1409 */
1412 */
1410 Notebook.prototype._session_started = function(){
1413 Notebook.prototype._session_started = function(){
1411 this.kernel = this.session.kernel;
1414 this.kernel = this.session.kernel;
1412 var ncells = this.ncells();
1415 var ncells = this.ncells();
1413 for (var i=0; i<ncells; i++) {
1416 for (var i=0; i<ncells; i++) {
1414 var cell = this.get_cell(i);
1417 var cell = this.get_cell(i);
1415 if (cell instanceof IPython.CodeCell) {
1418 if (cell instanceof IPython.CodeCell) {
1416 cell.set_kernel(this.session.kernel);
1419 cell.set_kernel(this.session.kernel);
1417 }
1420 }
1418 }
1421 }
1419 };
1422 };
1420
1423
1421 /**
1424 /**
1422 * Prompt the user to restart the IPython kernel.
1425 * Prompt the user to restart the IPython kernel.
1423 *
1426 *
1424 * @method restart_kernel
1427 * @method restart_kernel
1425 */
1428 */
1426 Notebook.prototype.restart_kernel = function () {
1429 Notebook.prototype.restart_kernel = function () {
1427 var that = this;
1430 var that = this;
1428 IPython.dialog.modal({
1431 IPython.dialog.modal({
1429 title : "Restart kernel or continue running?",
1432 title : "Restart kernel or continue running?",
1430 body : $("<p/>").text(
1433 body : $("<p/>").text(
1431 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1434 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1432 ),
1435 ),
1433 buttons : {
1436 buttons : {
1434 "Continue running" : {},
1437 "Continue running" : {},
1435 "Restart" : {
1438 "Restart" : {
1436 "class" : "btn-danger",
1439 "class" : "btn-danger",
1437 "click" : function() {
1440 "click" : function() {
1438 that.session.restart_kernel();
1441 that.session.restart_kernel();
1439 }
1442 }
1440 }
1443 }
1441 }
1444 }
1442 });
1445 });
1443 };
1446 };
1444
1447
1445 /**
1448 /**
1446 * Execute or render cell outputs and go into command mode.
1449 * Execute or render cell outputs and go into command mode.
1447 *
1450 *
1448 * @method execute_cell
1451 * @method execute_cell
1449 */
1452 */
1450 Notebook.prototype.execute_cell = function () {
1453 Notebook.prototype.execute_cell = function () {
1451 // mode = shift, ctrl, alt
1454 // mode = shift, ctrl, alt
1452 var cell = this.get_selected_cell();
1455 var cell = this.get_selected_cell();
1453 var cell_index = this.find_cell_index(cell);
1456 var cell_index = this.find_cell_index(cell);
1454
1457
1455 cell.execute();
1458 cell.execute();
1456 this.command_mode();
1459 this.command_mode();
1457 this.set_dirty(true);
1460 this.set_dirty(true);
1458 };
1461 };
1459
1462
1460 /**
1463 /**
1461 * Execute or render cell outputs and insert a new cell below.
1464 * Execute or render cell outputs and insert a new cell below.
1462 *
1465 *
1463 * @method execute_cell_and_insert_below
1466 * @method execute_cell_and_insert_below
1464 */
1467 */
1465 Notebook.prototype.execute_cell_and_insert_below = function () {
1468 Notebook.prototype.execute_cell_and_insert_below = function () {
1466 var cell = this.get_selected_cell();
1469 var cell = this.get_selected_cell();
1467 var cell_index = this.find_cell_index(cell);
1470 var cell_index = this.find_cell_index(cell);
1468
1471
1469 cell.execute();
1472 cell.execute();
1470
1473
1471 // If we are at the end always insert a new cell and return
1474 // If we are at the end always insert a new cell and return
1472 if (cell_index === (this.ncells()-1)) {
1475 if (cell_index === (this.ncells()-1)) {
1473 this.command_mode();
1476 this.command_mode();
1474 this.insert_cell_below('code');
1477 this.insert_cell_below('code');
1475 this.select(cell_index+1);
1478 this.select(cell_index+1);
1476 this.edit_mode();
1479 this.edit_mode();
1477 this.scroll_to_bottom();
1480 this.scroll_to_bottom();
1478 this.set_dirty(true);
1481 this.set_dirty(true);
1479 return;
1482 return;
1480 }
1483 }
1481
1484
1482 this.command_mode();
1485 this.command_mode();
1483 this.insert_cell_below('code');
1486 this.insert_cell_below('code');
1484 this.select(cell_index+1);
1487 this.select(cell_index+1);
1485 this.edit_mode();
1488 this.edit_mode();
1486 this.set_dirty(true);
1489 this.set_dirty(true);
1487 };
1490 };
1488
1491
1489 /**
1492 /**
1490 * Execute or render cell outputs and select the next cell.
1493 * Execute or render cell outputs and select the next cell.
1491 *
1494 *
1492 * @method execute_cell_and_select_below
1495 * @method execute_cell_and_select_below
1493 */
1496 */
1494 Notebook.prototype.execute_cell_and_select_below = function () {
1497 Notebook.prototype.execute_cell_and_select_below = function () {
1495
1498
1496 var cell = this.get_selected_cell();
1499 var cell = this.get_selected_cell();
1497 var cell_index = this.find_cell_index(cell);
1500 var cell_index = this.find_cell_index(cell);
1498
1501
1499 cell.execute();
1502 cell.execute();
1500
1503
1501 // If we are at the end always insert a new cell and return
1504 // If we are at the end always insert a new cell and return
1502 if (cell_index === (this.ncells()-1)) {
1505 if (cell_index === (this.ncells()-1)) {
1503 this.command_mode();
1506 this.command_mode();
1504 this.insert_cell_below('code');
1507 this.insert_cell_below('code');
1505 this.select(cell_index+1);
1508 this.select(cell_index+1);
1506 this.edit_mode();
1509 this.edit_mode();
1507 this.scroll_to_bottom();
1510 this.scroll_to_bottom();
1508 this.set_dirty(true);
1511 this.set_dirty(true);
1509 return;
1512 return;
1510 }
1513 }
1511
1514
1512 this.command_mode();
1515 this.command_mode();
1513 this.select(cell_index+1);
1516 this.select(cell_index+1);
1514 this.focus_cell();
1517 this.focus_cell();
1515 this.set_dirty(true);
1518 this.set_dirty(true);
1516 };
1519 };
1517
1520
1518 /**
1521 /**
1519 * Execute all cells below the selected cell.
1522 * Execute all cells below the selected cell.
1520 *
1523 *
1521 * @method execute_cells_below
1524 * @method execute_cells_below
1522 */
1525 */
1523 Notebook.prototype.execute_cells_below = function () {
1526 Notebook.prototype.execute_cells_below = function () {
1524 this.execute_cell_range(this.get_selected_index(), this.ncells());
1527 this.execute_cell_range(this.get_selected_index(), this.ncells());
1525 this.scroll_to_bottom();
1528 this.scroll_to_bottom();
1526 };
1529 };
1527
1530
1528 /**
1531 /**
1529 * Execute all cells above the selected cell.
1532 * Execute all cells above the selected cell.
1530 *
1533 *
1531 * @method execute_cells_above
1534 * @method execute_cells_above
1532 */
1535 */
1533 Notebook.prototype.execute_cells_above = function () {
1536 Notebook.prototype.execute_cells_above = function () {
1534 this.execute_cell_range(0, this.get_selected_index());
1537 this.execute_cell_range(0, this.get_selected_index());
1535 };
1538 };
1536
1539
1537 /**
1540 /**
1538 * Execute all cells.
1541 * Execute all cells.
1539 *
1542 *
1540 * @method execute_all_cells
1543 * @method execute_all_cells
1541 */
1544 */
1542 Notebook.prototype.execute_all_cells = function () {
1545 Notebook.prototype.execute_all_cells = function () {
1543 this.execute_cell_range(0, this.ncells());
1546 this.execute_cell_range(0, this.ncells());
1544 this.scroll_to_bottom();
1547 this.scroll_to_bottom();
1545 };
1548 };
1546
1549
1547 /**
1550 /**
1548 * Execute a contiguous range of cells.
1551 * Execute a contiguous range of cells.
1549 *
1552 *
1550 * @method execute_cell_range
1553 * @method execute_cell_range
1551 * @param {Number} start Index of the first cell to execute (inclusive)
1554 * @param {Number} start Index of the first cell to execute (inclusive)
1552 * @param {Number} end Index of the last cell to execute (exclusive)
1555 * @param {Number} end Index of the last cell to execute (exclusive)
1553 */
1556 */
1554 Notebook.prototype.execute_cell_range = function (start, end) {
1557 Notebook.prototype.execute_cell_range = function (start, end) {
1555 this.command_mode();
1558 this.command_mode();
1556 for (var i=start; i<end; i++) {
1559 for (var i=start; i<end; i++) {
1557 this.select(i);
1560 this.select(i);
1558 this.execute_cell();
1561 this.execute_cell();
1559 }
1562 }
1560 };
1563 };
1561
1564
1562 // Persistance and loading
1565 // Persistance and loading
1563
1566
1564 /**
1567 /**
1565 * Getter method for this notebook's name.
1568 * Getter method for this notebook's name.
1566 *
1569 *
1567 * @method get_notebook_name
1570 * @method get_notebook_name
1568 * @return {String} This notebook's name (excluding file extension)
1571 * @return {String} This notebook's name (excluding file extension)
1569 */
1572 */
1570 Notebook.prototype.get_notebook_name = function () {
1573 Notebook.prototype.get_notebook_name = function () {
1571 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1574 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1572 return nbname;
1575 return nbname;
1573 };
1576 };
1574
1577
1575 /**
1578 /**
1576 * Setter method for this notebook's name.
1579 * Setter method for this notebook's name.
1577 *
1580 *
1578 * @method set_notebook_name
1581 * @method set_notebook_name
1579 * @param {String} name A new name for this notebook
1582 * @param {String} name A new name for this notebook
1580 */
1583 */
1581 Notebook.prototype.set_notebook_name = function (name) {
1584 Notebook.prototype.set_notebook_name = function (name) {
1582 this.notebook_name = name;
1585 this.notebook_name = name;
1583 };
1586 };
1584
1587
1585 /**
1588 /**
1586 * Check that a notebook's name is valid.
1589 * Check that a notebook's name is valid.
1587 *
1590 *
1588 * @method test_notebook_name
1591 * @method test_notebook_name
1589 * @param {String} nbname A name for this notebook
1592 * @param {String} nbname A name for this notebook
1590 * @return {Boolean} True if the name is valid, false if invalid
1593 * @return {Boolean} True if the name is valid, false if invalid
1591 */
1594 */
1592 Notebook.prototype.test_notebook_name = function (nbname) {
1595 Notebook.prototype.test_notebook_name = function (nbname) {
1593 nbname = nbname || '';
1596 nbname = nbname || '';
1594 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1597 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1595 return true;
1598 return true;
1596 } else {
1599 } else {
1597 return false;
1600 return false;
1598 }
1601 }
1599 };
1602 };
1600
1603
1601 /**
1604 /**
1602 * Load a notebook from JSON (.ipynb).
1605 * Load a notebook from JSON (.ipynb).
1603 *
1606 *
1604 * This currently handles one worksheet: others are deleted.
1607 * This currently handles one worksheet: others are deleted.
1605 *
1608 *
1606 * @method fromJSON
1609 * @method fromJSON
1607 * @param {Object} data JSON representation of a notebook
1610 * @param {Object} data JSON representation of a notebook
1608 */
1611 */
1609 Notebook.prototype.fromJSON = function (data) {
1612 Notebook.prototype.fromJSON = function (data) {
1610 var content = data.content;
1613 var content = data.content;
1611 var ncells = this.ncells();
1614 var ncells = this.ncells();
1612 var i;
1615 var i;
1613 for (i=0; i<ncells; i++) {
1616 for (i=0; i<ncells; i++) {
1614 // Always delete cell 0 as they get renumbered as they are deleted.
1617 // Always delete cell 0 as they get renumbered as they are deleted.
1615 this.delete_cell(0);
1618 this.delete_cell(0);
1616 }
1619 }
1617 // Save the metadata and name.
1620 // Save the metadata and name.
1618 this.metadata = content.metadata;
1621 this.metadata = content.metadata;
1619 this.notebook_name = data.name;
1622 this.notebook_name = data.name;
1620 var trusted = true;
1623 var trusted = true;
1621 // Only handle 1 worksheet for now.
1624 // Only handle 1 worksheet for now.
1622 var worksheet = content.worksheets[0];
1625 var worksheet = content.worksheets[0];
1623 if (worksheet !== undefined) {
1626 if (worksheet !== undefined) {
1624 if (worksheet.metadata) {
1627 if (worksheet.metadata) {
1625 this.worksheet_metadata = worksheet.metadata;
1628 this.worksheet_metadata = worksheet.metadata;
1626 }
1629 }
1627 var new_cells = worksheet.cells;
1630 var new_cells = worksheet.cells;
1628 ncells = new_cells.length;
1631 ncells = new_cells.length;
1629 var cell_data = null;
1632 var cell_data = null;
1630 var new_cell = null;
1633 var new_cell = null;
1631 for (i=0; i<ncells; i++) {
1634 for (i=0; i<ncells; i++) {
1632 cell_data = new_cells[i];
1635 cell_data = new_cells[i];
1633 // VERSIONHACK: plaintext -> raw
1636 // VERSIONHACK: plaintext -> raw
1634 // handle never-released plaintext name for raw cells
1637 // handle never-released plaintext name for raw cells
1635 if (cell_data.cell_type === 'plaintext'){
1638 if (cell_data.cell_type === 'plaintext'){
1636 cell_data.cell_type = 'raw';
1639 cell_data.cell_type = 'raw';
1637 }
1640 }
1638
1641
1639 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1642 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1640 new_cell.fromJSON(cell_data);
1643 new_cell.fromJSON(cell_data);
1641 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1644 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1642 trusted = false;
1645 trusted = false;
1643 }
1646 }
1644 }
1647 }
1645 }
1648 }
1646 if (trusted != this.trusted) {
1649 if (trusted != this.trusted) {
1647 this.trusted = trusted;
1650 this.trusted = trusted;
1648 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1651 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1649 }
1652 }
1650 if (content.worksheets.length > 1) {
1653 if (content.worksheets.length > 1) {
1651 IPython.dialog.modal({
1654 IPython.dialog.modal({
1652 title : "Multiple worksheets",
1655 title : "Multiple worksheets",
1653 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1656 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1654 "but this version of IPython can only handle the first. " +
1657 "but this version of IPython can only handle the first. " +
1655 "If you save this notebook, worksheets after the first will be lost.",
1658 "If you save this notebook, worksheets after the first will be lost.",
1656 buttons : {
1659 buttons : {
1657 OK : {
1660 OK : {
1658 class : "btn-danger"
1661 class : "btn-danger"
1659 }
1662 }
1660 }
1663 }
1661 });
1664 });
1662 }
1665 }
1663 };
1666 };
1664
1667
1665 /**
1668 /**
1666 * Dump this notebook into a JSON-friendly object.
1669 * Dump this notebook into a JSON-friendly object.
1667 *
1670 *
1668 * @method toJSON
1671 * @method toJSON
1669 * @return {Object} A JSON-friendly representation of this notebook.
1672 * @return {Object} A JSON-friendly representation of this notebook.
1670 */
1673 */
1671 Notebook.prototype.toJSON = function () {
1674 Notebook.prototype.toJSON = function () {
1672 var cells = this.get_cells();
1675 var cells = this.get_cells();
1673 var ncells = cells.length;
1676 var ncells = cells.length;
1674 var cell_array = new Array(ncells);
1677 var cell_array = new Array(ncells);
1675 var trusted = true;
1678 var trusted = true;
1676 for (var i=0; i<ncells; i++) {
1679 for (var i=0; i<ncells; i++) {
1677 var cell = cells[i];
1680 var cell = cells[i];
1678 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1681 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1679 trusted = false;
1682 trusted = false;
1680 }
1683 }
1681 cell_array[i] = cell.toJSON();
1684 cell_array[i] = cell.toJSON();
1682 }
1685 }
1683 var data = {
1686 var data = {
1684 // Only handle 1 worksheet for now.
1687 // Only handle 1 worksheet for now.
1685 worksheets : [{
1688 worksheets : [{
1686 cells: cell_array,
1689 cells: cell_array,
1687 metadata: this.worksheet_metadata
1690 metadata: this.worksheet_metadata
1688 }],
1691 }],
1689 metadata : this.metadata
1692 metadata : this.metadata
1690 };
1693 };
1691 if (trusted != this.trusted) {
1694 if (trusted != this.trusted) {
1692 this.trusted = trusted;
1695 this.trusted = trusted;
1693 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1696 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1694 }
1697 }
1695 return data;
1698 return data;
1696 };
1699 };
1697
1700
1698 /**
1701 /**
1699 * Start an autosave timer, for periodically saving the notebook.
1702 * Start an autosave timer, for periodically saving the notebook.
1700 *
1703 *
1701 * @method set_autosave_interval
1704 * @method set_autosave_interval
1702 * @param {Integer} interval the autosave interval in milliseconds
1705 * @param {Integer} interval the autosave interval in milliseconds
1703 */
1706 */
1704 Notebook.prototype.set_autosave_interval = function (interval) {
1707 Notebook.prototype.set_autosave_interval = function (interval) {
1705 var that = this;
1708 var that = this;
1706 // clear previous interval, so we don't get simultaneous timers
1709 // clear previous interval, so we don't get simultaneous timers
1707 if (this.autosave_timer) {
1710 if (this.autosave_timer) {
1708 clearInterval(this.autosave_timer);
1711 clearInterval(this.autosave_timer);
1709 }
1712 }
1710
1713
1711 this.autosave_interval = this.minimum_autosave_interval = interval;
1714 this.autosave_interval = this.minimum_autosave_interval = interval;
1712 if (interval) {
1715 if (interval) {
1713 this.autosave_timer = setInterval(function() {
1716 this.autosave_timer = setInterval(function() {
1714 if (that.dirty) {
1717 if (that.dirty) {
1715 that.save_notebook();
1718 that.save_notebook();
1716 }
1719 }
1717 }, interval);
1720 }, interval);
1718 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1721 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1719 } else {
1722 } else {
1720 this.autosave_timer = null;
1723 this.autosave_timer = null;
1721 $([IPython.events]).trigger("autosave_disabled.Notebook");
1724 $([IPython.events]).trigger("autosave_disabled.Notebook");
1722 }
1725 }
1723 };
1726 };
1724
1727
1725 /**
1728 /**
1726 * Save this notebook on the server.
1729 * Save this notebook on the server. This becomes a notebook instance's
1730 * .save_notebook method *after* the entire notebook has been loaded.
1727 *
1731 *
1728 * @method save_notebook
1732 * @method save_notebook
1729 */
1733 */
1730 Notebook.prototype.save_notebook = function (extra_settings) {
1734 Notebook.prototype.save_notebook = function (extra_settings) {
1731 // Create a JSON model to be sent to the server.
1735 // Create a JSON model to be sent to the server.
1732 var model = {};
1736 var model = {};
1733 model.name = this.notebook_name;
1737 model.name = this.notebook_name;
1734 model.path = this.notebook_path;
1738 model.path = this.notebook_path;
1735 model.content = this.toJSON();
1739 model.content = this.toJSON();
1736 model.content.nbformat = this.nbformat;
1740 model.content.nbformat = this.nbformat;
1737 model.content.nbformat_minor = this.nbformat_minor;
1741 model.content.nbformat_minor = this.nbformat_minor;
1738 // time the ajax call for autosave tuning purposes.
1742 // time the ajax call for autosave tuning purposes.
1739 var start = new Date().getTime();
1743 var start = new Date().getTime();
1740 // We do the call with settings so we can set cache to false.
1744 // We do the call with settings so we can set cache to false.
1741 var settings = {
1745 var settings = {
1742 processData : false,
1746 processData : false,
1743 cache : false,
1747 cache : false,
1744 type : "PUT",
1748 type : "PUT",
1745 data : JSON.stringify(model),
1749 data : JSON.stringify(model),
1746 headers : {'Content-Type': 'application/json'},
1750 headers : {'Content-Type': 'application/json'},
1747 success : $.proxy(this.save_notebook_success, this, start),
1751 success : $.proxy(this.save_notebook_success, this, start),
1748 error : $.proxy(this.save_notebook_error, this)
1752 error : $.proxy(this.save_notebook_error, this)
1749 };
1753 };
1750 if (extra_settings) {
1754 if (extra_settings) {
1751 for (var key in extra_settings) {
1755 for (var key in extra_settings) {
1752 settings[key] = extra_settings[key];
1756 settings[key] = extra_settings[key];
1753 }
1757 }
1754 }
1758 }
1755 $([IPython.events]).trigger('notebook_saving.Notebook');
1759 $([IPython.events]).trigger('notebook_saving.Notebook');
1756 var url = utils.url_join_encode(
1760 var url = utils.url_join_encode(
1757 this.base_url,
1761 this.base_url,
1758 'api/notebooks',
1762 'api/notebooks',
1759 this.notebook_path,
1763 this.notebook_path,
1760 this.notebook_name
1764 this.notebook_name
1761 );
1765 );
1762 $.ajax(url, settings);
1766 $.ajax(url, settings);
1763 };
1767 };
1764
1768
1765 /**
1769 /**
1766 * Success callback for saving a notebook.
1770 * Success callback for saving a notebook.
1767 *
1771 *
1768 * @method save_notebook_success
1772 * @method save_notebook_success
1769 * @param {Integer} start the time when the save request started
1773 * @param {Integer} start the time when the save request started
1770 * @param {Object} data JSON representation of a notebook
1774 * @param {Object} data JSON representation of a notebook
1771 * @param {String} status Description of response status
1775 * @param {String} status Description of response status
1772 * @param {jqXHR} xhr jQuery Ajax object
1776 * @param {jqXHR} xhr jQuery Ajax object
1773 */
1777 */
1774 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1778 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1775 this.set_dirty(false);
1779 this.set_dirty(false);
1776 $([IPython.events]).trigger('notebook_saved.Notebook');
1780 $([IPython.events]).trigger('notebook_saved.Notebook');
1777 this._update_autosave_interval(start);
1781 this._update_autosave_interval(start);
1778 if (this._checkpoint_after_save) {
1782 if (this._checkpoint_after_save) {
1779 this.create_checkpoint();
1783 this.create_checkpoint();
1780 this._checkpoint_after_save = false;
1784 this._checkpoint_after_save = false;
1781 }
1785 }
1782 };
1786 };
1783
1787
1784 /**
1788 /**
1785 * update the autosave interval based on how long the last save took
1789 * update the autosave interval based on how long the last save took
1786 *
1790 *
1787 * @method _update_autosave_interval
1791 * @method _update_autosave_interval
1788 * @param {Integer} timestamp when the save request started
1792 * @param {Integer} timestamp when the save request started
1789 */
1793 */
1790 Notebook.prototype._update_autosave_interval = function (start) {
1794 Notebook.prototype._update_autosave_interval = function (start) {
1791 var duration = (new Date().getTime() - start);
1795 var duration = (new Date().getTime() - start);
1792 if (this.autosave_interval) {
1796 if (this.autosave_interval) {
1793 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1797 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1794 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1798 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1795 // round to 10 seconds, otherwise we will be setting a new interval too often
1799 // round to 10 seconds, otherwise we will be setting a new interval too often
1796 interval = 10000 * Math.round(interval / 10000);
1800 interval = 10000 * Math.round(interval / 10000);
1797 // set new interval, if it's changed
1801 // set new interval, if it's changed
1798 if (interval != this.autosave_interval) {
1802 if (interval != this.autosave_interval) {
1799 this.set_autosave_interval(interval);
1803 this.set_autosave_interval(interval);
1800 }
1804 }
1801 }
1805 }
1802 };
1806 };
1803
1807
1804 /**
1808 /**
1805 * Failure callback for saving a notebook.
1809 * Failure callback for saving a notebook.
1806 *
1810 *
1807 * @method save_notebook_error
1811 * @method save_notebook_error
1808 * @param {jqXHR} xhr jQuery Ajax object
1812 * @param {jqXHR} xhr jQuery Ajax object
1809 * @param {String} status Description of response status
1813 * @param {String} status Description of response status
1810 * @param {String} error HTTP error message
1814 * @param {String} error HTTP error message
1811 */
1815 */
1812 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1816 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1813 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1817 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1814 };
1818 };
1815
1819
1816 /**
1820 /**
1817 * Explicitly trust the output of this notebook.
1821 * Explicitly trust the output of this notebook.
1818 *
1822 *
1819 * @method trust_notebook
1823 * @method trust_notebook
1820 */
1824 */
1821 Notebook.prototype.trust_notebook = function (extra_settings) {
1825 Notebook.prototype.trust_notebook = function (extra_settings) {
1822 var body = $("<div>").append($("<p>")
1826 var body = $("<div>").append($("<p>")
1823 .text("A trusted IPython notebook may execute hidden malicious code ")
1827 .text("A trusted IPython notebook may execute hidden malicious code ")
1824 .append($("<strong>")
1828 .append($("<strong>")
1825 .append(
1829 .append(
1826 $("<em>").text("when you open it")
1830 $("<em>").text("when you open it")
1827 )
1831 )
1828 ).append(".").append(
1832 ).append(".").append(
1829 " Selecting trust will immediately reload this notebook in a trusted state."
1833 " Selecting trust will immediately reload this notebook in a trusted state."
1830 ).append(
1834 ).append(
1831 " For more information, see the "
1835 " For more information, see the "
1832 ).append($("<a>").attr("href", "http://ipython.org/security.html")
1836 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1833 .text("IPython security documentation")
1837 .text("IPython security documentation")
1834 ).append(".")
1838 ).append(".")
1835 );
1839 );
1836
1840
1837 var nb = this;
1841 var nb = this;
1838 IPython.dialog.modal({
1842 IPython.dialog.modal({
1839 title: "Trust this notebook?",
1843 title: "Trust this notebook?",
1840 body: body,
1844 body: body,
1841
1845
1842 buttons: {
1846 buttons: {
1843 Cancel : {},
1847 Cancel : {},
1844 Trust : {
1848 Trust : {
1845 class : "btn-danger",
1849 class : "btn-danger",
1846 click : function () {
1850 click : function () {
1847 var cells = nb.get_cells();
1851 var cells = nb.get_cells();
1848 for (var i = 0; i < cells.length; i++) {
1852 for (var i = 0; i < cells.length; i++) {
1849 var cell = cells[i];
1853 var cell = cells[i];
1850 if (cell.cell_type == 'code') {
1854 if (cell.cell_type == 'code') {
1851 cell.output_area.trusted = true;
1855 cell.output_area.trusted = true;
1852 }
1856 }
1853 }
1857 }
1854 $([IPython.events]).on('notebook_saved.Notebook', function () {
1858 $([IPython.events]).on('notebook_saved.Notebook', function () {
1855 window.location.reload();
1859 window.location.reload();
1856 });
1860 });
1857 nb.save_notebook();
1861 nb.save_notebook();
1858 }
1862 }
1859 }
1863 }
1860 }
1864 }
1861 });
1865 });
1862 };
1866 };
1863
1867
1864 Notebook.prototype.new_notebook = function(){
1868 Notebook.prototype.new_notebook = function(){
1865 var path = this.notebook_path;
1869 var path = this.notebook_path;
1866 var base_url = this.base_url;
1870 var base_url = this.base_url;
1867 var settings = {
1871 var settings = {
1868 processData : false,
1872 processData : false,
1869 cache : false,
1873 cache : false,
1870 type : "POST",
1874 type : "POST",
1871 dataType : "json",
1875 dataType : "json",
1872 async : false,
1876 async : false,
1873 success : function (data, status, xhr){
1877 success : function (data, status, xhr){
1874 var notebook_name = data.name;
1878 var notebook_name = data.name;
1875 window.open(
1879 window.open(
1876 utils.url_join_encode(
1880 utils.url_join_encode(
1877 base_url,
1881 base_url,
1878 'notebooks',
1882 'notebooks',
1879 path,
1883 path,
1880 notebook_name
1884 notebook_name
1881 ),
1885 ),
1882 '_blank'
1886 '_blank'
1883 );
1887 );
1884 }
1888 }
1885 };
1889 };
1886 var url = utils.url_join_encode(
1890 var url = utils.url_join_encode(
1887 base_url,
1891 base_url,
1888 'api/notebooks',
1892 'api/notebooks',
1889 path
1893 path
1890 );
1894 );
1891 $.ajax(url,settings);
1895 $.ajax(url,settings);
1892 };
1896 };
1893
1897
1894
1898
1895 Notebook.prototype.copy_notebook = function(){
1899 Notebook.prototype.copy_notebook = function(){
1896 var path = this.notebook_path;
1900 var path = this.notebook_path;
1897 var base_url = this.base_url;
1901 var base_url = this.base_url;
1898 var settings = {
1902 var settings = {
1899 processData : false,
1903 processData : false,
1900 cache : false,
1904 cache : false,
1901 type : "POST",
1905 type : "POST",
1902 dataType : "json",
1906 dataType : "json",
1903 data : JSON.stringify({copy_from : this.notebook_name}),
1907 data : JSON.stringify({copy_from : this.notebook_name}),
1904 async : false,
1908 async : false,
1905 success : function (data, status, xhr) {
1909 success : function (data, status, xhr) {
1906 window.open(utils.url_join_encode(
1910 window.open(utils.url_join_encode(
1907 base_url,
1911 base_url,
1908 'notebooks',
1912 'notebooks',
1909 data.path,
1913 data.path,
1910 data.name
1914 data.name
1911 ), '_blank');
1915 ), '_blank');
1912 }
1916 }
1913 };
1917 };
1914 var url = utils.url_join_encode(
1918 var url = utils.url_join_encode(
1915 base_url,
1919 base_url,
1916 'api/notebooks',
1920 'api/notebooks',
1917 path
1921 path
1918 );
1922 );
1919 $.ajax(url,settings);
1923 $.ajax(url,settings);
1920 };
1924 };
1921
1925
1922 Notebook.prototype.rename = function (nbname) {
1926 Notebook.prototype.rename = function (nbname) {
1923 var that = this;
1927 var that = this;
1924 if (!nbname.match(/\.ipynb$/)) {
1928 if (!nbname.match(/\.ipynb$/)) {
1925 nbname = nbname + ".ipynb";
1929 nbname = nbname + ".ipynb";
1926 }
1930 }
1927 var data = {name: nbname};
1931 var data = {name: nbname};
1928 var settings = {
1932 var settings = {
1929 processData : false,
1933 processData : false,
1930 cache : false,
1934 cache : false,
1931 type : "PATCH",
1935 type : "PATCH",
1932 data : JSON.stringify(data),
1936 data : JSON.stringify(data),
1933 dataType: "json",
1937 dataType: "json",
1934 headers : {'Content-Type': 'application/json'},
1938 headers : {'Content-Type': 'application/json'},
1935 success : $.proxy(that.rename_success, this),
1939 success : $.proxy(that.rename_success, this),
1936 error : $.proxy(that.rename_error, this)
1940 error : $.proxy(that.rename_error, this)
1937 };
1941 };
1938 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1942 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1939 var url = utils.url_join_encode(
1943 var url = utils.url_join_encode(
1940 this.base_url,
1944 this.base_url,
1941 'api/notebooks',
1945 'api/notebooks',
1942 this.notebook_path,
1946 this.notebook_path,
1943 this.notebook_name
1947 this.notebook_name
1944 );
1948 );
1945 $.ajax(url, settings);
1949 $.ajax(url, settings);
1946 };
1950 };
1947
1951
1948 Notebook.prototype.delete = function () {
1952 Notebook.prototype.delete = function () {
1949 var that = this;
1953 var that = this;
1950 var settings = {
1954 var settings = {
1951 processData : false,
1955 processData : false,
1952 cache : false,
1956 cache : false,
1953 type : "DELETE",
1957 type : "DELETE",
1954 dataType: "json",
1958 dataType: "json",
1955 };
1959 };
1956 var url = utils.url_join_encode(
1960 var url = utils.url_join_encode(
1957 this.base_url,
1961 this.base_url,
1958 'api/notebooks',
1962 'api/notebooks',
1959 this.notebook_path,
1963 this.notebook_path,
1960 this.notebook_name
1964 this.notebook_name
1961 );
1965 );
1962 $.ajax(url, settings);
1966 $.ajax(url, settings);
1963 };
1967 };
1964
1968
1965
1969
1966 Notebook.prototype.rename_success = function (json, status, xhr) {
1970 Notebook.prototype.rename_success = function (json, status, xhr) {
1967 var name = this.notebook_name = json.name;
1971 var name = this.notebook_name = json.name;
1968 var path = json.path;
1972 var path = json.path;
1969 this.session.rename_notebook(name, path);
1973 this.session.rename_notebook(name, path);
1970 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1974 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1971 };
1975 };
1972
1976
1973 Notebook.prototype.rename_error = function (xhr, status, error) {
1977 Notebook.prototype.rename_error = function (xhr, status, error) {
1974 var that = this;
1978 var that = this;
1975 var dialog = $('<div/>').append(
1979 var dialog = $('<div/>').append(
1976 $("<p/>").addClass("rename-message")
1980 $("<p/>").addClass("rename-message")
1977 .text('This notebook name already exists.')
1981 .text('This notebook name already exists.')
1978 );
1982 );
1979 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1983 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1980 IPython.dialog.modal({
1984 IPython.dialog.modal({
1981 title: "Notebook Rename Error!",
1985 title: "Notebook Rename Error!",
1982 body: dialog,
1986 body: dialog,
1983 buttons : {
1987 buttons : {
1984 "Cancel": {},
1988 "Cancel": {},
1985 "OK": {
1989 "OK": {
1986 class: "btn-primary",
1990 class: "btn-primary",
1987 click: function () {
1991 click: function () {
1988 IPython.save_widget.rename_notebook();
1992 IPython.save_widget.rename_notebook();
1989 }}
1993 }}
1990 },
1994 },
1991 open : function (event, ui) {
1995 open : function (event, ui) {
1992 var that = $(this);
1996 var that = $(this);
1993 // Upon ENTER, click the OK button.
1997 // Upon ENTER, click the OK button.
1994 that.find('input[type="text"]').keydown(function (event, ui) {
1998 that.find('input[type="text"]').keydown(function (event, ui) {
1995 if (event.which === IPython.keyboard.keycodes.enter) {
1999 if (event.which === IPython.keyboard.keycodes.enter) {
1996 that.find('.btn-primary').first().click();
2000 that.find('.btn-primary').first().click();
1997 }
2001 }
1998 });
2002 });
1999 that.find('input[type="text"]').focus();
2003 that.find('input[type="text"]').focus();
2000 }
2004 }
2001 });
2005 });
2002 };
2006 };
2003
2007
2004 /**
2008 /**
2005 * Request a notebook's data from the server.
2009 * Request a notebook's data from the server.
2006 *
2010 *
2007 * @method load_notebook
2011 * @method load_notebook
2008 * @param {String} notebook_name and path A notebook to load
2012 * @param {String} notebook_name and path A notebook to load
2009 */
2013 */
2010 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2014 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2011 var that = this;
2015 var that = this;
2012 this.notebook_name = notebook_name;
2016 this.notebook_name = notebook_name;
2013 this.notebook_path = notebook_path;
2017 this.notebook_path = notebook_path;
2014 // We do the call with settings so we can set cache to false.
2018 // We do the call with settings so we can set cache to false.
2015 var settings = {
2019 var settings = {
2016 processData : false,
2020 processData : false,
2017 cache : false,
2021 cache : false,
2018 type : "GET",
2022 type : "GET",
2019 dataType : "json",
2023 dataType : "json",
2020 success : $.proxy(this.load_notebook_success,this),
2024 success : $.proxy(this.load_notebook_success,this),
2021 error : $.proxy(this.load_notebook_error,this),
2025 error : $.proxy(this.load_notebook_error,this),
2022 };
2026 };
2023 $([IPython.events]).trigger('notebook_loading.Notebook');
2027 $([IPython.events]).trigger('notebook_loading.Notebook');
2024 var url = utils.url_join_encode(
2028 var url = utils.url_join_encode(
2025 this.base_url,
2029 this.base_url,
2026 'api/notebooks',
2030 'api/notebooks',
2027 this.notebook_path,
2031 this.notebook_path,
2028 this.notebook_name
2032 this.notebook_name
2029 );
2033 );
2030 $.ajax(url, settings);
2034 $.ajax(url, settings);
2031 };
2035 };
2032
2036
2033 /**
2037 /**
2034 * Success callback for loading a notebook from the server.
2038 * Success callback for loading a notebook from the server.
2035 *
2039 *
2036 * Load notebook data from the JSON response.
2040 * Load notebook data from the JSON response.
2037 *
2041 *
2038 * @method load_notebook_success
2042 * @method load_notebook_success
2039 * @param {Object} data JSON representation of a notebook
2043 * @param {Object} data JSON representation of a notebook
2040 * @param {String} status Description of response status
2044 * @param {String} status Description of response status
2041 * @param {jqXHR} xhr jQuery Ajax object
2045 * @param {jqXHR} xhr jQuery Ajax object
2042 */
2046 */
2043 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2047 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2044 this.fromJSON(data);
2048 this.fromJSON(data);
2045 if (this.ncells() === 0) {
2049 if (this.ncells() === 0) {
2046 this.insert_cell_below('code');
2050 this.insert_cell_below('code');
2047 this.edit_mode(0);
2051 this.edit_mode(0);
2048 } else {
2052 } else {
2049 this.select(0);
2053 this.select(0);
2050 this.handle_command_mode(this.get_cell(0));
2054 this.handle_command_mode(this.get_cell(0));
2051 }
2055 }
2052 this.set_dirty(false);
2056 this.set_dirty(false);
2053 this.scroll_to_top();
2057 this.scroll_to_top();
2054 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2058 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2055 var msg = "This notebook has been converted from an older " +
2059 var msg = "This notebook has been converted from an older " +
2056 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2060 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2057 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2061 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2058 "newer notebook format will be used and older versions of IPython " +
2062 "newer notebook format will be used and older versions of IPython " +
2059 "may not be able to read it. To keep the older version, close the " +
2063 "may not be able to read it. To keep the older version, close the " +
2060 "notebook without saving it.";
2064 "notebook without saving it.";
2061 IPython.dialog.modal({
2065 IPython.dialog.modal({
2062 title : "Notebook converted",
2066 title : "Notebook converted",
2063 body : msg,
2067 body : msg,
2064 buttons : {
2068 buttons : {
2065 OK : {
2069 OK : {
2066 class : "btn-primary"
2070 class : "btn-primary"
2067 }
2071 }
2068 }
2072 }
2069 });
2073 });
2070 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2074 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2071 var that = this;
2075 var that = this;
2072 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2076 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2073 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2077 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2074 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2078 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2075 this_vs + ". You can still work with this notebook, but some features " +
2079 this_vs + ". You can still work with this notebook, but some features " +
2076 "introduced in later notebook versions may not be available.";
2080 "introduced in later notebook versions may not be available.";
2077
2081
2078 IPython.dialog.modal({
2082 IPython.dialog.modal({
2079 title : "Newer Notebook",
2083 title : "Newer Notebook",
2080 body : msg,
2084 body : msg,
2081 buttons : {
2085 buttons : {
2082 OK : {
2086 OK : {
2083 class : "btn-danger"
2087 class : "btn-danger"
2084 }
2088 }
2085 }
2089 }
2086 });
2090 });
2087
2091
2088 }
2092 }
2089
2093
2090 // Create the session after the notebook is completely loaded to prevent
2094 // Create the session after the notebook is completely loaded to prevent
2091 // code execution upon loading, which is a security risk.
2095 // code execution upon loading, which is a security risk.
2092 if (this.session === null) {
2096 if (this.session === null) {
2093 this.start_session();
2097 this.start_session();
2094 }
2098 }
2095 // load our checkpoint list
2099 // load our checkpoint list
2096 this.list_checkpoints();
2100 this.list_checkpoints();
2097
2101
2098 // load toolbar state
2102 // load toolbar state
2099 if (this.metadata.celltoolbar) {
2103 if (this.metadata.celltoolbar) {
2100 IPython.CellToolbar.global_show();
2104 IPython.CellToolbar.global_show();
2101 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2105 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2102 }
2106 }
2103
2107
2108 // now that we're fully loaded, it is safe to restore save functionality
2109 delete(this.save_notebook);
2104 $([IPython.events]).trigger('notebook_loaded.Notebook');
2110 $([IPython.events]).trigger('notebook_loaded.Notebook');
2105 };
2111 };
2106
2112
2107 /**
2113 /**
2108 * Failure callback for loading a notebook from the server.
2114 * Failure callback for loading a notebook from the server.
2109 *
2115 *
2110 * @method load_notebook_error
2116 * @method load_notebook_error
2111 * @param {jqXHR} xhr jQuery Ajax object
2117 * @param {jqXHR} xhr jQuery Ajax object
2112 * @param {String} status Description of response status
2118 * @param {String} status Description of response status
2113 * @param {String} error HTTP error message
2119 * @param {String} error HTTP error message
2114 */
2120 */
2115 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2121 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2116 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2122 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2117 var msg;
2123 var msg;
2118 if (xhr.status === 400) {
2124 if (xhr.status === 400) {
2119 msg = error;
2125 msg = error;
2120 } else if (xhr.status === 500) {
2126 } else if (xhr.status === 500) {
2121 msg = "An unknown error occurred while loading this notebook. " +
2127 msg = "An unknown error occurred while loading this notebook. " +
2122 "This version can load notebook formats " +
2128 "This version can load notebook formats " +
2123 "v" + this.nbformat + " or earlier.";
2129 "v" + this.nbformat + " or earlier.";
2124 }
2130 }
2125 IPython.dialog.modal({
2131 IPython.dialog.modal({
2126 title: "Error loading notebook",
2132 title: "Error loading notebook",
2127 body : msg,
2133 body : msg,
2128 buttons : {
2134 buttons : {
2129 "OK": {}
2135 "OK": {}
2130 }
2136 }
2131 });
2137 });
2132 };
2138 };
2133
2139
2134 /********************* checkpoint-related *********************/
2140 /********************* checkpoint-related *********************/
2135
2141
2136 /**
2142 /**
2137 * Save the notebook then immediately create a checkpoint.
2143 * Save the notebook then immediately create a checkpoint.
2138 *
2144 *
2139 * @method save_checkpoint
2145 * @method save_checkpoint
2140 */
2146 */
2141 Notebook.prototype.save_checkpoint = function () {
2147 Notebook.prototype.save_checkpoint = function () {
2142 this._checkpoint_after_save = true;
2148 this._checkpoint_after_save = true;
2143 this.save_notebook();
2149 this.save_notebook();
2144 };
2150 };
2145
2151
2146 /**
2152 /**
2147 * Add a checkpoint for this notebook.
2153 * Add a checkpoint for this notebook.
2148 * for use as a callback from checkpoint creation.
2154 * for use as a callback from checkpoint creation.
2149 *
2155 *
2150 * @method add_checkpoint
2156 * @method add_checkpoint
2151 */
2157 */
2152 Notebook.prototype.add_checkpoint = function (checkpoint) {
2158 Notebook.prototype.add_checkpoint = function (checkpoint) {
2153 var found = false;
2159 var found = false;
2154 for (var i = 0; i < this.checkpoints.length; i++) {
2160 for (var i = 0; i < this.checkpoints.length; i++) {
2155 var existing = this.checkpoints[i];
2161 var existing = this.checkpoints[i];
2156 if (existing.id == checkpoint.id) {
2162 if (existing.id == checkpoint.id) {
2157 found = true;
2163 found = true;
2158 this.checkpoints[i] = checkpoint;
2164 this.checkpoints[i] = checkpoint;
2159 break;
2165 break;
2160 }
2166 }
2161 }
2167 }
2162 if (!found) {
2168 if (!found) {
2163 this.checkpoints.push(checkpoint);
2169 this.checkpoints.push(checkpoint);
2164 }
2170 }
2165 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2171 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2166 };
2172 };
2167
2173
2168 /**
2174 /**
2169 * List checkpoints for this notebook.
2175 * List checkpoints for this notebook.
2170 *
2176 *
2171 * @method list_checkpoints
2177 * @method list_checkpoints
2172 */
2178 */
2173 Notebook.prototype.list_checkpoints = function () {
2179 Notebook.prototype.list_checkpoints = function () {
2174 var url = utils.url_join_encode(
2180 var url = utils.url_join_encode(
2175 this.base_url,
2181 this.base_url,
2176 'api/notebooks',
2182 'api/notebooks',
2177 this.notebook_path,
2183 this.notebook_path,
2178 this.notebook_name,
2184 this.notebook_name,
2179 'checkpoints'
2185 'checkpoints'
2180 );
2186 );
2181 $.get(url).done(
2187 $.get(url).done(
2182 $.proxy(this.list_checkpoints_success, this)
2188 $.proxy(this.list_checkpoints_success, this)
2183 ).fail(
2189 ).fail(
2184 $.proxy(this.list_checkpoints_error, this)
2190 $.proxy(this.list_checkpoints_error, this)
2185 );
2191 );
2186 };
2192 };
2187
2193
2188 /**
2194 /**
2189 * Success callback for listing checkpoints.
2195 * Success callback for listing checkpoints.
2190 *
2196 *
2191 * @method list_checkpoint_success
2197 * @method list_checkpoint_success
2192 * @param {Object} data JSON representation of a checkpoint
2198 * @param {Object} data JSON representation of a checkpoint
2193 * @param {String} status Description of response status
2199 * @param {String} status Description of response status
2194 * @param {jqXHR} xhr jQuery Ajax object
2200 * @param {jqXHR} xhr jQuery Ajax object
2195 */
2201 */
2196 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2202 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2197 data = $.parseJSON(data);
2203 data = $.parseJSON(data);
2198 this.checkpoints = data;
2204 this.checkpoints = data;
2199 if (data.length) {
2205 if (data.length) {
2200 this.last_checkpoint = data[data.length - 1];
2206 this.last_checkpoint = data[data.length - 1];
2201 } else {
2207 } else {
2202 this.last_checkpoint = null;
2208 this.last_checkpoint = null;
2203 }
2209 }
2204 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2210 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2205 };
2211 };
2206
2212
2207 /**
2213 /**
2208 * Failure callback for listing a checkpoint.
2214 * Failure callback for listing a checkpoint.
2209 *
2215 *
2210 * @method list_checkpoint_error
2216 * @method list_checkpoint_error
2211 * @param {jqXHR} xhr jQuery Ajax object
2217 * @param {jqXHR} xhr jQuery Ajax object
2212 * @param {String} status Description of response status
2218 * @param {String} status Description of response status
2213 * @param {String} error_msg HTTP error message
2219 * @param {String} error_msg HTTP error message
2214 */
2220 */
2215 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2221 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2216 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2222 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2217 };
2223 };
2218
2224
2219 /**
2225 /**
2220 * Create a checkpoint of this notebook on the server from the most recent save.
2226 * Create a checkpoint of this notebook on the server from the most recent save.
2221 *
2227 *
2222 * @method create_checkpoint
2228 * @method create_checkpoint
2223 */
2229 */
2224 Notebook.prototype.create_checkpoint = function () {
2230 Notebook.prototype.create_checkpoint = function () {
2225 var url = utils.url_join_encode(
2231 var url = utils.url_join_encode(
2226 this.base_url,
2232 this.base_url,
2227 'api/notebooks',
2233 'api/notebooks',
2228 this.notebook_path,
2234 this.notebook_path,
2229 this.notebook_name,
2235 this.notebook_name,
2230 'checkpoints'
2236 'checkpoints'
2231 );
2237 );
2232 $.post(url).done(
2238 $.post(url).done(
2233 $.proxy(this.create_checkpoint_success, this)
2239 $.proxy(this.create_checkpoint_success, this)
2234 ).fail(
2240 ).fail(
2235 $.proxy(this.create_checkpoint_error, this)
2241 $.proxy(this.create_checkpoint_error, this)
2236 );
2242 );
2237 };
2243 };
2238
2244
2239 /**
2245 /**
2240 * Success callback for creating a checkpoint.
2246 * Success callback for creating a checkpoint.
2241 *
2247 *
2242 * @method create_checkpoint_success
2248 * @method create_checkpoint_success
2243 * @param {Object} data JSON representation of a checkpoint
2249 * @param {Object} data JSON representation of a checkpoint
2244 * @param {String} status Description of response status
2250 * @param {String} status Description of response status
2245 * @param {jqXHR} xhr jQuery Ajax object
2251 * @param {jqXHR} xhr jQuery Ajax object
2246 */
2252 */
2247 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2253 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2248 data = $.parseJSON(data);
2254 data = $.parseJSON(data);
2249 this.add_checkpoint(data);
2255 this.add_checkpoint(data);
2250 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2256 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2251 };
2257 };
2252
2258
2253 /**
2259 /**
2254 * Failure callback for creating a checkpoint.
2260 * Failure callback for creating a checkpoint.
2255 *
2261 *
2256 * @method create_checkpoint_error
2262 * @method create_checkpoint_error
2257 * @param {jqXHR} xhr jQuery Ajax object
2263 * @param {jqXHR} xhr jQuery Ajax object
2258 * @param {String} status Description of response status
2264 * @param {String} status Description of response status
2259 * @param {String} error_msg HTTP error message
2265 * @param {String} error_msg HTTP error message
2260 */
2266 */
2261 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2267 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2262 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2268 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2263 };
2269 };
2264
2270
2265 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2271 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2266 var that = this;
2272 var that = this;
2267 checkpoint = checkpoint || this.last_checkpoint;
2273 checkpoint = checkpoint || this.last_checkpoint;
2268 if ( ! checkpoint ) {
2274 if ( ! checkpoint ) {
2269 console.log("restore dialog, but no checkpoint to restore to!");
2275 console.log("restore dialog, but no checkpoint to restore to!");
2270 return;
2276 return;
2271 }
2277 }
2272 var body = $('<div/>').append(
2278 var body = $('<div/>').append(
2273 $('<p/>').addClass("p-space").text(
2279 $('<p/>').addClass("p-space").text(
2274 "Are you sure you want to revert the notebook to " +
2280 "Are you sure you want to revert the notebook to " +
2275 "the latest checkpoint?"
2281 "the latest checkpoint?"
2276 ).append(
2282 ).append(
2277 $("<strong/>").text(
2283 $("<strong/>").text(
2278 " This cannot be undone."
2284 " This cannot be undone."
2279 )
2285 )
2280 )
2286 )
2281 ).append(
2287 ).append(
2282 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2288 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2283 ).append(
2289 ).append(
2284 $('<p/>').addClass("p-space").text(
2290 $('<p/>').addClass("p-space").text(
2285 Date(checkpoint.last_modified)
2291 Date(checkpoint.last_modified)
2286 ).css("text-align", "center")
2292 ).css("text-align", "center")
2287 );
2293 );
2288
2294
2289 IPython.dialog.modal({
2295 IPython.dialog.modal({
2290 title : "Revert notebook to checkpoint",
2296 title : "Revert notebook to checkpoint",
2291 body : body,
2297 body : body,
2292 buttons : {
2298 buttons : {
2293 Revert : {
2299 Revert : {
2294 class : "btn-danger",
2300 class : "btn-danger",
2295 click : function () {
2301 click : function () {
2296 that.restore_checkpoint(checkpoint.id);
2302 that.restore_checkpoint(checkpoint.id);
2297 }
2303 }
2298 },
2304 },
2299 Cancel : {}
2305 Cancel : {}
2300 }
2306 }
2301 });
2307 });
2302 };
2308 };
2303
2309
2304 /**
2310 /**
2305 * Restore the notebook to a checkpoint state.
2311 * Restore the notebook to a checkpoint state.
2306 *
2312 *
2307 * @method restore_checkpoint
2313 * @method restore_checkpoint
2308 * @param {String} checkpoint ID
2314 * @param {String} checkpoint ID
2309 */
2315 */
2310 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2316 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2311 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2317 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2312 var url = utils.url_join_encode(
2318 var url = utils.url_join_encode(
2313 this.base_url,
2319 this.base_url,
2314 'api/notebooks',
2320 'api/notebooks',
2315 this.notebook_path,
2321 this.notebook_path,
2316 this.notebook_name,
2322 this.notebook_name,
2317 'checkpoints',
2323 'checkpoints',
2318 checkpoint
2324 checkpoint
2319 );
2325 );
2320 $.post(url).done(
2326 $.post(url).done(
2321 $.proxy(this.restore_checkpoint_success, this)
2327 $.proxy(this.restore_checkpoint_success, this)
2322 ).fail(
2328 ).fail(
2323 $.proxy(this.restore_checkpoint_error, this)
2329 $.proxy(this.restore_checkpoint_error, this)
2324 );
2330 );
2325 };
2331 };
2326
2332
2327 /**
2333 /**
2328 * Success callback for restoring a notebook to a checkpoint.
2334 * Success callback for restoring a notebook to a checkpoint.
2329 *
2335 *
2330 * @method restore_checkpoint_success
2336 * @method restore_checkpoint_success
2331 * @param {Object} data (ignored, should be empty)
2337 * @param {Object} data (ignored, should be empty)
2332 * @param {String} status Description of response status
2338 * @param {String} status Description of response status
2333 * @param {jqXHR} xhr jQuery Ajax object
2339 * @param {jqXHR} xhr jQuery Ajax object
2334 */
2340 */
2335 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2341 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2336 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2342 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2337 this.load_notebook(this.notebook_name, this.notebook_path);
2343 this.load_notebook(this.notebook_name, this.notebook_path);
2338 };
2344 };
2339
2345
2340 /**
2346 /**
2341 * Failure callback for restoring a notebook to a checkpoint.
2347 * Failure callback for restoring a notebook to a checkpoint.
2342 *
2348 *
2343 * @method restore_checkpoint_error
2349 * @method restore_checkpoint_error
2344 * @param {jqXHR} xhr jQuery Ajax object
2350 * @param {jqXHR} xhr jQuery Ajax object
2345 * @param {String} status Description of response status
2351 * @param {String} status Description of response status
2346 * @param {String} error_msg HTTP error message
2352 * @param {String} error_msg HTTP error message
2347 */
2353 */
2348 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2354 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2349 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2355 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2350 };
2356 };
2351
2357
2352 /**
2358 /**
2353 * Delete a notebook checkpoint.
2359 * Delete a notebook checkpoint.
2354 *
2360 *
2355 * @method delete_checkpoint
2361 * @method delete_checkpoint
2356 * @param {String} checkpoint ID
2362 * @param {String} checkpoint ID
2357 */
2363 */
2358 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2364 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2359 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2365 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2360 var url = utils.url_join_encode(
2366 var url = utils.url_join_encode(
2361 this.base_url,
2367 this.base_url,
2362 'api/notebooks',
2368 'api/notebooks',
2363 this.notebook_path,
2369 this.notebook_path,
2364 this.notebook_name,
2370 this.notebook_name,
2365 'checkpoints',
2371 'checkpoints',
2366 checkpoint
2372 checkpoint
2367 );
2373 );
2368 $.ajax(url, {
2374 $.ajax(url, {
2369 type: 'DELETE',
2375 type: 'DELETE',
2370 success: $.proxy(this.delete_checkpoint_success, this),
2376 success: $.proxy(this.delete_checkpoint_success, this),
2371 error: $.proxy(this.delete_notebook_error,this)
2377 error: $.proxy(this.delete_notebook_error,this)
2372 });
2378 });
2373 };
2379 };
2374
2380
2375 /**
2381 /**
2376 * Success callback for deleting a notebook checkpoint
2382 * Success callback for deleting a notebook checkpoint
2377 *
2383 *
2378 * @method delete_checkpoint_success
2384 * @method delete_checkpoint_success
2379 * @param {Object} data (ignored, should be empty)
2385 * @param {Object} data (ignored, should be empty)
2380 * @param {String} status Description of response status
2386 * @param {String} status Description of response status
2381 * @param {jqXHR} xhr jQuery Ajax object
2387 * @param {jqXHR} xhr jQuery Ajax object
2382 */
2388 */
2383 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2389 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2384 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2390 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2385 this.load_notebook(this.notebook_name, this.notebook_path);
2391 this.load_notebook(this.notebook_name, this.notebook_path);
2386 };
2392 };
2387
2393
2388 /**
2394 /**
2389 * Failure callback for deleting a notebook checkpoint.
2395 * Failure callback for deleting a notebook checkpoint.
2390 *
2396 *
2391 * @method delete_checkpoint_error
2397 * @method delete_checkpoint_error
2392 * @param {jqXHR} xhr jQuery Ajax object
2398 * @param {jqXHR} xhr jQuery Ajax object
2393 * @param {String} status Description of response status
2399 * @param {String} status Description of response status
2394 * @param {String} error_msg HTTP error message
2400 * @param {String} error_msg HTTP error message
2395 */
2401 */
2396 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2402 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2397 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2403 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2398 };
2404 };
2399
2405
2400
2406
2401 IPython.Notebook = Notebook;
2407 IPython.Notebook = Notebook;
2402
2408
2403
2409
2404 return IPython;
2410 return IPython;
2405
2411
2406 }(IPython));
2412 }(IPython));
2407
2413
@@ -1,235 +1,235 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
2 // Copyright (C) 2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notification widget
9 // Notification widget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16
16
17 var NotificationArea = function (selector) {
17 var NotificationArea = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 }
21 }
22 this.widget_dict = {};
22 this.widget_dict = {};
23 };
23 };
24
24
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 var uuid = utils.uuid();
26 var uuid = utils.uuid();
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 var tdiv = $('<div>')
29 var tdiv = $('<div>')
30 .attr('id',uuid)
30 .attr('id',uuid)
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 .addClass('border-box-sizing')
32 .addClass('border-box-sizing')
33 .addClass(css_class)
33 .addClass(css_class)
34 .hide()
34 .hide()
35 .text(msg);
35 .text(msg);
36
36
37 $(this.selector).append(tdiv);
37 $(this.selector).append(tdiv);
38 var tmout = Math.max(1500,(timeout||1500));
38 var tmout = Math.max(1500,(timeout||1500));
39 tdiv.fadeIn(100);
39 tdiv.fadeIn(100);
40
40
41 setTimeout(function () {
41 setTimeout(function () {
42 tdiv.fadeOut(100, function () {tdiv.remove();});
42 tdiv.fadeOut(100, function () {tdiv.remove();});
43 }, tmout);
43 }, tmout);
44 };
44 };
45
45
46 NotificationArea.prototype.widget = function(name) {
46 NotificationArea.prototype.widget = function(name) {
47 if(this.widget_dict[name] === undefined) {
47 if(this.widget_dict[name] === undefined) {
48 return this.new_notification_widget(name);
48 return this.new_notification_widget(name);
49 }
49 }
50 return this.get_widget(name);
50 return this.get_widget(name);
51 };
51 };
52
52
53 NotificationArea.prototype.get_widget = function(name) {
53 NotificationArea.prototype.get_widget = function(name) {
54 if(this.widget_dict[name] === undefined) {
54 if(this.widget_dict[name] === undefined) {
55 throw('no widgets with this name');
55 throw('no widgets with this name');
56 }
56 }
57 return this.widget_dict[name];
57 return this.widget_dict[name];
58 };
58 };
59
59
60 NotificationArea.prototype.new_notification_widget = function(name) {
60 NotificationArea.prototype.new_notification_widget = function(name) {
61 if(this.widget_dict[name] !== undefined) {
61 if(this.widget_dict[name] !== undefined) {
62 throw('widget with that name already exists ! ');
62 throw('widget with that name already exists ! ');
63 }
63 }
64 var div = $('<div/>').attr('id','notification_'+name);
64 var div = $('<div/>').attr('id','notification_'+name);
65 $(this.selector).append(div);
65 $(this.selector).append(div);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
67 return this.widget_dict[name];
67 return this.widget_dict[name];
68 };
68 };
69
69
70 NotificationArea.prototype.init_notification_widgets = function() {
70 NotificationArea.prototype.init_notification_widgets = function() {
71 var knw = this.new_notification_widget('kernel');
71 var knw = this.new_notification_widget('kernel');
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
74
74
75 // Command/Edit mode
75 // Command/Edit mode
76 $([IPython.events]).on('edit_mode.Notebook',function () {
76 $([IPython.events]).on('edit_mode.Notebook',function () {
77 IPython.save_widget.update_document_title();
77 IPython.save_widget.update_document_title();
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 });
79 });
80
80
81 $([IPython.events]).on('command_mode.Notebook',function () {
81 $([IPython.events]).on('command_mode.Notebook',function () {
82 IPython.save_widget.update_document_title();
82 IPython.save_widget.update_document_title();
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 });
84 });
85
85
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
88
88
89 // Kernel events
89 // Kernel events
90 $([IPython.events]).on('status_idle.Kernel',function () {
90 $([IPython.events]).on('status_idle.Kernel',function () {
91 IPython.save_widget.update_document_title();
91 IPython.save_widget.update_document_title();
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 });
93 });
94
94
95 $([IPython.events]).on('status_busy.Kernel',function () {
95 $([IPython.events]).on('status_busy.Kernel',function () {
96 window.document.title='(Busy) '+window.document.title;
96 window.document.title='(Busy) '+window.document.title;
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 });
98 });
99
99
100 $([IPython.events]).on('status_restarting.Kernel',function () {
100 $([IPython.events]).on('status_restarting.Kernel',function () {
101 IPython.save_widget.update_document_title();
101 IPython.save_widget.update_document_title();
102 knw.set_message("Restarting kernel", 2000);
102 knw.set_message("Restarting kernel", 2000);
103 });
103 });
104
104
105 $([IPython.events]).on('status_interrupting.Kernel',function () {
105 $([IPython.events]).on('status_interrupting.Kernel',function () {
106 knw.set_message("Interrupting kernel", 2000);
106 knw.set_message("Interrupting kernel", 2000);
107 });
107 });
108
108
109 // Start the kernel indicator in the busy state, and send a kernel_info request.
109 // Start the kernel indicator in the busy state, and send a kernel_info request.
110 // When the kernel_info reply arrives, the kernel is idle.
110 // When the kernel_info reply arrives, the kernel is idle.
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112
112
113 $([IPython.events]).on('status_started.Kernel', function (evt, data) {
113 $([IPython.events]).on('status_started.Kernel', function (evt, data) {
114 data.kernel.kernel_info(function () {
114 data.kernel.kernel_info(function () {
115 $([IPython.events]).trigger('status_idle.Kernel');
115 $([IPython.events]).trigger('status_idle.Kernel');
116 });
116 });
117 });
117 });
118
118
119 $([IPython.events]).on('status_dead.Kernel',function () {
119 $([IPython.events]).on('status_dead.Kernel',function () {
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 ' It is possible the kernel cannot be restarted.' +
121 ' It is possible the kernel cannot be restarted.' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
123 ' the notebook, but running code will no longer work until the notebook' +
123 ' the notebook, but running code will no longer work until the notebook' +
124 ' is reopened.';
124 ' is reopened.';
125
125
126 IPython.dialog.modal({
126 IPython.dialog.modal({
127 title: "Dead kernel",
127 title: "Dead kernel",
128 body : msg,
128 body : msg,
129 buttons : {
129 buttons : {
130 "Manual Restart": {
130 "Manual Restart": {
131 class: "btn-danger",
131 class: "btn-danger",
132 click: function () {
132 click: function () {
133 $([IPython.events]).trigger('status_restarting.Kernel');
133 $([IPython.events]).trigger('status_restarting.Kernel');
134 IPython.notebook.start_kernel();
134 IPython.notebook.start_kernel();
135 }
135 }
136 },
136 },
137 "Don't restart": {}
137 "Don't restart": {}
138 }
138 }
139 });
139 });
140 });
140 });
141
141
142 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
142 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
143 var kernel = data.kernel;
143 var kernel = data.kernel;
144 var ws_url = data.ws_url;
144 var ws_url = data.ws_url;
145 var early = data.early;
145 var early = data.early;
146 var msg;
146 var msg;
147 if (!early) {
147 if (!early) {
148 knw.set_message('Reconnecting WebSockets', 1000);
148 knw.set_message('Reconnecting WebSockets', 1000);
149 setTimeout(function () {
149 setTimeout(function () {
150 kernel.start_channels();
150 kernel.start_channels();
151 }, 5000);
151 }, 5000);
152 return;
152 return;
153 }
153 }
154 console.log('WebSocket connection failed: ', ws_url);
154 console.log('WebSocket connection failed: ', ws_url);
155 msg = "A WebSocket connection could not be established." +
155 msg = "A WebSocket connection could not be established." +
156 " You will NOT be able to run code. Check your" +
156 " You will NOT be able to run code. Check your" +
157 " network connection or notebook server configuration.";
157 " network connection or notebook server configuration.";
158 IPython.dialog.modal({
158 IPython.dialog.modal({
159 title: "WebSocket connection failed",
159 title: "WebSocket connection failed",
160 body: msg,
160 body: msg,
161 buttons : {
161 buttons : {
162 "OK": {},
162 "OK": {},
163 "Reconnect": {
163 "Reconnect": {
164 click: function () {
164 click: function () {
165 knw.set_message('Reconnecting WebSockets', 1000);
165 knw.set_message('Reconnecting WebSockets', 1000);
166 setTimeout(function () {
166 setTimeout(function () {
167 kernel.start_channels();
167 kernel.start_channels();
168 }, 5000);
168 }, 5000);
169 }
169 }
170 }
170 }
171 }
171 }
172 });
172 });
173 });
173 });
174
174
175
175
176 var nnw = this.new_notification_widget('notebook');
176 var nnw = this.new_notification_widget('notebook');
177
177
178 // Notebook events
178 // Notebook events
179 $([IPython.events]).on('notebook_loading.Notebook', function () {
179 $([IPython.events]).on('notebook_loading.Notebook', function () {
180 nnw.set_message("Loading notebook",500);
180 nnw.set_message("Loading notebook",500);
181 });
181 });
182 $([IPython.events]).on('notebook_loaded.Notebook', function () {
182 $([IPython.events]).on('notebook_loaded.Notebook', function () {
183 nnw.set_message("Notebook loaded",500);
183 nnw.set_message("Notebook loaded",500);
184 });
184 });
185 $([IPython.events]).on('notebook_saving.Notebook', function () {
185 $([IPython.events]).on('notebook_saving.Notebook', function () {
186 nnw.set_message("Saving notebook",500);
186 nnw.set_message("Saving notebook",500);
187 });
187 });
188 $([IPython.events]).on('notebook_saved.Notebook', function () {
188 $([IPython.events]).on('notebook_saved.Notebook', function () {
189 nnw.set_message("Notebook saved",2000);
189 nnw.set_message("Notebook saved",2000);
190 });
190 });
191 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
191 $([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message("Notebook save failed");
192 nnw.set_message(data || "Notebook save failed");
193 });
193 });
194
194
195 // Checkpoint events
195 // Checkpoint events
196 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
196 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
197 var msg = "Checkpoint created";
197 var msg = "Checkpoint created";
198 if (data.last_modified) {
198 if (data.last_modified) {
199 var d = new Date(data.last_modified);
199 var d = new Date(data.last_modified);
200 msg = msg + ": " + d.format("HH:MM:ss");
200 msg = msg + ": " + d.format("HH:MM:ss");
201 }
201 }
202 nnw.set_message(msg, 2000);
202 nnw.set_message(msg, 2000);
203 });
203 });
204 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
204 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
205 nnw.set_message("Checkpoint failed");
205 nnw.set_message("Checkpoint failed");
206 });
206 });
207 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
207 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
208 nnw.set_message("Checkpoint deleted", 500);
208 nnw.set_message("Checkpoint deleted", 500);
209 });
209 });
210 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
210 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
211 nnw.set_message("Checkpoint delete failed");
211 nnw.set_message("Checkpoint delete failed");
212 });
212 });
213 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
213 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
214 nnw.set_message("Restoring to checkpoint...", 500);
214 nnw.set_message("Restoring to checkpoint...", 500);
215 });
215 });
216 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
216 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
217 nnw.set_message("Checkpoint restore failed");
217 nnw.set_message("Checkpoint restore failed");
218 });
218 });
219
219
220 // Autosave events
220 // Autosave events
221 $([IPython.events]).on('autosave_disabled.Notebook', function () {
221 $([IPython.events]).on('autosave_disabled.Notebook', function () {
222 nnw.set_message("Autosave disabled", 2000);
222 nnw.set_message("Autosave disabled", 2000);
223 });
223 });
224 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
224 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 });
226 });
227
227
228 };
228 };
229
229
230 IPython.NotificationArea = NotificationArea;
230 IPython.NotificationArea = NotificationArea;
231
231
232 return IPython;
232 return IPython;
233
233
234 }(IPython));
234 }(IPython));
235
235
@@ -1,874 +1,878 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.trusted = true;
34 this.trusted = true;
35 this.clear_queued = null;
35 this.clear_queued = null;
36 if (prompt_area === undefined) {
36 if (prompt_area === undefined) {
37 this.prompt_area = true;
37 this.prompt_area = true;
38 } else {
38 } else {
39 this.prompt_area = prompt_area;
39 this.prompt_area = prompt_area;
40 }
40 }
41 this.create_elements();
41 this.create_elements();
42 this.style();
42 this.style();
43 this.bind_events();
43 this.bind_events();
44 };
44 };
45
45
46
46
47 /**
47 /**
48 * Class prototypes
48 * Class prototypes
49 **/
49 **/
50
50
51 OutputArea.prototype.create_elements = function () {
51 OutputArea.prototype.create_elements = function () {
52 this.element = $("<div/>");
52 this.element = $("<div/>");
53 this.collapse_button = $("<div/>");
53 this.collapse_button = $("<div/>");
54 this.prompt_overlay = $("<div/>");
54 this.prompt_overlay = $("<div/>");
55 this.wrapper.append(this.prompt_overlay);
55 this.wrapper.append(this.prompt_overlay);
56 this.wrapper.append(this.element);
56 this.wrapper.append(this.element);
57 this.wrapper.append(this.collapse_button);
57 this.wrapper.append(this.collapse_button);
58 };
58 };
59
59
60
60
61 OutputArea.prototype.style = function () {
61 OutputArea.prototype.style = function () {
62 this.collapse_button.hide();
62 this.collapse_button.hide();
63 this.prompt_overlay.hide();
63 this.prompt_overlay.hide();
64
64
65 this.wrapper.addClass('output_wrapper');
65 this.wrapper.addClass('output_wrapper');
66 this.element.addClass('output');
66 this.element.addClass('output');
67
67
68 this.collapse_button.addClass("btn output_collapsed");
68 this.collapse_button.addClass("btn output_collapsed");
69 this.collapse_button.attr('title', 'click to expand output');
69 this.collapse_button.attr('title', 'click to expand output');
70 this.collapse_button.text('. . .');
70 this.collapse_button.text('. . .');
71
71
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74
74
75 this.collapse();
75 this.collapse();
76 };
76 };
77
77
78 /**
78 /**
79 * Should the OutputArea scroll?
79 * Should the OutputArea scroll?
80 * Returns whether the height (in lines) exceeds a threshold.
80 * Returns whether the height (in lines) exceeds a threshold.
81 *
81 *
82 * @private
82 * @private
83 * @method _should_scroll
83 * @method _should_scroll
84 * @param [lines=100]{Integer}
84 * @param [lines=100]{Integer}
85 * @return {Bool}
85 * @return {Bool}
86 *
86 *
87 */
87 */
88 OutputArea.prototype._should_scroll = function (lines) {
88 OutputArea.prototype._should_scroll = function (lines) {
89 if (lines <=0 ){ return }
89 if (lines <=0 ){ return }
90 if (!lines) {
90 if (!lines) {
91 lines = 100;
91 lines = 100;
92 }
92 }
93 // line-height from http://stackoverflow.com/questions/1185151
93 // line-height from http://stackoverflow.com/questions/1185151
94 var fontSize = this.element.css('font-size');
94 var fontSize = this.element.css('font-size');
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96
96
97 return (this.element.height() > lines * lineHeight);
97 return (this.element.height() > lines * lineHeight);
98 };
98 };
99
99
100
100
101 OutputArea.prototype.bind_events = function () {
101 OutputArea.prototype.bind_events = function () {
102 var that = this;
102 var that = this;
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105
105
106 this.element.resize(function () {
106 this.element.resize(function () {
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 if ( IPython.utils.browser[0] === "Firefox" ) {
108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 return;
109 return;
110 }
110 }
111 // maybe scroll output,
111 // maybe scroll output,
112 // if it's grown large enough and hasn't already been scrolled.
112 // if it's grown large enough and hasn't already been scrolled.
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 that.scroll_area();
114 that.scroll_area();
115 }
115 }
116 });
116 });
117 this.collapse_button.click(function () {
117 this.collapse_button.click(function () {
118 that.expand();
118 that.expand();
119 });
119 });
120 };
120 };
121
121
122
122
123 OutputArea.prototype.collapse = function () {
123 OutputArea.prototype.collapse = function () {
124 if (!this.collapsed) {
124 if (!this.collapsed) {
125 this.element.hide();
125 this.element.hide();
126 this.prompt_overlay.hide();
126 this.prompt_overlay.hide();
127 if (this.element.html()){
127 if (this.element.html()){
128 this.collapse_button.show();
128 this.collapse_button.show();
129 }
129 }
130 this.collapsed = true;
130 this.collapsed = true;
131 }
131 }
132 };
132 };
133
133
134
134
135 OutputArea.prototype.expand = function () {
135 OutputArea.prototype.expand = function () {
136 if (this.collapsed) {
136 if (this.collapsed) {
137 this.collapse_button.hide();
137 this.collapse_button.hide();
138 this.element.show();
138 this.element.show();
139 this.prompt_overlay.show();
139 this.prompt_overlay.show();
140 this.collapsed = false;
140 this.collapsed = false;
141 }
141 }
142 };
142 };
143
143
144
144
145 OutputArea.prototype.toggle_output = function () {
145 OutputArea.prototype.toggle_output = function () {
146 if (this.collapsed) {
146 if (this.collapsed) {
147 this.expand();
147 this.expand();
148 } else {
148 } else {
149 this.collapse();
149 this.collapse();
150 }
150 }
151 };
151 };
152
152
153
153
154 OutputArea.prototype.scroll_area = function () {
154 OutputArea.prototype.scroll_area = function () {
155 this.element.addClass('output_scroll');
155 this.element.addClass('output_scroll');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 this.scrolled = true;
157 this.scrolled = true;
158 };
158 };
159
159
160
160
161 OutputArea.prototype.unscroll_area = function () {
161 OutputArea.prototype.unscroll_area = function () {
162 this.element.removeClass('output_scroll');
162 this.element.removeClass('output_scroll');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 this.scrolled = false;
164 this.scrolled = false;
165 };
165 };
166
166
167 /**
167 /**
168 *
168 *
169 * Scroll OutputArea if height supperior than a threshold (in lines).
169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 *
170 *
171 * Threshold is a maximum number of lines. If unspecified, defaults to
171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 * OutputArea.minimum_scroll_threshold.
172 * OutputArea.minimum_scroll_threshold.
173 *
173 *
174 * Negative threshold will prevent the OutputArea from ever scrolling.
174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 *
175 *
176 * @method scroll_if_long
176 * @method scroll_if_long
177 *
177 *
178 * @param [lines=20]{Number} Default to 20 if not set,
178 * @param [lines=20]{Number} Default to 20 if not set,
179 * behavior undefined for value of `0`.
179 * behavior undefined for value of `0`.
180 *
180 *
181 **/
181 **/
182 OutputArea.prototype.scroll_if_long = function (lines) {
182 OutputArea.prototype.scroll_if_long = function (lines) {
183 var n = lines | OutputArea.minimum_scroll_threshold;
183 var n = lines | OutputArea.minimum_scroll_threshold;
184 if(n <= 0){
184 if(n <= 0){
185 return
185 return
186 }
186 }
187
187
188 if (this._should_scroll(n)) {
188 if (this._should_scroll(n)) {
189 // only allow scrolling long-enough output
189 // only allow scrolling long-enough output
190 this.scroll_area();
190 this.scroll_area();
191 }
191 }
192 };
192 };
193
193
194
194
195 OutputArea.prototype.toggle_scroll = function () {
195 OutputArea.prototype.toggle_scroll = function () {
196 if (this.scrolled) {
196 if (this.scrolled) {
197 this.unscroll_area();
197 this.unscroll_area();
198 } else {
198 } else {
199 // only allow scrolling long-enough output
199 // only allow scrolling long-enough output
200 this.scroll_if_long();
200 this.scroll_if_long();
201 }
201 }
202 };
202 };
203
203
204
204
205 // typeset with MathJax if MathJax is available
205 // typeset with MathJax if MathJax is available
206 OutputArea.prototype.typeset = function () {
206 OutputArea.prototype.typeset = function () {
207 if (window.MathJax){
207 if (window.MathJax){
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 }
209 }
210 };
210 };
211
211
212
212
213 OutputArea.prototype.handle_output = function (msg) {
213 OutputArea.prototype.handle_output = function (msg) {
214 var json = {};
214 var json = {};
215 var msg_type = json.output_type = msg.header.msg_type;
215 var msg_type = json.output_type = msg.header.msg_type;
216 var content = msg.content;
216 var content = msg.content;
217 if (msg_type === "stream") {
217 if (msg_type === "stream") {
218 json.text = content.data;
218 json.text = content.data;
219 json.stream = content.name;
219 json.stream = content.name;
220 } else if (msg_type === "display_data") {
220 } else if (msg_type === "display_data") {
221 json = content.data;
221 json = content.data;
222 json.output_type = msg_type;
222 json.output_type = msg_type;
223 json.metadata = content.metadata;
223 json.metadata = content.metadata;
224 } else if (msg_type === "pyout") {
224 } else if (msg_type === "pyout") {
225 json = content.data;
225 json = content.data;
226 json.output_type = msg_type;
226 json.output_type = msg_type;
227 json.metadata = content.metadata;
227 json.metadata = content.metadata;
228 json.prompt_number = content.execution_count;
228 json.prompt_number = content.execution_count;
229 } else if (msg_type === "pyerr") {
229 } else if (msg_type === "pyerr") {
230 json.ename = content.ename;
230 json.ename = content.ename;
231 json.evalue = content.evalue;
231 json.evalue = content.evalue;
232 json.traceback = content.traceback;
232 json.traceback = content.traceback;
233 }
233 }
234 this.append_output(json);
234 this.append_output(json);
235 };
235 };
236
236
237
237
238 OutputArea.prototype.rename_keys = function (data, key_map) {
238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 var remapped = {};
239 var remapped = {};
240 for (var key in data) {
240 for (var key in data) {
241 var new_key = key_map[key] || key;
241 var new_key = key_map[key] || key;
242 remapped[new_key] = data[key];
242 remapped[new_key] = data[key];
243 }
243 }
244 return remapped;
244 return remapped;
245 };
245 };
246
246
247
247
248 OutputArea.output_types = [
248 OutputArea.output_types = [
249 'application/javascript',
249 'application/javascript',
250 'text/html',
250 'text/html',
251 'text/latex',
251 'text/latex',
252 'image/svg+xml',
252 'image/svg+xml',
253 'image/png',
253 'image/png',
254 'image/jpeg',
254 'image/jpeg',
255 'application/pdf',
255 'application/pdf',
256 'text/plain'
256 'text/plain'
257 ];
257 ];
258
258
259 OutputArea.prototype.validate_output = function (json) {
259 OutputArea.prototype.validate_output = function (json) {
260 // scrub invalid outputs
260 // scrub invalid outputs
261 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // TODO: right now everything is a string, but JSON really shouldn't be.
262 // nbformat 4 will fix that.
262 // nbformat 4 will fix that.
263 $.map(OutputArea.output_types, function(key){
263 $.map(OutputArea.output_types, function(key){
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 console.log("Invalid type for " + key, json[key]);
265 console.log("Invalid type for " + key, json[key]);
266 delete json[key];
266 delete json[key];
267 }
267 }
268 });
268 });
269 return json;
269 return json;
270 };
270 };
271
271
272 OutputArea.prototype.append_output = function (json) {
272 OutputArea.prototype.append_output = function (json) {
273 this.expand();
273 this.expand();
274 // Clear the output if clear is queued.
274 // Clear the output if clear is queued.
275 var needs_height_reset = false;
275 var needs_height_reset = false;
276 if (this.clear_queued) {
276 if (this.clear_queued) {
277 this.clear_output(false);
277 this.clear_output(false);
278 needs_height_reset = true;
278 needs_height_reset = true;
279 }
279 }
280
280
281 // validate output data types
281 // validate output data types
282 json = this.validate_output(json);
282 json = this.validate_output(json);
283
283
284 if (json.output_type === 'pyout') {
284 if (json.output_type === 'pyout') {
285 this.append_pyout(json);
285 this.append_pyout(json);
286 } else if (json.output_type === 'pyerr') {
286 } else if (json.output_type === 'pyerr') {
287 this.append_pyerr(json);
287 this.append_pyerr(json);
288 } else if (json.output_type === 'display_data') {
288 } else if (json.output_type === 'display_data') {
289 this.append_display_data(json);
289 this.append_display_data(json);
290 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
291 this.append_stream(json);
291 this.append_stream(json);
292 }
292 }
293
293
294 this.outputs.push(json);
294 this.outputs.push(json);
295
295
296 // Only reset the height to automatic if the height is currently
296 // Only reset the height to automatic if the height is currently
297 // fixed (done by wait=True flag on clear_output).
297 // fixed (done by wait=True flag on clear_output).
298 if (needs_height_reset) {
298 if (needs_height_reset) {
299 this.element.height('');
299 this.element.height('');
300 }
300 }
301
301
302 var that = this;
302 var that = this;
303 setTimeout(function(){that.element.trigger('resize');}, 100);
303 setTimeout(function(){that.element.trigger('resize');}, 100);
304 };
304 };
305
305
306
306
307 OutputArea.prototype.create_output_area = function () {
307 OutputArea.prototype.create_output_area = function () {
308 var oa = $("<div/>").addClass("output_area");
308 var oa = $("<div/>").addClass("output_area");
309 if (this.prompt_area) {
309 if (this.prompt_area) {
310 oa.append($('<div/>').addClass('prompt'));
310 oa.append($('<div/>').addClass('prompt'));
311 }
311 }
312 return oa;
312 return oa;
313 };
313 };
314
314
315
315
316 function _get_metadata_key(metadata, key, mime) {
316 function _get_metadata_key(metadata, key, mime) {
317 var mime_md = metadata[mime];
317 var mime_md = metadata[mime];
318 // mime-specific higher priority
318 // mime-specific higher priority
319 if (mime_md && mime_md[key] !== undefined) {
319 if (mime_md && mime_md[key] !== undefined) {
320 return mime_md[key];
320 return mime_md[key];
321 }
321 }
322 // fallback on global
322 // fallback on global
323 return metadata[key];
323 return metadata[key];
324 }
324 }
325
325
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
328 if (_get_metadata_key(md, 'isolated', mime)) {
328 if (_get_metadata_key(md, 'isolated', mime)) {
329 // Create an iframe to isolate the subarea from the rest of the
329 // Create an iframe to isolate the subarea from the rest of the
330 // document
330 // document
331 var iframe = $('<iframe/>').addClass('box-flex1');
331 var iframe = $('<iframe/>').addClass('box-flex1');
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
333 iframe.attr('frameborder', 0);
333 iframe.attr('frameborder', 0);
334 iframe.attr('scrolling', 'auto');
334 iframe.attr('scrolling', 'auto');
335
335
336 // Once the iframe is loaded, the subarea is dynamically inserted
336 // Once the iframe is loaded, the subarea is dynamically inserted
337 iframe.on('load', function() {
337 iframe.on('load', function() {
338 // Workaround needed by Firefox, to properly render svg inside
338 // Workaround needed by Firefox, to properly render svg inside
339 // iframes, see http://stackoverflow.com/questions/10177190/
339 // iframes, see http://stackoverflow.com/questions/10177190/
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
341 this.contentDocument.open();
341 this.contentDocument.open();
342
342
343 // Insert the subarea into the iframe
343 // Insert the subarea into the iframe
344 // We must directly write the html. When using Jquery's append
344 // We must directly write the html. When using Jquery's append
345 // method, javascript is evaluated in the parent document and
345 // method, javascript is evaluated in the parent document and
346 // not in the iframe document. At this point, subarea doesn't
346 // not in the iframe document. At this point, subarea doesn't
347 // contain any user content.
347 // contain any user content.
348 this.contentDocument.write(subarea.html());
348 this.contentDocument.write(subarea.html());
349
349
350 this.contentDocument.close();
350 this.contentDocument.close();
351
351
352 var body = this.contentDocument.body;
352 var body = this.contentDocument.body;
353 // Adjust the iframe height automatically
353 // Adjust the iframe height automatically
354 iframe.height(body.scrollHeight + 'px');
354 iframe.height(body.scrollHeight + 'px');
355 });
355 });
356
356
357 // Elements should be appended to the inner subarea and not to the
357 // Elements should be appended to the inner subarea and not to the
358 // iframe
358 // iframe
359 iframe.append = function(that) {
359 iframe.append = function(that) {
360 subarea.append(that);
360 subarea.append(that);
361 };
361 };
362
362
363 return iframe;
363 return iframe;
364 } else {
364 } else {
365 return subarea;
365 return subarea;
366 }
366 }
367 }
367 }
368
368
369
369
370 OutputArea.prototype._append_javascript_error = function (err, element) {
370 OutputArea.prototype._append_javascript_error = function (err, element) {
371 // display a message when a javascript error occurs in display output
371 // display a message when a javascript error occurs in display output
372 var msg = "Javascript error adding output!"
372 var msg = "Javascript error adding output!"
373 if ( element === undefined ) return;
373 if ( element === undefined ) return;
374 element
374 element
375 .append($('<div/>').text(msg).addClass('js-error'))
375 .append($('<div/>').text(msg).addClass('js-error'))
376 .append($('<div/>').text(err.toString()).addClass('js-error'))
376 .append($('<div/>').text(err.toString()).addClass('js-error'))
377 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
377 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
378 };
378 };
379
379
380 OutputArea.prototype._safe_append = function (toinsert) {
380 OutputArea.prototype._safe_append = function (toinsert) {
381 // safely append an item to the document
381 // safely append an item to the document
382 // this is an object created by user code,
382 // this is an object created by user code,
383 // and may have errors, which should not be raised
383 // and may have errors, which should not be raised
384 // under any circumstances.
384 // under any circumstances.
385 try {
385 try {
386 this.element.append(toinsert);
386 this.element.append(toinsert);
387 } catch(err) {
387 } catch(err) {
388 console.log(err);
388 console.log(err);
389 // Create an actual output_area and output_subarea, which creates
389 // Create an actual output_area and output_subarea, which creates
390 // the prompt area and the proper indentation.
390 // the prompt area and the proper indentation.
391 var toinsert = this.create_output_area();
391 var toinsert = this.create_output_area();
392 var subarea = $('<div/>').addClass('output_subarea');
392 var subarea = $('<div/>').addClass('output_subarea');
393 toinsert.append(subarea);
393 toinsert.append(subarea);
394 this._append_javascript_error(err, subarea);
394 this._append_javascript_error(err, subarea);
395 this.element.append(toinsert);
395 this.element.append(toinsert);
396 }
396 }
397 };
397 };
398
398
399
399
400 OutputArea.prototype.append_pyout = function (json) {
400 OutputArea.prototype.append_pyout = function (json) {
401 var n = json.prompt_number || ' ';
401 var n = json.prompt_number || ' ';
402 var toinsert = this.create_output_area();
402 var toinsert = this.create_output_area();
403 if (this.prompt_area) {
403 if (this.prompt_area) {
404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
405 }
405 }
406 var inserted = this.append_mime_type(json, toinsert);
406 var inserted = this.append_mime_type(json, toinsert);
407 if (inserted) {
407 if (inserted) {
408 inserted.addClass('output_pyout');
408 inserted.addClass('output_pyout');
409 }
409 }
410 this._safe_append(toinsert);
410 this._safe_append(toinsert);
411 // If we just output latex, typeset it.
411 // If we just output latex, typeset it.
412 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
412 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
413 this.typeset();
413 this.typeset();
414 }
414 }
415 };
415 };
416
416
417
417
418 OutputArea.prototype.append_pyerr = function (json) {
418 OutputArea.prototype.append_pyerr = function (json) {
419 var tb = json.traceback;
419 var tb = json.traceback;
420 if (tb !== undefined && tb.length > 0) {
420 if (tb !== undefined && tb.length > 0) {
421 var s = '';
421 var s = '';
422 var len = tb.length;
422 var len = tb.length;
423 for (var i=0; i<len; i++) {
423 for (var i=0; i<len; i++) {
424 s = s + tb[i] + '\n';
424 s = s + tb[i] + '\n';
425 }
425 }
426 s = s + '\n';
426 s = s + '\n';
427 var toinsert = this.create_output_area();
427 var toinsert = this.create_output_area();
428 var append_text = OutputArea.append_map['text/plain'];
428 var append_text = OutputArea.append_map['text/plain'];
429 if (append_text) {
429 if (append_text) {
430 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
430 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
431 }
431 }
432 this._safe_append(toinsert);
432 this._safe_append(toinsert);
433 }
433 }
434 };
434 };
435
435
436
436
437 OutputArea.prototype.append_stream = function (json) {
437 OutputArea.prototype.append_stream = function (json) {
438 // temporary fix: if stream undefined (json file written prior to this patch),
438 // temporary fix: if stream undefined (json file written prior to this patch),
439 // default to most likely stdout:
439 // default to most likely stdout:
440 if (json.stream === undefined){
440 if (json.stream === undefined){
441 json.stream = 'stdout';
441 json.stream = 'stdout';
442 }
442 }
443 var text = json.text;
443 var text = json.text;
444 var subclass = "output_"+json.stream;
444 var subclass = "output_"+json.stream;
445 if (this.outputs.length > 0){
445 if (this.outputs.length > 0){
446 // have at least one output to consider
446 // have at least one output to consider
447 var last = this.outputs[this.outputs.length-1];
447 var last = this.outputs[this.outputs.length-1];
448 if (last.output_type == 'stream' && json.stream == last.stream){
448 if (last.output_type == 'stream' && json.stream == last.stream){
449 // latest output was in the same stream,
449 // latest output was in the same stream,
450 // so append directly into its pre tag
450 // so append directly into its pre tag
451 // escape ANSI & HTML specials:
451 // escape ANSI & HTML specials:
452 var pre = this.element.find('div.'+subclass).last().find('pre');
452 var pre = this.element.find('div.'+subclass).last().find('pre');
453 var html = utils.fixCarriageReturn(
453 var html = utils.fixCarriageReturn(
454 pre.html() + utils.fixConsole(text));
454 pre.html() + utils.fixConsole(text));
455 // The only user content injected with this HTML call is
455 // The only user content injected with this HTML call is
456 // escaped by the fixConsole() method.
456 // escaped by the fixConsole() method.
457 pre.html(html);
457 pre.html(html);
458 return;
458 return;
459 }
459 }
460 }
460 }
461
461
462 if (!text.replace("\r", "")) {
462 if (!text.replace("\r", "")) {
463 // text is nothing (empty string, \r, etc.)
463 // text is nothing (empty string, \r, etc.)
464 // so don't append any elements, which might add undesirable space
464 // so don't append any elements, which might add undesirable space
465 return;
465 return;
466 }
466 }
467
467
468 // If we got here, attach a new div
468 // If we got here, attach a new div
469 var toinsert = this.create_output_area();
469 var toinsert = this.create_output_area();
470 var append_text = OutputArea.append_map['text/plain'];
470 var append_text = OutputArea.append_map['text/plain'];
471 if (append_text) {
471 if (append_text) {
472 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
472 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
473 }
473 }
474 this._safe_append(toinsert);
474 this._safe_append(toinsert);
475 };
475 };
476
476
477
477
478 OutputArea.prototype.append_display_data = function (json) {
478 OutputArea.prototype.append_display_data = function (json) {
479 var toinsert = this.create_output_area();
479 var toinsert = this.create_output_area();
480 if (this.append_mime_type(json, toinsert)) {
480 if (this.append_mime_type(json, toinsert)) {
481 this._safe_append(toinsert);
481 this._safe_append(toinsert);
482 // If we just output latex, typeset it.
482 // If we just output latex, typeset it.
483 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
483 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
484 this.typeset();
484 this.typeset();
485 }
485 }
486 }
486 }
487 };
487 };
488
488
489
489
490 OutputArea.safe_outputs = {
490 OutputArea.safe_outputs = {
491 'text/plain' : true,
491 'text/plain' : true,
492 'text/latex' : true,
492 'text/latex' : true,
493 'image/png' : true,
493 'image/png' : true,
494 'image/jpeg' : true
494 'image/jpeg' : true
495 };
495 };
496
496
497 OutputArea.prototype.append_mime_type = function (json, element) {
497 OutputArea.prototype.append_mime_type = function (json, element) {
498 for (var type_i in OutputArea.display_order) {
498 for (var type_i in OutputArea.display_order) {
499 var type = OutputArea.display_order[type_i];
499 var type = OutputArea.display_order[type_i];
500 var append = OutputArea.append_map[type];
500 var append = OutputArea.append_map[type];
501 if ((json[type] !== undefined) && append) {
501 if ((json[type] !== undefined) && append) {
502 var value = json[type];
502 var value = json[type];
503 if (!this.trusted && !OutputArea.safe_outputs[type]) {
503 if (!this.trusted && !OutputArea.safe_outputs[type]) {
504 // not trusted, sanitize HTML
504 // not trusted, sanitize HTML
505 if (type==='text/html' || type==='text/svg') {
505 if (type==='text/html' || type==='text/svg') {
506 value = IPython.security.sanitize_html(value);
506 value = IPython.security.sanitize_html(value);
507 } else {
507 } else {
508 // don't display if we don't know how to sanitize it
508 // don't display if we don't know how to sanitize it
509 console.log("Ignoring untrusted " + type + " output.");
509 console.log("Ignoring untrusted " + type + " output.");
510 continue;
510 continue;
511 }
511 }
512 }
512 }
513 var md = json.metadata || {};
513 var md = json.metadata || {};
514 var toinsert = append.apply(this, [value, md, element]);
514 var toinsert = append.apply(this, [value, md, element]);
515 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
515 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
516 return toinsert;
516 return toinsert;
517 }
517 }
518 }
518 }
519 return null;
519 return null;
520 };
520 };
521
521
522
522
523 var append_html = function (html, md, element) {
523 var append_html = function (html, md, element) {
524 var type = 'text/html';
524 var type = 'text/html';
525 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
525 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
526 IPython.keyboard_manager.register_events(toinsert);
526 IPython.keyboard_manager.register_events(toinsert);
527 toinsert.append(html);
527 toinsert.append(html);
528 element.append(toinsert);
528 element.append(toinsert);
529 return toinsert;
529 return toinsert;
530 };
530 };
531
531
532
532
533 var append_javascript = function (js, md, element) {
533 var append_javascript = function (js, md, element) {
534 // We just eval the JS code, element appears in the local scope.
534 // We just eval the JS code, element appears in the local scope.
535 var type = 'application/javascript';
535 var type = 'application/javascript';
536 var toinsert = this.create_output_subarea(md, "output_javascript", type);
536 var toinsert = this.create_output_subarea(md, "output_javascript", type);
537 IPython.keyboard_manager.register_events(toinsert);
537 IPython.keyboard_manager.register_events(toinsert);
538 element.append(toinsert);
538 element.append(toinsert);
539 // FIXME TODO : remove `container element for 3.0`
539 // FIXME TODO : remove `container element for 3.0`
540 //backward compat, js should be eval'ed in a context where `container` is defined.
540 //backward compat, js should be eval'ed in a context where `container` is defined.
541 var container = element;
541 var container = element;
542 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
542 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
543 // end backward compat
543 // end backward compat
544
545 // Fix for ipython/issues/5293, make sure `element` is the area which
546 // output can be inserted into at the time of JS execution.
547 element = toinsert;
544 try {
548 try {
545 eval(js);
549 eval(js);
546 } catch(err) {
550 } catch(err) {
547 console.log(err);
551 console.log(err);
548 this._append_javascript_error(err, toinsert);
552 this._append_javascript_error(err, toinsert);
549 }
553 }
550 return toinsert;
554 return toinsert;
551 };
555 };
552
556
553
557
554 var append_text = function (data, md, element) {
558 var append_text = function (data, md, element) {
555 var type = 'text/plain';
559 var type = 'text/plain';
556 var toinsert = this.create_output_subarea(md, "output_text", type);
560 var toinsert = this.create_output_subarea(md, "output_text", type);
557 // escape ANSI & HTML specials in plaintext:
561 // escape ANSI & HTML specials in plaintext:
558 data = utils.fixConsole(data);
562 data = utils.fixConsole(data);
559 data = utils.fixCarriageReturn(data);
563 data = utils.fixCarriageReturn(data);
560 data = utils.autoLinkUrls(data);
564 data = utils.autoLinkUrls(data);
561 // The only user content injected with this HTML call is
565 // The only user content injected with this HTML call is
562 // escaped by the fixConsole() method.
566 // escaped by the fixConsole() method.
563 toinsert.append($("<pre/>").html(data));
567 toinsert.append($("<pre/>").html(data));
564 element.append(toinsert);
568 element.append(toinsert);
565 return toinsert;
569 return toinsert;
566 };
570 };
567
571
568
572
569 var append_svg = function (svg, md, element) {
573 var append_svg = function (svg, md, element) {
570 var type = 'image/svg+xml';
574 var type = 'image/svg+xml';
571 var toinsert = this.create_output_subarea(md, "output_svg", type);
575 var toinsert = this.create_output_subarea(md, "output_svg", type);
572 toinsert.append(svg);
576 toinsert.append(svg);
573 element.append(toinsert);
577 element.append(toinsert);
574 return toinsert;
578 return toinsert;
575 };
579 };
576
580
577
581
578 OutputArea.prototype._dblclick_to_reset_size = function (img) {
582 OutputArea.prototype._dblclick_to_reset_size = function (img) {
579 // wrap image after it's loaded on the page,
583 // wrap image after it's loaded on the page,
580 // otherwise the measured initial size will be incorrect
584 // otherwise the measured initial size will be incorrect
581 img.on("load", function (){
585 img.on("load", function (){
582 var h0 = img.height();
586 var h0 = img.height();
583 var w0 = img.width();
587 var w0 = img.width();
584 if (!(h0 && w0)) {
588 if (!(h0 && w0)) {
585 // zero size, don't make it resizable
589 // zero size, don't make it resizable
586 return;
590 return;
587 }
591 }
588 img.resizable({
592 img.resizable({
589 aspectRatio: true,
593 aspectRatio: true,
590 autoHide: true
594 autoHide: true
591 });
595 });
592 img.dblclick(function () {
596 img.dblclick(function () {
593 // resize wrapper & image together for some reason:
597 // resize wrapper & image together for some reason:
594 img.parent().height(h0);
598 img.parent().height(h0);
595 img.height(h0);
599 img.height(h0);
596 img.parent().width(w0);
600 img.parent().width(w0);
597 img.width(w0);
601 img.width(w0);
598 });
602 });
599 });
603 });
600 };
604 };
601
605
602 var set_width_height = function (img, md, mime) {
606 var set_width_height = function (img, md, mime) {
603 // set width and height of an img element from metadata
607 // set width and height of an img element from metadata
604 var height = _get_metadata_key(md, 'height', mime);
608 var height = _get_metadata_key(md, 'height', mime);
605 if (height !== undefined) img.attr('height', height);
609 if (height !== undefined) img.attr('height', height);
606 var width = _get_metadata_key(md, 'width', mime);
610 var width = _get_metadata_key(md, 'width', mime);
607 if (width !== undefined) img.attr('width', width);
611 if (width !== undefined) img.attr('width', width);
608 };
612 };
609
613
610 var append_png = function (png, md, element) {
614 var append_png = function (png, md, element) {
611 var type = 'image/png';
615 var type = 'image/png';
612 var toinsert = this.create_output_subarea(md, "output_png", type);
616 var toinsert = this.create_output_subarea(md, "output_png", type);
613 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
617 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
614 set_width_height(img, md, 'image/png');
618 set_width_height(img, md, 'image/png');
615 this._dblclick_to_reset_size(img);
619 this._dblclick_to_reset_size(img);
616 toinsert.append(img);
620 toinsert.append(img);
617 element.append(toinsert);
621 element.append(toinsert);
618 return toinsert;
622 return toinsert;
619 };
623 };
620
624
621
625
622 var append_jpeg = function (jpeg, md, element) {
626 var append_jpeg = function (jpeg, md, element) {
623 var type = 'image/jpeg';
627 var type = 'image/jpeg';
624 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
628 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
625 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
629 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
626 set_width_height(img, md, 'image/jpeg');
630 set_width_height(img, md, 'image/jpeg');
627 this._dblclick_to_reset_size(img);
631 this._dblclick_to_reset_size(img);
628 toinsert.append(img);
632 toinsert.append(img);
629 element.append(toinsert);
633 element.append(toinsert);
630 return toinsert;
634 return toinsert;
631 };
635 };
632
636
633
637
634 var append_pdf = function (pdf, md, element) {
638 var append_pdf = function (pdf, md, element) {
635 var type = 'application/pdf';
639 var type = 'application/pdf';
636 var toinsert = this.create_output_subarea(md, "output_pdf", type);
640 var toinsert = this.create_output_subarea(md, "output_pdf", type);
637 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
641 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
638 a.attr('target', '_blank');
642 a.attr('target', '_blank');
639 a.text('View PDF')
643 a.text('View PDF')
640 toinsert.append(a);
644 toinsert.append(a);
641 element.append(toinsert);
645 element.append(toinsert);
642 return toinsert;
646 return toinsert;
643 }
647 }
644
648
645 var append_latex = function (latex, md, element) {
649 var append_latex = function (latex, md, element) {
646 // This method cannot do the typesetting because the latex first has to
650 // This method cannot do the typesetting because the latex first has to
647 // be on the page.
651 // be on the page.
648 var type = 'text/latex';
652 var type = 'text/latex';
649 var toinsert = this.create_output_subarea(md, "output_latex", type);
653 var toinsert = this.create_output_subarea(md, "output_latex", type);
650 toinsert.append(latex);
654 toinsert.append(latex);
651 element.append(toinsert);
655 element.append(toinsert);
652 return toinsert;
656 return toinsert;
653 };
657 };
654
658
655
659
656 OutputArea.prototype.append_raw_input = function (msg) {
660 OutputArea.prototype.append_raw_input = function (msg) {
657 var that = this;
661 var that = this;
658 this.expand();
662 this.expand();
659 var content = msg.content;
663 var content = msg.content;
660 var area = this.create_output_area();
664 var area = this.create_output_area();
661
665
662 // disable any other raw_inputs, if they are left around
666 // disable any other raw_inputs, if they are left around
663 $("div.output_subarea.raw_input_container").remove();
667 $("div.output_subarea.raw_input_container").remove();
664
668
665 area.append(
669 area.append(
666 $("<div/>")
670 $("<div/>")
667 .addClass("box-flex1 output_subarea raw_input_container")
671 .addClass("box-flex1 output_subarea raw_input_container")
668 .append(
672 .append(
669 $("<span/>")
673 $("<span/>")
670 .addClass("raw_input_prompt")
674 .addClass("raw_input_prompt")
671 .text(content.prompt)
675 .text(content.prompt)
672 )
676 )
673 .append(
677 .append(
674 $("<input/>")
678 $("<input/>")
675 .addClass("raw_input")
679 .addClass("raw_input")
676 .attr('type', 'text')
680 .attr('type', 'text')
677 .attr("size", 47)
681 .attr("size", 47)
678 .keydown(function (event, ui) {
682 .keydown(function (event, ui) {
679 // make sure we submit on enter,
683 // make sure we submit on enter,
680 // and don't re-execute the *cell* on shift-enter
684 // and don't re-execute the *cell* on shift-enter
681 if (event.which === IPython.keyboard.keycodes.enter) {
685 if (event.which === IPython.keyboard.keycodes.enter) {
682 that._submit_raw_input();
686 that._submit_raw_input();
683 return false;
687 return false;
684 }
688 }
685 })
689 })
686 )
690 )
687 );
691 );
688
692
689 this.element.append(area);
693 this.element.append(area);
690 var raw_input = area.find('input.raw_input');
694 var raw_input = area.find('input.raw_input');
691 // Register events that enable/disable the keyboard manager while raw
695 // Register events that enable/disable the keyboard manager while raw
692 // input is focused.
696 // input is focused.
693 IPython.keyboard_manager.register_events(raw_input);
697 IPython.keyboard_manager.register_events(raw_input);
694 // Note, the following line used to read raw_input.focus().focus().
698 // Note, the following line used to read raw_input.focus().focus().
695 // This seemed to be needed otherwise only the cell would be focused.
699 // This seemed to be needed otherwise only the cell would be focused.
696 // But with the modal UI, this seems to work fine with one call to focus().
700 // But with the modal UI, this seems to work fine with one call to focus().
697 raw_input.focus();
701 raw_input.focus();
698 }
702 }
699
703
700 OutputArea.prototype._submit_raw_input = function (evt) {
704 OutputArea.prototype._submit_raw_input = function (evt) {
701 var container = this.element.find("div.raw_input_container");
705 var container = this.element.find("div.raw_input_container");
702 var theprompt = container.find("span.raw_input_prompt");
706 var theprompt = container.find("span.raw_input_prompt");
703 var theinput = container.find("input.raw_input");
707 var theinput = container.find("input.raw_input");
704 var value = theinput.val();
708 var value = theinput.val();
705 var content = {
709 var content = {
706 output_type : 'stream',
710 output_type : 'stream',
707 name : 'stdout',
711 name : 'stdout',
708 text : theprompt.text() + value + '\n'
712 text : theprompt.text() + value + '\n'
709 }
713 }
710 // remove form container
714 // remove form container
711 container.parent().remove();
715 container.parent().remove();
712 // replace with plaintext version in stdout
716 // replace with plaintext version in stdout
713 this.append_output(content, false);
717 this.append_output(content, false);
714 $([IPython.events]).trigger('send_input_reply.Kernel', value);
718 $([IPython.events]).trigger('send_input_reply.Kernel', value);
715 }
719 }
716
720
717
721
718 OutputArea.prototype.handle_clear_output = function (msg) {
722 OutputArea.prototype.handle_clear_output = function (msg) {
719 // msg spec v4 had stdout, stderr, display keys
723 // msg spec v4 had stdout, stderr, display keys
720 // v4.1 replaced these with just wait
724 // v4.1 replaced these with just wait
721 // The default behavior is the same (stdout=stderr=display=True, wait=False),
725 // The default behavior is the same (stdout=stderr=display=True, wait=False),
722 // so v4 messages will still be properly handled,
726 // so v4 messages will still be properly handled,
723 // except for the rarely used clearing less than all output.
727 // except for the rarely used clearing less than all output.
724 this.clear_output(msg.content.wait || false);
728 this.clear_output(msg.content.wait || false);
725 };
729 };
726
730
727
731
728 OutputArea.prototype.clear_output = function(wait) {
732 OutputArea.prototype.clear_output = function(wait) {
729 if (wait) {
733 if (wait) {
730
734
731 // If a clear is queued, clear before adding another to the queue.
735 // If a clear is queued, clear before adding another to the queue.
732 if (this.clear_queued) {
736 if (this.clear_queued) {
733 this.clear_output(false);
737 this.clear_output(false);
734 };
738 };
735
739
736 this.clear_queued = true;
740 this.clear_queued = true;
737 } else {
741 } else {
738
742
739 // Fix the output div's height if the clear_output is waiting for
743 // Fix the output div's height if the clear_output is waiting for
740 // new output (it is being used in an animation).
744 // new output (it is being used in an animation).
741 if (this.clear_queued) {
745 if (this.clear_queued) {
742 var height = this.element.height();
746 var height = this.element.height();
743 this.element.height(height);
747 this.element.height(height);
744 this.clear_queued = false;
748 this.clear_queued = false;
745 }
749 }
746
750
747 // clear all, no need for logic
751 // clear all, no need for logic
748 this.element.html("");
752 this.element.html("");
749 this.outputs = [];
753 this.outputs = [];
750 this.trusted = true;
754 this.trusted = true;
751 this.unscroll_area();
755 this.unscroll_area();
752 return;
756 return;
753 };
757 };
754 };
758 };
755
759
756
760
757 // JSON serialization
761 // JSON serialization
758
762
759 OutputArea.prototype.fromJSON = function (outputs) {
763 OutputArea.prototype.fromJSON = function (outputs) {
760 var len = outputs.length;
764 var len = outputs.length;
761 var data;
765 var data;
762
766
763 for (var i=0; i<len; i++) {
767 for (var i=0; i<len; i++) {
764 data = outputs[i];
768 data = outputs[i];
765 var msg_type = data.output_type;
769 var msg_type = data.output_type;
766 if (msg_type === "display_data" || msg_type === "pyout") {
770 if (msg_type === "display_data" || msg_type === "pyout") {
767 // convert short keys to mime keys
771 // convert short keys to mime keys
768 // TODO: remove mapping of short keys when we update to nbformat 4
772 // TODO: remove mapping of short keys when we update to nbformat 4
769 data = this.rename_keys(data, OutputArea.mime_map_r);
773 data = this.rename_keys(data, OutputArea.mime_map_r);
770 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
774 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
771 }
775 }
772
776
773 this.append_output(data);
777 this.append_output(data);
774 }
778 }
775 };
779 };
776
780
777
781
778 OutputArea.prototype.toJSON = function () {
782 OutputArea.prototype.toJSON = function () {
779 var outputs = [];
783 var outputs = [];
780 var len = this.outputs.length;
784 var len = this.outputs.length;
781 var data;
785 var data;
782 for (var i=0; i<len; i++) {
786 for (var i=0; i<len; i++) {
783 data = this.outputs[i];
787 data = this.outputs[i];
784 var msg_type = data.output_type;
788 var msg_type = data.output_type;
785 if (msg_type === "display_data" || msg_type === "pyout") {
789 if (msg_type === "display_data" || msg_type === "pyout") {
786 // convert mime keys to short keys
790 // convert mime keys to short keys
787 data = this.rename_keys(data, OutputArea.mime_map);
791 data = this.rename_keys(data, OutputArea.mime_map);
788 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
792 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
789 }
793 }
790 outputs[i] = data;
794 outputs[i] = data;
791 }
795 }
792 return outputs;
796 return outputs;
793 };
797 };
794
798
795 /**
799 /**
796 * Class properties
800 * Class properties
797 **/
801 **/
798
802
799 /**
803 /**
800 * Threshold to trigger autoscroll when the OutputArea is resized,
804 * Threshold to trigger autoscroll when the OutputArea is resized,
801 * typically when new outputs are added.
805 * typically when new outputs are added.
802 *
806 *
803 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
807 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
804 * unless it is < 0, in which case autoscroll will never be triggered
808 * unless it is < 0, in which case autoscroll will never be triggered
805 *
809 *
806 * @property auto_scroll_threshold
810 * @property auto_scroll_threshold
807 * @type Number
811 * @type Number
808 * @default 100
812 * @default 100
809 *
813 *
810 **/
814 **/
811 OutputArea.auto_scroll_threshold = 100;
815 OutputArea.auto_scroll_threshold = 100;
812
816
813 /**
817 /**
814 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
818 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
815 * shorter than this are never scrolled.
819 * shorter than this are never scrolled.
816 *
820 *
817 * @property minimum_scroll_threshold
821 * @property minimum_scroll_threshold
818 * @type Number
822 * @type Number
819 * @default 20
823 * @default 20
820 *
824 *
821 **/
825 **/
822 OutputArea.minimum_scroll_threshold = 20;
826 OutputArea.minimum_scroll_threshold = 20;
823
827
824
828
825
829
826 OutputArea.mime_map = {
830 OutputArea.mime_map = {
827 "text/plain" : "text",
831 "text/plain" : "text",
828 "text/html" : "html",
832 "text/html" : "html",
829 "image/svg+xml" : "svg",
833 "image/svg+xml" : "svg",
830 "image/png" : "png",
834 "image/png" : "png",
831 "image/jpeg" : "jpeg",
835 "image/jpeg" : "jpeg",
832 "text/latex" : "latex",
836 "text/latex" : "latex",
833 "application/json" : "json",
837 "application/json" : "json",
834 "application/javascript" : "javascript",
838 "application/javascript" : "javascript",
835 };
839 };
836
840
837 OutputArea.mime_map_r = {
841 OutputArea.mime_map_r = {
838 "text" : "text/plain",
842 "text" : "text/plain",
839 "html" : "text/html",
843 "html" : "text/html",
840 "svg" : "image/svg+xml",
844 "svg" : "image/svg+xml",
841 "png" : "image/png",
845 "png" : "image/png",
842 "jpeg" : "image/jpeg",
846 "jpeg" : "image/jpeg",
843 "latex" : "text/latex",
847 "latex" : "text/latex",
844 "json" : "application/json",
848 "json" : "application/json",
845 "javascript" : "application/javascript",
849 "javascript" : "application/javascript",
846 };
850 };
847
851
848 OutputArea.display_order = [
852 OutputArea.display_order = [
849 'application/javascript',
853 'application/javascript',
850 'text/html',
854 'text/html',
851 'text/latex',
855 'text/latex',
852 'image/svg+xml',
856 'image/svg+xml',
853 'image/png',
857 'image/png',
854 'image/jpeg',
858 'image/jpeg',
855 'application/pdf',
859 'application/pdf',
856 'text/plain'
860 'text/plain'
857 ];
861 ];
858
862
859 OutputArea.append_map = {
863 OutputArea.append_map = {
860 "text/plain" : append_text,
864 "text/plain" : append_text,
861 "text/html" : append_html,
865 "text/html" : append_html,
862 "image/svg+xml" : append_svg,
866 "image/svg+xml" : append_svg,
863 "image/png" : append_png,
867 "image/png" : append_png,
864 "image/jpeg" : append_jpeg,
868 "image/jpeg" : append_jpeg,
865 "text/latex" : append_latex,
869 "text/latex" : append_latex,
866 "application/javascript" : append_javascript,
870 "application/javascript" : append_javascript,
867 "application/pdf" : append_pdf
871 "application/pdf" : append_pdf
868 };
872 };
869
873
870 IPython.OutputArea = OutputArea;
874 IPython.OutputArea = OutputArea;
871
875
872 return IPython;
876 return IPython;
873
877
874 }(IPython));
878 }(IPython));
@@ -1,391 +1,387 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7 //============================================================================
7 //============================================================================
8 // Tooltip
8 // Tooltip
9 //============================================================================
9 //============================================================================
10 //
10 //
11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
12 //
12 //
13 // you can configure the differents action of pressing tab several times in a row by
13 // you can configure the differents action of pressing tab several times in a row by
14 // setting/appending different fonction in the array
14 // setting/appending different fonction in the array
15 // IPython.tooltip.tabs_functions
15 // IPython.tooltip.tabs_functions
16 //
16 //
17 // eg :
17 // eg :
18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
19 //
19 //
20 var IPython = (function (IPython) {
20 var IPython = (function (IPython) {
21 "use strict";
21 "use strict";
22
22
23 var utils = IPython.utils;
23 var utils = IPython.utils;
24
24
25 // tooltip constructor
25 // tooltip constructor
26 var Tooltip = function () {
26 var Tooltip = function () {
27 var that = this;
27 var that = this;
28 this.time_before_tooltip = 1200;
28 this.time_before_tooltip = 1200;
29
29
30 // handle to html
30 // handle to html
31 this.tooltip = $('#tooltip');
31 this.tooltip = $('#tooltip');
32 this._hidden = true;
32 this._hidden = true;
33
33
34 // variable for consecutive call
34 // variable for consecutive call
35 this._old_cell = null;
35 this._old_cell = null;
36 this._old_request = null;
36 this._old_request = null;
37 this._consecutive_counter = 0;
37 this._consecutive_counter = 0;
38
38
39 // 'sticky ?'
39 // 'sticky ?'
40 this._sticky = false;
40 this._sticky = false;
41
41
42 // display tooltip if the docstring is empty?
42 // display tooltip if the docstring is empty?
43 this._hide_if_no_docstring = false;
43 this._hide_if_no_docstring = false;
44
44
45 // contain the button in the upper right corner
45 // contain the button in the upper right corner
46 this.buttons = $('<div/>').addClass('tooltipbuttons');
46 this.buttons = $('<div/>').addClass('tooltipbuttons');
47
47
48 // will contain the docstring
48 // will contain the docstring
49 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
49 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
50
50
51 // build the buttons menu on the upper right
51 // build the buttons menu on the upper right
52 // expand the tooltip to see more
52 // expand the tooltip to see more
53 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
53 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
54 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
54 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
55 that.expand();
55 that.expand();
56 }).append(
56 }).append(
57 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
57 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
58
58
59 // open in pager
59 // open in pager
60 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
60 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
61 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
61 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
62 morelink.append(morespan);
62 morelink.append(morespan);
63 morelink.click(function () {
63 morelink.click(function () {
64 that.showInPager(that._old_cell);
64 that.showInPager(that._old_cell);
65 });
65 });
66
66
67 // close the tooltip
67 // close the tooltip
68 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
68 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
69 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
69 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
70 closelink.append(closespan);
70 closelink.append(closespan);
71 closelink.click(function () {
71 closelink.click(function () {
72 that.remove_and_cancel_tooltip(true);
72 that.remove_and_cancel_tooltip(true);
73 });
73 });
74
74
75 this._clocklink = $('<a/>').attr('href', "#");
75 this._clocklink = $('<a/>').attr('href', "#");
76 this._clocklink.attr('role', "button");
76 this._clocklink.attr('role', "button");
77 this._clocklink.addClass('ui-button');
77 this._clocklink.addClass('ui-button');
78 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
78 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
79 var clockspan = $('<span/>').text('Close');
79 var clockspan = $('<span/>').text('Close');
80 clockspan.addClass('ui-icon');
80 clockspan.addClass('ui-icon');
81 clockspan.addClass('ui-icon-clock');
81 clockspan.addClass('ui-icon-clock');
82 this._clocklink.append(clockspan);
82 this._clocklink.append(clockspan);
83 this._clocklink.click(function () {
83 this._clocklink.click(function () {
84 that.cancel_stick();
84 that.cancel_stick();
85 });
85 });
86
86
87
87
88
88
89
89
90 //construct the tooltip
90 //construct the tooltip
91 // add in the reverse order you want them to appear
91 // add in the reverse order you want them to appear
92 this.buttons.append(closelink);
92 this.buttons.append(closelink);
93 this.buttons.append(expandlink);
93 this.buttons.append(expandlink);
94 this.buttons.append(morelink);
94 this.buttons.append(morelink);
95 this.buttons.append(this._clocklink);
95 this.buttons.append(this._clocklink);
96 this._clocklink.hide();
96 this._clocklink.hide();
97
97
98
98
99 // we need a phony element to make the small arrow
99 // we need a phony element to make the small arrow
100 // of the tooltip in css
100 // of the tooltip in css
101 // we will move the arrow later
101 // we will move the arrow later
102 this.arrow = $('<div/>').addClass('pretooltiparrow');
102 this.arrow = $('<div/>').addClass('pretooltiparrow');
103 this.tooltip.append(this.buttons);
103 this.tooltip.append(this.buttons);
104 this.tooltip.append(this.arrow);
104 this.tooltip.append(this.arrow);
105 this.tooltip.append(this.text);
105 this.tooltip.append(this.text);
106
106
107 // function that will be called if you press tab 1, 2, 3... times in a row
107 // function that will be called if you press tab 1, 2, 3... times in a row
108 this.tabs_functions = [function (cell, text) {
108 this.tabs_functions = [function (cell, text) {
109 that._request_tooltip(cell, text);
109 that._request_tooltip(cell, text);
110 }, function () {
110 }, function () {
111 that.expand();
111 that.expand();
112 }, function () {
112 }, function () {
113 that.stick();
113 that.stick();
114 }, function (cell) {
114 }, function (cell) {
115 that.cancel_stick();
115 that.cancel_stick();
116 that.showInPager(cell);
116 that.showInPager(cell);
117 }];
117 }];
118 // call after all the tabs function above have bee call to clean their effects
118 // call after all the tabs function above have bee call to clean their effects
119 // if necessary
119 // if necessary
120 this.reset_tabs_function = function (cell, text) {
120 this.reset_tabs_function = function (cell, text) {
121 this._old_cell = (cell) ? cell : null;
121 this._old_cell = (cell) ? cell : null;
122 this._old_request = (text) ? text : null;
122 this._old_request = (text) ? text : null;
123 this._consecutive_counter = 0;
123 this._consecutive_counter = 0;
124 };
124 };
125 };
125 };
126
126
127 Tooltip.prototype.is_visible = function () {
127 Tooltip.prototype.is_visible = function () {
128 return !this._hidden;
128 return !this._hidden;
129 };
129 };
130
130
131 Tooltip.prototype.showInPager = function (cell) {
131 Tooltip.prototype.showInPager = function (cell) {
132 // reexecute last call in pager by appending ? to show back in pager
132 // reexecute last call in pager by appending ? to show back in pager
133 var that = this;
133 var that = this;
134 var empty = function () {};
134 var callbacks = {'shell' : {
135 cell.kernel.execute(
135 'payload' : {
136 that.name + '?', {
136 'page' : $.proxy(cell._open_with_pager, cell)
137 'execute_reply': empty,
137 }
138 'output': empty,
138 }
139 'clear_output': empty,
139 };
140 'cell': cell
140 cell.kernel.execute(that.name + '?', callbacks, {'silent': false, 'store_history': true});
141 }, {
142 'silent': false,
143 'store_history': true
144 });
145 this.remove_and_cancel_tooltip();
141 this.remove_and_cancel_tooltip();
146 };
142 };
147
143
148 // grow the tooltip verticaly
144 // grow the tooltip verticaly
149 Tooltip.prototype.expand = function () {
145 Tooltip.prototype.expand = function () {
150 this.text.removeClass('smalltooltip');
146 this.text.removeClass('smalltooltip');
151 this.text.addClass('bigtooltip');
147 this.text.addClass('bigtooltip');
152 $('#expanbutton').hide('slow');
148 $('#expanbutton').hide('slow');
153 };
149 };
154
150
155 // deal with all the logic of hiding the tooltip
151 // deal with all the logic of hiding the tooltip
156 // and reset it's status
152 // and reset it's status
157 Tooltip.prototype._hide = function () {
153 Tooltip.prototype._hide = function () {
158 this._hidden = true;
154 this._hidden = true;
159 this.tooltip.fadeOut('fast');
155 this.tooltip.fadeOut('fast');
160 $('#expanbutton').show('slow');
156 $('#expanbutton').show('slow');
161 this.text.removeClass('bigtooltip');
157 this.text.removeClass('bigtooltip');
162 this.text.addClass('smalltooltip');
158 this.text.addClass('smalltooltip');
163 // keep scroll top to be sure to always see the first line
159 // keep scroll top to be sure to always see the first line
164 this.text.scrollTop(0);
160 this.text.scrollTop(0);
165 this.code_mirror = null;
161 this.code_mirror = null;
166 };
162 };
167
163
168 // return true on successfully removing a visible tooltip; otherwise return
164 // return true on successfully removing a visible tooltip; otherwise return
169 // false.
165 // false.
170 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
166 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
171 // note that we don't handle closing directly inside the calltip
167 // note that we don't handle closing directly inside the calltip
172 // as in the completer, because it is not focusable, so won't
168 // as in the completer, because it is not focusable, so won't
173 // get the event.
169 // get the event.
174 this.cancel_pending();
170 this.cancel_pending();
175 if (!this._hidden) {
171 if (!this._hidden) {
176 if (force || !this._sticky) {
172 if (force || !this._sticky) {
177 this.cancel_stick();
173 this.cancel_stick();
178 this._hide();
174 this._hide();
179 }
175 }
180 this.reset_tabs_function();
176 this.reset_tabs_function();
181 return true;
177 return true;
182 } else {
178 } else {
183 return false;
179 return false;
184 }
180 }
185 };
181 };
186
182
187 // cancel autocall done after '(' for example.
183 // cancel autocall done after '(' for example.
188 Tooltip.prototype.cancel_pending = function () {
184 Tooltip.prototype.cancel_pending = function () {
189 if (this._tooltip_timeout !== null) {
185 if (this._tooltip_timeout !== null) {
190 clearTimeout(this._tooltip_timeout);
186 clearTimeout(this._tooltip_timeout);
191 this._tooltip_timeout = null;
187 this._tooltip_timeout = null;
192 }
188 }
193 };
189 };
194
190
195 // will trigger tooltip after timeout
191 // will trigger tooltip after timeout
196 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
192 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
197 var that = this;
193 var that = this;
198 this._tooltip_timeout = setTimeout(function () {
194 this._tooltip_timeout = setTimeout(function () {
199 that.request(cell, hide_if_no_docstring);
195 that.request(cell, hide_if_no_docstring);
200 }, that.time_before_tooltip);
196 }, that.time_before_tooltip);
201 };
197 };
202
198
203 // easy access for julia monkey patching.
199 // easy access for julia monkey patching.
204 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
200 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
205
201
206 Tooltip.prototype.extract_oir_token = function(line){
202 Tooltip.prototype.extract_oir_token = function(line){
207 // use internally just to make the request to the kernel
203 // use internally just to make the request to the kernel
208 // Feel free to shorten this logic if you are better
204 // Feel free to shorten this logic if you are better
209 // than me in regEx
205 // than me in regEx
210 // basicaly you shoul be able to get xxx.xxx.xxx from
206 // basicaly you shoul be able to get xxx.xxx.xxx from
211 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
207 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
212 // remove everything between matchin bracket (need to iterate)
208 // remove everything between matchin bracket (need to iterate)
213 var matchBracket = /\([^\(\)]+\)/g;
209 var matchBracket = /\([^\(\)]+\)/g;
214 var endBracket = /\([^\(]*$/g;
210 var endBracket = /\([^\(]*$/g;
215 var oldline = line;
211 var oldline = line;
216
212
217 line = line.replace(matchBracket, "");
213 line = line.replace(matchBracket, "");
218 while (oldline != line) {
214 while (oldline != line) {
219 oldline = line;
215 oldline = line;
220 line = line.replace(matchBracket, "");
216 line = line.replace(matchBracket, "");
221 }
217 }
222 // remove everything after last open bracket
218 // remove everything after last open bracket
223 line = line.replace(endBracket, "");
219 line = line.replace(endBracket, "");
224 // reset the regex object
220 // reset the regex object
225 Tooltip.last_token_re.lastIndex = 0;
221 Tooltip.last_token_re.lastIndex = 0;
226 return Tooltip.last_token_re.exec(line);
222 return Tooltip.last_token_re.exec(line);
227 };
223 };
228
224
229 Tooltip.prototype._request_tooltip = function (cell, line) {
225 Tooltip.prototype._request_tooltip = function (cell, line) {
230 var callbacks = $.proxy(this._show, this);
226 var callbacks = $.proxy(this._show, this);
231 var oir_token = this.extract_oir_token(line);
227 var oir_token = this.extract_oir_token(line);
232 var msg_id = cell.kernel.object_info(oir_token, callbacks);
228 var msg_id = cell.kernel.object_info(oir_token, callbacks);
233 };
229 };
234
230
235 // make an imediate completion request
231 // make an imediate completion request
236 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
232 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
237 // request(codecell)
233 // request(codecell)
238 // Deal with extracting the text from the cell and counting
234 // Deal with extracting the text from the cell and counting
239 // call in a row
235 // call in a row
240 this.cancel_pending();
236 this.cancel_pending();
241 var editor = cell.code_mirror;
237 var editor = cell.code_mirror;
242 var cursor = editor.getCursor();
238 var cursor = editor.getCursor();
243 var text = editor.getRange({
239 var text = editor.getRange({
244 line: cursor.line,
240 line: cursor.line,
245 ch: 0
241 ch: 0
246 }, cursor).trim();
242 }, cursor).trim();
247
243
248 this._hide_if_no_docstring = hide_if_no_docstring;
244 this._hide_if_no_docstring = hide_if_no_docstring;
249
245
250 if(editor.somethingSelected()){
246 if(editor.somethingSelected()){
251 text = editor.getSelection();
247 text = editor.getSelection();
252 }
248 }
253
249
254 // need a permanent handel to code_mirror for future auto recall
250 // need a permanent handel to code_mirror for future auto recall
255 this.code_mirror = editor;
251 this.code_mirror = editor;
256
252
257 // now we treat the different number of keypress
253 // now we treat the different number of keypress
258 // first if same cell, same text, increment counter by 1
254 // first if same cell, same text, increment counter by 1
259 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
255 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
260 this._consecutive_counter++;
256 this._consecutive_counter++;
261 } else {
257 } else {
262 // else reset
258 // else reset
263 this.cancel_stick();
259 this.cancel_stick();
264 this.reset_tabs_function (cell, text);
260 this.reset_tabs_function (cell, text);
265 }
261 }
266
262
267 // don't do anything if line beggin with '(' or is empty
263 // don't do anything if line beggin with '(' or is empty
268 if (text === "" || text === "(") {
264 if (text === "" || text === "(") {
269 return;
265 return;
270 }
266 }
271
267
272 this.tabs_functions[this._consecutive_counter](cell, text);
268 this.tabs_functions[this._consecutive_counter](cell, text);
273
269
274 // then if we are at the end of list function, reset
270 // then if we are at the end of list function, reset
275 if (this._consecutive_counter == this.tabs_functions.length) {
271 if (this._consecutive_counter == this.tabs_functions.length) {
276 this.reset_tabs_function (cell, text);
272 this.reset_tabs_function (cell, text);
277 }
273 }
278
274
279 return;
275 return;
280 };
276 };
281
277
282 // cancel the option of having the tooltip to stick
278 // cancel the option of having the tooltip to stick
283 Tooltip.prototype.cancel_stick = function () {
279 Tooltip.prototype.cancel_stick = function () {
284 clearTimeout(this._stick_timeout);
280 clearTimeout(this._stick_timeout);
285 this._stick_timeout = null;
281 this._stick_timeout = null;
286 this._clocklink.hide('slow');
282 this._clocklink.hide('slow');
287 this._sticky = false;
283 this._sticky = false;
288 };
284 };
289
285
290 // put the tooltip in a sicky state for 10 seconds
286 // put the tooltip in a sicky state for 10 seconds
291 // it won't be removed by remove_and_cancell() unless you called with
287 // it won't be removed by remove_and_cancell() unless you called with
292 // the first parameter set to true.
288 // the first parameter set to true.
293 // remove_and_cancell_tooltip(true)
289 // remove_and_cancell_tooltip(true)
294 Tooltip.prototype.stick = function (time) {
290 Tooltip.prototype.stick = function (time) {
295 time = (time !== undefined) ? time : 10;
291 time = (time !== undefined) ? time : 10;
296 var that = this;
292 var that = this;
297 this._sticky = true;
293 this._sticky = true;
298 this._clocklink.show('slow');
294 this._clocklink.show('slow');
299 this._stick_timeout = setTimeout(function () {
295 this._stick_timeout = setTimeout(function () {
300 that._sticky = false;
296 that._sticky = false;
301 that._clocklink.hide('slow');
297 that._clocklink.hide('slow');
302 }, time * 1000);
298 }, time * 1000);
303 };
299 };
304
300
305 // should be called with the kernel reply to actually show the tooltip
301 // should be called with the kernel reply to actually show the tooltip
306 Tooltip.prototype._show = function (reply) {
302 Tooltip.prototype._show = function (reply) {
307 // move the bubble if it is not hidden
303 // move the bubble if it is not hidden
308 // otherwise fade it
304 // otherwise fade it
309 var content = reply.content;
305 var content = reply.content;
310 if (!content.found) {
306 if (!content.found) {
311 // object not found, nothing to show
307 // object not found, nothing to show
312 return;
308 return;
313 }
309 }
314 this.name = content.name;
310 this.name = content.name;
315
311
316 // do some math to have the tooltip arrow on more or less on left or right
312 // do some math to have the tooltip arrow on more or less on left or right
317 // width of the editor
313 // width of the editor
318 var w = $(this.code_mirror.getScrollerElement()).width();
314 var w = $(this.code_mirror.getScrollerElement()).width();
319 // ofset of the editor
315 // ofset of the editor
320 var o = $(this.code_mirror.getScrollerElement()).offset();
316 var o = $(this.code_mirror.getScrollerElement()).offset();
321
317
322 // whatever anchor/head order but arrow at mid x selection
318 // whatever anchor/head order but arrow at mid x selection
323 var anchor = this.code_mirror.cursorCoords(false);
319 var anchor = this.code_mirror.cursorCoords(false);
324 var head = this.code_mirror.cursorCoords(true);
320 var head = this.code_mirror.cursorCoords(true);
325 var xinit = (head.left+anchor.left)/2;
321 var xinit = (head.left+anchor.left)/2;
326 var xinter = o.left + (xinit - o.left) / w * (w - 450);
322 var xinter = o.left + (xinit - o.left) / w * (w - 450);
327 var posarrowleft = xinit - xinter;
323 var posarrowleft = xinit - xinter;
328
324
329 if (this._hidden === false) {
325 if (this._hidden === false) {
330 this.tooltip.animate({
326 this.tooltip.animate({
331 'left': xinter - 30 + 'px',
327 'left': xinter - 30 + 'px',
332 'top': (head.bottom + 10) + 'px'
328 'top': (head.bottom + 10) + 'px'
333 });
329 });
334 } else {
330 } else {
335 this.tooltip.css({
331 this.tooltip.css({
336 'left': xinter - 30 + 'px'
332 'left': xinter - 30 + 'px'
337 });
333 });
338 this.tooltip.css({
334 this.tooltip.css({
339 'top': (head.bottom + 10) + 'px'
335 'top': (head.bottom + 10) + 'px'
340 });
336 });
341 }
337 }
342 this.arrow.animate({
338 this.arrow.animate({
343 'left': posarrowleft + 'px'
339 'left': posarrowleft + 'px'
344 });
340 });
345
341
346 // build docstring
342 // build docstring
347 var defstring = content.call_def;
343 var defstring = content.call_def;
348 if (!defstring) {
344 if (!defstring) {
349 defstring = content.init_definition;
345 defstring = content.init_definition;
350 }
346 }
351 if (!defstring) {
347 if (!defstring) {
352 defstring = content.definition;
348 defstring = content.definition;
353 }
349 }
354
350
355 var docstring = content.call_docstring;
351 var docstring = content.call_docstring;
356 if (!docstring) {
352 if (!docstring) {
357 docstring = content.init_docstring;
353 docstring = content.init_docstring;
358 }
354 }
359 if (!docstring) {
355 if (!docstring) {
360 docstring = content.docstring;
356 docstring = content.docstring;
361 }
357 }
362
358
363 if (!docstring) {
359 if (!docstring) {
364 // For reals this time, no docstring
360 // For reals this time, no docstring
365 if (this._hide_if_no_docstring) {
361 if (this._hide_if_no_docstring) {
366 return;
362 return;
367 } else {
363 } else {
368 docstring = "<empty docstring>";
364 docstring = "<empty docstring>";
369 }
365 }
370 }
366 }
371
367
372 this._hidden = false;
368 this._hidden = false;
373 this.tooltip.fadeIn('fast');
369 this.tooltip.fadeIn('fast');
374 this.text.children().remove();
370 this.text.children().remove();
375
371
376 // Any HTML within the docstring is escaped by the fixConsole() method.
372 // Any HTML within the docstring is escaped by the fixConsole() method.
377 var pre = $('<pre/>').html(utils.fixConsole(docstring));
373 var pre = $('<pre/>').html(utils.fixConsole(docstring));
378 if (defstring) {
374 if (defstring) {
379 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
375 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
380 this.text.append(defstring_html);
376 this.text.append(defstring_html);
381 }
377 }
382 this.text.append(pre);
378 this.text.append(pre);
383 // keep scroll top to be sure to always see the first line
379 // keep scroll top to be sure to always see the first line
384 this.text.scrollTop(0);
380 this.text.scrollTop(0);
385 };
381 };
386
382
387 IPython.Tooltip = Tooltip;
383 IPython.Tooltip = Tooltip;
388
384
389 return IPython;
385 return IPython;
390
386
391 }(IPython));
387 }(IPython));
@@ -1,54 +1,62 b''
1 div.cell {
1 div.cell {
2 border: 1px solid transparent;
2 border: 1px solid transparent;
3 .vbox();
3 .vbox();
4
4
5 &.selected {
5 &.selected {
6 .corner-all;
6 .corner-all;
7 border : thin @border_color solid;
7 border : thin @border_color solid;
8 }
8 }
9
9
10 &.edit_mode {
10 &.edit_mode {
11 .corner-all;
11 .corner-all;
12 border : thin green solid;
12 border : thin green solid;
13 }
13 }
14 }
14 }
15
15
16 div.cell {
16 div.cell {
17 width: 100%;
17 width: 100%;
18 padding: 5px 5px 5px 0px;
18 padding: 5px 5px 5px 0px;
19 /* This acts as a spacer between cells, that is outside the border */
19 /* This acts as a spacer between cells, that is outside the border */
20 margin: 0px;
20 margin: 0px;
21 outline: none;
21 outline: none;
22 }
22 }
23
23
24 div.prompt {
24 div.prompt {
25 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
25 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
26 min-width: 11ex;
26 min-width: 11ex;
27 /* This padding is tuned to match the padding on the CodeMirror editor. */
27 /* This padding is tuned to match the padding on the CodeMirror editor. */
28 padding: @code_padding;
28 padding: @code_padding;
29 margin: 0px;
29 margin: 0px;
30 font-family: @monoFontFamily;
30 font-family: @monoFontFamily;
31 text-align: right;
31 text-align: right;
32 /* This has to match that of the the CodeMirror class line-height below */
32 /* This has to match that of the the CodeMirror class line-height below */
33 line-height: @code_line_height;
33 line-height: @code_line_height;
34 }
34 }
35
35
36 @media (max-width: 480px) {
37 // prompts are in the main column on small screens,
38 // so text should be left-aligned
39 div.prompt {
40 text-align: left;
41 }
42 }
43
36 div.inner_cell {
44 div.inner_cell {
37 .vbox();
45 .vbox();
38 .box-flex1();
46 .box-flex1();
39 }
47 }
40
48
41 /* input_area and input_prompt must match in top border and margin for alignment */
49 /* input_area and input_prompt must match in top border and margin for alignment */
42 div.input_area {
50 div.input_area {
43 border: 1px solid @light_border_color;
51 border: 1px solid @light_border_color;
44 .corner-all;
52 .corner-all;
45 background: @cell_background;
53 background: @cell_background;
46 }
54 }
47
55
48 /* This is needed so that empty prompt areas can collapse to zero height when there
56 /* This is needed so that empty prompt areas can collapse to zero height when there
49 is no content in the output_subarea and the prompt. The main purpose of this is
57 is no content in the output_subarea and the prompt. The main purpose of this is
50 to make sure that empty JavaScript output_subareas have no height. */
58 to make sure that empty JavaScript output_subareas have no height. */
51 div.prompt:empty {
59 div.prompt:empty {
52 padding-top: 0;
60 padding-top: 0;
53 padding-bottom: 0;
61 padding-bottom: 0;
54 }
62 }
@@ -1,40 +1,46 b''
1 div.code_cell {
1 div.code_cell {
2 }
2 }
3
3
4 /* any special styling for code cells that are currently running goes here */
4 /* any special styling for code cells that are currently running goes here */
5 div.code_cell.running {
5 div.code_cell.running {
6 }
6 }
7
7
8 div.input {
8 div.input {
9 page-break-inside: avoid;
9 page-break-inside: avoid;
10 .hbox();
10 .hbox();
11 }
11 }
12
12
13 @media (max-width: 480px) {
14 // move prompts above code on small screens
15 div.input {
16 .vbox();
17 }
18 }
19
13 /* input_area and input_prompt must match in top border and margin for alignment */
20 /* input_area and input_prompt must match in top border and margin for alignment */
14 div.input_prompt {
21 div.input_prompt {
15 color: navy;
22 color: navy;
16 border-top: 1px solid transparent;
23 border-top: 1px solid transparent;
17 }
24 }
18
25
19
20 // The styles related to div.highlight are for nbconvert HTML output only. This works
26 // The styles related to div.highlight are for nbconvert HTML output only. This works
21 // because the .highlight div isn't present in the live notebook. We could put this into
27 // because the .highlight div isn't present in the live notebook. We could put this into
22 // nbconvert, but it easily falls out of sync, can't use our less variables and doesn't
28 // nbconvert, but it easily falls out of sync, can't use our less variables and doesn't
23 // help the basic template when paired with our CSS.
29 // help the basic template when paired with our CSS.
24
30
25 div.input_area > div.highlight {
31 div.input_area > div.highlight {
26 margin: @code_padding;
32 margin: @code_padding;
27 border: none;
33 border: none;
28 padding: 0px;
34 padding: 0px;
29 background-color: transparent;
35 background-color: transparent;
30 }
36 }
31
37
32 div.input_area > div.highlight > pre {
38 div.input_area > div.highlight > pre {
33 margin: 0px;
39 margin: 0px;
34 border: 0px;
40 border: 0px;
35 padding: 0px;
41 padding: 0px;
36 background-color: transparent;
42 background-color: transparent;
37 font-size: @notebook_font_size;
43 font-size: @notebook_font_size;
38 line-height: @code_line_height;
44 line-height: @code_line_height;
39 }
45 }
40
46
@@ -1,69 +1,77 b''
1
1
2 body {
2 body {
3 background-color: @bodyBackground;
3 background-color: @bodyBackground;
4 }
4 }
5
5
6 body.notebook_app {
6 body.notebook_app {
7 overflow: hidden;
7 overflow: hidden;
8 }
8 }
9
9
10 @media (max-width: 767px) {
11 // remove bootstrap-responsive's body padding on small screens
12 body.notebook_app {
13 padding-left: 0px;
14 padding-right: 0px;
15 }
16 }
17
10 span#notebook_name {
18 span#notebook_name {
11 height: 1em;
19 height: 1em;
12 line-height: 1em;
20 line-height: 1em;
13 padding: 3px;
21 padding: 3px;
14 border: none;
22 border: none;
15 font-size: 146.5%;
23 font-size: 146.5%;
16 }
24 }
17
25
18 div#notebook_panel {
26 div#notebook_panel {
19 margin: 0px 0px 0px 0px;
27 margin: 0px 0px 0px 0px;
20 padding: 0px;
28 padding: 0px;
21 .box-shadow(0 -1px 10px rgba(0,0,0,.1));
29 .box-shadow(0 -1px 10px rgba(0,0,0,.1));
22 }
30 }
23 div#notebook {
31 div#notebook {
24 font-size: @notebook_font_size;
32 font-size: @notebook_font_size;
25 line-height: @notebook_line_height;
33 line-height: @notebook_line_height;
26 overflow-y: scroll;
34 overflow-y: scroll;
27 overflow-x: auto;
35 overflow-x: auto;
28 width: 100%;
36 width: 100%;
29 /* This spaces the cell away from the edge of the notebook area */
37 /* This spaces the cell away from the edge of the notebook area */
30 padding: 1em 0 1em 0;
38 padding: 1em 0 1em 0;
31 margin: 0px;
39 margin: 0px;
32 border-top: 1px solid @border_color;
40 border-top: 1px solid @border_color;
33 outline: none;
41 outline: none;
34 .border-box-sizing();
42 .border-box-sizing();
35 }
43 }
36
44
37 div.ui-widget-content {
45 div.ui-widget-content {
38 border: 1px solid @border_color;
46 border: 1px solid @border_color;
39 outline: none;
47 outline: none;
40 }
48 }
41
49
42 pre.dialog {
50 pre.dialog {
43 background-color: @cell_background;
51 background-color: @cell_background;
44 border: 1px solid #ddd;
52 border: 1px solid #ddd;
45 .corner-all;
53 .corner-all;
46 padding: 0.4em;
54 padding: 0.4em;
47 padding-left: 2em;
55 padding-left: 2em;
48 }
56 }
49
57
50 p.dialog {
58 p.dialog {
51 padding : 0.2em;
59 padding : 0.2em;
52 }
60 }
53
61
54 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
62 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
55 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
63 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
56 */
64 */
57 pre, code, kbd, samp { white-space: pre-wrap; }
65 pre, code, kbd, samp { white-space: pre-wrap; }
58
66
59 #fonttest {
67 #fonttest {
60 font-family: @monoFontFamily;
68 font-family: @monoFontFamily;
61 }
69 }
62
70
63 p {
71 p {
64 margin-bottom:0;
72 margin-bottom:0;
65 }
73 }
66
74
67 .end_space {
75 .end_space {
68 height: 200px;
76 height: 200px;
69 }
77 }
@@ -1,169 +1,176 b''
1 div.output_wrapper {
1 div.output_wrapper {
2 /* this position must be relative to enable descendents to be absolute within it */
2 /* this position must be relative to enable descendents to be absolute within it */
3 position: relative;
3 position: relative;
4 .vbox()
4 .vbox()
5 }
5 }
6
6
7 /* class for the output area when it should be height-limited */
7 /* class for the output area when it should be height-limited */
8 div.output_scroll {
8 div.output_scroll {
9 /* ideally, this would be max-height, but FF barfs all over that */
9 /* ideally, this would be max-height, but FF barfs all over that */
10 height: 24em;
10 height: 24em;
11 /* FF needs this *and the wrapper* to specify full width, or it will shrinkwrap */
11 /* FF needs this *and the wrapper* to specify full width, or it will shrinkwrap */
12 width: 100%;
12 width: 100%;
13
13
14 overflow: auto;
14 overflow: auto;
15 .corner-all;
15 .corner-all;
16 .box-shadow(inset 0 2px 8px rgba(0, 0, 0, .8));
16 .box-shadow(inset 0 2px 8px rgba(0, 0, 0, .8));
17 display: block;
17 display: block;
18 }
18 }
19
19
20 /* output div while it is collapsed */
20 /* output div while it is collapsed */
21 div.output_collapsed {
21 div.output_collapsed {
22 margin: 0px;
22 margin: 0px;
23 padding: 0px;
23 padding: 0px;
24 .vbox();
24 .vbox();
25 }
25 }
26
26
27 div.out_prompt_overlay {
27 div.out_prompt_overlay {
28 height: 100%;
28 height: 100%;
29 padding: 0px @code_padding;
29 padding: 0px @code_padding;
30 position: absolute;
30 position: absolute;
31 .corner-all;
31 .corner-all;
32 }
32 }
33
33
34 div.out_prompt_overlay:hover {
34 div.out_prompt_overlay:hover {
35 /* use inner shadow to get border that is computed the same on WebKit/FF */
35 /* use inner shadow to get border that is computed the same on WebKit/FF */
36 .box-shadow(inset 0 0 1px #000);
36 .box-shadow(inset 0 0 1px #000);
37 background: rgba(240, 240, 240, 0.5);
37 background: rgba(240, 240, 240, 0.5);
38 }
38 }
39
39
40 div.output_prompt {
40 div.output_prompt {
41 color: darkred;
41 color: darkred;
42 }
42 }
43
43
44 /* This class is the outer container of all output sections. */
44 /* This class is the outer container of all output sections. */
45 div.output_area {
45 div.output_area {
46 padding: 0px;
46 padding: 0px;
47 page-break-inside: avoid;
47 page-break-inside: avoid;
48 .hbox();
48 .hbox();
49
49
50 .MathJax_Display {
50 .MathJax_Display {
51 // Inside a CodeCell, elements are left justified
51 // Inside a CodeCell, elements are left justified
52 text-align: left !important;
52 text-align: left !important;
53 }
53 }
54
54
55 .rendered_html {
55 .rendered_html {
56 // Inside a CodeCell, elements are left justified
56 // Inside a CodeCell, elements are left justified
57 table {
57 table {
58 margin-left: 0;
58 margin-left: 0;
59 margin-right: 0;
59 margin-right: 0;
60 }
60 }
61
61
62 img {
62 img {
63 margin-left: 0;
63 margin-left: 0;
64 margin-right: 0;
64 margin-right: 0;
65 }
65 }
66 }
66 }
67 }
67 }
68
68
69 /* This is needed to protect the pre formating from global settings such
69 /* This is needed to protect the pre formating from global settings such
70 as that of bootstrap */
70 as that of bootstrap */
71 .output {
71 .output {
72 .vbox();
72 .vbox();
73 }
73 }
74
74
75 @media (max-width: 480px) {
76 // move prompts above output on small screens
77 div.output_area {
78 .vbox();
79 }
80 }
81
75 div.output_area pre {
82 div.output_area pre {
76 margin: 0;
83 margin: 0;
77 padding: 0;
84 padding: 0;
78 border: 0;
85 border: 0;
79 font-size: 100%;
86 font-size: 100%;
80 vertical-align: baseline;
87 vertical-align: baseline;
81 color: black;
88 color: black;
82 background-color: transparent;
89 background-color: transparent;
83 .border-radius(0);
90 .border-radius(0);
84 line-height: inherit;
91 line-height: inherit;
85 }
92 }
86
93
87 /* This class is for the output subarea inside the output_area and after
94 /* This class is for the output subarea inside the output_area and after
88 the prompt div. */
95 the prompt div. */
89 div.output_subarea {
96 div.output_subarea {
90 padding: @code_padding @code_padding 0.0em @code_padding;
97 padding: @code_padding @code_padding 0.0em @code_padding;
91 .box-flex1();
98 .box-flex1();
92 }
99 }
93
100
94 /* The rest of the output_* classes are for special styling of the different
101 /* The rest of the output_* classes are for special styling of the different
95 output types */
102 output types */
96
103
97 /* all text output has this class: */
104 /* all text output has this class: */
98 div.output_text {
105 div.output_text {
99 text-align: left;
106 text-align: left;
100 color: @textColor;
107 color: @textColor;
101 /* This has to match that of the the CodeMirror class line-height below */
108 /* This has to match that of the the CodeMirror class line-height below */
102 line-height: @code_line_height;
109 line-height: @code_line_height;
103 }
110 }
104
111
105 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
112 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
106 div.output_stream {
113 div.output_stream {
107 }
114 }
108
115
109 div.output_stdout {
116 div.output_stdout {
110 }
117 }
111
118
112 div.output_stderr {
119 div.output_stderr {
113 background: #fdd; /* very light red background for stderr */
120 background: #fdd; /* very light red background for stderr */
114 }
121 }
115
122
116 div.output_latex {
123 div.output_latex {
117 text-align: left;
124 text-align: left;
118 }
125 }
119
126
120 div.output_html {
127 div.output_html {
121 }
128 }
122
129
123 div.output_png {
130 div.output_png {
124 }
131 }
125
132
126 div.output_jpeg {
133 div.output_jpeg {
127 }
134 }
128
135
129 /* Empty output_javascript divs should have no height */
136 /* Empty output_javascript divs should have no height */
130 div.output_javascript:empty {
137 div.output_javascript:empty {
131 padding: 0;
138 padding: 0;
132 }
139 }
133
140
134 .js-error {
141 .js-error {
135 color: darkred;
142 color: darkred;
136 }
143 }
137
144
138 /* raw_input styles */
145 /* raw_input styles */
139
146
140 div.raw_input_container {
147 div.raw_input_container {
141 font-family: @monoFontFamily;
148 font-family: @monoFontFamily;
142 // for some reason, em padding doesn't compute the same for raw_input
149 // for some reason, em padding doesn't compute the same for raw_input
143 // that is not the first input, but px does
150 // that is not the first input, but px does
144 padding-top: 5px;
151 padding-top: 5px;
145 }
152 }
146
153
147 span.raw_input_prompt {
154 span.raw_input_prompt {
148 /* nothing needed here */
155 /* nothing needed here */
149 }
156 }
150
157
151 input.raw_input {
158 input.raw_input {
152 font-family: inherit;
159 font-family: inherit;
153 font-size: inherit;
160 font-size: inherit;
154 color: inherit;
161 color: inherit;
155 width: auto;
162 width: auto;
156 /* make sure input baseline aligns with prompt */
163 /* make sure input baseline aligns with prompt */
157 vertical-align: baseline;
164 vertical-align: baseline;
158 /* padding + margin = 0.5em between prompt and cursor */
165 /* padding + margin = 0.5em between prompt and cursor */
159 padding: 0em 0.25em;
166 padding: 0em 0.25em;
160 margin: 0em 0.25em;
167 margin: 0em 0.25em;
161 }
168 }
162
169
163 input.raw_input:focus {
170 input.raw_input:focus {
164 box-shadow: none;
171 box-shadow: none;
165 }
172 }
166
173
167 p.p-space {
174 p.p-space {
168 margin-bottom: 10px;
175 margin-bottom: 10px;
169 }
176 }
@@ -1,30 +1,36 b''
1 div.text_cell {
1 div.text_cell {
2 padding: 5px 5px 5px 0px;
2 padding: 5px 5px 5px 0px;
3 .hbox();
3 .hbox();
4 }
4 }
5 @media (max-width: 480px) {
6 // remove prompt indentation on small screens
7 div.text_cell > div.prompt {
8 display: none;
9 }
10 }
5
11
6 div.text_cell_render {
12 div.text_cell_render {
7 /*font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;*/
13 /*font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;*/
8 outline: none;
14 outline: none;
9 resize: none;
15 resize: none;
10 width: inherit;
16 width: inherit;
11 border-style: none;
17 border-style: none;
12 padding: 0.5em 0.5em 0.5em @code_padding;
18 padding: 0.5em 0.5em 0.5em @code_padding;
13 color: @textColor;
19 color: @textColor;
14 }
20 }
15
21
16 a.anchor-link:link {
22 a.anchor-link:link {
17 text-decoration: none;
23 text-decoration: none;
18 padding: 0px 20px;
24 padding: 0px 20px;
19 visibility: hidden;
25 visibility: hidden;
20 }
26 }
21
27
22 h1,h2,h3,h4,h5,h6 {
28 h1,h2,h3,h4,h5,h6 {
23 &:hover .anchor-link {
29 &:hover .anchor-link {
24 visibility: visible;
30 visibility: visible;
25 }
31 }
26 }
32 }
27
33
28 div.cell.text_cell.rendered {
34 div.cell.text_cell.rendered {
29 padding: 0px;
35 padding: 0px;
30 }
36 }
@@ -1,199 +1,199 b''
1 .clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}
1 .clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}
2 .clearfix:after{clear:both}
2 .clearfix:after{clear:both}
3 .hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}
3 .hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}
4 .input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
4 .input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
5 code{color:#000}
5 code{color:#000}
6 .border-box-sizing{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
6 .border-box-sizing{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
7 .corner-all{border-radius:4px}
7 .corner-all{border-radius:4px}
8 .hbox{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}
8 .hbox{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}
9 .hbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
9 .hbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
10 .vbox{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}
10 .vbox{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}
11 .vbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
11 .vbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
12 .hbox.reverse,.vbox.reverse,.reverse{-webkit-box-direction:reverse;-moz-box-direction:reverse;box-direction:reverse;flex-direction:row-reverse}
12 .hbox.reverse,.vbox.reverse,.reverse{-webkit-box-direction:reverse;-moz-box-direction:reverse;box-direction:reverse;flex-direction:row-reverse}
13 .hbox.box-flex0,.vbox.box-flex0,.box-flex0{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none;width:auto}
13 .hbox.box-flex0,.vbox.box-flex0,.box-flex0{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none;width:auto}
14 .hbox.box-flex1,.vbox.box-flex1,.box-flex1{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
14 .hbox.box-flex1,.vbox.box-flex1,.box-flex1{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
15 .hbox.box-flex,.vbox.box-flex,.box-flex{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
15 .hbox.box-flex,.vbox.box-flex,.box-flex{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
16 .hbox.box-flex2,.vbox.box-flex2,.box-flex2{-webkit-box-flex:2;-moz-box-flex:2;box-flex:2;flex:2}
16 .hbox.box-flex2,.vbox.box-flex2,.box-flex2{-webkit-box-flex:2;-moz-box-flex:2;box-flex:2;flex:2}
17 .box-group1{-webkit-box-flex-group:1;-moz-box-flex-group:1;box-flex-group:1}
17 .box-group1{-webkit-box-flex-group:1;-moz-box-flex-group:1;box-flex-group:1}
18 .box-group2{-webkit-box-flex-group:2;-moz-box-flex-group:2;box-flex-group:2}
18 .box-group2{-webkit-box-flex-group:2;-moz-box-flex-group:2;box-flex-group:2}
19 .hbox.start,.vbox.start,.start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start}
19 .hbox.start,.vbox.start,.start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start}
20 .hbox.end,.vbox.end,.end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;justify-content:flex-end}
20 .hbox.end,.vbox.end,.end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;justify-content:flex-end}
21 .hbox.center,.vbox.center,.center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;justify-content:center}
21 .hbox.center,.vbox.center,.center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;justify-content:center}
22 .hbox.align-start,.vbox.align-start,.align-start{-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
22 .hbox.align-start,.vbox.align-start,.align-start{-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
23 .hbox.align-end,.vbox.align-end,.align-end{-webkit-box-align:end;-moz-box-align:end;box-align:end;align-items:flex-end}
23 .hbox.align-end,.vbox.align-end,.align-end{-webkit-box-align:end;-moz-box-align:end;box-align:end;align-items:flex-end}
24 .hbox.align-center,.vbox.align-center,.align-center{-webkit-box-align:center;-moz-box-align:center;box-align:center;align-items:center}
24 .hbox.align-center,.vbox.align-center,.align-center{-webkit-box-align:center;-moz-box-align:center;box-align:center;align-items:center}
25 div.error{margin:2em;text-align:center}
25 div.error{margin:2em;text-align:center}
26 div.error>h1{font-size:500%;line-height:normal}
26 div.error>h1{font-size:500%;line-height:normal}
27 div.error>p{font-size:200%;line-height:normal}
27 div.error>p{font-size:200%;line-height:normal}
28 div.traceback-wrapper{text-align:left;max-width:800px;margin:auto}
28 div.traceback-wrapper{text-align:left;max-width:800px;margin:auto}
29 .center-nav{display:inline-block;margin-bottom:-4px}
29 .center-nav{display:inline-block;margin-bottom:-4px}
30 .alternate_upload{background-color:none;display:inline}
30 .alternate_upload{background-color:none;display:inline}
31 .alternate_upload.form{padding:0;margin:0}
31 .alternate_upload.form{padding:0;margin:0}
32 .alternate_upload input.fileinput{background-color:#f00;position:relative;opacity:0;z-index:2;width:295px;margin-left:163px;cursor:pointer;height:26px}
32 .alternate_upload input.fileinput{background-color:#f00;position:relative;opacity:0;z-index:2;width:295px;margin-left:163px;cursor:pointer;height:26px}
33 ul#tabs{margin-bottom:4px}
33 ul#tabs{margin-bottom:4px}
34 ul#tabs a{padding-top:4px;padding-bottom:4px}
34 ul#tabs a{padding-top:4px;padding-bottom:4px}
35 ul.breadcrumb a:focus,ul.breadcrumb a:hover{text-decoration:none}
35 ul.breadcrumb a:focus,ul.breadcrumb a:hover{text-decoration:none}
36 ul.breadcrumb i.icon-home{font-size:16px;margin-right:4px}
36 ul.breadcrumb i.icon-home{font-size:16px;margin-right:4px}
37 ul.breadcrumb span{color:#5e5e5e}
37 ul.breadcrumb span{color:#5e5e5e}
38 .list_toolbar{padding:4px 0 4px 0}
38 .list_toolbar{padding:4px 0 4px 0}
39 .list_toolbar [class*="span"]{min-height:26px}
39 .list_toolbar [class*="span"]{min-height:26px}
40 .list_header{font-weight:bold}
40 .list_header{font-weight:bold}
41 .list_container{margin-top:4px;margin-bottom:20px;border:1px solid #ababab;border-radius:4px}
41 .list_container{margin-top:4px;margin-bottom:20px;border:1px solid #ababab;border-radius:4px}
42 .list_container>div{border-bottom:1px solid #ababab}.list_container>div:hover .list-item{background-color:#f00}
42 .list_container>div{border-bottom:1px solid #ababab}.list_container>div:hover .list-item{background-color:#f00}
43 .list_container>div:last-child{border:none}
43 .list_container>div:last-child{border:none}
44 .list_item:hover .list_item{background-color:#ddd}
44 .list_item:hover .list_item{background-color:#ddd}
45 .list_item a{text-decoration:none}
45 .list_item a{text-decoration:none}
46 .list_header>div,.list_item>div{padding-top:4px;padding-bottom:4px;padding-left:7px;padding-right:7px;height:22px;line-height:22px}
46 .list_header>div,.list_item>div{padding-top:4px;padding-bottom:4px;padding-left:7px;padding-right:7px;height:22px;line-height:22px}
47 .item_name{line-height:22px;height:26px}
47 .item_name{line-height:22px;height:26px}
48 .item_icon{font-size:14px;color:#5e5e5e;margin-right:7px}
48 .item_icon{font-size:14px;color:#5e5e5e;margin-right:7px}
49 .item_buttons{line-height:1em}
49 .item_buttons{line-height:1em}
50 .toolbar_info{height:26px;line-height:26px}
50 .toolbar_info{height:26px;line-height:26px}
51 input.nbname_input,input.engine_num_input{padding-top:3px;padding-bottom:3px;height:14px;line-height:14px;margin:0}
51 input.nbname_input,input.engine_num_input{padding-top:3px;padding-bottom:3px;height:14px;line-height:14px;margin:0}
52 input.engine_num_input{width:60px}
52 input.engine_num_input{width:60px}
53 .highlight_text{color:#00f}
53 .highlight_text{color:#00f}
54 #project_name>.breadcrumb{padding:0;margin-bottom:0;background-color:transparent;font-weight:bold}
54 #project_name>.breadcrumb{padding:0;margin-bottom:0;background-color:transparent;font-weight:bold}
55 .ansibold{font-weight:bold}
55 .ansibold{font-weight:bold}
56 .ansiblack{color:#000}
56 .ansiblack{color:#000}
57 .ansired{color:#8b0000}
57 .ansired{color:#8b0000}
58 .ansigreen{color:#006400}
58 .ansigreen{color:#006400}
59 .ansiyellow{color:#a52a2a}
59 .ansiyellow{color:#a52a2a}
60 .ansiblue{color:#00008b}
60 .ansiblue{color:#00008b}
61 .ansipurple{color:#9400d3}
61 .ansipurple{color:#9400d3}
62 .ansicyan{color:#4682b4}
62 .ansicyan{color:#4682b4}
63 .ansigray{color:#808080}
63 .ansigray{color:#808080}
64 .ansibgblack{background-color:#000}
64 .ansibgblack{background-color:#000}
65 .ansibgred{background-color:#f00}
65 .ansibgred{background-color:#f00}
66 .ansibggreen{background-color:#008000}
66 .ansibggreen{background-color:#008000}
67 .ansibgyellow{background-color:#ff0}
67 .ansibgyellow{background-color:#ff0}
68 .ansibgblue{background-color:#00f}
68 .ansibgblue{background-color:#00f}
69 .ansibgpurple{background-color:#f0f}
69 .ansibgpurple{background-color:#f0f}
70 .ansibgcyan{background-color:#0ff}
70 .ansibgcyan{background-color:#0ff}
71 .ansibggray{background-color:#808080}
71 .ansibggray{background-color:#808080}
72 div.cell{border:1px solid transparent;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.cell.selected{border-radius:4px;border:thin #ababab solid}
72 div.cell{border:1px solid transparent;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.cell.selected{border-radius:4px;border:thin #ababab solid}
73 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
73 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
74 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
74 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
75 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
75 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
76 div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
76 @media (max-width:480px){div.prompt{text-align:left}}div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
77 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
77 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
78 div.prompt:empty{padding-top:0;padding-bottom:0}
78 div.prompt:empty{padding-top:0;padding-bottom:0}
79 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
79 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
80 div.input_prompt{color:#000080;border-top:1px solid transparent}
80 @media (max-width:480px){div.input{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.input_prompt{color:#000080;border-top:1px solid transparent}
81 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
81 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
82 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
82 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
83 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
83 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
84 .CodeMirror-scroll{overflow-y:hidden;overflow-x:auto}
84 .CodeMirror-scroll{overflow-y:hidden;overflow-x:auto}
85 @-moz-document url-prefix(){.CodeMirror-scroll{overflow-x:hidden}}.CodeMirror-lines{padding:.4em}
85 @-moz-document url-prefix(){.CodeMirror-scroll{overflow-x:hidden}}.CodeMirror-lines{padding:.4em}
86 .CodeMirror-linenumber{padding:0 8px 0 4px}
86 .CodeMirror-linenumber{padding:0 8px 0 4px}
87 .CodeMirror-gutters{border-bottom-left-radius:4px;border-top-left-radius:4px}
87 .CodeMirror-gutters{border-bottom-left-radius:4px;border-top-left-radius:4px}
88 .CodeMirror pre{padding:0;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
88 .CodeMirror pre{padding:0;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
89 pre code{display:block;padding:.5em}
89 pre code{display:block;padding:.5em}
90 .highlight-base,pre code,pre .subst,pre .tag .title,pre .lisp .title,pre .clojure .built_in,pre .nginx .title{color:#000}
90 .highlight-base,pre code,pre .subst,pre .tag .title,pre .lisp .title,pre .clojure .built_in,pre .nginx .title{color:#000}
91 .highlight-string,pre .string,pre .constant,pre .parent,pre .tag .value,pre .rules .value,pre .rules .value .number,pre .preprocessor,pre .ruby .symbol,pre .ruby .symbol .string,pre .aggregate,pre .template_tag,pre .django .variable,pre .smalltalk .class,pre .addition,pre .flow,pre .stream,pre .bash .variable,pre .apache .tag,pre .apache .cbracket,pre .tex .command,pre .tex .special,pre .erlang_repl .function_or_atom,pre .markdown .header{color:#ba2121}
91 .highlight-string,pre .string,pre .constant,pre .parent,pre .tag .value,pre .rules .value,pre .rules .value .number,pre .preprocessor,pre .ruby .symbol,pre .ruby .symbol .string,pre .aggregate,pre .template_tag,pre .django .variable,pre .smalltalk .class,pre .addition,pre .flow,pre .stream,pre .bash .variable,pre .apache .tag,pre .apache .cbracket,pre .tex .command,pre .tex .special,pre .erlang_repl .function_or_atom,pre .markdown .header{color:#ba2121}
92 .highlight-comment,pre .comment,pre .annotation,pre .template_comment,pre .diff .header,pre .chunk,pre .markdown .blockquote{color:#408080;font-style:italic}
92 .highlight-comment,pre .comment,pre .annotation,pre .template_comment,pre .diff .header,pre .chunk,pre .markdown .blockquote{color:#408080;font-style:italic}
93 .highlight-number,pre .number,pre .date,pre .regexp,pre .literal,pre .smalltalk .symbol,pre .smalltalk .char,pre .go .constant,pre .change,pre .markdown .bullet,pre .markdown .link_url{color:#080}
93 .highlight-number,pre .number,pre .date,pre .regexp,pre .literal,pre .smalltalk .symbol,pre .smalltalk .char,pre .go .constant,pre .change,pre .markdown .bullet,pre .markdown .link_url{color:#080}
94 pre .label,pre .javadoc,pre .ruby .string,pre .decorator,pre .filter .argument,pre .localvars,pre .array,pre .attr_selector,pre .important,pre .pseudo,pre .pi,pre .doctype,pre .deletion,pre .envvar,pre .shebang,pre .apache .sqbracket,pre .nginx .built_in,pre .tex .formula,pre .erlang_repl .reserved,pre .prompt,pre .markdown .link_label,pre .vhdl .attribute,pre .clojure .attribute,pre .coffeescript .property{color:#88f}
94 pre .label,pre .javadoc,pre .ruby .string,pre .decorator,pre .filter .argument,pre .localvars,pre .array,pre .attr_selector,pre .important,pre .pseudo,pre .pi,pre .doctype,pre .deletion,pre .envvar,pre .shebang,pre .apache .sqbracket,pre .nginx .built_in,pre .tex .formula,pre .erlang_repl .reserved,pre .prompt,pre .markdown .link_label,pre .vhdl .attribute,pre .clojure .attribute,pre .coffeescript .property{color:#88f}
95 .highlight-keyword,pre .keyword,pre .id,pre .phpdoc,pre .aggregate,pre .css .tag,pre .javadoctag,pre .phpdoc,pre .yardoctag,pre .smalltalk .class,pre .winutils,pre .bash .variable,pre .apache .tag,pre .go .typename,pre .tex .command,pre .markdown .strong,pre .request,pre .status{color:#008000;font-weight:bold}
95 .highlight-keyword,pre .keyword,pre .id,pre .phpdoc,pre .aggregate,pre .css .tag,pre .javadoctag,pre .phpdoc,pre .yardoctag,pre .smalltalk .class,pre .winutils,pre .bash .variable,pre .apache .tag,pre .go .typename,pre .tex .command,pre .markdown .strong,pre .request,pre .status{color:#008000;font-weight:bold}
96 .highlight-builtin,pre .built_in{color:#008000}
96 .highlight-builtin,pre .built_in{color:#008000}
97 pre .markdown .emphasis{font-style:italic}
97 pre .markdown .emphasis{font-style:italic}
98 pre .nginx .built_in{font-weight:normal}
98 pre .nginx .built_in{font-weight:normal}
99 pre .coffeescript .javascript,pre .javascript .xml,pre .tex .formula,pre .xml .javascript,pre .xml .vbscript,pre .xml .css,pre .xml .cdata{opacity:.5}
99 pre .coffeescript .javascript,pre .javascript .xml,pre .tex .formula,pre .xml .javascript,pre .xml .vbscript,pre .xml .css,pre .xml .cdata{opacity:.5}
100 .cm-s-ipython span.cm-variable{color:#000}
100 .cm-s-ipython span.cm-variable{color:#000}
101 .cm-s-ipython span.cm-keyword{color:#008000;font-weight:bold}
101 .cm-s-ipython span.cm-keyword{color:#008000;font-weight:bold}
102 .cm-s-ipython span.cm-number{color:#080}
102 .cm-s-ipython span.cm-number{color:#080}
103 .cm-s-ipython span.cm-comment{color:#408080;font-style:italic}
103 .cm-s-ipython span.cm-comment{color:#408080;font-style:italic}
104 .cm-s-ipython span.cm-string{color:#ba2121}
104 .cm-s-ipython span.cm-string{color:#ba2121}
105 .cm-s-ipython span.cm-builtin{color:#008000}
105 .cm-s-ipython span.cm-builtin{color:#008000}
106 .cm-s-ipython span.cm-error{color:#f00}
106 .cm-s-ipython span.cm-error{color:#f00}
107 .cm-s-ipython span.cm-operator{color:#a2f;font-weight:bold}
107 .cm-s-ipython span.cm-operator{color:#a2f;font-weight:bold}
108 .cm-s-ipython span.cm-meta{color:#a2f}
108 .cm-s-ipython span.cm-meta{color:#a2f}
109 .cm-s-ipython span.cm-tab{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);background-position:right;background-repeat:no-repeat}
109 .cm-s-ipython span.cm-tab{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);background-position:right;background-repeat:no-repeat}
110 div.output_wrapper{position:relative;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}
110 div.output_wrapper{position:relative;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}
111 div.output_scroll{height:24em;width:100%;overflow:auto;border-radius:4px;-webkit-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);-moz-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);display:block}
111 div.output_scroll{height:24em;width:100%;overflow:auto;border-radius:4px;-webkit-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);-moz-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);display:block}
112 div.output_collapsed{margin:0;padding:0;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}
112 div.output_collapsed{margin:0;padding:0;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}
113 div.out_prompt_overlay{height:100%;padding:0 .4em;position:absolute;border-radius:4px}
113 div.out_prompt_overlay{height:100%;padding:0 .4em;position:absolute;border-radius:4px}
114 div.out_prompt_overlay:hover{-webkit-box-shadow:inset 0 0 1px #000;-moz-box-shadow:inset 0 0 1px #000;box-shadow:inset 0 0 1px #000;background:rgba(240,240,240,0.5)}
114 div.out_prompt_overlay:hover{-webkit-box-shadow:inset 0 0 1px #000;-moz-box-shadow:inset 0 0 1px #000;box-shadow:inset 0 0 1px #000;background:rgba(240,240,240,0.5)}
115 div.output_prompt{color:#8b0000}
115 div.output_prompt{color:#8b0000}
116 div.output_area{padding:0;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}div.output_area .MathJax_Display{text-align:left !important}
116 div.output_area{padding:0;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}div.output_area .MathJax_Display{text-align:left !important}
117 div.output_area .rendered_html table{margin-left:0;margin-right:0}
117 div.output_area .rendered_html table{margin-left:0;margin-right:0}
118 div.output_area .rendered_html img{margin-left:0;margin-right:0}
118 div.output_area .rendered_html img{margin-left:0;margin-right:0}
119 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
119 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
120 div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
120 @media (max-width:480px){div.output_area{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
121 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
121 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
122 div.output_text{text-align:left;color:#000;line-height:1.21429em}
122 div.output_text{text-align:left;color:#000;line-height:1.21429em}
123 div.output_stderr{background:#fdd;}
123 div.output_stderr{background:#fdd;}
124 div.output_latex{text-align:left}
124 div.output_latex{text-align:left}
125 div.output_javascript:empty{padding:0}
125 div.output_javascript:empty{padding:0}
126 .js-error{color:#8b0000}
126 .js-error{color:#8b0000}
127 div.raw_input_container{font-family:monospace;padding-top:5px}
127 div.raw_input_container{font-family:monospace;padding-top:5px}
128 span.raw_input_prompt{}
128 span.raw_input_prompt{}
129 input.raw_input{font-family:inherit;font-size:inherit;color:inherit;width:auto;vertical-align:baseline;padding:0 .25em;margin:0 .25em}
129 input.raw_input{font-family:inherit;font-size:inherit;color:inherit;width:auto;vertical-align:baseline;padding:0 .25em;margin:0 .25em}
130 input.raw_input:focus{box-shadow:none}
130 input.raw_input:focus{box-shadow:none}
131 p.p-space{margin-bottom:10px}
131 p.p-space{margin-bottom:10px}
132 .rendered_html{color:#000;}.rendered_html em{font-style:italic}
132 .rendered_html{color:#000;}.rendered_html em{font-style:italic}
133 .rendered_html strong{font-weight:bold}
133 .rendered_html strong{font-weight:bold}
134 .rendered_html u{text-decoration:underline}
134 .rendered_html u{text-decoration:underline}
135 .rendered_html :link{text-decoration:underline}
135 .rendered_html :link{text-decoration:underline}
136 .rendered_html :visited{text-decoration:underline}
136 .rendered_html :visited{text-decoration:underline}
137 .rendered_html h1{font-size:185.7%;margin:1.08em 0 0 0;font-weight:bold;line-height:1}
137 .rendered_html h1{font-size:185.7%;margin:1.08em 0 0 0;font-weight:bold;line-height:1}
138 .rendered_html h2{font-size:157.1%;margin:1.27em 0 0 0;font-weight:bold;line-height:1}
138 .rendered_html h2{font-size:157.1%;margin:1.27em 0 0 0;font-weight:bold;line-height:1}
139 .rendered_html h3{font-size:128.6%;margin:1.55em 0 0 0;font-weight:bold;line-height:1}
139 .rendered_html h3{font-size:128.6%;margin:1.55em 0 0 0;font-weight:bold;line-height:1}
140 .rendered_html h4{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1}
140 .rendered_html h4{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1}
141 .rendered_html h5{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
141 .rendered_html h5{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
142 .rendered_html h6{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
142 .rendered_html h6{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
143 .rendered_html h1:first-child{margin-top:.538em}
143 .rendered_html h1:first-child{margin-top:.538em}
144 .rendered_html h2:first-child{margin-top:.636em}
144 .rendered_html h2:first-child{margin-top:.636em}
145 .rendered_html h3:first-child{margin-top:.777em}
145 .rendered_html h3:first-child{margin-top:.777em}
146 .rendered_html h4:first-child{margin-top:1em}
146 .rendered_html h4:first-child{margin-top:1em}
147 .rendered_html h5:first-child{margin-top:1em}
147 .rendered_html h5:first-child{margin-top:1em}
148 .rendered_html h6:first-child{margin-top:1em}
148 .rendered_html h6:first-child{margin-top:1em}
149 .rendered_html ul{list-style:disc;margin:0 2em}
149 .rendered_html ul{list-style:disc;margin:0 2em}
150 .rendered_html ul ul{list-style:square;margin:0 2em}
150 .rendered_html ul ul{list-style:square;margin:0 2em}
151 .rendered_html ul ul ul{list-style:circle;margin:0 2em}
151 .rendered_html ul ul ul{list-style:circle;margin:0 2em}
152 .rendered_html ol{list-style:decimal;margin:0 2em}
152 .rendered_html ol{list-style:decimal;margin:0 2em}
153 .rendered_html ol ol{list-style:upper-alpha;margin:0 2em}
153 .rendered_html ol ol{list-style:upper-alpha;margin:0 2em}
154 .rendered_html ol ol ol{list-style:lower-alpha;margin:0 2em}
154 .rendered_html ol ol ol{list-style:lower-alpha;margin:0 2em}
155 .rendered_html ol ol ol ol{list-style:lower-roman;margin:0 2em}
155 .rendered_html ol ol ol ol{list-style:lower-roman;margin:0 2em}
156 .rendered_html ol ol ol ol ol{list-style:decimal;margin:0 2em}
156 .rendered_html ol ol ol ol ol{list-style:decimal;margin:0 2em}
157 .rendered_html *+ul{margin-top:1em}
157 .rendered_html *+ul{margin-top:1em}
158 .rendered_html *+ol{margin-top:1em}
158 .rendered_html *+ol{margin-top:1em}
159 .rendered_html hr{color:#000;background-color:#000}
159 .rendered_html hr{color:#000;background-color:#000}
160 .rendered_html pre{margin:1em 2em}
160 .rendered_html pre{margin:1em 2em}
161 .rendered_html pre,.rendered_html code{border:0;background-color:#fff;color:#000;font-size:100%;padding:0}
161 .rendered_html pre,.rendered_html code{border:0;background-color:#fff;color:#000;font-size:100%;padding:0}
162 .rendered_html blockquote{margin:1em 2em}
162 .rendered_html blockquote{margin:1em 2em}
163 .rendered_html table{margin-left:auto;margin-right:auto;border:1px solid #000;border-collapse:collapse}
163 .rendered_html table{margin-left:auto;margin-right:auto;border:1px solid #000;border-collapse:collapse}
164 .rendered_html tr,.rendered_html th,.rendered_html td{border:1px solid #000;border-collapse:collapse;margin:1em 2em}
164 .rendered_html tr,.rendered_html th,.rendered_html td{border:1px solid #000;border-collapse:collapse;margin:1em 2em}
165 .rendered_html td,.rendered_html th{text-align:left;vertical-align:middle;padding:4px}
165 .rendered_html td,.rendered_html th{text-align:left;vertical-align:middle;padding:4px}
166 .rendered_html th{font-weight:bold}
166 .rendered_html th{font-weight:bold}
167 .rendered_html *+table{margin-top:1em}
167 .rendered_html *+table{margin-top:1em}
168 .rendered_html p{text-align:justify}
168 .rendered_html p{text-align:justify}
169 .rendered_html *+p{margin-top:1em}
169 .rendered_html *+p{margin-top:1em}
170 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
170 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
171 .rendered_html *+img{margin-top:1em}
171 .rendered_html *+img{margin-top:1em}
172 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
172 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
173 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
173 @media (max-width:480px){div.text_cell>div.prompt{display:none}}div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
174 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
174 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
175 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
175 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
176 div.cell.text_cell.rendered{padding:0}
176 div.cell.text_cell.rendered{padding:0}
177 .widget-area{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}.widget-area .widget-subarea{padding:.44em .4em .4em 1px;margin-left:6px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;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:2;-moz-box-flex:2;box-flex:2;flex:2;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
177 .widget-area{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}.widget-area .widget-subarea{padding:.44em .4em .4em 1px;margin-left:6px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;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:2;-moz-box-flex:2;box-flex:2;flex:2;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
178 .widget-hlabel{min-width:10ex;padding-right:8px;padding-top:3px;text-align:right;vertical-align:text-top}
178 .widget-hlabel{min-width:10ex;padding-right:8px;padding-top:3px;text-align:right;vertical-align:text-top}
179 .widget-vlabel{padding-bottom:5px;text-align:center;vertical-align:text-bottom}
179 .widget-vlabel{padding-bottom:5px;text-align:center;vertical-align:text-bottom}
180 .widget-hreadout{padding-left:8px;padding-top:3px;text-align:left;vertical-align:text-top}
180 .widget-hreadout{padding-left:8px;padding-top:3px;text-align:left;vertical-align:text-top}
181 .widget-vreadout{padding-top:5px;text-align:center;vertical-align:text-top}
181 .widget-vreadout{padding-top:5px;text-align:center;vertical-align:text-top}
182 .slide-track{border:1px solid #ccc;background:#fff;border-radius:4px;}
182 .slide-track{border:1px solid #ccc;background:#fff;border-radius:4px;}
183 .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:348px;height:5px;max-height:5px;margin-top:11px;margin-bottom:10px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-hslider .ui-slider{border:0 !important;background:none !important;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;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important}
183 .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:348px;height:5px;max-height:5px;margin-top:11px;margin-bottom:10px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-hslider .ui-slider{border:0 !important;background:none !important;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;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important}
184 .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-vslider .ui-slider{border:0 !important;background:none !important;margin-left:-4px;margin-top:5px;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}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px}
184 .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-vslider .ui-slider{border:0 !important;background:none !important;margin-left:-4px;margin-top:5px;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}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px}
185 .widget-text{width:350px;margin:0 !important}
185 .widget-text{width:350px;margin:0 !important}
186 .widget-listbox{width:364px;margin-bottom:0}
186 .widget-listbox{width:364px;margin-bottom:0}
187 .widget-numeric-text{width:150px;margin:0 !important}
187 .widget-numeric-text{width:150px;margin:0 !important}
188 .widget-progress{width:363px}.widget-progress .bar{-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none}
188 .widget-progress{width:363px}.widget-progress .bar{-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none}
189 .widget-combo-btn{min-width:138px;}
189 .widget-combo-btn{min-width:138px;}
190 .widget-box{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
190 .widget-box{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
191 .widget-hbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
191 .widget-hbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
192 .widget-hbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;height:30px}
192 .widget-hbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;height:30px}
193 .widget-vbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
193 .widget-vbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
194 .widget-vbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;width:30px}
194 .widget-vbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;width:30px}
195 .widget-modal{overflow:hidden;position:absolute !important;top:0;left:0;margin-left:0 !important}
195 .widget-modal{overflow:hidden;position:absolute !important;top:0;left:0;margin-left:0 !important}
196 .widget-modal-body{max-height:none !important}
196 .widget-modal-body{max-height:none !important}
197 .widget-container{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
197 .widget-container{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
198 .widget-radio-box{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;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding-top:4px}
198 .widget-radio-box{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;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding-top:4px}
199 .docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important}
199 .docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important}
@@ -1,1536 +1,1536 b''
1 article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}
1 article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}
2 audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
2 audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
3 audio:not([controls]){display:none}
3 audio:not([controls]){display:none}
4 html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}
4 html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}
5 a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}
5 a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}
6 a:hover,a:active{outline:0}
6 a:hover,a:active{outline:0}
7 sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}
7 sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}
8 sup{top:-0.5em}
8 sup{top:-0.5em}
9 sub{bottom:-0.25em}
9 sub{bottom:-0.25em}
10 img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}
10 img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}
11 #map_canvas img,.google-maps img{max-width:none}
11 #map_canvas img,.google-maps img{max-width:none}
12 button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}
12 button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}
13 button,input{*overflow:visible;line-height:normal}
13 button,input{*overflow:visible;line-height:normal}
14 button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}
14 button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}
15 button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
15 button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
16 label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}
16 label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}
17 input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}
17 input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}
18 input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}
18 input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}
19 textarea{overflow:auto;vertical-align:top}
19 textarea{overflow:auto;vertical-align:top}
20 @media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important} a,a:visited{text-decoration:underline} a[href]:after{content:" (" attr(href) ")"} abbr[title]:after{content:" (" attr(title) ")"} .ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""} pre,blockquote{border:1px solid #999;page-break-inside:avoid} thead{display:table-header-group} tr,img{page-break-inside:avoid} img{max-width:100% !important} @page {margin:.5cm}p,h2,h3{orphans:3;widows:3} h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:20px;color:#000;background-color:#fff}
20 @media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important} a,a:visited{text-decoration:underline} a[href]:after{content:" (" attr(href) ")"} abbr[title]:after{content:" (" attr(title) ")"} .ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""} pre,blockquote{border:1px solid #999;page-break-inside:avoid} thead{display:table-header-group} tr,img{page-break-inside:avoid} img{max-width:100% !important} @page {margin:.5cm}p,h2,h3{orphans:3;widows:3} h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:20px;color:#000;background-color:#fff}
21 a{color:#08c;text-decoration:none}
21 a{color:#08c;text-decoration:none}
22 a:hover,a:focus{color:#005580;text-decoration:underline}
22 a:hover,a:focus{color:#005580;text-decoration:underline}
23 .img-rounded{border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
23 .img-rounded{border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
24 .img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}
24 .img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}
25 .img-circle{border-radius:500px;-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}
25 .img-circle{border-radius:500px;-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}
26 .row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0}
26 .row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0}
27 .row:after{clear:both}
27 .row:after{clear:both}
28 [class*="span"]{float:left;min-height:1px;margin-left:20px}
28 [class*="span"]{float:left;min-height:1px;margin-left:20px}
29 .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}
29 .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}
30 .span12{width:940px}
30 .span12{width:940px}
31 .span11{width:860px}
31 .span11{width:860px}
32 .span10{width:780px}
32 .span10{width:780px}
33 .span9{width:700px}
33 .span9{width:700px}
34 .span8{width:620px}
34 .span8{width:620px}
35 .span7{width:540px}
35 .span7{width:540px}
36 .span6{width:460px}
36 .span6{width:460px}
37 .span5{width:380px}
37 .span5{width:380px}
38 .span4{width:300px}
38 .span4{width:300px}
39 .span3{width:220px}
39 .span3{width:220px}
40 .span2{width:140px}
40 .span2{width:140px}
41 .span1{width:60px}
41 .span1{width:60px}
42 .offset12{margin-left:980px}
42 .offset12{margin-left:980px}
43 .offset11{margin-left:900px}
43 .offset11{margin-left:900px}
44 .offset10{margin-left:820px}
44 .offset10{margin-left:820px}
45 .offset9{margin-left:740px}
45 .offset9{margin-left:740px}
46 .offset8{margin-left:660px}
46 .offset8{margin-left:660px}
47 .offset7{margin-left:580px}
47 .offset7{margin-left:580px}
48 .offset6{margin-left:500px}
48 .offset6{margin-left:500px}
49 .offset5{margin-left:420px}
49 .offset5{margin-left:420px}
50 .offset4{margin-left:340px}
50 .offset4{margin-left:340px}
51 .offset3{margin-left:260px}
51 .offset3{margin-left:260px}
52 .offset2{margin-left:180px}
52 .offset2{margin-left:180px}
53 .offset1{margin-left:100px}
53 .offset1{margin-left:100px}
54 .row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0}
54 .row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0}
55 .row-fluid:after{clear:both}
55 .row-fluid:after{clear:both}
56 .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%}
56 .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%}
57 .row-fluid [class*="span"]:first-child{margin-left:0}
57 .row-fluid [class*="span"]:first-child{margin-left:0}
58 .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}
58 .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}
59 .row-fluid .span12{width:100%;*width:99.94680851063829%}
59 .row-fluid .span12{width:100%;*width:99.94680851063829%}
60 .row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}
60 .row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}
61 .row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}
61 .row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}
62 .row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}
62 .row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}
63 .row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}
63 .row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}
64 .row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}
64 .row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}
65 .row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}
65 .row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}
66 .row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}
66 .row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}
67 .row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}
67 .row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}
68 .row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}
68 .row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}
69 .row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}
69 .row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}
70 .row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}
70 .row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}
71 .row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}
71 .row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}
72 .row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}
72 .row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}
73 .row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}
73 .row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}
74 .row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}
74 .row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}
75 .row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}
75 .row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}
76 .row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}
76 .row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}
77 .row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}
77 .row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}
78 .row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}
78 .row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}
79 .row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}
79 .row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}
80 .row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}
80 .row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}
81 .row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}
81 .row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}
82 .row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}
82 .row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}
83 .row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}
83 .row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}
84 .row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}
84 .row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}
85 .row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}
85 .row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}
86 .row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}
86 .row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}
87 .row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}
87 .row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}
88 .row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}
88 .row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}
89 .row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}
89 .row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}
90 .row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}
90 .row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}
91 .row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}
91 .row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}
92 .row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}
92 .row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}
93 .row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}
93 .row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}
94 .row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}
94 .row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}
95 [class*="span"].hide,.row-fluid [class*="span"].hide{display:none}
95 [class*="span"].hide,.row-fluid [class*="span"].hide{display:none}
96 [class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}
96 [class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}
97 .container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;content:"";line-height:0}
97 .container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;content:"";line-height:0}
98 .container:after{clear:both}
98 .container:after{clear:both}
99 .container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0}
99 .container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0}
100 .container-fluid:after{clear:both}
100 .container-fluid:after{clear:both}
101 p{margin:0 0 10px}
101 p{margin:0 0 10px}
102 .lead{margin-bottom:20px;font-size:19.5px;font-weight:200;line-height:30px}
102 .lead{margin-bottom:20px;font-size:19.5px;font-weight:200;line-height:30px}
103 small{font-size:85%}
103 small{font-size:85%}
104 strong{font-weight:bold}
104 strong{font-weight:bold}
105 em{font-style:italic}
105 em{font-style:italic}
106 cite{font-style:normal}
106 cite{font-style:normal}
107 .muted{color:#999}
107 .muted{color:#999}
108 a.muted:hover,a.muted:focus{color:#808080}
108 a.muted:hover,a.muted:focus{color:#808080}
109 .text-warning{color:#c09853}
109 .text-warning{color:#c09853}
110 a.text-warning:hover,a.text-warning:focus{color:#a47e3c}
110 a.text-warning:hover,a.text-warning:focus{color:#a47e3c}
111 .text-error{color:#b94a48}
111 .text-error{color:#b94a48}
112 a.text-error:hover,a.text-error:focus{color:#953b39}
112 a.text-error:hover,a.text-error:focus{color:#953b39}
113 .text-info{color:#3a87ad}
113 .text-info{color:#3a87ad}
114 a.text-info:hover,a.text-info:focus{color:#2d6987}
114 a.text-info:hover,a.text-info:focus{color:#2d6987}
115 .text-success{color:#468847}
115 .text-success{color:#468847}
116 a.text-success:hover,a.text-success:focus{color:#356635}
116 a.text-success:hover,a.text-success:focus{color:#356635}
117 .text-left{text-align:left}
117 .text-left{text-align:left}
118 .text-right{text-align:right}
118 .text-right{text-align:right}
119 .text-center{text-align:center}
119 .text-center{text-align:center}
120 h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}
120 h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}
121 h1,h2,h3{line-height:40px}
121 h1,h2,h3{line-height:40px}
122 h1{font-size:35.75px}
122 h1{font-size:35.75px}
123 h2{font-size:29.25px}
123 h2{font-size:29.25px}
124 h3{font-size:22.75px}
124 h3{font-size:22.75px}
125 h4{font-size:16.25px}
125 h4{font-size:16.25px}
126 h5{font-size:13px}
126 h5{font-size:13px}
127 h6{font-size:11.049999999999999px}
127 h6{font-size:11.049999999999999px}
128 h1 small{font-size:22.75px}
128 h1 small{font-size:22.75px}
129 h2 small{font-size:16.25px}
129 h2 small{font-size:16.25px}
130 h3 small{font-size:13px}
130 h3 small{font-size:13px}
131 h4 small{font-size:13px}
131 h4 small{font-size:13px}
132 .page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}
132 .page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}
133 ul,ol{padding:0;margin:0 0 10px 25px}
133 ul,ol{padding:0;margin:0 0 10px 25px}
134 ul ul,ul ol,ol ol,ol ul{margin-bottom:0}
134 ul ul,ul ol,ol ol,ol ul{margin-bottom:0}
135 li{line-height:20px}
135 li{line-height:20px}
136 ul.unstyled,ol.unstyled{margin-left:0;list-style:none}
136 ul.unstyled,ol.unstyled{margin-left:0;list-style:none}
137 ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;*zoom:1;padding-left:5px;padding-right:5px}
137 ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;*zoom:1;padding-left:5px;padding-right:5px}
138 dl{margin-bottom:20px}
138 dl{margin-bottom:20px}
139 dt,dd{line-height:20px}
139 dt,dd{line-height:20px}
140 dt{font-weight:bold}
140 dt{font-weight:bold}
141 dd{margin-left:10px}
141 dd{margin-left:10px}
142 .dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0}
142 .dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0}
143 .dl-horizontal:after{clear:both}
143 .dl-horizontal:after{clear:both}
144 .dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
144 .dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
145 .dl-horizontal dd{margin-left:180px}
145 .dl-horizontal dd{margin-left:180px}
146 hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}
146 hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}
147 abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}
147 abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}
148 abbr.initialism{font-size:90%;text-transform:uppercase}
148 abbr.initialism{font-size:90%;text-transform:uppercase}
149 blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16.25px;font-weight:300;line-height:1.25}
149 blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16.25px;font-weight:300;line-height:1.25}
150 blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}
150 blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}
151 blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}
151 blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}
152 blockquote.pull-right small:before{content:''}
152 blockquote.pull-right small:before{content:''}
153 blockquote.pull-right small:after{content:'\00A0 \2014'}
153 blockquote.pull-right small:after{content:'\00A0 \2014'}
154 q:before,q:after,blockquote:before,blockquote:after{content:""}
154 q:before,q:after,blockquote:before,blockquote:after{content:""}
155 address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}
155 address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}
156 code,pre{padding:0 3px 2px;font-family:monospace;font-size:11px;color:#333;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
156 code,pre{padding:0 3px 2px;font-family:monospace;font-size:11px;color:#333;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
157 code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap}
157 code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap}
158 pre{display:block;padding:9.5px;margin:0 0 10px;font-size:12px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}
158 pre{display:block;padding:9.5px;margin:0 0 10px;font-size:12px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}
159 pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}
159 pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}
160 .pre-scrollable{max-height:340px;overflow-y:scroll}
160 .pre-scrollable{max-height:340px;overflow-y:scroll}
161 form{margin:0 0 20px}
161 form{margin:0 0 20px}
162 fieldset{padding:0;margin:0;border:0}
162 fieldset{padding:0;margin:0;border:0}
163 legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:19.5px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}
163 legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:19.5px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}
164 label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:20px}
164 label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:20px}
165 input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}
165 input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}
166 label{display:block;margin-bottom:5px}
166 label{display:block;margin-bottom:5px}
167 select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:13px;line-height:20px;color:#555;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;vertical-align:middle}
167 select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:13px;line-height:20px;color:#555;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;vertical-align:middle}
168 input,textarea,.uneditable-input{width:206px}
168 input,textarea,.uneditable-input{width:206px}
169 textarea{height:auto}
169 textarea{height:auto}
170 textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)}
170 textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)}
171 input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal}
171 input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal}
172 input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}
172 input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}
173 select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}
173 select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}
174 select{width:220px;border:1px solid #ccc;background-color:#fff}
174 select{width:220px;border:1px solid #ccc;background-color:#fff}
175 select[multiple],select[size]{height:auto}
175 select[multiple],select[size]{height:auto}
176 select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}
176 select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}
177 .uneditable-input,.uneditable-textarea{color:#999;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);cursor:not-allowed}
177 .uneditable-input,.uneditable-textarea{color:#999;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);cursor:not-allowed}
178 .uneditable-input{overflow:hidden;white-space:nowrap}
178 .uneditable-input{overflow:hidden;white-space:nowrap}
179 .uneditable-textarea{width:auto;height:auto}
179 .uneditable-textarea{width:auto;height:auto}
180 input:-moz-placeholder,textarea:-moz-placeholder{color:#999}
180 input:-moz-placeholder,textarea:-moz-placeholder{color:#999}
181 input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}
181 input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}
182 input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}
182 input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}
183 .radio,.checkbox{min-height:20px;padding-left:20px}
183 .radio,.checkbox{min-height:20px;padding-left:20px}
184 .radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}
184 .radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}
185 .controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}
185 .controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}
186 .radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}
186 .radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}
187 .radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}
187 .radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}
188 .input-mini{width:60px}
188 .input-mini{width:60px}
189 .input-small{width:90px}
189 .input-small{width:90px}
190 .input-medium{width:150px}
190 .input-medium{width:150px}
191 .input-large{width:210px}
191 .input-large{width:210px}
192 .input-xlarge{width:270px}
192 .input-xlarge{width:270px}
193 .input-xxlarge{width:530px}
193 .input-xxlarge{width:530px}
194 input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}
194 input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}
195 .input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}
195 .input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}
196 input,textarea,.uneditable-input{margin-left:0}
196 input,textarea,.uneditable-input{margin-left:0}
197 .controls-row [class*="span"]+[class*="span"]{margin-left:20px}
197 .controls-row [class*="span"]+[class*="span"]{margin-left:20px}
198 input.span12,textarea.span12,.uneditable-input.span12{width:926px}
198 input.span12,textarea.span12,.uneditable-input.span12{width:926px}
199 input.span11,textarea.span11,.uneditable-input.span11{width:846px}
199 input.span11,textarea.span11,.uneditable-input.span11{width:846px}
200 input.span10,textarea.span10,.uneditable-input.span10{width:766px}
200 input.span10,textarea.span10,.uneditable-input.span10{width:766px}
201 input.span9,textarea.span9,.uneditable-input.span9{width:686px}
201 input.span9,textarea.span9,.uneditable-input.span9{width:686px}
202 input.span8,textarea.span8,.uneditable-input.span8{width:606px}
202 input.span8,textarea.span8,.uneditable-input.span8{width:606px}
203 input.span7,textarea.span7,.uneditable-input.span7{width:526px}
203 input.span7,textarea.span7,.uneditable-input.span7{width:526px}
204 input.span6,textarea.span6,.uneditable-input.span6{width:446px}
204 input.span6,textarea.span6,.uneditable-input.span6{width:446px}
205 input.span5,textarea.span5,.uneditable-input.span5{width:366px}
205 input.span5,textarea.span5,.uneditable-input.span5{width:366px}
206 input.span4,textarea.span4,.uneditable-input.span4{width:286px}
206 input.span4,textarea.span4,.uneditable-input.span4{width:286px}
207 input.span3,textarea.span3,.uneditable-input.span3{width:206px}
207 input.span3,textarea.span3,.uneditable-input.span3{width:206px}
208 input.span2,textarea.span2,.uneditable-input.span2{width:126px}
208 input.span2,textarea.span2,.uneditable-input.span2{width:126px}
209 input.span1,textarea.span1,.uneditable-input.span1{width:46px}
209 input.span1,textarea.span1,.uneditable-input.span1{width:46px}
210 .controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0}
210 .controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0}
211 .controls-row:after{clear:both}
211 .controls-row:after{clear:both}
212 .controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}
212 .controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}
213 .controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}
213 .controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}
214 input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}
214 input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}
215 input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}
215 input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}
216 .control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}
216 .control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}
217 .control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}
217 .control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}
218 .control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}
218 .control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}
219 .control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}
219 .control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}
220 .control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}
220 .control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}
221 .control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}
221 .control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}
222 .control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}
222 .control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}
223 .control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}
223 .control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}
224 .control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}
224 .control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}
225 .control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}
225 .control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}
226 .control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}
226 .control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}
227 .control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}
227 .control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}
228 .control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}
228 .control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}
229 .control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}
229 .control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}
230 .control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}
230 .control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}
231 .control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}
231 .control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}
232 input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}
232 input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}
233 .form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0}
233 .form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0}
234 .form-actions:after{clear:both}
234 .form-actions:after{clear:both}
235 .help-block,.help-inline{color:#262626}
235 .help-block,.help-inline{color:#262626}
236 .help-block{display:block;margin-bottom:10px}
236 .help-block{display:block;margin-bottom:10px}
237 .help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px}
237 .help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px}
238 .input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:13px}
238 .input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:13px}
239 .input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}
239 .input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}
240 .input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:13px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}
240 .input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:13px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}
241 .input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
241 .input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
242 .input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}
242 .input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}
243 .input-prepend .add-on,.input-prepend .btn{margin-right:-1px}
243 .input-prepend .add-on,.input-prepend .btn{margin-right:-1px}
244 .input-prepend .add-on:first-child,.input-prepend .btn:first-child{border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}
244 .input-prepend .add-on:first-child,.input-prepend .btn:first-child{border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}
245 .input-append input,.input-append select,.input-append .uneditable-input{border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
245 .input-append input,.input-append select,.input-append .uneditable-input{border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
246 .input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}
246 .input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}
247 .input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
247 .input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
248 .input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
248 .input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
249 .input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}
249 .input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}
250 .input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
250 .input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
251 .input-prepend.input-append .btn-group:first-child{margin-left:0}
251 .input-prepend.input-append .btn-group:first-child{margin-left:0}
252 input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;border-radius:15px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}
252 input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;border-radius:15px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}
253 .form-search .input-append .search-query,.form-search .input-prepend .search-query{border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
253 .form-search .input-append .search-query,.form-search .input-prepend .search-query{border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
254 .form-search .input-append .search-query{border-radius:14px 0 0 14px;-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}
254 .form-search .input-append .search-query{border-radius:14px 0 0 14px;-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}
255 .form-search .input-append .btn{border-radius:0 14px 14px 0;-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}
255 .form-search .input-append .btn{border-radius:0 14px 14px 0;-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}
256 .form-search .input-prepend .search-query{border-radius:0 14px 14px 0;-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}
256 .form-search .input-prepend .search-query{border-radius:0 14px 14px 0;-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}
257 .form-search .input-prepend .btn{border-radius:14px 0 0 14px;-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}
257 .form-search .input-prepend .btn{border-radius:14px 0 0 14px;-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}
258 .form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle}
258 .form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle}
259 .form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}
259 .form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}
260 .form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}
260 .form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}
261 .form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}
261 .form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}
262 .form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}
262 .form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}
263 .form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}
263 .form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}
264 .control-group{margin-bottom:10px}
264 .control-group{margin-bottom:10px}
265 legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}
265 legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}
266 .form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0}
266 .form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0}
267 .form-horizontal .control-group:after{clear:both}
267 .form-horizontal .control-group:after{clear:both}
268 .form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}
268 .form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}
269 .form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}
269 .form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}
270 .form-horizontal .help-block{margin-bottom:0}
270 .form-horizontal .help-block{margin-bottom:0}
271 .form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}
271 .form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}
272 .form-horizontal .form-actions{padding-left:180px}
272 .form-horizontal .form-actions{padding-left:180px}
273 table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}
273 table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}
274 .table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}
274 .table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}
275 .table th{font-weight:bold}
275 .table th{font-weight:bold}
276 .table thead th{vertical-align:bottom}
276 .table thead th{vertical-align:bottom}
277 .table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}
277 .table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}
278 .table tbody+tbody{border-top:2px solid #ddd}
278 .table tbody+tbody{border-top:2px solid #ddd}
279 .table .table{background-color:#fff}
279 .table .table{background-color:#fff}
280 .table-condensed th,.table-condensed td{padding:4px 5px}
280 .table-condensed th,.table-condensed td{padding:4px 5px}
281 .table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}
281 .table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}
282 .table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}
282 .table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}
283 .table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}
283 .table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}
284 .table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px}
284 .table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px}
285 .table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
285 .table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
286 .table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}
286 .table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}
287 .table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0}
287 .table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0}
288 .table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0}
288 .table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0}
289 .table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}
289 .table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}
290 .table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px}
290 .table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px}
291 .table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}
291 .table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}
292 .table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}
292 .table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}
293 table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}
293 table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}
294 .table td.span1,.table th.span1{float:none;width:44px;margin-left:0}
294 .table td.span1,.table th.span1{float:none;width:44px;margin-left:0}
295 .table td.span2,.table th.span2{float:none;width:124px;margin-left:0}
295 .table td.span2,.table th.span2{float:none;width:124px;margin-left:0}
296 .table td.span3,.table th.span3{float:none;width:204px;margin-left:0}
296 .table td.span3,.table th.span3{float:none;width:204px;margin-left:0}
297 .table td.span4,.table th.span4{float:none;width:284px;margin-left:0}
297 .table td.span4,.table th.span4{float:none;width:284px;margin-left:0}
298 .table td.span5,.table th.span5{float:none;width:364px;margin-left:0}
298 .table td.span5,.table th.span5{float:none;width:364px;margin-left:0}
299 .table td.span6,.table th.span6{float:none;width:444px;margin-left:0}
299 .table td.span6,.table th.span6{float:none;width:444px;margin-left:0}
300 .table td.span7,.table th.span7{float:none;width:524px;margin-left:0}
300 .table td.span7,.table th.span7{float:none;width:524px;margin-left:0}
301 .table td.span8,.table th.span8{float:none;width:604px;margin-left:0}
301 .table td.span8,.table th.span8{float:none;width:604px;margin-left:0}
302 .table td.span9,.table th.span9{float:none;width:684px;margin-left:0}
302 .table td.span9,.table th.span9{float:none;width:684px;margin-left:0}
303 .table td.span10,.table th.span10{float:none;width:764px;margin-left:0}
303 .table td.span10,.table th.span10{float:none;width:764px;margin-left:0}
304 .table td.span11,.table th.span11{float:none;width:844px;margin-left:0}
304 .table td.span11,.table th.span11{float:none;width:844px;margin-left:0}
305 .table td.span12,.table th.span12{float:none;width:924px;margin-left:0}
305 .table td.span12,.table th.span12{float:none;width:924px;margin-left:0}
306 .table tbody tr.success>td{background-color:#dff0d8}
306 .table tbody tr.success>td{background-color:#dff0d8}
307 .table tbody tr.error>td{background-color:#f2dede}
307 .table tbody tr.error>td{background-color:#f2dede}
308 .table tbody tr.warning>td{background-color:#fcf8e3}
308 .table tbody tr.warning>td{background-color:#fcf8e3}
309 .table tbody tr.info>td{background-color:#d9edf7}
309 .table tbody tr.info>td{background-color:#d9edf7}
310 .table-hover tbody tr.success:hover>td{background-color:#d0e9c6}
310 .table-hover tbody tr.success:hover>td{background-color:#d0e9c6}
311 .table-hover tbody tr.error:hover>td{background-color:#ebcccc}
311 .table-hover tbody tr.error:hover>td{background-color:#ebcccc}
312 .table-hover tbody tr.warning:hover>td{background-color:#faf2cc}
312 .table-hover tbody tr.warning:hover>td{background-color:#faf2cc}
313 .table-hover tbody tr.info:hover>td{background-color:#c4e3f3}
313 .table-hover tbody tr.info:hover>td{background-color:#c4e3f3}
314 [class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px}
314 [class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px}
315 .icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}
315 .icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}
316 .icon-glass{background-position:0 0}
316 .icon-glass{background-position:0 0}
317 .icon-music{background-position:-24px 0}
317 .icon-music{background-position:-24px 0}
318 .icon-search{background-position:-48px 0}
318 .icon-search{background-position:-48px 0}
319 .icon-envelope{background-position:-72px 0}
319 .icon-envelope{background-position:-72px 0}
320 .icon-heart{background-position:-96px 0}
320 .icon-heart{background-position:-96px 0}
321 .icon-star{background-position:-120px 0}
321 .icon-star{background-position:-120px 0}
322 .icon-star-empty{background-position:-144px 0}
322 .icon-star-empty{background-position:-144px 0}
323 .icon-user{background-position:-168px 0}
323 .icon-user{background-position:-168px 0}
324 .icon-film{background-position:-192px 0}
324 .icon-film{background-position:-192px 0}
325 .icon-th-large{background-position:-216px 0}
325 .icon-th-large{background-position:-216px 0}
326 .icon-th{background-position:-240px 0}
326 .icon-th{background-position:-240px 0}
327 .icon-th-list{background-position:-264px 0}
327 .icon-th-list{background-position:-264px 0}
328 .icon-ok{background-position:-288px 0}
328 .icon-ok{background-position:-288px 0}
329 .icon-remove{background-position:-312px 0}
329 .icon-remove{background-position:-312px 0}
330 .icon-zoom-in{background-position:-336px 0}
330 .icon-zoom-in{background-position:-336px 0}
331 .icon-zoom-out{background-position:-360px 0}
331 .icon-zoom-out{background-position:-360px 0}
332 .icon-off{background-position:-384px 0}
332 .icon-off{background-position:-384px 0}
333 .icon-signal{background-position:-408px 0}
333 .icon-signal{background-position:-408px 0}
334 .icon-cog{background-position:-432px 0}
334 .icon-cog{background-position:-432px 0}
335 .icon-trash{background-position:-456px 0}
335 .icon-trash{background-position:-456px 0}
336 .icon-home{background-position:0 -24px}
336 .icon-home{background-position:0 -24px}
337 .icon-file{background-position:-24px -24px}
337 .icon-file{background-position:-24px -24px}
338 .icon-time{background-position:-48px -24px}
338 .icon-time{background-position:-48px -24px}
339 .icon-road{background-position:-72px -24px}
339 .icon-road{background-position:-72px -24px}
340 .icon-download-alt{background-position:-96px -24px}
340 .icon-download-alt{background-position:-96px -24px}
341 .icon-download{background-position:-120px -24px}
341 .icon-download{background-position:-120px -24px}
342 .icon-upload{background-position:-144px -24px}
342 .icon-upload{background-position:-144px -24px}
343 .icon-inbox{background-position:-168px -24px}
343 .icon-inbox{background-position:-168px -24px}
344 .icon-play-circle{background-position:-192px -24px}
344 .icon-play-circle{background-position:-192px -24px}
345 .icon-repeat{background-position:-216px -24px}
345 .icon-repeat{background-position:-216px -24px}
346 .icon-refresh{background-position:-240px -24px}
346 .icon-refresh{background-position:-240px -24px}
347 .icon-list-alt{background-position:-264px -24px}
347 .icon-list-alt{background-position:-264px -24px}
348 .icon-lock{background-position:-287px -24px}
348 .icon-lock{background-position:-287px -24px}
349 .icon-flag{background-position:-312px -24px}
349 .icon-flag{background-position:-312px -24px}
350 .icon-headphones{background-position:-336px -24px}
350 .icon-headphones{background-position:-336px -24px}
351 .icon-volume-off{background-position:-360px -24px}
351 .icon-volume-off{background-position:-360px -24px}
352 .icon-volume-down{background-position:-384px -24px}
352 .icon-volume-down{background-position:-384px -24px}
353 .icon-volume-up{background-position:-408px -24px}
353 .icon-volume-up{background-position:-408px -24px}
354 .icon-qrcode{background-position:-432px -24px}
354 .icon-qrcode{background-position:-432px -24px}
355 .icon-barcode{background-position:-456px -24px}
355 .icon-barcode{background-position:-456px -24px}
356 .icon-tag{background-position:0 -48px}
356 .icon-tag{background-position:0 -48px}
357 .icon-tags{background-position:-25px -48px}
357 .icon-tags{background-position:-25px -48px}
358 .icon-book{background-position:-48px -48px}
358 .icon-book{background-position:-48px -48px}
359 .icon-bookmark{background-position:-72px -48px}
359 .icon-bookmark{background-position:-72px -48px}
360 .icon-print{background-position:-96px -48px}
360 .icon-print{background-position:-96px -48px}
361 .icon-camera{background-position:-120px -48px}
361 .icon-camera{background-position:-120px -48px}
362 .icon-font{background-position:-144px -48px}
362 .icon-font{background-position:-144px -48px}
363 .icon-bold{background-position:-167px -48px}
363 .icon-bold{background-position:-167px -48px}
364 .icon-italic{background-position:-192px -48px}
364 .icon-italic{background-position:-192px -48px}
365 .icon-text-height{background-position:-216px -48px}
365 .icon-text-height{background-position:-216px -48px}
366 .icon-text-width{background-position:-240px -48px}
366 .icon-text-width{background-position:-240px -48px}
367 .icon-align-left{background-position:-264px -48px}
367 .icon-align-left{background-position:-264px -48px}
368 .icon-align-center{background-position:-288px -48px}
368 .icon-align-center{background-position:-288px -48px}
369 .icon-align-right{background-position:-312px -48px}
369 .icon-align-right{background-position:-312px -48px}
370 .icon-align-justify{background-position:-336px -48px}
370 .icon-align-justify{background-position:-336px -48px}
371 .icon-list{background-position:-360px -48px}
371 .icon-list{background-position:-360px -48px}
372 .icon-indent-left{background-position:-384px -48px}
372 .icon-indent-left{background-position:-384px -48px}
373 .icon-indent-right{background-position:-408px -48px}
373 .icon-indent-right{background-position:-408px -48px}
374 .icon-facetime-video{background-position:-432px -48px}
374 .icon-facetime-video{background-position:-432px -48px}
375 .icon-picture{background-position:-456px -48px}
375 .icon-picture{background-position:-456px -48px}
376 .icon-pencil{background-position:0 -72px}
376 .icon-pencil{background-position:0 -72px}
377 .icon-map-marker{background-position:-24px -72px}
377 .icon-map-marker{background-position:-24px -72px}
378 .icon-adjust{background-position:-48px -72px}
378 .icon-adjust{background-position:-48px -72px}
379 .icon-tint{background-position:-72px -72px}
379 .icon-tint{background-position:-72px -72px}
380 .icon-edit{background-position:-96px -72px}
380 .icon-edit{background-position:-96px -72px}
381 .icon-share{background-position:-120px -72px}
381 .icon-share{background-position:-120px -72px}
382 .icon-check{background-position:-144px -72px}
382 .icon-check{background-position:-144px -72px}
383 .icon-move{background-position:-168px -72px}
383 .icon-move{background-position:-168px -72px}
384 .icon-step-backward{background-position:-192px -72px}
384 .icon-step-backward{background-position:-192px -72px}
385 .icon-fast-backward{background-position:-216px -72px}
385 .icon-fast-backward{background-position:-216px -72px}
386 .icon-backward{background-position:-240px -72px}
386 .icon-backward{background-position:-240px -72px}
387 .icon-play{background-position:-264px -72px}
387 .icon-play{background-position:-264px -72px}
388 .icon-pause{background-position:-288px -72px}
388 .icon-pause{background-position:-288px -72px}
389 .icon-stop{background-position:-312px -72px}
389 .icon-stop{background-position:-312px -72px}
390 .icon-forward{background-position:-336px -72px}
390 .icon-forward{background-position:-336px -72px}
391 .icon-fast-forward{background-position:-360px -72px}
391 .icon-fast-forward{background-position:-360px -72px}
392 .icon-step-forward{background-position:-384px -72px}
392 .icon-step-forward{background-position:-384px -72px}
393 .icon-eject{background-position:-408px -72px}
393 .icon-eject{background-position:-408px -72px}
394 .icon-chevron-left{background-position:-432px -72px}
394 .icon-chevron-left{background-position:-432px -72px}
395 .icon-chevron-right{background-position:-456px -72px}
395 .icon-chevron-right{background-position:-456px -72px}
396 .icon-plus-sign{background-position:0 -96px}
396 .icon-plus-sign{background-position:0 -96px}
397 .icon-minus-sign{background-position:-24px -96px}
397 .icon-minus-sign{background-position:-24px -96px}
398 .icon-remove-sign{background-position:-48px -96px}
398 .icon-remove-sign{background-position:-48px -96px}
399 .icon-ok-sign{background-position:-72px -96px}
399 .icon-ok-sign{background-position:-72px -96px}
400 .icon-question-sign{background-position:-96px -96px}
400 .icon-question-sign{background-position:-96px -96px}
401 .icon-info-sign{background-position:-120px -96px}
401 .icon-info-sign{background-position:-120px -96px}
402 .icon-screenshot{background-position:-144px -96px}
402 .icon-screenshot{background-position:-144px -96px}
403 .icon-remove-circle{background-position:-168px -96px}
403 .icon-remove-circle{background-position:-168px -96px}
404 .icon-ok-circle{background-position:-192px -96px}
404 .icon-ok-circle{background-position:-192px -96px}
405 .icon-ban-circle{background-position:-216px -96px}
405 .icon-ban-circle{background-position:-216px -96px}
406 .icon-arrow-left{background-position:-240px -96px}
406 .icon-arrow-left{background-position:-240px -96px}
407 .icon-arrow-right{background-position:-264px -96px}
407 .icon-arrow-right{background-position:-264px -96px}
408 .icon-arrow-up{background-position:-289px -96px}
408 .icon-arrow-up{background-position:-289px -96px}
409 .icon-arrow-down{background-position:-312px -96px}
409 .icon-arrow-down{background-position:-312px -96px}
410 .icon-share-alt{background-position:-336px -96px}
410 .icon-share-alt{background-position:-336px -96px}
411 .icon-resize-full{background-position:-360px -96px}
411 .icon-resize-full{background-position:-360px -96px}
412 .icon-resize-small{background-position:-384px -96px}
412 .icon-resize-small{background-position:-384px -96px}
413 .icon-plus{background-position:-408px -96px}
413 .icon-plus{background-position:-408px -96px}
414 .icon-minus{background-position:-433px -96px}
414 .icon-minus{background-position:-433px -96px}
415 .icon-asterisk{background-position:-456px -96px}
415 .icon-asterisk{background-position:-456px -96px}
416 .icon-exclamation-sign{background-position:0 -120px}
416 .icon-exclamation-sign{background-position:0 -120px}
417 .icon-gift{background-position:-24px -120px}
417 .icon-gift{background-position:-24px -120px}
418 .icon-leaf{background-position:-48px -120px}
418 .icon-leaf{background-position:-48px -120px}
419 .icon-fire{background-position:-72px -120px}
419 .icon-fire{background-position:-72px -120px}
420 .icon-eye-open{background-position:-96px -120px}
420 .icon-eye-open{background-position:-96px -120px}
421 .icon-eye-close{background-position:-120px -120px}
421 .icon-eye-close{background-position:-120px -120px}
422 .icon-warning-sign{background-position:-144px -120px}
422 .icon-warning-sign{background-position:-144px -120px}
423 .icon-plane{background-position:-168px -120px}
423 .icon-plane{background-position:-168px -120px}
424 .icon-calendar{background-position:-192px -120px}
424 .icon-calendar{background-position:-192px -120px}
425 .icon-random{background-position:-216px -120px;width:16px}
425 .icon-random{background-position:-216px -120px;width:16px}
426 .icon-comment{background-position:-240px -120px}
426 .icon-comment{background-position:-240px -120px}
427 .icon-magnet{background-position:-264px -120px}
427 .icon-magnet{background-position:-264px -120px}
428 .icon-chevron-up{background-position:-288px -120px}
428 .icon-chevron-up{background-position:-288px -120px}
429 .icon-chevron-down{background-position:-313px -119px}
429 .icon-chevron-down{background-position:-313px -119px}
430 .icon-retweet{background-position:-336px -120px}
430 .icon-retweet{background-position:-336px -120px}
431 .icon-shopping-cart{background-position:-360px -120px}
431 .icon-shopping-cart{background-position:-360px -120px}
432 .icon-folder-close{background-position:-384px -120px;width:16px}
432 .icon-folder-close{background-position:-384px -120px;width:16px}
433 .icon-folder-open{background-position:-408px -120px;width:16px}
433 .icon-folder-open{background-position:-408px -120px;width:16px}
434 .icon-resize-vertical{background-position:-432px -119px}
434 .icon-resize-vertical{background-position:-432px -119px}
435 .icon-resize-horizontal{background-position:-456px -118px}
435 .icon-resize-horizontal{background-position:-456px -118px}
436 .icon-hdd{background-position:0 -144px}
436 .icon-hdd{background-position:0 -144px}
437 .icon-bullhorn{background-position:-24px -144px}
437 .icon-bullhorn{background-position:-24px -144px}
438 .icon-bell{background-position:-48px -144px}
438 .icon-bell{background-position:-48px -144px}
439 .icon-certificate{background-position:-72px -144px}
439 .icon-certificate{background-position:-72px -144px}
440 .icon-thumbs-up{background-position:-96px -144px}
440 .icon-thumbs-up{background-position:-96px -144px}
441 .icon-thumbs-down{background-position:-120px -144px}
441 .icon-thumbs-down{background-position:-120px -144px}
442 .icon-hand-right{background-position:-144px -144px}
442 .icon-hand-right{background-position:-144px -144px}
443 .icon-hand-left{background-position:-168px -144px}
443 .icon-hand-left{background-position:-168px -144px}
444 .icon-hand-up{background-position:-192px -144px}
444 .icon-hand-up{background-position:-192px -144px}
445 .icon-hand-down{background-position:-216px -144px}
445 .icon-hand-down{background-position:-216px -144px}
446 .icon-circle-arrow-right{background-position:-240px -144px}
446 .icon-circle-arrow-right{background-position:-240px -144px}
447 .icon-circle-arrow-left{background-position:-264px -144px}
447 .icon-circle-arrow-left{background-position:-264px -144px}
448 .icon-circle-arrow-up{background-position:-288px -144px}
448 .icon-circle-arrow-up{background-position:-288px -144px}
449 .icon-circle-arrow-down{background-position:-312px -144px}
449 .icon-circle-arrow-down{background-position:-312px -144px}
450 .icon-globe{background-position:-336px -144px}
450 .icon-globe{background-position:-336px -144px}
451 .icon-wrench{background-position:-360px -144px}
451 .icon-wrench{background-position:-360px -144px}
452 .icon-tasks{background-position:-384px -144px}
452 .icon-tasks{background-position:-384px -144px}
453 .icon-filter{background-position:-408px -144px}
453 .icon-filter{background-position:-408px -144px}
454 .icon-briefcase{background-position:-432px -144px}
454 .icon-briefcase{background-position:-432px -144px}
455 .icon-fullscreen{background-position:-456px -144px}
455 .icon-fullscreen{background-position:-456px -144px}
456 .dropup,.dropdown{position:relative}
456 .dropup,.dropdown{position:relative}
457 .dropdown-toggle{*margin-bottom:-3px}
457 .dropdown-toggle{*margin-bottom:-3px}
458 .dropdown-toggle:active,.open .dropdown-toggle{outline:0}
458 .dropdown-toggle:active,.open .dropdown-toggle{outline:0}
459 .caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}
459 .caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}
460 .dropdown .caret{margin-top:8px;margin-left:2px}
460 .dropdown .caret{margin-top:8px;margin-left:2px}
461 .dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}
461 .dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}
462 .dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}
462 .dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}
463 .dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}
463 .dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}
464 .dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top, #08c, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0077b3));background-image:-webkit-linear-gradient(top, #08c, #0077b3);background-image:-o-linear-gradient(top, #08c, #0077b3);background-image:linear-gradient(to bottom, #08c, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)}
464 .dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top, #08c, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0077b3));background-image:-webkit-linear-gradient(top, #08c, #0077b3);background-image:-o-linear-gradient(top, #08c, #0077b3);background-image:linear-gradient(to bottom, #08c, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)}
465 .dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#0081c2;background-image:-moz-linear-gradient(top, #08c, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0077b3));background-image:-webkit-linear-gradient(top, #08c, #0077b3);background-image:-o-linear-gradient(top, #08c, #0077b3);background-image:linear-gradient(to bottom, #08c, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)}
465 .dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#0081c2;background-image:-moz-linear-gradient(top, #08c, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0077b3));background-image:-webkit-linear-gradient(top, #08c, #0077b3);background-image:-o-linear-gradient(top, #08c, #0077b3);background-image:linear-gradient(to bottom, #08c, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)}
466 .dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}
466 .dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}
467 .dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:default}
467 .dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:default}
468 .open{*z-index:1000}.open>.dropdown-menu{display:block}
468 .open{*z-index:1000}.open>.dropdown-menu{display:block}
469 .dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}
469 .dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}
470 .pull-right>.dropdown-menu{right:0;left:auto}
470 .pull-right>.dropdown-menu{right:0;left:auto}
471 .dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}
471 .dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}
472 .dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}
472 .dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}
473 .dropdown-submenu{position:relative}
473 .dropdown-submenu{position:relative}
474 .dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;border-radius:0 6px 6px 6px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}
474 .dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;border-radius:0 6px 6px 6px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}
475 .dropdown-submenu:hover>.dropdown-menu{display:block}
475 .dropdown-submenu:hover>.dropdown-menu{display:block}
476 .dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;border-radius:5px 5px 5px 0;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}
476 .dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;border-radius:5px 5px 5px 0;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}
477 .dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#ccc;margin-top:5px;margin-right:-10px}
477 .dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#ccc;margin-top:5px;margin-right:-10px}
478 .dropdown-submenu:hover>a:after{border-left-color:#fff}
478 .dropdown-submenu:hover>a:after{border-left-color:#fff}
479 .dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;border-radius:6px 0 6px 6px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}
479 .dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;border-radius:6px 0 6px 6px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}
480 .dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px}
480 .dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px}
481 .typeahead{z-index:1051;margin-top:2px;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
481 .typeahead{z-index:1051;margin-top:2px;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
482 .well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}
482 .well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}
483 .well-large{padding:24px;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
483 .well-large{padding:24px;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
484 .well-small{padding:9px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
484 .well-small{padding:9px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
485 .fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}
485 .fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}
486 .collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}
486 .collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}
487 .close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}
487 .close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}
488 button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}
488 button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}
489 .btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:13px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #fff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #fff, #e6e6e6);background-image:-o-linear-gradient(top, #fff, #e6e6e6);background-image:linear-gradient(to bottom, #fff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}
489 .btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:13px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #fff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #fff, #e6e6e6);background-image:-o-linear-gradient(top, #fff, #e6e6e6);background-image:linear-gradient(to bottom, #fff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}
490 .btn:active,.btn.active{background-color:#ccc \9}
490 .btn:active,.btn.active{background-color:#ccc \9}
491 .btn:first-child{*margin-left:0}
491 .btn:first-child{*margin-left:0}
492 .btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}
492 .btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}
493 .btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}
493 .btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}
494 .btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)}
494 .btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)}
495 .btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}
495 .btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}
496 .btn-large{padding:11px 19px;font-size:16.25px;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
496 .btn-large{padding:11px 19px;font-size:16.25px;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
497 .btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}
497 .btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}
498 .btn-small{padding:2px 10px;font-size:11.049999999999999px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
498 .btn-small{padding:2px 10px;font-size:11.049999999999999px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
499 .btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}
499 .btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}
500 .btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}
500 .btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}
501 .btn-mini{padding:0 6px;font-size:9.75px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
501 .btn-mini{padding:0 6px;font-size:9.75px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
502 .btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
502 .btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
503 .btn-block+.btn-block{margin-top:5px}
503 .btn-block+.btn-block{margin-top:5px}
504 input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}
504 input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}
505 .btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}
505 .btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}
506 .btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #08c, #04c);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#04c));background-image:-webkit-linear-gradient(top, #08c, #04c);background-image:-o-linear-gradient(top, #08c, #04c);background-image:linear-gradient(to bottom, #08c, #04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}
506 .btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #08c, #04c);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#04c));background-image:-webkit-linear-gradient(top, #08c, #04c);background-image:-o-linear-gradient(top, #08c, #04c);background-image:linear-gradient(to bottom, #08c, #04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}
507 .btn-primary:active,.btn-primary.active{background-color:#039 \9}
507 .btn-primary:active,.btn-primary.active{background-color:#039 \9}
508 .btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}
508 .btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}
509 .btn-warning:active,.btn-warning.active{background-color:#c67605 \9}
509 .btn-warning:active,.btn-warning.active{background-color:#c67605 \9}
510 .btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}
510 .btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}
511 .btn-danger:active,.btn-danger.active{background-color:#942a25 \9}
511 .btn-danger:active,.btn-danger.active{background-color:#942a25 \9}
512 .btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}
512 .btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}
513 .btn-success:active,.btn-success.active{background-color:#408140 \9}
513 .btn-success:active,.btn-success.active{background-color:#408140 \9}
514 .btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}
514 .btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}
515 .btn-info:active,.btn-info.active{background-color:#24748c \9}
515 .btn-info:active,.btn-info.active{background-color:#24748c \9}
516 .btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;background-image:-moz-linear-gradient(top, #444, #222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444), to(#222));background-image:-webkit-linear-gradient(top, #444, #222);background-image:-o-linear-gradient(top, #444, #222);background-image:linear-gradient(to bottom, #444, #222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}
516 .btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;background-image:-moz-linear-gradient(top, #444, #222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444), to(#222));background-image:-webkit-linear-gradient(top, #444, #222);background-image:-o-linear-gradient(top, #444, #222);background-image:linear-gradient(to bottom, #444, #222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}
517 .btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}
517 .btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}
518 button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}
518 button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}
519 button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}
519 button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}
520 button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}
520 button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}
521 button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}
521 button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}
522 .btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}
522 .btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}
523 .btn-link{border-color:transparent;cursor:pointer;color:#08c;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
523 .btn-link{border-color:transparent;cursor:pointer;color:#08c;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
524 .btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}
524 .btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}
525 .btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}
525 .btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}
526 .btn-group{position:relative;display:inline-block;*display:inline;*zoom:1;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em}.btn-group:first-child{*margin-left:0}
526 .btn-group{position:relative;display:inline-block;*display:inline;*zoom:1;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em}.btn-group:first-child{*margin-left:0}
527 .btn-group+.btn-group{margin-left:5px}
527 .btn-group+.btn-group{margin-left:5px}
528 .btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}
528 .btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}
529 .btn-group>.btn{position:relative;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
529 .btn-group>.btn{position:relative;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
530 .btn-group>.btn+.btn{margin-left:-1px}
530 .btn-group>.btn+.btn{margin-left:-1px}
531 .btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:13px}
531 .btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:13px}
532 .btn-group>.btn-mini{font-size:9.75px}
532 .btn-group>.btn-mini{font-size:9.75px}
533 .btn-group>.btn-small{font-size:11.049999999999999px}
533 .btn-group>.btn-small{font-size:11.049999999999999px}
534 .btn-group>.btn-large{font-size:16.25px}
534 .btn-group>.btn-large{font-size:16.25px}
535 .btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
535 .btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
536 .btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}
536 .btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}
537 .btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px}
537 .btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px}
538 .btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px}
538 .btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px}
539 .btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}
539 .btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}
540 .btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}
540 .btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}
541 .btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:5px;*padding-bottom:5px}
541 .btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:5px;*padding-bottom:5px}
542 .btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px}
542 .btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px}
543 .btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}
543 .btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}
544 .btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px}
544 .btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px}
545 .btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)}
545 .btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)}
546 .btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}
546 .btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}
547 .btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}
547 .btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}
548 .btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}
548 .btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}
549 .btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}
549 .btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}
550 .btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}
550 .btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}
551 .btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}
551 .btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}
552 .btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}
552 .btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}
553 .btn .caret{margin-top:8px;margin-left:0}
553 .btn .caret{margin-top:8px;margin-left:0}
554 .btn-large .caret{margin-top:6px}
554 .btn-large .caret{margin-top:6px}
555 .btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px}
555 .btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px}
556 .btn-mini .caret,.btn-small .caret{margin-top:8px}
556 .btn-mini .caret,.btn-small .caret{margin-top:8px}
557 .dropup .btn-large .caret{border-bottom-width:5px}
557 .dropup .btn-large .caret{border-bottom-width:5px}
558 .btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}
558 .btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}
559 .btn-group-vertical{display:inline-block;*display:inline;*zoom:1}
559 .btn-group-vertical{display:inline-block;*display:inline;*zoom:1}
560 .btn-group-vertical>.btn{display:block;float:none;max-width:100%;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
560 .btn-group-vertical>.btn{display:block;float:none;max-width:100%;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
561 .btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px}
561 .btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px}
562 .btn-group-vertical>.btn:first-child{border-radius:4px 4px 0 0;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}
562 .btn-group-vertical>.btn:first-child{border-radius:4px 4px 0 0;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}
563 .btn-group-vertical>.btn:last-child{border-radius:0 0 4px 4px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}
563 .btn-group-vertical>.btn:last-child{border-radius:0 0 4px 4px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}
564 .btn-group-vertical>.btn-large:first-child{border-radius:6px 6px 0 0;-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}
564 .btn-group-vertical>.btn-large:first-child{border-radius:6px 6px 0 0;-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}
565 .btn-group-vertical>.btn-large:last-child{border-radius:0 0 6px 6px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}
565 .btn-group-vertical>.btn-large:last-child{border-radius:0 0 6px 6px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}
566 .alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
566 .alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
567 .alert,.alert h4{color:#c09853}
567 .alert,.alert h4{color:#c09853}
568 .alert h4{margin:0}
568 .alert h4{margin:0}
569 .alert .close{position:relative;top:-2px;right:-21px;line-height:20px}
569 .alert .close{position:relative;top:-2px;right:-21px;line-height:20px}
570 .alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847}
570 .alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847}
571 .alert-success h4{color:#468847}
571 .alert-success h4{color:#468847}
572 .alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48}
572 .alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48}
573 .alert-danger h4,.alert-error h4{color:#b94a48}
573 .alert-danger h4,.alert-error h4{color:#b94a48}
574 .alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}
574 .alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}
575 .alert-info h4{color:#3a87ad}
575 .alert-info h4{color:#3a87ad}
576 .alert-block{padding-top:14px;padding-bottom:14px}
576 .alert-block{padding-top:14px;padding-bottom:14px}
577 .alert-block>p,.alert-block>ul{margin-bottom:0}
577 .alert-block>p,.alert-block>ul{margin-bottom:0}
578 .alert-block p+p{margin-top:5px}
578 .alert-block p+p{margin-top:5px}
579 .nav{margin-left:0;margin-bottom:20px;list-style:none}
579 .nav{margin-left:0;margin-bottom:20px;list-style:none}
580 .nav>li>a{display:block}
580 .nav>li>a{display:block}
581 .nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}
581 .nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}
582 .nav>li>a>img{max-width:none}
582 .nav>li>a>img{max-width:none}
583 .nav>.pull-right{float:right}
583 .nav>.pull-right{float:right}
584 .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}
584 .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}
585 .nav li+.nav-header{margin-top:9px}
585 .nav li+.nav-header{margin-top:9px}
586 .nav-list{padding-left:15px;padding-right:15px;margin-bottom:0}
586 .nav-list{padding-left:15px;padding-right:15px;margin-bottom:0}
587 .nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}
587 .nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}
588 .nav-list>li>a{padding:3px 15px}
588 .nav-list>li>a{padding:3px 15px}
589 .nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}
589 .nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}
590 .nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}
590 .nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}
591 .nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}
591 .nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}
592 .nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0}
592 .nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0}
593 .nav-tabs:after,.nav-pills:after{clear:both}
593 .nav-tabs:after,.nav-pills:after{clear:both}
594 .nav-tabs>li,.nav-pills>li{float:left}
594 .nav-tabs>li,.nav-pills>li{float:left}
595 .nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}
595 .nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}
596 .nav-tabs{border-bottom:1px solid #ddd}
596 .nav-tabs{border-bottom:1px solid #ddd}
597 .nav-tabs>li{margin-bottom:-1px}
597 .nav-tabs>li{margin-bottom:-1px}
598 .nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;border-radius:4px 4px 0 0;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}
598 .nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;border-radius:4px 4px 0 0;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}
599 .nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}
599 .nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}
600 .nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}
600 .nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}
601 .nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}
601 .nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}
602 .nav-stacked>li{float:none}
602 .nav-stacked>li{float:none}
603 .nav-stacked>li>a{margin-right:0}
603 .nav-stacked>li>a{margin-right:0}
604 .nav-tabs.nav-stacked{border-bottom:0}
604 .nav-tabs.nav-stacked{border-bottom:0}
605 .nav-tabs.nav-stacked>li>a{border:1px solid #ddd;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
605 .nav-tabs.nav-stacked>li>a{border:1px solid #ddd;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
606 .nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}
606 .nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}
607 .nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
607 .nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
608 .nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{border-color:#ddd;z-index:2}
608 .nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{border-color:#ddd;z-index:2}
609 .nav-pills.nav-stacked>li>a{margin-bottom:3px}
609 .nav-pills.nav-stacked>li>a{margin-bottom:3px}
610 .nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}
610 .nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}
611 .nav-tabs .dropdown-menu{border-radius:0 0 6px 6px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}
611 .nav-tabs .dropdown-menu{border-radius:0 0 6px 6px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}
612 .nav-pills .dropdown-menu{border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
612 .nav-pills .dropdown-menu{border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
613 .nav .dropdown-toggle .caret{border-top-color:#08c;border-bottom-color:#08c;margin-top:6px}
613 .nav .dropdown-toggle .caret{border-top-color:#08c;border-bottom-color:#08c;margin-top:6px}
614 .nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}
614 .nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}
615 .nav-tabs .dropdown-toggle .caret{margin-top:8px}
615 .nav-tabs .dropdown-toggle .caret{margin-top:8px}
616 .nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}
616 .nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}
617 .nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}
617 .nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}
618 .nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}
618 .nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}
619 .nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}
619 .nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}
620 .nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}
620 .nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}
621 .tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}
621 .tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}
622 .tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0}
622 .tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0}
623 .tabbable:after{clear:both}
623 .tabbable:after{clear:both}
624 .tab-content{overflow:auto}
624 .tab-content{overflow:auto}
625 .tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}
625 .tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}
626 .tab-content>.tab-pane,.pill-content>.pill-pane{display:none}
626 .tab-content>.tab-pane,.pill-content>.pill-pane{display:none}
627 .tab-content>.active,.pill-content>.active{display:block}
627 .tab-content>.active,.pill-content>.active{display:block}
628 .tabs-below>.nav-tabs{border-top:1px solid #ddd}
628 .tabs-below>.nav-tabs{border-top:1px solid #ddd}
629 .tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}
629 .tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}
630 .tabs-below>.nav-tabs>li>a{border-radius:0 0 4px 4px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-bottom-color:transparent;border-top-color:#ddd}
630 .tabs-below>.nav-tabs>li>a{border-radius:0 0 4px 4px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-bottom-color:transparent;border-top-color:#ddd}
631 .tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}
631 .tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}
632 .tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}
632 .tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}
633 .tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}
633 .tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}
634 .tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}
634 .tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}
635 .tabs-left>.nav-tabs>li>a{margin-right:-1px;border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}
635 .tabs-left>.nav-tabs>li>a{margin-right:-1px;border-radius:4px 0 0 4px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}
636 .tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}
636 .tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}
637 .tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}
637 .tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}
638 .tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}
638 .tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}
639 .tabs-right>.nav-tabs>li>a{margin-left:-1px;border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
639 .tabs-right>.nav-tabs>li>a{margin-left:-1px;border-radius:0 4px 4px 0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}
640 .tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}
640 .tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}
641 .tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}
641 .tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}
642 .nav>.disabled>a{color:#999}
642 .nav>.disabled>a{color:#999}
643 .nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;background-color:transparent;cursor:default}
643 .nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;background-color:transparent;cursor:default}
644 .navbar{overflow:visible;margin-bottom:20px;*position:relative;*z-index:2}
644 .navbar{overflow:visible;margin-bottom:20px;*position:relative;*z-index:2}
645 .navbar-inner{min-height:36px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #fff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #fff, #f2f2f2);background-image:-o-linear-gradient(top, #fff, #f2f2f2);background-image:linear-gradient(to bottom, #fff, #f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065);*zoom:1}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0}
645 .navbar-inner{min-height:36px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #fff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #fff, #f2f2f2);background-image:-o-linear-gradient(top, #fff, #f2f2f2);background-image:linear-gradient(to bottom, #fff, #f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065);*zoom:1}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0}
646 .navbar-inner:after{clear:both}
646 .navbar-inner:after{clear:both}
647 .navbar .container{width:auto}
647 .navbar .container{width:auto}
648 .nav-collapse.collapse{height:auto;overflow:visible}
648 .nav-collapse.collapse{height:auto;overflow:visible}
649 .navbar .brand{float:left;display:block;padding:8px 20px 8px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}
649 .navbar .brand{float:left;display:block;padding:8px 20px 8px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}
650 .navbar-text{margin-bottom:0;line-height:36px;color:#777}
650 .navbar-text{margin-bottom:0;line-height:36px;color:#777}
651 .navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}
651 .navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}
652 .navbar .divider-vertical{height:36px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #fff}
652 .navbar .divider-vertical{height:36px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #fff}
653 .navbar .btn,.navbar .btn-group{margin-top:3px}
653 .navbar .btn,.navbar .btn-group{margin-top:3px}
654 .navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}
654 .navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}
655 .navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0}
655 .navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0}
656 .navbar-form:after{clear:both}
656 .navbar-form:after{clear:both}
657 .navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:3px}
657 .navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:3px}
658 .navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}
658 .navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}
659 .navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}
659 .navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}
660 .navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}
660 .navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}
661 .navbar-search{position:relative;float:left;margin-top:3px;margin-bottom:0}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;border-radius:15px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}
661 .navbar-search{position:relative;float:left;margin-top:3px;margin-bottom:0}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;border-radius:15px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}
662 .navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
662 .navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
663 .navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}
663 .navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}
664 .navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}
664 .navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}
665 .navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}
665 .navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}
666 .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
666 .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
667 .navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}
667 .navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}
668 .navbar-fixed-top{top:0}
668 .navbar-fixed-top{top:0}
669 .navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1)}
669 .navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1)}
670 .navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,.1);box-shadow:0 -1px 10px rgba(0,0,0,.1)}
670 .navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,.1);box-shadow:0 -1px 10px rgba(0,0,0,.1)}
671 .navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}
671 .navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}
672 .navbar .nav.pull-right{float:right;margin-right:0}
672 .navbar .nav.pull-right{float:right;margin-right:0}
673 .navbar .nav>li{float:left}
673 .navbar .nav>li{float:left}
674 .navbar .nav>li>a{float:none;padding:8px 15px 8px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}
674 .navbar .nav>li>a{float:none;padding:8px 15px 8px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}
675 .navbar .nav .dropdown-toggle .caret{margin-top:8px}
675 .navbar .nav .dropdown-toggle .caret{margin-top:8px}
676 .navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333;text-decoration:none}
676 .navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333;text-decoration:none}
677 .navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}
677 .navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}
678 .navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e5e5e5;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}
678 .navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e5e5e5;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}
679 .navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}
679 .navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}
680 .navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}
680 .navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}
681 .btn-navbar .icon-bar+.icon-bar{margin-top:3px}
681 .btn-navbar .icon-bar+.icon-bar{margin-top:3px}
682 .navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute;top:-7px;left:9px}
682 .navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute;top:-7px;left:9px}
683 .navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:10px}
683 .navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:10px}
684 .navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);border-bottom:0;bottom:-7px;top:auto}
684 .navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);border-bottom:0;bottom:-7px;top:auto}
685 .navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #fff;border-bottom:0;bottom:-6px;top:auto}
685 .navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #fff;border-bottom:0;bottom:-6px;top:auto}
686 .navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}
686 .navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}
687 .navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e5e5e5;color:#555}
687 .navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e5e5e5;color:#555}
688 .navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}
688 .navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}
689 .navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}
689 .navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}
690 .navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px}
690 .navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px}
691 .navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px}
691 .navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px}
692 .navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;border-radius:6px 0 6px 6px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}
692 .navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;border-radius:6px 0 6px 6px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}
693 .navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top, #222, #111);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222), to(#111));background-image:-webkit-linear-gradient(top, #222, #111);background-image:-o-linear-gradient(top, #222, #111);background-image:linear-gradient(to bottom, #222, #111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525}
693 .navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top, #222, #111);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222), to(#111));background-image:-webkit-linear-gradient(top, #222, #111);background-image:-o-linear-gradient(top, #222, #111);background-image:linear-gradient(to bottom, #222, #111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525}
694 .navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}
694 .navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}
695 .navbar-inverse .brand{color:#999}
695 .navbar-inverse .brand{color:#999}
696 .navbar-inverse .navbar-text{color:#999}
696 .navbar-inverse .navbar-text{color:#999}
697 .navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#fff}
697 .navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#fff}
698 .navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}
698 .navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}
699 .navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}
699 .navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}
700 .navbar-inverse .divider-vertical{border-left-color:#111;border-right-color:#222}
700 .navbar-inverse .divider-vertical{border-left-color:#111;border-right-color:#222}
701 .navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111;color:#fff}
701 .navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111;color:#fff}
702 .navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}
702 .navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}
703 .navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}
703 .navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}
704 .navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}
704 .navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}
705 .navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}
705 .navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}
706 .navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}
706 .navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}
707 .navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}
707 .navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}
708 .navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15);outline:0}
708 .navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15);outline:0}
709 .navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}
709 .navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}
710 .navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}
710 .navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}
711 .breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #fff}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}
711 .breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #fff}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}
712 .breadcrumb>.active{color:#999}
712 .breadcrumb>.active{color:#999}
713 .pagination{margin:20px 0}
713 .pagination{margin:20px 0}
714 .pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}
714 .pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}
715 .pagination ul>li{display:inline}
715 .pagination ul>li{display:inline}
716 .pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}
716 .pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}
717 .pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}
717 .pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}
718 .pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}
718 .pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}
719 .pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;background-color:transparent;cursor:default}
719 .pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;background-color:transparent;cursor:default}
720 .pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
720 .pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}
721 .pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}
721 .pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}
722 .pagination-centered{text-align:center}
722 .pagination-centered{text-align:center}
723 .pagination-right{text-align:right}
723 .pagination-right{text-align:right}
724 .pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:16.25px}
724 .pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:16.25px}
725 .pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px}
725 .pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px}
726 .pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px}
726 .pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px}
727 .pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-top-left-radius:3px;-moz-border-radius-topleft:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-moz-border-radius-bottomleft:3px;border-bottom-left-radius:3px}
727 .pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-top-left-radius:3px;-moz-border-radius-topleft:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-moz-border-radius-bottomleft:3px;border-bottom-left-radius:3px}
728 .pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;-moz-border-radius-topright:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-moz-border-radius-bottomright:3px;border-bottom-right-radius:3px}
728 .pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;-moz-border-radius-topright:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-moz-border-radius-bottomright:3px;border-bottom-right-radius:3px}
729 .pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.049999999999999px}
729 .pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.049999999999999px}
730 .pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:9.75px}
730 .pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:9.75px}
731 .pager{margin:20px 0;list-style:none;text-align:center;*zoom:1}.pager:before,.pager:after{display:table;content:"";line-height:0}
731 .pager{margin:20px 0;list-style:none;text-align:center;*zoom:1}.pager:before,.pager:after{display:table;content:"";line-height:0}
732 .pager:after{clear:both}
732 .pager:after{clear:both}
733 .pager li{display:inline}
733 .pager li{display:inline}
734 .pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}
734 .pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}
735 .pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}
735 .pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}
736 .pager .next>a,.pager .next>span{float:right}
736 .pager .next>a,.pager .next>span{float:right}
737 .pager .previous>a,.pager .previous>span{float:left}
737 .pager .previous>a,.pager .previous>span{float:left}
738 .pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:default}
738 .pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:default}
739 .modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}
739 .modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}
740 .modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}
740 .modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}
741 .modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:none}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%}
741 .modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:none}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%}
742 .modal.fade.in{top:10%}
742 .modal.fade.in{top:10%}
743 .modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}
743 .modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}
744 .modal-header h3{margin:0;line-height:30px}
744 .modal-header h3{margin:0;line-height:30px}
745 .modal-body{position:relative;overflow-y:auto;max-height:400px;padding:15px}
745 .modal-body{position:relative;overflow-y:auto;max-height:400px;padding:15px}
746 .modal-form{margin-bottom:0}
746 .modal-form{margin-bottom:0}
747 .modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff;*zoom:1}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0}
747 .modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff;*zoom:1}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0}
748 .modal-footer:after{clear:both}
748 .modal-footer:after{clear:both}
749 .modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}
749 .modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}
750 .modal-footer .btn-group .btn+.btn{margin-left:-1px}
750 .modal-footer .btn-group .btn+.btn{margin-left:-1px}
751 .modal-footer .btn-block+.btn-block{margin-left:0}
751 .modal-footer .btn-block+.btn-block{margin-left:0}
752 .tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}
752 .tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}
753 .tooltip.top{margin-top:-3px;padding:5px 0}
753 .tooltip.top{margin-top:-3px;padding:5px 0}
754 .tooltip.right{margin-left:3px;padding:0 5px}
754 .tooltip.right{margin-left:3px;padding:0 5px}
755 .tooltip.bottom{margin-top:3px;padding:5px 0}
755 .tooltip.bottom{margin-top:3px;padding:5px 0}
756 .tooltip.left{margin-left:-3px;padding:0 5px}
756 .tooltip.left{margin-left:-3px;padding:0 5px}
757 .tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
757 .tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
758 .tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}
758 .tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}
759 .tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}
759 .tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}
760 .tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}
760 .tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}
761 .tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}
761 .tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}
762 .tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}
762 .tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}
763 .popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}
763 .popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}
764 .popover.right{margin-left:10px}
764 .popover.right{margin-left:10px}
765 .popover.bottom{margin-top:10px}
765 .popover.bottom{margin-top:10px}
766 .popover.left{margin-left:-10px}
766 .popover.left{margin-left:-10px}
767 .popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}
767 .popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}
768 .popover-content{padding:9px 14px}
768 .popover-content{padding:9px 14px}
769 .popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}
769 .popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}
770 .popover .arrow{border-width:11px}
770 .popover .arrow{border-width:11px}
771 .popover .arrow:after{border-width:10px;content:""}
771 .popover .arrow:after{border-width:10px;content:""}
772 .popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}
772 .popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}
773 .popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}
773 .popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}
774 .popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}
774 .popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}
775 .popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}
775 .popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}
776 .thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0}
776 .thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0}
777 .thumbnails:after{clear:both}
777 .thumbnails:after{clear:both}
778 .row-fluid .thumbnails{margin-left:0}
778 .row-fluid .thumbnails{margin-left:0}
779 .thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}
779 .thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}
780 .thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}
780 .thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}
781 a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}
781 a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}
782 .thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto}
782 .thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto}
783 .thumbnail .caption{padding:9px;color:#555}
783 .thumbnail .caption{padding:9px;color:#555}
784 .media,.media-body{overflow:hidden;*overflow:visible;zoom:1}
784 .media,.media-body{overflow:hidden;*overflow:visible;zoom:1}
785 .media,.media .media{margin-top:15px}
785 .media,.media .media{margin-top:15px}
786 .media:first-child{margin-top:0}
786 .media:first-child{margin-top:0}
787 .media-object{display:block}
787 .media-object{display:block}
788 .media-heading{margin:0 0 5px}
788 .media-heading{margin:0 0 5px}
789 .media>.pull-left{margin-right:10px}
789 .media>.pull-left{margin-right:10px}
790 .media>.pull-right{margin-left:10px}
790 .media>.pull-right{margin-left:10px}
791 .media-list{margin-left:0;list-style:none}
791 .media-list{margin-left:0;list-style:none}
792 .label,.badge{display:inline-block;padding:2px 4px;font-size:10.998px;font-weight:bold;line-height:14px;color:#fff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#999}
792 .label,.badge{display:inline-block;padding:2px 4px;font-size:10.998px;font-weight:bold;line-height:14px;color:#fff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#999}
793 .label{border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
793 .label{border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
794 .badge{padding-left:9px;padding-right:9px;border-radius:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}
794 .badge{padding-left:9px;padding-right:9px;border-radius:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}
795 .label:empty,.badge:empty{display:none}
795 .label:empty,.badge:empty{display:none}
796 a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}
796 a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}
797 .label-important,.badge-important{background-color:#b94a48}
797 .label-important,.badge-important{background-color:#b94a48}
798 .label-important[href],.badge-important[href]{background-color:#953b39}
798 .label-important[href],.badge-important[href]{background-color:#953b39}
799 .label-warning,.badge-warning{background-color:#f89406}
799 .label-warning,.badge-warning{background-color:#f89406}
800 .label-warning[href],.badge-warning[href]{background-color:#c67605}
800 .label-warning[href],.badge-warning[href]{background-color:#c67605}
801 .label-success,.badge-success{background-color:#468847}
801 .label-success,.badge-success{background-color:#468847}
802 .label-success[href],.badge-success[href]{background-color:#356635}
802 .label-success[href],.badge-success[href]{background-color:#356635}
803 .label-info,.badge-info{background-color:#3a87ad}
803 .label-info,.badge-info{background-color:#3a87ad}
804 .label-info[href],.badge-info[href]{background-color:#2d6987}
804 .label-info[href],.badge-info[href]{background-color:#2d6987}
805 .label-inverse,.badge-inverse{background-color:#333}
805 .label-inverse,.badge-inverse{background-color:#333}
806 .label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}
806 .label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}
807 .btn .label,.btn .badge{position:relative;top:-1px}
807 .btn .label,.btn .badge{position:relative;top:-1px}
808 .btn-mini .label,.btn-mini .badge{top:0}
808 .btn-mini .label,.btn-mini .badge{top:0}
809 @-webkit-keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0} to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
809 @-webkit-keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0} to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0} to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
810 .progress .bar{width:0;height:100%;color:#fff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}
810 .progress .bar{width:0;height:100%;color:#fff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}
811 .progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15)}
811 .progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15)}
812 .progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}
812 .progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}
813 .progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}
813 .progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}
814 .progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0)}
814 .progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0)}
815 .progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
815 .progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
816 .progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0)}
816 .progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0)}
817 .progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
817 .progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
818 .progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0)}
818 .progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0)}
819 .progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
819 .progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
820 .progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0)}
820 .progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0)}
821 .progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
821 .progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,0.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,0.15)), color-stop(.75, rgba(255,255,255,0.15)), color-stop(.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}
822 .accordion{margin-bottom:20px}
822 .accordion{margin-bottom:20px}
823 .accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
823 .accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
824 .accordion-heading{border-bottom:0}
824 .accordion-heading{border-bottom:0}
825 .accordion-heading .accordion-toggle{display:block;padding:8px 15px}
825 .accordion-heading .accordion-toggle{display:block;padding:8px 15px}
826 .accordion-toggle{cursor:pointer}
826 .accordion-toggle{cursor:pointer}
827 .accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}
827 .accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}
828 .carousel{position:relative;margin-bottom:20px;line-height:1}
828 .carousel{position:relative;margin-bottom:20px;line-height:1}
829 .carousel-inner{overflow:hidden;width:100%;position:relative}
829 .carousel-inner{overflow:hidden;width:100%;position:relative}
830 .carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}
830 .carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}
831 .carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}
831 .carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}
832 .carousel-inner>.active{left:0}
832 .carousel-inner>.active{left:0}
833 .carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}
833 .carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}
834 .carousel-inner>.next{left:100%}
834 .carousel-inner>.next{left:100%}
835 .carousel-inner>.prev{left:-100%}
835 .carousel-inner>.prev{left:-100%}
836 .carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}
836 .carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}
837 .carousel-inner>.active.left{left:-100%}
837 .carousel-inner>.active.left{left:-100%}
838 .carousel-inner>.active.right{left:100%}
838 .carousel-inner>.active.right{left:100%}
839 .carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{left:auto;right:15px}
839 .carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{left:auto;right:15px}
840 .carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}
840 .carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}
841 .carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}
841 .carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}
842 .carousel-indicators .active{background-color:#fff}
842 .carousel-indicators .active{background-color:#fff}
843 .carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}
843 .carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}
844 .carousel-caption h4,.carousel-caption p{color:#fff;line-height:20px}
844 .carousel-caption h4,.carousel-caption p{color:#fff;line-height:20px}
845 .carousel-caption h4{margin:0 0 5px}
845 .carousel-caption h4{margin:0 0 5px}
846 .carousel-caption p{margin-bottom:0}
846 .carousel-caption p{margin-bottom:0}
847 .hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px}
847 .hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px}
848 .hero-unit li{line-height:30px}
848 .hero-unit li{line-height:30px}
849 .pull-right{float:right}
849 .pull-right{float:right}
850 .pull-left{float:left}
850 .pull-left{float:left}
851 .hide{display:none}
851 .hide{display:none}
852 .show{display:block}
852 .show{display:block}
853 .invisible{visibility:hidden}
853 .invisible{visibility:hidden}
854 .affix{position:fixed}
854 .affix{position:fixed}
855 .clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}
855 .clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}
856 .clearfix:after{clear:both}
856 .clearfix:after{clear:both}
857 .hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}
857 .hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}
858 .input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
858 .input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
859 @-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}
859 @-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}
860 .visible-phone{display:none !important}
860 .visible-phone{display:none !important}
861 .visible-tablet{display:none !important}
861 .visible-tablet{display:none !important}
862 .hidden-desktop{display:none !important}
862 .hidden-desktop{display:none !important}
863 .visible-desktop{display:inherit !important}
863 .visible-desktop{display:inherit !important}
864 @media (min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit !important} .visible-desktop{display:none !important} .visible-tablet{display:inherit !important} .hidden-tablet{display:none !important}}@media (max-width:767px){.hidden-desktop{display:inherit !important} .visible-desktop{display:none !important} .visible-phone{display:inherit !important} .hidden-phone{display:none !important}}.visible-print{display:none !important}
864 @media (min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit !important} .visible-desktop{display:none !important} .visible-tablet{display:inherit !important} .hidden-tablet{display:none !important}}@media (max-width:767px){.hidden-desktop{display:inherit !important} .visible-desktop{display:none !important} .visible-phone{display:inherit !important} .hidden-phone{display:none !important}}.visible-print{display:none !important}
865 @media print{.visible-print{display:inherit !important} .hidden-print{display:none !important}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0} .row:after{clear:both} [class*="span"]{float:left;min-height:1px;margin-left:30px} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px} .span12{width:1170px} .span11{width:1070px} .span10{width:970px} .span9{width:870px} .span8{width:770px} .span7{width:670px} .span6{width:570px} .span5{width:470px} .span4{width:370px} .span3{width:270px} .span2{width:170px} .span1{width:70px} .offset12{margin-left:1230px} .offset11{margin-left:1130px} .offset10{margin-left:1030px} .offset9{margin-left:930px} .offset8{margin-left:830px} .offset7{margin-left:730px} .offset6{margin-left:630px} .offset5{margin-left:530px} .offset4{margin-left:430px} .offset3{margin-left:330px} .offset2{margin-left:230px} .offset1{margin-left:130px} .row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0} .row-fluid:after{clear:both} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%} .row-fluid [class*="span"]:first-child{margin-left:0} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%} .row-fluid .span12{width:100%;*width:99.94680851063829%} .row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%} .row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%} .row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%} .row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%} .row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%} .row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%} .row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%} .row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%} .row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%} .row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%} .row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%} .row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%} .row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%} .row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%} .row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%} .row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%} .row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%} .row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%} .row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%} .row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%} .row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%} .row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%} .row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%} .row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%} .row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%} .row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%} .row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%} .row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%} .row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%} .row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%} .row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%} .row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%} .row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%} .row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%} .row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%} input,textarea,.uneditable-input{margin-left:0} .controls-row [class*="span"]+[class*="span"]{margin-left:30px} input.span12,textarea.span12,.uneditable-input.span12{width:1156px} input.span11,textarea.span11,.uneditable-input.span11{width:1056px} input.span10,textarea.span10,.uneditable-input.span10{width:956px} input.span9,textarea.span9,.uneditable-input.span9{width:856px} input.span8,textarea.span8,.uneditable-input.span8{width:756px} input.span7,textarea.span7,.uneditable-input.span7{width:656px} input.span6,textarea.span6,.uneditable-input.span6{width:556px} input.span5,textarea.span5,.uneditable-input.span5{width:456px} input.span4,textarea.span4,.uneditable-input.span4{width:356px} input.span3,textarea.span3,.uneditable-input.span3{width:256px} input.span2,textarea.span2,.uneditable-input.span2{width:156px} input.span1,textarea.span1,.uneditable-input.span1{width:56px} .thumbnails{margin-left:-30px} .thumbnails>li{margin-left:30px} .row-fluid .thumbnails{margin-left:0}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0} .row:after{clear:both} [class*="span"]{float:left;min-height:1px;margin-left:20px} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px} .span12{width:724px} .span11{width:662px} .span10{width:600px} .span9{width:538px} .span8{width:476px} .span7{width:414px} .span6{width:352px} .span5{width:290px} .span4{width:228px} .span3{width:166px} .span2{width:104px} .span1{width:42px} .offset12{margin-left:764px} .offset11{margin-left:702px} .offset10{margin-left:640px} .offset9{margin-left:578px} .offset8{margin-left:516px} .offset7{margin-left:454px} .offset6{margin-left:392px} .offset5{margin-left:330px} .offset4{margin-left:268px} .offset3{margin-left:206px} .offset2{margin-left:144px} .offset1{margin-left:82px} .row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0} .row-fluid:after{clear:both} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%} .row-fluid [class*="span"]:first-child{margin-left:0} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%} .row-fluid .span12{width:100%;*width:99.94680851063829%} .row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%} .row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%} .row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%} .row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%} .row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%} .row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%} .row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%} .row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%} .row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%} .row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%} .row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%} .row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%} .row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%} .row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%} .row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%} .row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%} .row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%} .row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%} .row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%} .row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%} .row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%} .row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%} .row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%} .row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%} .row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%} .row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%} .row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%} .row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%} .row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%} .row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%} .row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%} .row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%} .row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%} .row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%} .row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%} input,textarea,.uneditable-input{margin-left:0} .controls-row [class*="span"]+[class*="span"]{margin-left:20px} input.span12,textarea.span12,.uneditable-input.span12{width:710px} input.span11,textarea.span11,.uneditable-input.span11{width:648px} input.span10,textarea.span10,.uneditable-input.span10{width:586px} input.span9,textarea.span9,.uneditable-input.span9{width:524px} input.span8,textarea.span8,.uneditable-input.span8{width:462px} input.span7,textarea.span7,.uneditable-input.span7{width:400px} input.span6,textarea.span6,.uneditable-input.span6{width:338px} input.span5,textarea.span5,.uneditable-input.span5{width:276px} input.span4,textarea.span4,.uneditable-input.span4{width:214px} input.span3,textarea.span3,.uneditable-input.span3{width:152px} input.span2,textarea.span2,.uneditable-input.span2{width:90px} input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media (max-width:767px){body{padding-left:20px;padding-right:20px} .navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px} .container-fluid{padding:0} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left} .dl-horizontal dd{margin-left:0} .container{width:auto} .row-fluid{width:100%} .row,.thumbnails{margin-left:0} .thumbnails>li{float:none;margin-left:0} [class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} .span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} .row-fluid [class*="offset"]:first-child{margin-left:0} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto} .controls-row [class*="span"]+[class*="span"]{margin-left:0} .modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0}.modal.fade{top:-100px} .modal.fade.in{top:20px}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0)} .page-header h1 small{display:block;line-height:20px} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc} .form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left} .form-horizontal .controls{margin-left:0} .form-horizontal .control-list{padding-top:0} .form-horizontal .form-actions{padding-left:10px;padding-right:10px} .media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px} .media-object{margin-right:0;margin-left:0} .modal{top:10px;left:10px;right:10px} .modal-header .close{padding:10px;margin:-10px} .carousel-caption{position:static}}@media (max-width:979px){body{padding-top:0} .navbar-fixed-top,.navbar-fixed-bottom{position:static} .navbar-fixed-top{margin-bottom:20px} .navbar-fixed-bottom{margin-top:20px} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px} .navbar .container{width:auto;padding:0} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px} .nav-collapse{clear:both} .nav-collapse .nav{float:none;margin:0 0 10px} .nav-collapse .nav>li{float:none} .nav-collapse .nav>li>a{margin-bottom:2px} .nav-collapse .nav>.divider-vertical{display:none} .nav-collapse .nav .nav-header{color:#777;text-shadow:none} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px} .nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2} .navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999} .navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111} .nav-collapse.in .btn-group{margin-top:5px;padding:0} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none} .nav-collapse .open>.dropdown-menu{display:block} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none} .nav-collapse .dropdown-menu .divider{display:none} .nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)} .navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0} .navbar .btn-navbar{display:block} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px}}@media (min-width:979px + 1){.nav-collapse.collapse{height:auto !important;overflow:visible !important}}@font-face{font-family:'FontAwesome';src:url('../components/font-awesome/font/fontawesome-webfont.eot?v=3.2.1');src:url('../components/font-awesome/font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../components/font-awesome/font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../components/font-awesome/font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../components/font-awesome/font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em}
865 @media print{.visible-print{display:inherit !important} .hidden-print{display:none !important}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0} .row:after{clear:both} [class*="span"]{float:left;min-height:1px;margin-left:30px} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px} .span12{width:1170px} .span11{width:1070px} .span10{width:970px} .span9{width:870px} .span8{width:770px} .span7{width:670px} .span6{width:570px} .span5{width:470px} .span4{width:370px} .span3{width:270px} .span2{width:170px} .span1{width:70px} .offset12{margin-left:1230px} .offset11{margin-left:1130px} .offset10{margin-left:1030px} .offset9{margin-left:930px} .offset8{margin-left:830px} .offset7{margin-left:730px} .offset6{margin-left:630px} .offset5{margin-left:530px} .offset4{margin-left:430px} .offset3{margin-left:330px} .offset2{margin-left:230px} .offset1{margin-left:130px} .row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0} .row-fluid:after{clear:both} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%} .row-fluid [class*="span"]:first-child{margin-left:0} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%} .row-fluid .span12{width:100%;*width:99.94680851063829%} .row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%} .row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%} .row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%} .row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%} .row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%} .row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%} .row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%} .row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%} .row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%} .row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%} .row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%} .row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%} .row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%} .row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%} .row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%} .row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%} .row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%} .row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%} .row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%} .row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%} .row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%} .row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%} .row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%} .row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%} .row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%} .row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%} .row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%} .row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%} .row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%} .row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%} .row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%} .row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%} .row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%} .row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%} .row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%} input,textarea,.uneditable-input{margin-left:0} .controls-row [class*="span"]+[class*="span"]{margin-left:30px} input.span12,textarea.span12,.uneditable-input.span12{width:1156px} input.span11,textarea.span11,.uneditable-input.span11{width:1056px} input.span10,textarea.span10,.uneditable-input.span10{width:956px} input.span9,textarea.span9,.uneditable-input.span9{width:856px} input.span8,textarea.span8,.uneditable-input.span8{width:756px} input.span7,textarea.span7,.uneditable-input.span7{width:656px} input.span6,textarea.span6,.uneditable-input.span6{width:556px} input.span5,textarea.span5,.uneditable-input.span5{width:456px} input.span4,textarea.span4,.uneditable-input.span4{width:356px} input.span3,textarea.span3,.uneditable-input.span3{width:256px} input.span2,textarea.span2,.uneditable-input.span2{width:156px} input.span1,textarea.span1,.uneditable-input.span1{width:56px} .thumbnails{margin-left:-30px} .thumbnails>li{margin-left:30px} .row-fluid .thumbnails{margin-left:0}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0} .row:after{clear:both} [class*="span"]{float:left;min-height:1px;margin-left:20px} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px} .span12{width:724px} .span11{width:662px} .span10{width:600px} .span9{width:538px} .span8{width:476px} .span7{width:414px} .span6{width:352px} .span5{width:290px} .span4{width:228px} .span3{width:166px} .span2{width:104px} .span1{width:42px} .offset12{margin-left:764px} .offset11{margin-left:702px} .offset10{margin-left:640px} .offset9{margin-left:578px} .offset8{margin-left:516px} .offset7{margin-left:454px} .offset6{margin-left:392px} .offset5{margin-left:330px} .offset4{margin-left:268px} .offset3{margin-left:206px} .offset2{margin-left:144px} .offset1{margin-left:82px} .row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0} .row-fluid:after{clear:both} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%} .row-fluid [class*="span"]:first-child{margin-left:0} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%} .row-fluid .span12{width:100%;*width:99.94680851063829%} .row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%} .row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%} .row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%} .row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%} .row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%} .row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%} .row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%} .row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%} .row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%} .row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%} .row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%} .row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%} .row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%} .row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%} .row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%} .row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%} .row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%} .row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%} .row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%} .row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%} .row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%} .row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%} .row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%} .row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%} .row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%} .row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%} .row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%} .row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%} .row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%} .row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%} .row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%} .row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%} .row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%} .row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%} .row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%} input,textarea,.uneditable-input{margin-left:0} .controls-row [class*="span"]+[class*="span"]{margin-left:20px} input.span12,textarea.span12,.uneditable-input.span12{width:710px} input.span11,textarea.span11,.uneditable-input.span11{width:648px} input.span10,textarea.span10,.uneditable-input.span10{width:586px} input.span9,textarea.span9,.uneditable-input.span9{width:524px} input.span8,textarea.span8,.uneditable-input.span8{width:462px} input.span7,textarea.span7,.uneditable-input.span7{width:400px} input.span6,textarea.span6,.uneditable-input.span6{width:338px} input.span5,textarea.span5,.uneditable-input.span5{width:276px} input.span4,textarea.span4,.uneditable-input.span4{width:214px} input.span3,textarea.span3,.uneditable-input.span3{width:152px} input.span2,textarea.span2,.uneditable-input.span2{width:90px} input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media (max-width:767px){body{padding-left:20px;padding-right:20px} .navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px} .container-fluid{padding:0} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left} .dl-horizontal dd{margin-left:0} .container{width:auto} .row-fluid{width:100%} .row,.thumbnails{margin-left:0} .thumbnails>li{float:none;margin-left:0} [class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} .span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} .row-fluid [class*="offset"]:first-child{margin-left:0} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto} .controls-row [class*="span"]+[class*="span"]{margin-left:0} .modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0}.modal.fade{top:-100px} .modal.fade.in{top:20px}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0)} .page-header h1 small{display:block;line-height:20px} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc} .form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left} .form-horizontal .controls{margin-left:0} .form-horizontal .control-list{padding-top:0} .form-horizontal .form-actions{padding-left:10px;padding-right:10px} .media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px} .media-object{margin-right:0;margin-left:0} .modal{top:10px;left:10px;right:10px} .modal-header .close{padding:10px;margin:-10px} .carousel-caption{position:static}}@media (max-width:979px){body{padding-top:0} .navbar-fixed-top,.navbar-fixed-bottom{position:static} .navbar-fixed-top{margin-bottom:20px} .navbar-fixed-bottom{margin-top:20px} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px} .navbar .container{width:auto;padding:0} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px} .nav-collapse{clear:both} .nav-collapse .nav{float:none;margin:0 0 10px} .nav-collapse .nav>li{float:none} .nav-collapse .nav>li>a{margin-bottom:2px} .nav-collapse .nav>.divider-vertical{display:none} .nav-collapse .nav .nav-header{color:#777;text-shadow:none} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px} .nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2} .navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999} .navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111} .nav-collapse.in .btn-group{margin-top:5px;padding:0} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none} .nav-collapse .open>.dropdown-menu{display:block} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none} .nav-collapse .dropdown-menu .divider{display:none} .nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)} .navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0} .navbar .btn-navbar{display:block} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px}}@media (min-width:979px + 1){.nav-collapse.collapse{height:auto !important;overflow:visible !important}}@font-face{font-family:'FontAwesome';src:url('../components/font-awesome/font/fontawesome-webfont.eot?v=3.2.1');src:url('../components/font-awesome/font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../components/font-awesome/font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../components/font-awesome/font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../components/font-awesome/font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em}
866 [class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}
866 [class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}
867 .icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}
867 .icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}
868 a [class^="icon-"],a [class*=" icon-"]{display:inline}
868 a [class^="icon-"],a [class*=" icon-"]{display:inline}
869 [class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:.2857142857142857em}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em}
869 [class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:.2857142857142857em}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em}
870 .icons-ul{margin-left:2.142857142857143em;list-style-type:none}.icons-ul>li{position:relative}
870 .icons-ul{margin-left:2.142857142857143em;list-style-type:none}.icons-ul>li{position:relative}
871 .icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit}
871 .icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit}
872 [class^="icon-"].hide,[class*=" icon-"].hide{display:none}
872 [class^="icon-"].hide,[class*=" icon-"].hide{display:none}
873 .icon-muted{color:#eee}
873 .icon-muted{color:#eee}
874 .icon-light{color:#fff}
874 .icon-light{color:#fff}
875 .icon-dark{color:#333}
875 .icon-dark{color:#333}
876 .icon-border{border:solid 1px #eee;padding:.2em .25em .15em;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
876 .icon-border{border:solid 1px #eee;padding:.2em .25em .15em;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
877 .icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
877 .icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
878 .icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}
878 .icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}
879 .icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
879 .icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;border-radius:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}
880 .icon-5x{font-size:5em}.icon-5x.icon-border{border-width:5px;border-radius:7px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}
880 .icon-5x{font-size:5em}.icon-5x.icon-border{border-width:5px;border-radius:7px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}
881 .pull-right{float:right}
881 .pull-right{float:right}
882 .pull-left{float:left}
882 .pull-left{float:left}
883 [class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}
883 [class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}
884 [class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}
884 [class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}
885 [class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}
885 [class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}
886 .icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}
886 .icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}
887 .btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}
887 .btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}
888 .btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}
888 .btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}
889 .nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}
889 .nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}
890 .btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}
890 .btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}
891 .btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}
891 .btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}
892 .btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}
892 .btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}
893 .btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}
893 .btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}
894 .btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}
894 .btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}
895 .btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}
895 .btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}
896 .nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit}
896 .nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit}
897 .icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em}
897 .icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em}
898 .icon-stack .icon-stack-base{font-size:2em;*line-height:1em}
898 .icon-stack .icon-stack-base{font-size:2em;*line-height:1em}
899 .icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}
899 .icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}
900 a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none}
900 a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none}
901 @-moz-keyframes spin{0%{-moz-transform:rotate(0deg)} 100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)} 100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)} 100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)} 100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)} 100%{transform:rotate(359deg)}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1)}
901 @-moz-keyframes spin{0%{-moz-transform:rotate(0deg)} 100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)} 100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)} 100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)} 100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)} 100%{transform:rotate(359deg)}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1)}
902 .icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2)}
902 .icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2)}
903 .icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3)}
903 .icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3)}
904 .icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}
904 .icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}
905 .icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}
905 .icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}
906 a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block}
906 a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block}
907 .icon-glass:before{content:"\f000"}
907 .icon-glass:before{content:"\f000"}
908 .icon-music:before{content:"\f001"}
908 .icon-music:before{content:"\f001"}
909 .icon-search:before{content:"\f002"}
909 .icon-search:before{content:"\f002"}
910 .icon-envelope-alt:before{content:"\f003"}
910 .icon-envelope-alt:before{content:"\f003"}
911 .icon-heart:before{content:"\f004"}
911 .icon-heart:before{content:"\f004"}
912 .icon-star:before{content:"\f005"}
912 .icon-star:before{content:"\f005"}
913 .icon-star-empty:before{content:"\f006"}
913 .icon-star-empty:before{content:"\f006"}
914 .icon-user:before{content:"\f007"}
914 .icon-user:before{content:"\f007"}
915 .icon-film:before{content:"\f008"}
915 .icon-film:before{content:"\f008"}
916 .icon-th-large:before{content:"\f009"}
916 .icon-th-large:before{content:"\f009"}
917 .icon-th:before{content:"\f00a"}
917 .icon-th:before{content:"\f00a"}
918 .icon-th-list:before{content:"\f00b"}
918 .icon-th-list:before{content:"\f00b"}
919 .icon-ok:before{content:"\f00c"}
919 .icon-ok:before{content:"\f00c"}
920 .icon-remove:before{content:"\f00d"}
920 .icon-remove:before{content:"\f00d"}
921 .icon-zoom-in:before{content:"\f00e"}
921 .icon-zoom-in:before{content:"\f00e"}
922 .icon-zoom-out:before{content:"\f010"}
922 .icon-zoom-out:before{content:"\f010"}
923 .icon-power-off:before,.icon-off:before{content:"\f011"}
923 .icon-power-off:before,.icon-off:before{content:"\f011"}
924 .icon-signal:before{content:"\f012"}
924 .icon-signal:before{content:"\f012"}
925 .icon-gear:before,.icon-cog:before{content:"\f013"}
925 .icon-gear:before,.icon-cog:before{content:"\f013"}
926 .icon-trash:before{content:"\f014"}
926 .icon-trash:before{content:"\f014"}
927 .icon-home:before{content:"\f015"}
927 .icon-home:before{content:"\f015"}
928 .icon-file-alt:before{content:"\f016"}
928 .icon-file-alt:before{content:"\f016"}
929 .icon-time:before{content:"\f017"}
929 .icon-time:before{content:"\f017"}
930 .icon-road:before{content:"\f018"}
930 .icon-road:before{content:"\f018"}
931 .icon-download-alt:before{content:"\f019"}
931 .icon-download-alt:before{content:"\f019"}
932 .icon-download:before{content:"\f01a"}
932 .icon-download:before{content:"\f01a"}
933 .icon-upload:before{content:"\f01b"}
933 .icon-upload:before{content:"\f01b"}
934 .icon-inbox:before{content:"\f01c"}
934 .icon-inbox:before{content:"\f01c"}
935 .icon-play-circle:before{content:"\f01d"}
935 .icon-play-circle:before{content:"\f01d"}
936 .icon-rotate-right:before,.icon-repeat:before{content:"\f01e"}
936 .icon-rotate-right:before,.icon-repeat:before{content:"\f01e"}
937 .icon-refresh:before{content:"\f021"}
937 .icon-refresh:before{content:"\f021"}
938 .icon-list-alt:before{content:"\f022"}
938 .icon-list-alt:before{content:"\f022"}
939 .icon-lock:before{content:"\f023"}
939 .icon-lock:before{content:"\f023"}
940 .icon-flag:before{content:"\f024"}
940 .icon-flag:before{content:"\f024"}
941 .icon-headphones:before{content:"\f025"}
941 .icon-headphones:before{content:"\f025"}
942 .icon-volume-off:before{content:"\f026"}
942 .icon-volume-off:before{content:"\f026"}
943 .icon-volume-down:before{content:"\f027"}
943 .icon-volume-down:before{content:"\f027"}
944 .icon-volume-up:before{content:"\f028"}
944 .icon-volume-up:before{content:"\f028"}
945 .icon-qrcode:before{content:"\f029"}
945 .icon-qrcode:before{content:"\f029"}
946 .icon-barcode:before{content:"\f02a"}
946 .icon-barcode:before{content:"\f02a"}
947 .icon-tag:before{content:"\f02b"}
947 .icon-tag:before{content:"\f02b"}
948 .icon-tags:before{content:"\f02c"}
948 .icon-tags:before{content:"\f02c"}
949 .icon-book:before{content:"\f02d"}
949 .icon-book:before{content:"\f02d"}
950 .icon-bookmark:before{content:"\f02e"}
950 .icon-bookmark:before{content:"\f02e"}
951 .icon-print:before{content:"\f02f"}
951 .icon-print:before{content:"\f02f"}
952 .icon-camera:before{content:"\f030"}
952 .icon-camera:before{content:"\f030"}
953 .icon-font:before{content:"\f031"}
953 .icon-font:before{content:"\f031"}
954 .icon-bold:before{content:"\f032"}
954 .icon-bold:before{content:"\f032"}
955 .icon-italic:before{content:"\f033"}
955 .icon-italic:before{content:"\f033"}
956 .icon-text-height:before{content:"\f034"}
956 .icon-text-height:before{content:"\f034"}
957 .icon-text-width:before{content:"\f035"}
957 .icon-text-width:before{content:"\f035"}
958 .icon-align-left:before{content:"\f036"}
958 .icon-align-left:before{content:"\f036"}
959 .icon-align-center:before{content:"\f037"}
959 .icon-align-center:before{content:"\f037"}
960 .icon-align-right:before{content:"\f038"}
960 .icon-align-right:before{content:"\f038"}
961 .icon-align-justify:before{content:"\f039"}
961 .icon-align-justify:before{content:"\f039"}
962 .icon-list:before{content:"\f03a"}
962 .icon-list:before{content:"\f03a"}
963 .icon-indent-left:before{content:"\f03b"}
963 .icon-indent-left:before{content:"\f03b"}
964 .icon-indent-right:before{content:"\f03c"}
964 .icon-indent-right:before{content:"\f03c"}
965 .icon-facetime-video:before{content:"\f03d"}
965 .icon-facetime-video:before{content:"\f03d"}
966 .icon-picture:before{content:"\f03e"}
966 .icon-picture:before{content:"\f03e"}
967 .icon-pencil:before{content:"\f040"}
967 .icon-pencil:before{content:"\f040"}
968 .icon-map-marker:before{content:"\f041"}
968 .icon-map-marker:before{content:"\f041"}
969 .icon-adjust:before{content:"\f042"}
969 .icon-adjust:before{content:"\f042"}
970 .icon-tint:before{content:"\f043"}
970 .icon-tint:before{content:"\f043"}
971 .icon-edit:before{content:"\f044"}
971 .icon-edit:before{content:"\f044"}
972 .icon-share:before{content:"\f045"}
972 .icon-share:before{content:"\f045"}
973 .icon-check:before{content:"\f046"}
973 .icon-check:before{content:"\f046"}
974 .icon-move:before{content:"\f047"}
974 .icon-move:before{content:"\f047"}
975 .icon-step-backward:before{content:"\f048"}
975 .icon-step-backward:before{content:"\f048"}
976 .icon-fast-backward:before{content:"\f049"}
976 .icon-fast-backward:before{content:"\f049"}
977 .icon-backward:before{content:"\f04a"}
977 .icon-backward:before{content:"\f04a"}
978 .icon-play:before{content:"\f04b"}
978 .icon-play:before{content:"\f04b"}
979 .icon-pause:before{content:"\f04c"}
979 .icon-pause:before{content:"\f04c"}
980 .icon-stop:before{content:"\f04d"}
980 .icon-stop:before{content:"\f04d"}
981 .icon-forward:before{content:"\f04e"}
981 .icon-forward:before{content:"\f04e"}
982 .icon-fast-forward:before{content:"\f050"}
982 .icon-fast-forward:before{content:"\f050"}
983 .icon-step-forward:before{content:"\f051"}
983 .icon-step-forward:before{content:"\f051"}
984 .icon-eject:before{content:"\f052"}
984 .icon-eject:before{content:"\f052"}
985 .icon-chevron-left:before{content:"\f053"}
985 .icon-chevron-left:before{content:"\f053"}
986 .icon-chevron-right:before{content:"\f054"}
986 .icon-chevron-right:before{content:"\f054"}
987 .icon-plus-sign:before{content:"\f055"}
987 .icon-plus-sign:before{content:"\f055"}
988 .icon-minus-sign:before{content:"\f056"}
988 .icon-minus-sign:before{content:"\f056"}
989 .icon-remove-sign:before{content:"\f057"}
989 .icon-remove-sign:before{content:"\f057"}
990 .icon-ok-sign:before{content:"\f058"}
990 .icon-ok-sign:before{content:"\f058"}
991 .icon-question-sign:before{content:"\f059"}
991 .icon-question-sign:before{content:"\f059"}
992 .icon-info-sign:before{content:"\f05a"}
992 .icon-info-sign:before{content:"\f05a"}
993 .icon-screenshot:before{content:"\f05b"}
993 .icon-screenshot:before{content:"\f05b"}
994 .icon-remove-circle:before{content:"\f05c"}
994 .icon-remove-circle:before{content:"\f05c"}
995 .icon-ok-circle:before{content:"\f05d"}
995 .icon-ok-circle:before{content:"\f05d"}
996 .icon-ban-circle:before{content:"\f05e"}
996 .icon-ban-circle:before{content:"\f05e"}
997 .icon-arrow-left:before{content:"\f060"}
997 .icon-arrow-left:before{content:"\f060"}
998 .icon-arrow-right:before{content:"\f061"}
998 .icon-arrow-right:before{content:"\f061"}
999 .icon-arrow-up:before{content:"\f062"}
999 .icon-arrow-up:before{content:"\f062"}
1000 .icon-arrow-down:before{content:"\f063"}
1000 .icon-arrow-down:before{content:"\f063"}
1001 .icon-mail-forward:before,.icon-share-alt:before{content:"\f064"}
1001 .icon-mail-forward:before,.icon-share-alt:before{content:"\f064"}
1002 .icon-resize-full:before{content:"\f065"}
1002 .icon-resize-full:before{content:"\f065"}
1003 .icon-resize-small:before{content:"\f066"}
1003 .icon-resize-small:before{content:"\f066"}
1004 .icon-plus:before{content:"\f067"}
1004 .icon-plus:before{content:"\f067"}
1005 .icon-minus:before{content:"\f068"}
1005 .icon-minus:before{content:"\f068"}
1006 .icon-asterisk:before{content:"\f069"}
1006 .icon-asterisk:before{content:"\f069"}
1007 .icon-exclamation-sign:before{content:"\f06a"}
1007 .icon-exclamation-sign:before{content:"\f06a"}
1008 .icon-gift:before{content:"\f06b"}
1008 .icon-gift:before{content:"\f06b"}
1009 .icon-leaf:before{content:"\f06c"}
1009 .icon-leaf:before{content:"\f06c"}
1010 .icon-fire:before{content:"\f06d"}
1010 .icon-fire:before{content:"\f06d"}
1011 .icon-eye-open:before{content:"\f06e"}
1011 .icon-eye-open:before{content:"\f06e"}
1012 .icon-eye-close:before{content:"\f070"}
1012 .icon-eye-close:before{content:"\f070"}
1013 .icon-warning-sign:before{content:"\f071"}
1013 .icon-warning-sign:before{content:"\f071"}
1014 .icon-plane:before{content:"\f072"}
1014 .icon-plane:before{content:"\f072"}
1015 .icon-calendar:before{content:"\f073"}
1015 .icon-calendar:before{content:"\f073"}
1016 .icon-random:before{content:"\f074"}
1016 .icon-random:before{content:"\f074"}
1017 .icon-comment:before{content:"\f075"}
1017 .icon-comment:before{content:"\f075"}
1018 .icon-magnet:before{content:"\f076"}
1018 .icon-magnet:before{content:"\f076"}
1019 .icon-chevron-up:before{content:"\f077"}
1019 .icon-chevron-up:before{content:"\f077"}
1020 .icon-chevron-down:before{content:"\f078"}
1020 .icon-chevron-down:before{content:"\f078"}
1021 .icon-retweet:before{content:"\f079"}
1021 .icon-retweet:before{content:"\f079"}
1022 .icon-shopping-cart:before{content:"\f07a"}
1022 .icon-shopping-cart:before{content:"\f07a"}
1023 .icon-folder-close:before{content:"\f07b"}
1023 .icon-folder-close:before{content:"\f07b"}
1024 .icon-folder-open:before{content:"\f07c"}
1024 .icon-folder-open:before{content:"\f07c"}
1025 .icon-resize-vertical:before{content:"\f07d"}
1025 .icon-resize-vertical:before{content:"\f07d"}
1026 .icon-resize-horizontal:before{content:"\f07e"}
1026 .icon-resize-horizontal:before{content:"\f07e"}
1027 .icon-bar-chart:before{content:"\f080"}
1027 .icon-bar-chart:before{content:"\f080"}
1028 .icon-twitter-sign:before{content:"\f081"}
1028 .icon-twitter-sign:before{content:"\f081"}
1029 .icon-facebook-sign:before{content:"\f082"}
1029 .icon-facebook-sign:before{content:"\f082"}
1030 .icon-camera-retro:before{content:"\f083"}
1030 .icon-camera-retro:before{content:"\f083"}
1031 .icon-key:before{content:"\f084"}
1031 .icon-key:before{content:"\f084"}
1032 .icon-gears:before,.icon-cogs:before{content:"\f085"}
1032 .icon-gears:before,.icon-cogs:before{content:"\f085"}
1033 .icon-comments:before{content:"\f086"}
1033 .icon-comments:before{content:"\f086"}
1034 .icon-thumbs-up-alt:before{content:"\f087"}
1034 .icon-thumbs-up-alt:before{content:"\f087"}
1035 .icon-thumbs-down-alt:before{content:"\f088"}
1035 .icon-thumbs-down-alt:before{content:"\f088"}
1036 .icon-star-half:before{content:"\f089"}
1036 .icon-star-half:before{content:"\f089"}
1037 .icon-heart-empty:before{content:"\f08a"}
1037 .icon-heart-empty:before{content:"\f08a"}
1038 .icon-signout:before{content:"\f08b"}
1038 .icon-signout:before{content:"\f08b"}
1039 .icon-linkedin-sign:before{content:"\f08c"}
1039 .icon-linkedin-sign:before{content:"\f08c"}
1040 .icon-pushpin:before{content:"\f08d"}
1040 .icon-pushpin:before{content:"\f08d"}
1041 .icon-external-link:before{content:"\f08e"}
1041 .icon-external-link:before{content:"\f08e"}
1042 .icon-signin:before{content:"\f090"}
1042 .icon-signin:before{content:"\f090"}
1043 .icon-trophy:before{content:"\f091"}
1043 .icon-trophy:before{content:"\f091"}
1044 .icon-github-sign:before{content:"\f092"}
1044 .icon-github-sign:before{content:"\f092"}
1045 .icon-upload-alt:before{content:"\f093"}
1045 .icon-upload-alt:before{content:"\f093"}
1046 .icon-lemon:before{content:"\f094"}
1046 .icon-lemon:before{content:"\f094"}
1047 .icon-phone:before{content:"\f095"}
1047 .icon-phone:before{content:"\f095"}
1048 .icon-unchecked:before,.icon-check-empty:before{content:"\f096"}
1048 .icon-unchecked:before,.icon-check-empty:before{content:"\f096"}
1049 .icon-bookmark-empty:before{content:"\f097"}
1049 .icon-bookmark-empty:before{content:"\f097"}
1050 .icon-phone-sign:before{content:"\f098"}
1050 .icon-phone-sign:before{content:"\f098"}
1051 .icon-twitter:before{content:"\f099"}
1051 .icon-twitter:before{content:"\f099"}
1052 .icon-facebook:before{content:"\f09a"}
1052 .icon-facebook:before{content:"\f09a"}
1053 .icon-github:before{content:"\f09b"}
1053 .icon-github:before{content:"\f09b"}
1054 .icon-unlock:before{content:"\f09c"}
1054 .icon-unlock:before{content:"\f09c"}
1055 .icon-credit-card:before{content:"\f09d"}
1055 .icon-credit-card:before{content:"\f09d"}
1056 .icon-rss:before{content:"\f09e"}
1056 .icon-rss:before{content:"\f09e"}
1057 .icon-hdd:before{content:"\f0a0"}
1057 .icon-hdd:before{content:"\f0a0"}
1058 .icon-bullhorn:before{content:"\f0a1"}
1058 .icon-bullhorn:before{content:"\f0a1"}
1059 .icon-bell:before{content:"\f0a2"}
1059 .icon-bell:before{content:"\f0a2"}
1060 .icon-certificate:before{content:"\f0a3"}
1060 .icon-certificate:before{content:"\f0a3"}
1061 .icon-hand-right:before{content:"\f0a4"}
1061 .icon-hand-right:before{content:"\f0a4"}
1062 .icon-hand-left:before{content:"\f0a5"}
1062 .icon-hand-left:before{content:"\f0a5"}
1063 .icon-hand-up:before{content:"\f0a6"}
1063 .icon-hand-up:before{content:"\f0a6"}
1064 .icon-hand-down:before{content:"\f0a7"}
1064 .icon-hand-down:before{content:"\f0a7"}
1065 .icon-circle-arrow-left:before{content:"\f0a8"}
1065 .icon-circle-arrow-left:before{content:"\f0a8"}
1066 .icon-circle-arrow-right:before{content:"\f0a9"}
1066 .icon-circle-arrow-right:before{content:"\f0a9"}
1067 .icon-circle-arrow-up:before{content:"\f0aa"}
1067 .icon-circle-arrow-up:before{content:"\f0aa"}
1068 .icon-circle-arrow-down:before{content:"\f0ab"}
1068 .icon-circle-arrow-down:before{content:"\f0ab"}
1069 .icon-globe:before{content:"\f0ac"}
1069 .icon-globe:before{content:"\f0ac"}
1070 .icon-wrench:before{content:"\f0ad"}
1070 .icon-wrench:before{content:"\f0ad"}
1071 .icon-tasks:before{content:"\f0ae"}
1071 .icon-tasks:before{content:"\f0ae"}
1072 .icon-filter:before{content:"\f0b0"}
1072 .icon-filter:before{content:"\f0b0"}
1073 .icon-briefcase:before{content:"\f0b1"}
1073 .icon-briefcase:before{content:"\f0b1"}
1074 .icon-fullscreen:before{content:"\f0b2"}
1074 .icon-fullscreen:before{content:"\f0b2"}
1075 .icon-group:before{content:"\f0c0"}
1075 .icon-group:before{content:"\f0c0"}
1076 .icon-link:before{content:"\f0c1"}
1076 .icon-link:before{content:"\f0c1"}
1077 .icon-cloud:before{content:"\f0c2"}
1077 .icon-cloud:before{content:"\f0c2"}
1078 .icon-beaker:before{content:"\f0c3"}
1078 .icon-beaker:before{content:"\f0c3"}
1079 .icon-cut:before{content:"\f0c4"}
1079 .icon-cut:before{content:"\f0c4"}
1080 .icon-copy:before{content:"\f0c5"}
1080 .icon-copy:before{content:"\f0c5"}
1081 .icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6"}
1081 .icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6"}
1082 .icon-save:before{content:"\f0c7"}
1082 .icon-save:before{content:"\f0c7"}
1083 .icon-sign-blank:before{content:"\f0c8"}
1083 .icon-sign-blank:before{content:"\f0c8"}
1084 .icon-reorder:before{content:"\f0c9"}
1084 .icon-reorder:before{content:"\f0c9"}
1085 .icon-list-ul:before{content:"\f0ca"}
1085 .icon-list-ul:before{content:"\f0ca"}
1086 .icon-list-ol:before{content:"\f0cb"}
1086 .icon-list-ol:before{content:"\f0cb"}
1087 .icon-strikethrough:before{content:"\f0cc"}
1087 .icon-strikethrough:before{content:"\f0cc"}
1088 .icon-underline:before{content:"\f0cd"}
1088 .icon-underline:before{content:"\f0cd"}
1089 .icon-table:before{content:"\f0ce"}
1089 .icon-table:before{content:"\f0ce"}
1090 .icon-magic:before{content:"\f0d0"}
1090 .icon-magic:before{content:"\f0d0"}
1091 .icon-truck:before{content:"\f0d1"}
1091 .icon-truck:before{content:"\f0d1"}
1092 .icon-pinterest:before{content:"\f0d2"}
1092 .icon-pinterest:before{content:"\f0d2"}
1093 .icon-pinterest-sign:before{content:"\f0d3"}
1093 .icon-pinterest-sign:before{content:"\f0d3"}
1094 .icon-google-plus-sign:before{content:"\f0d4"}
1094 .icon-google-plus-sign:before{content:"\f0d4"}
1095 .icon-google-plus:before{content:"\f0d5"}
1095 .icon-google-plus:before{content:"\f0d5"}
1096 .icon-money:before{content:"\f0d6"}
1096 .icon-money:before{content:"\f0d6"}
1097 .icon-caret-down:before{content:"\f0d7"}
1097 .icon-caret-down:before{content:"\f0d7"}
1098 .icon-caret-up:before{content:"\f0d8"}
1098 .icon-caret-up:before{content:"\f0d8"}
1099 .icon-caret-left:before{content:"\f0d9"}
1099 .icon-caret-left:before{content:"\f0d9"}
1100 .icon-caret-right:before{content:"\f0da"}
1100 .icon-caret-right:before{content:"\f0da"}
1101 .icon-columns:before{content:"\f0db"}
1101 .icon-columns:before{content:"\f0db"}
1102 .icon-sort:before{content:"\f0dc"}
1102 .icon-sort:before{content:"\f0dc"}
1103 .icon-sort-down:before{content:"\f0dd"}
1103 .icon-sort-down:before{content:"\f0dd"}
1104 .icon-sort-up:before{content:"\f0de"}
1104 .icon-sort-up:before{content:"\f0de"}
1105 .icon-envelope:before{content:"\f0e0"}
1105 .icon-envelope:before{content:"\f0e0"}
1106 .icon-linkedin:before{content:"\f0e1"}
1106 .icon-linkedin:before{content:"\f0e1"}
1107 .icon-rotate-left:before,.icon-undo:before{content:"\f0e2"}
1107 .icon-rotate-left:before,.icon-undo:before{content:"\f0e2"}
1108 .icon-legal:before{content:"\f0e3"}
1108 .icon-legal:before{content:"\f0e3"}
1109 .icon-dashboard:before{content:"\f0e4"}
1109 .icon-dashboard:before{content:"\f0e4"}
1110 .icon-comment-alt:before{content:"\f0e5"}
1110 .icon-comment-alt:before{content:"\f0e5"}
1111 .icon-comments-alt:before{content:"\f0e6"}
1111 .icon-comments-alt:before{content:"\f0e6"}
1112 .icon-bolt:before{content:"\f0e7"}
1112 .icon-bolt:before{content:"\f0e7"}
1113 .icon-sitemap:before{content:"\f0e8"}
1113 .icon-sitemap:before{content:"\f0e8"}
1114 .icon-umbrella:before{content:"\f0e9"}
1114 .icon-umbrella:before{content:"\f0e9"}
1115 .icon-paste:before{content:"\f0ea"}
1115 .icon-paste:before{content:"\f0ea"}
1116 .icon-lightbulb:before{content:"\f0eb"}
1116 .icon-lightbulb:before{content:"\f0eb"}
1117 .icon-exchange:before{content:"\f0ec"}
1117 .icon-exchange:before{content:"\f0ec"}
1118 .icon-cloud-download:before{content:"\f0ed"}
1118 .icon-cloud-download:before{content:"\f0ed"}
1119 .icon-cloud-upload:before{content:"\f0ee"}
1119 .icon-cloud-upload:before{content:"\f0ee"}
1120 .icon-user-md:before{content:"\f0f0"}
1120 .icon-user-md:before{content:"\f0f0"}
1121 .icon-stethoscope:before{content:"\f0f1"}
1121 .icon-stethoscope:before{content:"\f0f1"}
1122 .icon-suitcase:before{content:"\f0f2"}
1122 .icon-suitcase:before{content:"\f0f2"}
1123 .icon-bell-alt:before{content:"\f0f3"}
1123 .icon-bell-alt:before{content:"\f0f3"}
1124 .icon-coffee:before{content:"\f0f4"}
1124 .icon-coffee:before{content:"\f0f4"}
1125 .icon-food:before{content:"\f0f5"}
1125 .icon-food:before{content:"\f0f5"}
1126 .icon-file-text-alt:before{content:"\f0f6"}
1126 .icon-file-text-alt:before{content:"\f0f6"}
1127 .icon-building:before{content:"\f0f7"}
1127 .icon-building:before{content:"\f0f7"}
1128 .icon-hospital:before{content:"\f0f8"}
1128 .icon-hospital:before{content:"\f0f8"}
1129 .icon-ambulance:before{content:"\f0f9"}
1129 .icon-ambulance:before{content:"\f0f9"}
1130 .icon-medkit:before{content:"\f0fa"}
1130 .icon-medkit:before{content:"\f0fa"}
1131 .icon-fighter-jet:before{content:"\f0fb"}
1131 .icon-fighter-jet:before{content:"\f0fb"}
1132 .icon-beer:before{content:"\f0fc"}
1132 .icon-beer:before{content:"\f0fc"}
1133 .icon-h-sign:before{content:"\f0fd"}
1133 .icon-h-sign:before{content:"\f0fd"}
1134 .icon-plus-sign-alt:before{content:"\f0fe"}
1134 .icon-plus-sign-alt:before{content:"\f0fe"}
1135 .icon-double-angle-left:before{content:"\f100"}
1135 .icon-double-angle-left:before{content:"\f100"}
1136 .icon-double-angle-right:before{content:"\f101"}
1136 .icon-double-angle-right:before{content:"\f101"}
1137 .icon-double-angle-up:before{content:"\f102"}
1137 .icon-double-angle-up:before{content:"\f102"}
1138 .icon-double-angle-down:before{content:"\f103"}
1138 .icon-double-angle-down:before{content:"\f103"}
1139 .icon-angle-left:before{content:"\f104"}
1139 .icon-angle-left:before{content:"\f104"}
1140 .icon-angle-right:before{content:"\f105"}
1140 .icon-angle-right:before{content:"\f105"}
1141 .icon-angle-up:before{content:"\f106"}
1141 .icon-angle-up:before{content:"\f106"}
1142 .icon-angle-down:before{content:"\f107"}
1142 .icon-angle-down:before{content:"\f107"}
1143 .icon-desktop:before{content:"\f108"}
1143 .icon-desktop:before{content:"\f108"}
1144 .icon-laptop:before{content:"\f109"}
1144 .icon-laptop:before{content:"\f109"}
1145 .icon-tablet:before{content:"\f10a"}
1145 .icon-tablet:before{content:"\f10a"}
1146 .icon-mobile-phone:before{content:"\f10b"}
1146 .icon-mobile-phone:before{content:"\f10b"}
1147 .icon-circle-blank:before{content:"\f10c"}
1147 .icon-circle-blank:before{content:"\f10c"}
1148 .icon-quote-left:before{content:"\f10d"}
1148 .icon-quote-left:before{content:"\f10d"}
1149 .icon-quote-right:before{content:"\f10e"}
1149 .icon-quote-right:before{content:"\f10e"}
1150 .icon-spinner:before{content:"\f110"}
1150 .icon-spinner:before{content:"\f110"}
1151 .icon-circle:before{content:"\f111"}
1151 .icon-circle:before{content:"\f111"}
1152 .icon-mail-reply:before,.icon-reply:before{content:"\f112"}
1152 .icon-mail-reply:before,.icon-reply:before{content:"\f112"}
1153 .icon-github-alt:before{content:"\f113"}
1153 .icon-github-alt:before{content:"\f113"}
1154 .icon-folder-close-alt:before{content:"\f114"}
1154 .icon-folder-close-alt:before{content:"\f114"}
1155 .icon-folder-open-alt:before{content:"\f115"}
1155 .icon-folder-open-alt:before{content:"\f115"}
1156 .icon-expand-alt:before{content:"\f116"}
1156 .icon-expand-alt:before{content:"\f116"}
1157 .icon-collapse-alt:before{content:"\f117"}
1157 .icon-collapse-alt:before{content:"\f117"}
1158 .icon-smile:before{content:"\f118"}
1158 .icon-smile:before{content:"\f118"}
1159 .icon-frown:before{content:"\f119"}
1159 .icon-frown:before{content:"\f119"}
1160 .icon-meh:before{content:"\f11a"}
1160 .icon-meh:before{content:"\f11a"}
1161 .icon-gamepad:before{content:"\f11b"}
1161 .icon-gamepad:before{content:"\f11b"}
1162 .icon-keyboard:before{content:"\f11c"}
1162 .icon-keyboard:before{content:"\f11c"}
1163 .icon-flag-alt:before{content:"\f11d"}
1163 .icon-flag-alt:before{content:"\f11d"}
1164 .icon-flag-checkered:before{content:"\f11e"}
1164 .icon-flag-checkered:before{content:"\f11e"}
1165 .icon-terminal:before{content:"\f120"}
1165 .icon-terminal:before{content:"\f120"}
1166 .icon-code:before{content:"\f121"}
1166 .icon-code:before{content:"\f121"}
1167 .icon-reply-all:before{content:"\f122"}
1167 .icon-reply-all:before{content:"\f122"}
1168 .icon-mail-reply-all:before{content:"\f122"}
1168 .icon-mail-reply-all:before{content:"\f122"}
1169 .icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123"}
1169 .icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123"}
1170 .icon-location-arrow:before{content:"\f124"}
1170 .icon-location-arrow:before{content:"\f124"}
1171 .icon-crop:before{content:"\f125"}
1171 .icon-crop:before{content:"\f125"}
1172 .icon-code-fork:before{content:"\f126"}
1172 .icon-code-fork:before{content:"\f126"}
1173 .icon-unlink:before{content:"\f127"}
1173 .icon-unlink:before{content:"\f127"}
1174 .icon-question:before{content:"\f128"}
1174 .icon-question:before{content:"\f128"}
1175 .icon-info:before{content:"\f129"}
1175 .icon-info:before{content:"\f129"}
1176 .icon-exclamation:before{content:"\f12a"}
1176 .icon-exclamation:before{content:"\f12a"}
1177 .icon-superscript:before{content:"\f12b"}
1177 .icon-superscript:before{content:"\f12b"}
1178 .icon-subscript:before{content:"\f12c"}
1178 .icon-subscript:before{content:"\f12c"}
1179 .icon-eraser:before{content:"\f12d"}
1179 .icon-eraser:before{content:"\f12d"}
1180 .icon-puzzle-piece:before{content:"\f12e"}
1180 .icon-puzzle-piece:before{content:"\f12e"}
1181 .icon-microphone:before{content:"\f130"}
1181 .icon-microphone:before{content:"\f130"}
1182 .icon-microphone-off:before{content:"\f131"}
1182 .icon-microphone-off:before{content:"\f131"}
1183 .icon-shield:before{content:"\f132"}
1183 .icon-shield:before{content:"\f132"}
1184 .icon-calendar-empty:before{content:"\f133"}
1184 .icon-calendar-empty:before{content:"\f133"}
1185 .icon-fire-extinguisher:before{content:"\f134"}
1185 .icon-fire-extinguisher:before{content:"\f134"}
1186 .icon-rocket:before{content:"\f135"}
1186 .icon-rocket:before{content:"\f135"}
1187 .icon-maxcdn:before{content:"\f136"}
1187 .icon-maxcdn:before{content:"\f136"}
1188 .icon-chevron-sign-left:before{content:"\f137"}
1188 .icon-chevron-sign-left:before{content:"\f137"}
1189 .icon-chevron-sign-right:before{content:"\f138"}
1189 .icon-chevron-sign-right:before{content:"\f138"}
1190 .icon-chevron-sign-up:before{content:"\f139"}
1190 .icon-chevron-sign-up:before{content:"\f139"}
1191 .icon-chevron-sign-down:before{content:"\f13a"}
1191 .icon-chevron-sign-down:before{content:"\f13a"}
1192 .icon-html5:before{content:"\f13b"}
1192 .icon-html5:before{content:"\f13b"}
1193 .icon-css3:before{content:"\f13c"}
1193 .icon-css3:before{content:"\f13c"}
1194 .icon-anchor:before{content:"\f13d"}
1194 .icon-anchor:before{content:"\f13d"}
1195 .icon-unlock-alt:before{content:"\f13e"}
1195 .icon-unlock-alt:before{content:"\f13e"}
1196 .icon-bullseye:before{content:"\f140"}
1196 .icon-bullseye:before{content:"\f140"}
1197 .icon-ellipsis-horizontal:before{content:"\f141"}
1197 .icon-ellipsis-horizontal:before{content:"\f141"}
1198 .icon-ellipsis-vertical:before{content:"\f142"}
1198 .icon-ellipsis-vertical:before{content:"\f142"}
1199 .icon-rss-sign:before{content:"\f143"}
1199 .icon-rss-sign:before{content:"\f143"}
1200 .icon-play-sign:before{content:"\f144"}
1200 .icon-play-sign:before{content:"\f144"}
1201 .icon-ticket:before{content:"\f145"}
1201 .icon-ticket:before{content:"\f145"}
1202 .icon-minus-sign-alt:before{content:"\f146"}
1202 .icon-minus-sign-alt:before{content:"\f146"}
1203 .icon-check-minus:before{content:"\f147"}
1203 .icon-check-minus:before{content:"\f147"}
1204 .icon-level-up:before{content:"\f148"}
1204 .icon-level-up:before{content:"\f148"}
1205 .icon-level-down:before{content:"\f149"}
1205 .icon-level-down:before{content:"\f149"}
1206 .icon-check-sign:before{content:"\f14a"}
1206 .icon-check-sign:before{content:"\f14a"}
1207 .icon-edit-sign:before{content:"\f14b"}
1207 .icon-edit-sign:before{content:"\f14b"}
1208 .icon-external-link-sign:before{content:"\f14c"}
1208 .icon-external-link-sign:before{content:"\f14c"}
1209 .icon-share-sign:before{content:"\f14d"}
1209 .icon-share-sign:before{content:"\f14d"}
1210 .icon-compass:before{content:"\f14e"}
1210 .icon-compass:before{content:"\f14e"}
1211 .icon-collapse:before{content:"\f150"}
1211 .icon-collapse:before{content:"\f150"}
1212 .icon-collapse-top:before{content:"\f151"}
1212 .icon-collapse-top:before{content:"\f151"}
1213 .icon-expand:before{content:"\f152"}
1213 .icon-expand:before{content:"\f152"}
1214 .icon-euro:before,.icon-eur:before{content:"\f153"}
1214 .icon-euro:before,.icon-eur:before{content:"\f153"}
1215 .icon-gbp:before{content:"\f154"}
1215 .icon-gbp:before{content:"\f154"}
1216 .icon-dollar:before,.icon-usd:before{content:"\f155"}
1216 .icon-dollar:before,.icon-usd:before{content:"\f155"}
1217 .icon-rupee:before,.icon-inr:before{content:"\f156"}
1217 .icon-rupee:before,.icon-inr:before{content:"\f156"}
1218 .icon-yen:before,.icon-jpy:before{content:"\f157"}
1218 .icon-yen:before,.icon-jpy:before{content:"\f157"}
1219 .icon-renminbi:before,.icon-cny:before{content:"\f158"}
1219 .icon-renminbi:before,.icon-cny:before{content:"\f158"}
1220 .icon-won:before,.icon-krw:before{content:"\f159"}
1220 .icon-won:before,.icon-krw:before{content:"\f159"}
1221 .icon-bitcoin:before,.icon-btc:before{content:"\f15a"}
1221 .icon-bitcoin:before,.icon-btc:before{content:"\f15a"}
1222 .icon-file:before{content:"\f15b"}
1222 .icon-file:before{content:"\f15b"}
1223 .icon-file-text:before{content:"\f15c"}
1223 .icon-file-text:before{content:"\f15c"}
1224 .icon-sort-by-alphabet:before{content:"\f15d"}
1224 .icon-sort-by-alphabet:before{content:"\f15d"}
1225 .icon-sort-by-alphabet-alt:before{content:"\f15e"}
1225 .icon-sort-by-alphabet-alt:before{content:"\f15e"}
1226 .icon-sort-by-attributes:before{content:"\f160"}
1226 .icon-sort-by-attributes:before{content:"\f160"}
1227 .icon-sort-by-attributes-alt:before{content:"\f161"}
1227 .icon-sort-by-attributes-alt:before{content:"\f161"}
1228 .icon-sort-by-order:before{content:"\f162"}
1228 .icon-sort-by-order:before{content:"\f162"}
1229 .icon-sort-by-order-alt:before{content:"\f163"}
1229 .icon-sort-by-order-alt:before{content:"\f163"}
1230 .icon-thumbs-up:before{content:"\f164"}
1230 .icon-thumbs-up:before{content:"\f164"}
1231 .icon-thumbs-down:before{content:"\f165"}
1231 .icon-thumbs-down:before{content:"\f165"}
1232 .icon-youtube-sign:before{content:"\f166"}
1232 .icon-youtube-sign:before{content:"\f166"}
1233 .icon-youtube:before{content:"\f167"}
1233 .icon-youtube:before{content:"\f167"}
1234 .icon-xing:before{content:"\f168"}
1234 .icon-xing:before{content:"\f168"}
1235 .icon-xing-sign:before{content:"\f169"}
1235 .icon-xing-sign:before{content:"\f169"}
1236 .icon-youtube-play:before{content:"\f16a"}
1236 .icon-youtube-play:before{content:"\f16a"}
1237 .icon-dropbox:before{content:"\f16b"}
1237 .icon-dropbox:before{content:"\f16b"}
1238 .icon-stackexchange:before{content:"\f16c"}
1238 .icon-stackexchange:before{content:"\f16c"}
1239 .icon-instagram:before{content:"\f16d"}
1239 .icon-instagram:before{content:"\f16d"}
1240 .icon-flickr:before{content:"\f16e"}
1240 .icon-flickr:before{content:"\f16e"}
1241 .icon-adn:before{content:"\f170"}
1241 .icon-adn:before{content:"\f170"}
1242 .icon-bitbucket:before{content:"\f171"}
1242 .icon-bitbucket:before{content:"\f171"}
1243 .icon-bitbucket-sign:before{content:"\f172"}
1243 .icon-bitbucket-sign:before{content:"\f172"}
1244 .icon-tumblr:before{content:"\f173"}
1244 .icon-tumblr:before{content:"\f173"}
1245 .icon-tumblr-sign:before{content:"\f174"}
1245 .icon-tumblr-sign:before{content:"\f174"}
1246 .icon-long-arrow-down:before{content:"\f175"}
1246 .icon-long-arrow-down:before{content:"\f175"}
1247 .icon-long-arrow-up:before{content:"\f176"}
1247 .icon-long-arrow-up:before{content:"\f176"}
1248 .icon-long-arrow-left:before{content:"\f177"}
1248 .icon-long-arrow-left:before{content:"\f177"}
1249 .icon-long-arrow-right:before{content:"\f178"}
1249 .icon-long-arrow-right:before{content:"\f178"}
1250 .icon-apple:before{content:"\f179"}
1250 .icon-apple:before{content:"\f179"}
1251 .icon-windows:before{content:"\f17a"}
1251 .icon-windows:before{content:"\f17a"}
1252 .icon-android:before{content:"\f17b"}
1252 .icon-android:before{content:"\f17b"}
1253 .icon-linux:before{content:"\f17c"}
1253 .icon-linux:before{content:"\f17c"}
1254 .icon-dribbble:before{content:"\f17d"}
1254 .icon-dribbble:before{content:"\f17d"}
1255 .icon-skype:before{content:"\f17e"}
1255 .icon-skype:before{content:"\f17e"}
1256 .icon-foursquare:before{content:"\f180"}
1256 .icon-foursquare:before{content:"\f180"}
1257 .icon-trello:before{content:"\f181"}
1257 .icon-trello:before{content:"\f181"}
1258 .icon-female:before{content:"\f182"}
1258 .icon-female:before{content:"\f182"}
1259 .icon-male:before{content:"\f183"}
1259 .icon-male:before{content:"\f183"}
1260 .icon-gittip:before{content:"\f184"}
1260 .icon-gittip:before{content:"\f184"}
1261 .icon-sun:before{content:"\f185"}
1261 .icon-sun:before{content:"\f185"}
1262 .icon-moon:before{content:"\f186"}
1262 .icon-moon:before{content:"\f186"}
1263 .icon-archive:before{content:"\f187"}
1263 .icon-archive:before{content:"\f187"}
1264 .icon-bug:before{content:"\f188"}
1264 .icon-bug:before{content:"\f188"}
1265 .icon-vk:before{content:"\f189"}
1265 .icon-vk:before{content:"\f189"}
1266 .icon-weibo:before{content:"\f18a"}
1266 .icon-weibo:before{content:"\f18a"}
1267 .icon-renren:before{content:"\f18b"}
1267 .icon-renren:before{content:"\f18b"}
1268 code{color:#000}
1268 code{color:#000}
1269 .border-box-sizing{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
1269 .border-box-sizing{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
1270 .corner-all{border-radius:4px}
1270 .corner-all{border-radius:4px}
1271 .hbox{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}
1271 .hbox{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}
1272 .hbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
1272 .hbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
1273 .vbox{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}
1273 .vbox{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}
1274 .vbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
1274 .vbox>*{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none}
1275 .hbox.reverse,.vbox.reverse,.reverse{-webkit-box-direction:reverse;-moz-box-direction:reverse;box-direction:reverse;flex-direction:row-reverse}
1275 .hbox.reverse,.vbox.reverse,.reverse{-webkit-box-direction:reverse;-moz-box-direction:reverse;box-direction:reverse;flex-direction:row-reverse}
1276 .hbox.box-flex0,.vbox.box-flex0,.box-flex0{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none;width:auto}
1276 .hbox.box-flex0,.vbox.box-flex0,.box-flex0{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;flex:none;width:auto}
1277 .hbox.box-flex1,.vbox.box-flex1,.box-flex1{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1277 .hbox.box-flex1,.vbox.box-flex1,.box-flex1{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1278 .hbox.box-flex,.vbox.box-flex,.box-flex{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1278 .hbox.box-flex,.vbox.box-flex,.box-flex{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1279 .hbox.box-flex2,.vbox.box-flex2,.box-flex2{-webkit-box-flex:2;-moz-box-flex:2;box-flex:2;flex:2}
1279 .hbox.box-flex2,.vbox.box-flex2,.box-flex2{-webkit-box-flex:2;-moz-box-flex:2;box-flex:2;flex:2}
1280 .box-group1{-webkit-box-flex-group:1;-moz-box-flex-group:1;box-flex-group:1}
1280 .box-group1{-webkit-box-flex-group:1;-moz-box-flex-group:1;box-flex-group:1}
1281 .box-group2{-webkit-box-flex-group:2;-moz-box-flex-group:2;box-flex-group:2}
1281 .box-group2{-webkit-box-flex-group:2;-moz-box-flex-group:2;box-flex-group:2}
1282 .hbox.start,.vbox.start,.start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start}
1282 .hbox.start,.vbox.start,.start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start}
1283 .hbox.end,.vbox.end,.end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;justify-content:flex-end}
1283 .hbox.end,.vbox.end,.end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;justify-content:flex-end}
1284 .hbox.center,.vbox.center,.center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;justify-content:center}
1284 .hbox.center,.vbox.center,.center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;justify-content:center}
1285 .hbox.align-start,.vbox.align-start,.align-start{-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1285 .hbox.align-start,.vbox.align-start,.align-start{-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1286 .hbox.align-end,.vbox.align-end,.align-end{-webkit-box-align:end;-moz-box-align:end;box-align:end;align-items:flex-end}
1286 .hbox.align-end,.vbox.align-end,.align-end{-webkit-box-align:end;-moz-box-align:end;box-align:end;align-items:flex-end}
1287 .hbox.align-center,.vbox.align-center,.align-center{-webkit-box-align:center;-moz-box-align:center;box-align:center;align-items:center}
1287 .hbox.align-center,.vbox.align-center,.align-center{-webkit-box-align:center;-moz-box-align:center;box-align:center;align-items:center}
1288 div.error{margin:2em;text-align:center}
1288 div.error{margin:2em;text-align:center}
1289 div.error>h1{font-size:500%;line-height:normal}
1289 div.error>h1{font-size:500%;line-height:normal}
1290 div.error>p{font-size:200%;line-height:normal}
1290 div.error>p{font-size:200%;line-height:normal}
1291 div.traceback-wrapper{text-align:left;max-width:800px;margin:auto}
1291 div.traceback-wrapper{text-align:left;max-width:800px;margin:auto}
1292 body{background-color:#fff;position:absolute;left:0;right:0;top:0;bottom:0;overflow:visible}
1292 body{background-color:#fff;position:absolute;left:0;right:0;top:0;bottom:0;overflow:visible}
1293 div#header{display:none}
1293 div#header{display:none}
1294 #ipython_notebook{padding-left:16px}
1294 #ipython_notebook{padding-left:16px}
1295 #noscript{width:auto;padding-top:16px;padding-bottom:16px;text-align:center;font-size:22px;color:#f00;font-weight:bold}
1295 #noscript{width:auto;padding-top:16px;padding-bottom:16px;text-align:center;font-size:22px;color:#f00;font-weight:bold}
1296 #ipython_notebook img{font-family:Verdana,"Helvetica Neue",Arial,Helvetica,Geneva,sans-serif;height:24px;text-decoration:none;color:#000}
1296 #ipython_notebook img{font-family:Verdana,"Helvetica Neue",Arial,Helvetica,Geneva,sans-serif;height:24px;text-decoration:none;color:#000}
1297 #site{width:100%;display:none}
1297 #site{width:100%;display:none}
1298 .ui-button .ui-button-text{padding:.2em .8em;font-size:77%}
1298 .ui-button .ui-button-text{padding:.2em .8em;font-size:77%}
1299 input.ui-button{padding:.3em .9em}
1299 input.ui-button{padding:.3em .9em}
1300 .navbar span{margin-top:3px}
1300 .navbar span{margin-top:3px}
1301 span#login_widget{float:right}
1301 span#login_widget{float:right}
1302 .nav-header{text-transform:none}
1302 .nav-header{text-transform:none}
1303 .navbar-nobg{background-color:transparent;background-image:none}
1303 .navbar-nobg{background-color:transparent;background-image:none}
1304 #header>span{margin-top:10px}
1304 #header>span{margin-top:10px}
1305 .modal-body{max-height:500px}
1305 .modal-body{max-height:500px}
1306 @media (min-width:768px){.modal{width:700px;margin-left:-350px}}.center-nav{display:inline-block;margin-bottom:-4px}
1306 @media (min-width:768px){.modal{width:700px;margin-left:-350px}}.center-nav{display:inline-block;margin-bottom:-4px}
1307 .alternate_upload{background-color:none;display:inline}
1307 .alternate_upload{background-color:none;display:inline}
1308 .alternate_upload.form{padding:0;margin:0}
1308 .alternate_upload.form{padding:0;margin:0}
1309 .alternate_upload input.fileinput{background-color:#f00;position:relative;opacity:0;z-index:2;width:295px;margin-left:163px;cursor:pointer;height:26px}
1309 .alternate_upload input.fileinput{background-color:#f00;position:relative;opacity:0;z-index:2;width:295px;margin-left:163px;cursor:pointer;height:26px}
1310 ul#tabs{margin-bottom:4px}
1310 ul#tabs{margin-bottom:4px}
1311 ul#tabs a{padding-top:4px;padding-bottom:4px}
1311 ul#tabs a{padding-top:4px;padding-bottom:4px}
1312 ul.breadcrumb a:focus,ul.breadcrumb a:hover{text-decoration:none}
1312 ul.breadcrumb a:focus,ul.breadcrumb a:hover{text-decoration:none}
1313 ul.breadcrumb i.icon-home{font-size:16px;margin-right:4px}
1313 ul.breadcrumb i.icon-home{font-size:16px;margin-right:4px}
1314 ul.breadcrumb span{color:#5e5e5e}
1314 ul.breadcrumb span{color:#5e5e5e}
1315 .list_toolbar{padding:4px 0 4px 0}
1315 .list_toolbar{padding:4px 0 4px 0}
1316 .list_toolbar [class*="span"]{min-height:26px}
1316 .list_toolbar [class*="span"]{min-height:26px}
1317 .list_header{font-weight:bold}
1317 .list_header{font-weight:bold}
1318 .list_container{margin-top:4px;margin-bottom:20px;border:1px solid #ababab;border-radius:4px}
1318 .list_container{margin-top:4px;margin-bottom:20px;border:1px solid #ababab;border-radius:4px}
1319 .list_container>div{border-bottom:1px solid #ababab}.list_container>div:hover .list-item{background-color:#f00}
1319 .list_container>div{border-bottom:1px solid #ababab}.list_container>div:hover .list-item{background-color:#f00}
1320 .list_container>div:last-child{border:none}
1320 .list_container>div:last-child{border:none}
1321 .list_item:hover .list_item{background-color:#ddd}
1321 .list_item:hover .list_item{background-color:#ddd}
1322 .list_item a{text-decoration:none}
1322 .list_item a{text-decoration:none}
1323 .list_header>div,.list_item>div{padding-top:4px;padding-bottom:4px;padding-left:7px;padding-right:7px;height:22px;line-height:22px}
1323 .list_header>div,.list_item>div{padding-top:4px;padding-bottom:4px;padding-left:7px;padding-right:7px;height:22px;line-height:22px}
1324 .item_name{line-height:22px;height:26px}
1324 .item_name{line-height:22px;height:26px}
1325 .item_icon{font-size:14px;color:#5e5e5e;margin-right:7px}
1325 .item_icon{font-size:14px;color:#5e5e5e;margin-right:7px}
1326 .item_buttons{line-height:1em}
1326 .item_buttons{line-height:1em}
1327 .toolbar_info{height:26px;line-height:26px}
1327 .toolbar_info{height:26px;line-height:26px}
1328 input.nbname_input,input.engine_num_input{padding-top:3px;padding-bottom:3px;height:14px;line-height:14px;margin:0}
1328 input.nbname_input,input.engine_num_input{padding-top:3px;padding-bottom:3px;height:14px;line-height:14px;margin:0}
1329 input.engine_num_input{width:60px}
1329 input.engine_num_input{width:60px}
1330 .highlight_text{color:#00f}
1330 .highlight_text{color:#00f}
1331 #project_name>.breadcrumb{padding:0;margin-bottom:0;background-color:transparent;font-weight:bold}
1331 #project_name>.breadcrumb{padding:0;margin-bottom:0;background-color:transparent;font-weight:bold}
1332 .ansibold{font-weight:bold}
1332 .ansibold{font-weight:bold}
1333 .ansiblack{color:#000}
1333 .ansiblack{color:#000}
1334 .ansired{color:#8b0000}
1334 .ansired{color:#8b0000}
1335 .ansigreen{color:#006400}
1335 .ansigreen{color:#006400}
1336 .ansiyellow{color:#a52a2a}
1336 .ansiyellow{color:#a52a2a}
1337 .ansiblue{color:#00008b}
1337 .ansiblue{color:#00008b}
1338 .ansipurple{color:#9400d3}
1338 .ansipurple{color:#9400d3}
1339 .ansicyan{color:#4682b4}
1339 .ansicyan{color:#4682b4}
1340 .ansigray{color:#808080}
1340 .ansigray{color:#808080}
1341 .ansibgblack{background-color:#000}
1341 .ansibgblack{background-color:#000}
1342 .ansibgred{background-color:#f00}
1342 .ansibgred{background-color:#f00}
1343 .ansibggreen{background-color:#008000}
1343 .ansibggreen{background-color:#008000}
1344 .ansibgyellow{background-color:#ff0}
1344 .ansibgyellow{background-color:#ff0}
1345 .ansibgblue{background-color:#00f}
1345 .ansibgblue{background-color:#00f}
1346 .ansibgpurple{background-color:#f0f}
1346 .ansibgpurple{background-color:#f0f}
1347 .ansibgcyan{background-color:#0ff}
1347 .ansibgcyan{background-color:#0ff}
1348 .ansibggray{background-color:#808080}
1348 .ansibggray{background-color:#808080}
1349 div.cell{border:1px solid transparent;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.cell.selected{border-radius:4px;border:thin #ababab solid}
1349 div.cell{border:1px solid transparent;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.cell.selected{border-radius:4px;border:thin #ababab solid}
1350 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
1350 div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
1351 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
1351 div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
1352 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
1352 div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
1353 div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1353 @media (max-width:480px){div.prompt{text-align:left}}div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1354 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
1354 div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
1355 div.prompt:empty{padding-top:0;padding-bottom:0}
1355 div.prompt:empty{padding-top:0;padding-bottom:0}
1356 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1356 div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1357 div.input_prompt{color:#000080;border-top:1px solid transparent}
1357 @media (max-width:480px){div.input{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.input_prompt{color:#000080;border-top:1px solid transparent}
1358 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
1358 div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
1359 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
1359 div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
1360 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
1360 .CodeMirror{line-height:1.21429em;height:auto;background:none;}
1361 .CodeMirror-scroll{overflow-y:hidden;overflow-x:auto}
1361 .CodeMirror-scroll{overflow-y:hidden;overflow-x:auto}
1362 @-moz-document url-prefix(){.CodeMirror-scroll{overflow-x:hidden}}.CodeMirror-lines{padding:.4em}
1362 @-moz-document url-prefix(){.CodeMirror-scroll{overflow-x:hidden}}.CodeMirror-lines{padding:.4em}
1363 .CodeMirror-linenumber{padding:0 8px 0 4px}
1363 .CodeMirror-linenumber{padding:0 8px 0 4px}
1364 .CodeMirror-gutters{border-bottom-left-radius:4px;border-top-left-radius:4px}
1364 .CodeMirror-gutters{border-bottom-left-radius:4px;border-top-left-radius:4px}
1365 .CodeMirror pre{padding:0;border:0;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
1365 .CodeMirror pre{padding:0;border:0;border-radius:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
1366 pre code{display:block;padding:.5em}
1366 pre code{display:block;padding:.5em}
1367 .highlight-base,pre code,pre .subst,pre .tag .title,pre .lisp .title,pre .clojure .built_in,pre .nginx .title{color:#000}
1367 .highlight-base,pre code,pre .subst,pre .tag .title,pre .lisp .title,pre .clojure .built_in,pre .nginx .title{color:#000}
1368 .highlight-string,pre .string,pre .constant,pre .parent,pre .tag .value,pre .rules .value,pre .rules .value .number,pre .preprocessor,pre .ruby .symbol,pre .ruby .symbol .string,pre .aggregate,pre .template_tag,pre .django .variable,pre .smalltalk .class,pre .addition,pre .flow,pre .stream,pre .bash .variable,pre .apache .tag,pre .apache .cbracket,pre .tex .command,pre .tex .special,pre .erlang_repl .function_or_atom,pre .markdown .header{color:#ba2121}
1368 .highlight-string,pre .string,pre .constant,pre .parent,pre .tag .value,pre .rules .value,pre .rules .value .number,pre .preprocessor,pre .ruby .symbol,pre .ruby .symbol .string,pre .aggregate,pre .template_tag,pre .django .variable,pre .smalltalk .class,pre .addition,pre .flow,pre .stream,pre .bash .variable,pre .apache .tag,pre .apache .cbracket,pre .tex .command,pre .tex .special,pre .erlang_repl .function_or_atom,pre .markdown .header{color:#ba2121}
1369 .highlight-comment,pre .comment,pre .annotation,pre .template_comment,pre .diff .header,pre .chunk,pre .markdown .blockquote{color:#408080;font-style:italic}
1369 .highlight-comment,pre .comment,pre .annotation,pre .template_comment,pre .diff .header,pre .chunk,pre .markdown .blockquote{color:#408080;font-style:italic}
1370 .highlight-number,pre .number,pre .date,pre .regexp,pre .literal,pre .smalltalk .symbol,pre .smalltalk .char,pre .go .constant,pre .change,pre .markdown .bullet,pre .markdown .link_url{color:#080}
1370 .highlight-number,pre .number,pre .date,pre .regexp,pre .literal,pre .smalltalk .symbol,pre .smalltalk .char,pre .go .constant,pre .change,pre .markdown .bullet,pre .markdown .link_url{color:#080}
1371 pre .label,pre .javadoc,pre .ruby .string,pre .decorator,pre .filter .argument,pre .localvars,pre .array,pre .attr_selector,pre .important,pre .pseudo,pre .pi,pre .doctype,pre .deletion,pre .envvar,pre .shebang,pre .apache .sqbracket,pre .nginx .built_in,pre .tex .formula,pre .erlang_repl .reserved,pre .prompt,pre .markdown .link_label,pre .vhdl .attribute,pre .clojure .attribute,pre .coffeescript .property{color:#88f}
1371 pre .label,pre .javadoc,pre .ruby .string,pre .decorator,pre .filter .argument,pre .localvars,pre .array,pre .attr_selector,pre .important,pre .pseudo,pre .pi,pre .doctype,pre .deletion,pre .envvar,pre .shebang,pre .apache .sqbracket,pre .nginx .built_in,pre .tex .formula,pre .erlang_repl .reserved,pre .prompt,pre .markdown .link_label,pre .vhdl .attribute,pre .clojure .attribute,pre .coffeescript .property{color:#88f}
1372 .highlight-keyword,pre .keyword,pre .id,pre .phpdoc,pre .aggregate,pre .css .tag,pre .javadoctag,pre .phpdoc,pre .yardoctag,pre .smalltalk .class,pre .winutils,pre .bash .variable,pre .apache .tag,pre .go .typename,pre .tex .command,pre .markdown .strong,pre .request,pre .status{color:#008000;font-weight:bold}
1372 .highlight-keyword,pre .keyword,pre .id,pre .phpdoc,pre .aggregate,pre .css .tag,pre .javadoctag,pre .phpdoc,pre .yardoctag,pre .smalltalk .class,pre .winutils,pre .bash .variable,pre .apache .tag,pre .go .typename,pre .tex .command,pre .markdown .strong,pre .request,pre .status{color:#008000;font-weight:bold}
1373 .highlight-builtin,pre .built_in{color:#008000}
1373 .highlight-builtin,pre .built_in{color:#008000}
1374 pre .markdown .emphasis{font-style:italic}
1374 pre .markdown .emphasis{font-style:italic}
1375 pre .nginx .built_in{font-weight:normal}
1375 pre .nginx .built_in{font-weight:normal}
1376 pre .coffeescript .javascript,pre .javascript .xml,pre .tex .formula,pre .xml .javascript,pre .xml .vbscript,pre .xml .css,pre .xml .cdata{opacity:.5}
1376 pre .coffeescript .javascript,pre .javascript .xml,pre .tex .formula,pre .xml .javascript,pre .xml .vbscript,pre .xml .css,pre .xml .cdata{opacity:.5}
1377 .cm-s-ipython span.cm-variable{color:#000}
1377 .cm-s-ipython span.cm-variable{color:#000}
1378 .cm-s-ipython span.cm-keyword{color:#008000;font-weight:bold}
1378 .cm-s-ipython span.cm-keyword{color:#008000;font-weight:bold}
1379 .cm-s-ipython span.cm-number{color:#080}
1379 .cm-s-ipython span.cm-number{color:#080}
1380 .cm-s-ipython span.cm-comment{color:#408080;font-style:italic}
1380 .cm-s-ipython span.cm-comment{color:#408080;font-style:italic}
1381 .cm-s-ipython span.cm-string{color:#ba2121}
1381 .cm-s-ipython span.cm-string{color:#ba2121}
1382 .cm-s-ipython span.cm-builtin{color:#008000}
1382 .cm-s-ipython span.cm-builtin{color:#008000}
1383 .cm-s-ipython span.cm-error{color:#f00}
1383 .cm-s-ipython span.cm-error{color:#f00}
1384 .cm-s-ipython span.cm-operator{color:#a2f;font-weight:bold}
1384 .cm-s-ipython span.cm-operator{color:#a2f;font-weight:bold}
1385 .cm-s-ipython span.cm-meta{color:#a2f}
1385 .cm-s-ipython span.cm-meta{color:#a2f}
1386 .cm-s-ipython span.cm-tab{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);background-position:right;background-repeat:no-repeat}
1386 .cm-s-ipython span.cm-tab{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);background-position:right;background-repeat:no-repeat}
1387 div.output_wrapper{position:relative;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}
1387 div.output_wrapper{position:relative;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}
1388 div.output_scroll{height:24em;width:100%;overflow:auto;border-radius:4px;-webkit-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);-moz-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);display:block}
1388 div.output_scroll{height:24em;width:100%;overflow:auto;border-radius:4px;-webkit-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);-moz-box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);box-shadow:inset 0 2px 8px rgba(0,0,0,0.8);display:block}
1389 div.output_collapsed{margin:0;padding:0;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}
1389 div.output_collapsed{margin:0;padding:0;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}
1390 div.out_prompt_overlay{height:100%;padding:0 .4em;position:absolute;border-radius:4px}
1390 div.out_prompt_overlay{height:100%;padding:0 .4em;position:absolute;border-radius:4px}
1391 div.out_prompt_overlay:hover{-webkit-box-shadow:inset 0 0 1px #000;-moz-box-shadow:inset 0 0 1px #000;box-shadow:inset 0 0 1px #000;background:rgba(240,240,240,0.5)}
1391 div.out_prompt_overlay:hover{-webkit-box-shadow:inset 0 0 1px #000;-moz-box-shadow:inset 0 0 1px #000;box-shadow:inset 0 0 1px #000;background:rgba(240,240,240,0.5)}
1392 div.output_prompt{color:#8b0000}
1392 div.output_prompt{color:#8b0000}
1393 div.output_area{padding:0;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}div.output_area .MathJax_Display{text-align:left !important}
1393 div.output_area{padding:0;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}div.output_area .MathJax_Display{text-align:left !important}
1394 div.output_area .rendered_html table{margin-left:0;margin-right:0}
1394 div.output_area .rendered_html table{margin-left:0;margin-right:0}
1395 div.output_area .rendered_html img{margin-left:0;margin-right:0}
1395 div.output_area .rendered_html img{margin-left:0;margin-right:0}
1396 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
1396 .output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
1397 div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
1397 @media (max-width:480px){div.output_area{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
1398 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1398 div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1399 div.output_text{text-align:left;color:#000;line-height:1.21429em}
1399 div.output_text{text-align:left;color:#000;line-height:1.21429em}
1400 div.output_stderr{background:#fdd;}
1400 div.output_stderr{background:#fdd;}
1401 div.output_latex{text-align:left}
1401 div.output_latex{text-align:left}
1402 div.output_javascript:empty{padding:0}
1402 div.output_javascript:empty{padding:0}
1403 .js-error{color:#8b0000}
1403 .js-error{color:#8b0000}
1404 div.raw_input_container{font-family:monospace;padding-top:5px}
1404 div.raw_input_container{font-family:monospace;padding-top:5px}
1405 span.raw_input_prompt{}
1405 span.raw_input_prompt{}
1406 input.raw_input{font-family:inherit;font-size:inherit;color:inherit;width:auto;vertical-align:baseline;padding:0 .25em;margin:0 .25em}
1406 input.raw_input{font-family:inherit;font-size:inherit;color:inherit;width:auto;vertical-align:baseline;padding:0 .25em;margin:0 .25em}
1407 input.raw_input:focus{box-shadow:none}
1407 input.raw_input:focus{box-shadow:none}
1408 p.p-space{margin-bottom:10px}
1408 p.p-space{margin-bottom:10px}
1409 .rendered_html{color:#000;}.rendered_html em{font-style:italic}
1409 .rendered_html{color:#000;}.rendered_html em{font-style:italic}
1410 .rendered_html strong{font-weight:bold}
1410 .rendered_html strong{font-weight:bold}
1411 .rendered_html u{text-decoration:underline}
1411 .rendered_html u{text-decoration:underline}
1412 .rendered_html :link{text-decoration:underline}
1412 .rendered_html :link{text-decoration:underline}
1413 .rendered_html :visited{text-decoration:underline}
1413 .rendered_html :visited{text-decoration:underline}
1414 .rendered_html h1{font-size:185.7%;margin:1.08em 0 0 0;font-weight:bold;line-height:1}
1414 .rendered_html h1{font-size:185.7%;margin:1.08em 0 0 0;font-weight:bold;line-height:1}
1415 .rendered_html h2{font-size:157.1%;margin:1.27em 0 0 0;font-weight:bold;line-height:1}
1415 .rendered_html h2{font-size:157.1%;margin:1.27em 0 0 0;font-weight:bold;line-height:1}
1416 .rendered_html h3{font-size:128.6%;margin:1.55em 0 0 0;font-weight:bold;line-height:1}
1416 .rendered_html h3{font-size:128.6%;margin:1.55em 0 0 0;font-weight:bold;line-height:1}
1417 .rendered_html h4{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1}
1417 .rendered_html h4{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1}
1418 .rendered_html h5{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
1418 .rendered_html h5{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
1419 .rendered_html h6{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
1419 .rendered_html h6{font-size:100%;margin:2em 0 0 0;font-weight:bold;line-height:1;font-style:italic}
1420 .rendered_html h1:first-child{margin-top:.538em}
1420 .rendered_html h1:first-child{margin-top:.538em}
1421 .rendered_html h2:first-child{margin-top:.636em}
1421 .rendered_html h2:first-child{margin-top:.636em}
1422 .rendered_html h3:first-child{margin-top:.777em}
1422 .rendered_html h3:first-child{margin-top:.777em}
1423 .rendered_html h4:first-child{margin-top:1em}
1423 .rendered_html h4:first-child{margin-top:1em}
1424 .rendered_html h5:first-child{margin-top:1em}
1424 .rendered_html h5:first-child{margin-top:1em}
1425 .rendered_html h6:first-child{margin-top:1em}
1425 .rendered_html h6:first-child{margin-top:1em}
1426 .rendered_html ul{list-style:disc;margin:0 2em}
1426 .rendered_html ul{list-style:disc;margin:0 2em}
1427 .rendered_html ul ul{list-style:square;margin:0 2em}
1427 .rendered_html ul ul{list-style:square;margin:0 2em}
1428 .rendered_html ul ul ul{list-style:circle;margin:0 2em}
1428 .rendered_html ul ul ul{list-style:circle;margin:0 2em}
1429 .rendered_html ol{list-style:decimal;margin:0 2em}
1429 .rendered_html ol{list-style:decimal;margin:0 2em}
1430 .rendered_html ol ol{list-style:upper-alpha;margin:0 2em}
1430 .rendered_html ol ol{list-style:upper-alpha;margin:0 2em}
1431 .rendered_html ol ol ol{list-style:lower-alpha;margin:0 2em}
1431 .rendered_html ol ol ol{list-style:lower-alpha;margin:0 2em}
1432 .rendered_html ol ol ol ol{list-style:lower-roman;margin:0 2em}
1432 .rendered_html ol ol ol ol{list-style:lower-roman;margin:0 2em}
1433 .rendered_html ol ol ol ol ol{list-style:decimal;margin:0 2em}
1433 .rendered_html ol ol ol ol ol{list-style:decimal;margin:0 2em}
1434 .rendered_html *+ul{margin-top:1em}
1434 .rendered_html *+ul{margin-top:1em}
1435 .rendered_html *+ol{margin-top:1em}
1435 .rendered_html *+ol{margin-top:1em}
1436 .rendered_html hr{color:#000;background-color:#000}
1436 .rendered_html hr{color:#000;background-color:#000}
1437 .rendered_html pre{margin:1em 2em}
1437 .rendered_html pre{margin:1em 2em}
1438 .rendered_html pre,.rendered_html code{border:0;background-color:#fff;color:#000;font-size:100%;padding:0}
1438 .rendered_html pre,.rendered_html code{border:0;background-color:#fff;color:#000;font-size:100%;padding:0}
1439 .rendered_html blockquote{margin:1em 2em}
1439 .rendered_html blockquote{margin:1em 2em}
1440 .rendered_html table{margin-left:auto;margin-right:auto;border:1px solid #000;border-collapse:collapse}
1440 .rendered_html table{margin-left:auto;margin-right:auto;border:1px solid #000;border-collapse:collapse}
1441 .rendered_html tr,.rendered_html th,.rendered_html td{border:1px solid #000;border-collapse:collapse;margin:1em 2em}
1441 .rendered_html tr,.rendered_html th,.rendered_html td{border:1px solid #000;border-collapse:collapse;margin:1em 2em}
1442 .rendered_html td,.rendered_html th{text-align:left;vertical-align:middle;padding:4px}
1442 .rendered_html td,.rendered_html th{text-align:left;vertical-align:middle;padding:4px}
1443 .rendered_html th{font-weight:bold}
1443 .rendered_html th{font-weight:bold}
1444 .rendered_html *+table{margin-top:1em}
1444 .rendered_html *+table{margin-top:1em}
1445 .rendered_html p{text-align:justify}
1445 .rendered_html p{text-align:justify}
1446 .rendered_html *+p{margin-top:1em}
1446 .rendered_html *+p{margin-top:1em}
1447 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
1447 .rendered_html img{display:block;margin-left:auto;margin-right:auto}
1448 .rendered_html *+img{margin-top:1em}
1448 .rendered_html *+img{margin-top:1em}
1449 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1449 div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
1450 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
1450 @media (max-width:480px){div.text_cell>div.prompt{display:none}}div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
1451 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
1451 a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
1452 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
1452 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
1453 div.cell.text_cell.rendered{padding:0}
1453 div.cell.text_cell.rendered{padding:0}
1454 .widget-area{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}.widget-area .widget-subarea{padding:.44em .4em .4em 1px;margin-left:6px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;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:2;-moz-box-flex:2;box-flex:2;flex:2;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1454 .widget-area{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}.widget-area .widget-subarea{padding:.44em .4em .4em 1px;margin-left:6px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;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:2;-moz-box-flex:2;box-flex:2;flex:2;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1455 .widget-hlabel{min-width:10ex;padding-right:8px;padding-top:3px;text-align:right;vertical-align:text-top}
1455 .widget-hlabel{min-width:10ex;padding-right:8px;padding-top:3px;text-align:right;vertical-align:text-top}
1456 .widget-vlabel{padding-bottom:5px;text-align:center;vertical-align:text-bottom}
1456 .widget-vlabel{padding-bottom:5px;text-align:center;vertical-align:text-bottom}
1457 .widget-hreadout{padding-left:8px;padding-top:3px;text-align:left;vertical-align:text-top}
1457 .widget-hreadout{padding-left:8px;padding-top:3px;text-align:left;vertical-align:text-top}
1458 .widget-vreadout{padding-top:5px;text-align:center;vertical-align:text-top}
1458 .widget-vreadout{padding-top:5px;text-align:center;vertical-align:text-top}
1459 .slide-track{border:1px solid #ccc;background:#fff;border-radius:4px;}
1459 .slide-track{border:1px solid #ccc;background:#fff;border-radius:4px;}
1460 .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:348px;height:5px;max-height:5px;margin-top:11px;margin-bottom:10px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-hslider .ui-slider{border:0 !important;background:none !important;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;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important}
1460 .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:348px;height:5px;max-height:5px;margin-top:11px;margin-bottom:10px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-hslider .ui-slider{border:0 !important;background:none !important;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;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important}
1461 .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-vslider .ui-slider{border:0 !important;background:none !important;margin-left:-4px;margin-top:5px;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}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px}
1461 .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #ccc;background:#fff;border-radius:4px;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}.widget-vslider .ui-slider{border:0 !important;background:none !important;margin-left:-4px;margin-top:5px;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}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px}
1462 .widget-text{width:350px;margin:0 !important}
1462 .widget-text{width:350px;margin:0 !important}
1463 .widget-listbox{width:364px;margin-bottom:0}
1463 .widget-listbox{width:364px;margin-bottom:0}
1464 .widget-numeric-text{width:150px;margin:0 !important}
1464 .widget-numeric-text{width:150px;margin:0 !important}
1465 .widget-progress{width:363px}.widget-progress .bar{-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none}
1465 .widget-progress{width:363px}.widget-progress .bar{-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none}
1466 .widget-combo-btn{min-width:138px;}
1466 .widget-combo-btn{min-width:138px;}
1467 .widget-box{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1467 .widget-box{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1468 .widget-hbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
1468 .widget-hbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
1469 .widget-hbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;height:30px}
1469 .widget-hbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;height:30px}
1470 .widget-vbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
1470 .widget-vbox{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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}
1471 .widget-vbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;width:30px}
1471 .widget-vbox-single{margin:5px;-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;justify-content:flex-start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start;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;width:30px}
1472 .widget-modal{overflow:hidden;position:absolute !important;top:0;left:0;margin-left:0 !important}
1472 .widget-modal{overflow:hidden;position:absolute !important;top:0;left:0;margin-left:0 !important}
1473 .widget-modal-body{max-height:none !important}
1473 .widget-modal-body{max-height:none !important}
1474 .widget-container{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1474 .widget-container{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-box-align:start;-moz-box-align:start;box-align:start;align-items:flex-start}
1475 .widget-radio-box{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;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding-top:4px}
1475 .widget-radio-box{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;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding-top:4px}
1476 .docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important}
1476 .docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important}
1477 body{background-color:#fff}
1477 body{background-color:#fff}
1478 body.notebook_app{overflow:hidden}
1478 body.notebook_app{overflow:hidden}
1479 span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%}
1479 @media (max-width:767px){body.notebook_app{padding-left:0;padding-right:0}}span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%}
1480 div#notebook_panel{margin:0 0 0 0;padding:0;-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}
1480 div#notebook_panel{margin:0 0 0 0;padding:0;-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}
1481 div#notebook{font-size:14px;line-height:20px;overflow-y:scroll;overflow-x:auto;width:100%;padding:1em 0 1em 0;margin:0;border-top:1px solid #ababab;outline:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
1481 div#notebook{font-size:14px;line-height:20px;overflow-y:scroll;overflow-x:auto;width:100%;padding:1em 0 1em 0;margin:0;border-top:1px solid #ababab;outline:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
1482 div.ui-widget-content{border:1px solid #ababab;outline:none}
1482 div.ui-widget-content{border:1px solid #ababab;outline:none}
1483 pre.dialog{background-color:#f7f7f7;border:1px solid #ddd;border-radius:4px;padding:.4em;padding-left:2em}
1483 pre.dialog{background-color:#f7f7f7;border:1px solid #ddd;border-radius:4px;padding:.4em;padding-left:2em}
1484 p.dialog{padding:.2em}
1484 p.dialog{padding:.2em}
1485 pre,code,kbd,samp{white-space:pre-wrap}
1485 pre,code,kbd,samp{white-space:pre-wrap}
1486 #fonttest{font-family:monospace}
1486 #fonttest{font-family:monospace}
1487 p{margin-bottom:0}
1487 p{margin-bottom:0}
1488 .end_space{height:200px}
1488 .end_space{height:200px}
1489 .celltoolbar{border:thin solid #cfcfcf;border-bottom:none;background:#eee;border-radius:3px 3px 0 0;width:100%;-webkit-box-pack:end;height:22px;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;-webkit-box-direction:reverse;-moz-box-direction:reverse;box-direction:reverse;flex-direction:row-reverse}
1489 .celltoolbar{border:thin solid #cfcfcf;border-bottom:none;background:#eee;border-radius:3px 3px 0 0;width:100%;-webkit-box-pack:end;height:22px;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;-webkit-box-direction:reverse;-moz-box-direction:reverse;box-direction:reverse;flex-direction:row-reverse}
1490 .ctb_hideshow{display:none;vertical-align:bottom;padding-right:2px}
1490 .ctb_hideshow{display:none;vertical-align:bottom;padding-right:2px}
1491 .celltoolbar>div{padding-top:0}
1491 .celltoolbar>div{padding-top:0}
1492 .ctb_global_show .ctb_show.ctb_hideshow{display:block}
1492 .ctb_global_show .ctb_show.ctb_hideshow{display:block}
1493 .ctb_global_show .ctb_show+.input_area,.ctb_global_show .ctb_show+div.text_cell_input{border-top-right-radius:0;border-top-left-radius:0}
1493 .ctb_global_show .ctb_show+.input_area,.ctb_global_show .ctb_show+div.text_cell_input{border-top-right-radius:0;border-top-left-radius:0}
1494 .celltoolbar .button_container select{margin:10px;margin-top:1px;margin-bottom:0;padding:0;font-size:87%;width:auto;display:inline-block;height:18px;line-height:18px;vertical-align:top}
1494 .celltoolbar .button_container select{margin:10px;margin-top:1px;margin-bottom:0;padding:0;font-size:87%;width:auto;display:inline-block;height:18px;line-height:18px;vertical-align:top}
1495 .celltoolbar label{display:inline-block;height:15px;line-height:15px;vertical-align:top}
1495 .celltoolbar label{display:inline-block;height:15px;line-height:15px;vertical-align:top}
1496 .celltoolbar label span{font-size:85%}
1496 .celltoolbar label span{font-size:85%}
1497 .celltoolbar input[type=checkbox]{margin:0;margin-left:4px;margin-right:4px}
1497 .celltoolbar input[type=checkbox]{margin:0;margin-left:4px;margin-right:4px}
1498 .celltoolbar .ui-button{border:none;vertical-align:top;height:20px;min-width:30px}
1498 .celltoolbar .ui-button{border:none;vertical-align:top;height:20px;min-width:30px}
1499 .completions{position:absolute;z-index:10;overflow:hidden;border:1px solid #ababab;border-radius:4px;-webkit-box-shadow:0 6px 10px -1px #adadad;-moz-box-shadow:0 6px 10px -1px #adadad;box-shadow:0 6px 10px -1px #adadad}
1499 .completions{position:absolute;z-index:10;overflow:hidden;border:1px solid #ababab;border-radius:4px;-webkit-box-shadow:0 6px 10px -1px #adadad;-moz-box-shadow:0 6px 10px -1px #adadad;box-shadow:0 6px 10px -1px #adadad}
1500 .completions select{background:#fff;outline:none;border:none;padding:0;margin:0;overflow:auto;font-family:monospace;font-size:110%;color:#000;width:auto}
1500 .completions select{background:#fff;outline:none;border:none;padding:0;margin:0;overflow:auto;font-family:monospace;font-size:110%;color:#000;width:auto}
1501 .completions select option.context{color:#0064cd}
1501 .completions select option.context{color:#0064cd}
1502 #menubar .navbar-inner{min-height:28px;border-top:1px;border-radius:0 0 4px 4px}
1502 #menubar .navbar-inner{min-height:28px;border-top:1px;border-radius:0 0 4px 4px}
1503 #menubar .navbar{margin-bottom:8px}
1503 #menubar .navbar{margin-bottom:8px}
1504 .nav-wrapper{border-bottom:1px solid #d4d4d4}
1504 .nav-wrapper{border-bottom:1px solid #d4d4d4}
1505 #menubar li.dropdown{line-height:12px}
1505 #menubar li.dropdown{line-height:12px}
1506 i.menu-icon{padding-top:4px}
1506 i.menu-icon{padding-top:4px}
1507 ul#help_menu li a{overflow:hidden;padding-right:2.2em}ul#help_menu li a i{margin-right:-1.2em}
1507 ul#help_menu li a{overflow:hidden;padding-right:2.2em}ul#help_menu li a i{margin-right:-1.2em}
1508 #notification_area{z-index:10}
1508 #notification_area{z-index:10}
1509 .indicator_area{color:#777;padding:4px 3px;margin:0;width:11px;z-index:10;text-align:center}
1509 .indicator_area{color:#777;padding:4px 3px;margin:0;width:11px;z-index:10;text-align:center}
1510 #kernel_indicator{margin-right:-16px}
1510 #kernel_indicator{margin-right:-16px}
1511 .edit_mode_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f040"}
1511 .edit_mode_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f040"}
1512 .command_mode_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:' '}
1512 .command_mode_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:' '}
1513 .kernel_idle_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f10c"}
1513 .kernel_idle_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f10c"}
1514 .kernel_busy_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f111"}
1514 .kernel_busy_icon:before{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f111"}
1515 .notification_widget{color:#777;padding:1px 12px;margin:2px 4px;z-index:10;border:1px solid #ccc;border-radius:4px;background:rgba(240,240,240,0.5)}.notification_widget.span{padding-right:2px}
1515 .notification_widget{color:#777;padding:1px 12px;margin:2px 4px;z-index:10;border:1px solid #ccc;border-radius:4px;background:rgba(240,240,240,0.5)}.notification_widget.span{padding-right:2px}
1516 div#pager_splitter{height:8px}
1516 div#pager_splitter{height:8px}
1517 #pager-container{position:relative;padding:15px 0}
1517 #pager-container{position:relative;padding:15px 0}
1518 div#pager{font-size:14px;line-height:20px;overflow:auto;display:none}div#pager pre{font-size:13px;line-height:1.21429em;color:#000;background-color:#f7f7f7;padding:.4em}
1518 div#pager{font-size:14px;line-height:20px;overflow:auto;display:none}div#pager pre{font-size:13px;line-height:1.21429em;color:#000;background-color:#f7f7f7;padding:.4em}
1519 .quickhelp{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}
1519 .quickhelp{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}
1520 .shortcut_key{display:inline-block;width:20ex;text-align:right;font-family:monospace}
1520 .shortcut_key{display:inline-block;width:20ex;text-align:right;font-family:monospace}
1521 .shortcut_descr{display:inline-block;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1521 .shortcut_descr{display:inline-block;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
1522 span#save_widget{padding:0 5px;margin-top:12px}
1522 span#save_widget{padding:0 5px;margin-top:12px}
1523 span#checkpoint_status,span#autosave_status{font-size:small}
1523 span#checkpoint_status,span#autosave_status{font-size:small}
1524 @media (max-width:767px){span#save_widget{font-size:small} span#checkpoint_status,span#autosave_status{font-size:x-small}}@media (max-width:767px){span#checkpoint_status,span#autosave_status{display:none}}@media (min-width:768px) and (max-width:979px){span#checkpoint_status{display:none} span#autosave_status{font-size:x-small}}.toolbar{padding:0 10px;margin-top:-5px}.toolbar select,.toolbar label{width:auto;height:26px;vertical-align:middle;margin-right:2px;margin-bottom:0;display:inline;font-size:92%;margin-left:.3em;margin-right:.3em;padding:0;padding-top:3px}
1524 @media (max-width:767px){span#save_widget{font-size:small} span#checkpoint_status,span#autosave_status{font-size:x-small}}@media (max-width:767px){span#checkpoint_status,span#autosave_status{display:none}}@media (min-width:768px) and (max-width:979px){span#checkpoint_status{display:none} span#autosave_status{font-size:x-small}}.toolbar{padding:0 10px;margin-top:-5px}.toolbar select,.toolbar label{width:auto;height:26px;vertical-align:middle;margin-right:2px;margin-bottom:0;display:inline;font-size:92%;margin-left:.3em;margin-right:.3em;padding:0;padding-top:3px}
1525 .toolbar .btn{padding:2px 8px}
1525 .toolbar .btn{padding:2px 8px}
1526 .toolbar .btn-group{margin-top:0}
1526 .toolbar .btn-group{margin-top:0}
1527 .toolbar-inner{border:none !important;-webkit-box-shadow:none !important;-moz-box-shadow:none !important;box-shadow:none !important}
1527 .toolbar-inner{border:none !important;-webkit-box-shadow:none !important;-moz-box-shadow:none !important;box-shadow:none !important}
1528 #maintoolbar{margin-bottom:0}
1528 #maintoolbar{margin-bottom:0}
1529 @-moz-keyframes fadeOut{from{opacity:1} to{opacity:0}}@-webkit-keyframes fadeOut{from{opacity:1} to{opacity:0}}@-moz-keyframes fadeIn{from{opacity:0} to{opacity:1}}@-webkit-keyframes fadeIn{from{opacity:0} to{opacity:1}}.bigtooltip{overflow:auto;height:200px;-webkit-transition-property:height;-webkit-transition-duration:500ms;-moz-transition-property:height;-moz-transition-duration:500ms;transition-property:height;transition-duration:500ms}
1529 @-moz-keyframes fadeOut{from{opacity:1} to{opacity:0}}@-webkit-keyframes fadeOut{from{opacity:1} to{opacity:0}}@-moz-keyframes fadeIn{from{opacity:0} to{opacity:1}}@-webkit-keyframes fadeIn{from{opacity:0} to{opacity:1}}.bigtooltip{overflow:auto;height:200px;-webkit-transition-property:height;-webkit-transition-duration:500ms;-moz-transition-property:height;-moz-transition-duration:500ms;transition-property:height;transition-duration:500ms}
1530 .smalltooltip{-webkit-transition-property:height;-webkit-transition-duration:500ms;-moz-transition-property:height;-moz-transition-duration:500ms;transition-property:height;transition-duration:500ms;text-overflow:ellipsis;overflow:hidden;height:80px}
1530 .smalltooltip{-webkit-transition-property:height;-webkit-transition-duration:500ms;-moz-transition-property:height;-moz-transition-duration:500ms;transition-property:height;transition-duration:500ms;text-overflow:ellipsis;overflow:hidden;height:80px}
1531 .tooltipbuttons{position:absolute;padding-right:15px;top:0;right:0}
1531 .tooltipbuttons{position:absolute;padding-right:15px;top:0;right:0}
1532 .tooltiptext{padding-right:30px}
1532 .tooltiptext{padding-right:30px}
1533 .ipython_tooltip{max-width:700px;-webkit-animation:fadeOut 400ms;-moz-animation:fadeOut 400ms;animation:fadeOut 400ms;-webkit-animation:fadeIn 400ms;-moz-animation:fadeIn 400ms;animation:fadeIn 400ms;vertical-align:middle;background-color:#f7f7f7;overflow:visible;border:#ababab 1px solid;outline:none;padding:3px;margin:0;padding-left:7px;font-family:monospace;min-height:50px;-moz-box-shadow:0 6px 10px -1px #adadad;-webkit-box-shadow:0 6px 10px -1px #adadad;box-shadow:0 6px 10px -1px #adadad;border-radius:4px;position:absolute;z-index:2}.ipython_tooltip a{float:right}
1533 .ipython_tooltip{max-width:700px;-webkit-animation:fadeOut 400ms;-moz-animation:fadeOut 400ms;animation:fadeOut 400ms;-webkit-animation:fadeIn 400ms;-moz-animation:fadeIn 400ms;animation:fadeIn 400ms;vertical-align:middle;background-color:#f7f7f7;overflow:visible;border:#ababab 1px solid;outline:none;padding:3px;margin:0;padding-left:7px;font-family:monospace;min-height:50px;-moz-box-shadow:0 6px 10px -1px #adadad;-webkit-box-shadow:0 6px 10px -1px #adadad;box-shadow:0 6px 10px -1px #adadad;border-radius:4px;position:absolute;z-index:2}.ipython_tooltip a{float:right}
1534 .ipython_tooltip .tooltiptext pre{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;font-size:100%;background-color:#f7f7f7}
1534 .ipython_tooltip .tooltiptext pre{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;font-size:100%;background-color:#f7f7f7}
1535 .pretooltiparrow{left:0;margin:0;top:-16px;width:40px;height:16px;overflow:hidden;position:absolute}
1535 .pretooltiparrow{left:0;margin:0;top:-16px;width:40px;height:16px;overflow:hidden;position:absolute}
1536 .pretooltiparrow:before{background-color:#f7f7f7;border:1px #ababab solid;z-index:11;content:"";position:absolute;left:15px;top:10px;width:25px;height:25px;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg)}
1536 .pretooltiparrow:before{background-color:#f7f7f7;border:1px #ababab solid;z-index:11;content:"";position:absolute;left:15px;top:10px;width:25px;height:25px;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg)}
@@ -1,362 +1,362 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16
16
17 {{super()}}
17 {{super()}}
18
18
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20
20
21 {% endblock %}
21 {% endblock %}
22
22
23 {% block params %}
23 {% block params %}
24
24
25 data-project="{{project}}"
25 data-project="{{project}}"
26 data-base-url="{{base_url}}"
26 data-base-url="{{base_url}}"
27 data-notebook-name="{{notebook_name}}"
27 data-notebook-name="{{notebook_name}}"
28 data-notebook-path="{{notebook_path}}"
28 data-notebook-path="{{notebook_path}}"
29 class="notebook_app"
29 class="notebook_app"
30
30
31 {% endblock %}
31 {% endblock %}
32
32
33
33
34 {% block header %}
34 {% block header %}
35
35
36 <span id="save_widget" class="nav pull-left">
36 <span id="save_widget" class="nav pull-left">
37 <span id="notebook_name"></span>
37 <span id="notebook_name"></span>
38 <span id="checkpoint_status"></span>
38 <span id="checkpoint_status"></span>
39 <span id="autosave_status"></span>
39 <span id="autosave_status"></span>
40 </span>
40 </span>
41
41
42 {% endblock %}
42 {% endblock %}
43
43
44
44
45 {% block site %}
45 {% block site %}
46
46
47 <div id="menubar-container" class="container">
47 <div id="menubar-container" class="container">
48 <div id="menubar">
48 <div id="menubar">
49 <div class="navbar">
49 <div class="navbar">
50 <div class="navbar-inner">
50 <div class="navbar-inner">
51 <div class="container">
51 <div class="container">
52 <ul id="menus" class="nav">
52 <ul id="menus" class="nav">
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
54 <ul id="file_menu" class="dropdown-menu">
54 <ul id="file_menu" class="dropdown-menu">
55 <li id="new_notebook"
55 <li id="new_notebook"
56 title="Make a new notebook (Opens a new window)">
56 title="Make a new notebook (Opens a new window)">
57 <a href="#">New</a></li>
57 <a href="#">New</a></li>
58 <li id="open_notebook"
58 <li id="open_notebook"
59 title="Opens a new window with the Dashboard view">
59 title="Opens a new window with the Dashboard view">
60 <a href="#">Open...</a></li>
60 <a href="#">Open...</a></li>
61 <!-- <hr/> -->
61 <!-- <hr/> -->
62 <li class="divider"></li>
62 <li class="divider"></li>
63 <li id="copy_notebook"
63 <li id="copy_notebook"
64 title="Open a copy of this notebook's contents and start a new kernel">
64 title="Open a copy of this notebook's contents and start a new kernel">
65 <a href="#">Make a Copy...</a></li>
65 <a href="#">Make a Copy...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
68 <!-- <hr/> -->
68 <!-- <hr/> -->
69 <li class="divider"></li>
69 <li class="divider"></li>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
71 <ul class="dropdown-menu">
71 <ul class="dropdown-menu">
72 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
77 </ul>
77 </ul>
78 </li>
78 </li>
79 <li class="divider"></li>
79 <li class="divider"></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
82 <ul class="dropdown-menu">
82 <ul class="dropdown-menu">
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
87 </ul>
87 </ul>
88 </li>
88 </li>
89 <li class="divider"></li>
89 <li class="divider"></li>
90 <li id="trust_notebook"
90 <li id="trust_notebook"
91 title="Trust the output of this notebook">
91 title="Trust the output of this notebook">
92 <a href="#" >Trust Notebook</a></li>
92 <a href="#" >Trust Notebook</a></li>
93 <li class="divider"></li>
93 <li class="divider"></li>
94 <li id="kill_and_exit"
94 <li id="kill_and_exit"
95 title="Shutdown this notebook's kernel, and close this window">
95 title="Shutdown this notebook's kernel, and close this window">
96 <a href="#" >Close and halt</a></li>
96 <a href="#" >Close and halt</a></li>
97 </ul>
97 </ul>
98 </li>
98 </li>
99 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
99 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
100 <ul id="edit_menu" class="dropdown-menu">
100 <ul id="edit_menu" class="dropdown-menu">
101 <li id="cut_cell"><a href="#">Cut Cell</a></li>
101 <li id="cut_cell"><a href="#">Cut Cell</a></li>
102 <li id="copy_cell"><a href="#">Copy Cell</a></li>
102 <li id="copy_cell"><a href="#">Copy Cell</a></li>
103 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
103 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
104 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
104 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
105 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
105 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
106 <li id="delete_cell"><a href="#">Delete Cell</a></li>
106 <li id="delete_cell"><a href="#">Delete Cell</a></li>
107 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
107 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
108 <li class="divider"></li>
108 <li class="divider"></li>
109 <li id="split_cell"><a href="#">Split Cell</a></li>
109 <li id="split_cell"><a href="#">Split Cell</a></li>
110 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
110 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
111 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
111 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
112 <li class="divider"></li>
112 <li class="divider"></li>
113 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
113 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
114 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
114 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
115 <li class="divider"></li>
115 <li class="divider"></li>
116 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
116 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
117 </ul>
117 </ul>
118 </li>
118 </li>
119 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
119 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
120 <ul id="view_menu" class="dropdown-menu">
120 <ul id="view_menu" class="dropdown-menu">
121 <li id="toggle_header"
121 <li id="toggle_header"
122 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
122 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
123 <a href="#">Toggle Header</a></li>
123 <a href="#">Toggle Header</a></li>
124 <li id="toggle_toolbar"
124 <li id="toggle_toolbar"
125 title="Show/Hide the action icons (below menu bar)">
125 title="Show/Hide the action icons (below menu bar)">
126 <a href="#">Toggle Toolbar</a></li>
126 <a href="#">Toggle Toolbar</a></li>
127 </ul>
127 </ul>
128 </li>
128 </li>
129 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
129 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
130 <ul id="insert_menu" class="dropdown-menu">
130 <ul id="insert_menu" class="dropdown-menu">
131 <li id="insert_cell_above"
131 <li id="insert_cell_above"
132 title="Insert an empty Code cell above the currently active cell">
132 title="Insert an empty Code cell above the currently active cell">
133 <a href="#">Insert Cell Above</a></li>
133 <a href="#">Insert Cell Above</a></li>
134 <li id="insert_cell_below"
134 <li id="insert_cell_below"
135 title="Insert an empty Code cell below the currently active cell">
135 title="Insert an empty Code cell below the currently active cell">
136 <a href="#">Insert Cell Below</a></li>
136 <a href="#">Insert Cell Below</a></li>
137 </ul>
137 </ul>
138 </li>
138 </li>
139 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
139 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
140 <ul id="cell_menu" class="dropdown-menu">
140 <ul id="cell_menu" class="dropdown-menu">
141 <li id="run_cell" title="Run this cell, and move cursor to the next one">
141 <li id="run_cell" title="Run this cell, and move cursor to the next one">
142 <a href="#">Run</a></li>
142 <a href="#">Run</a></li>
143 <li id="run_cell_select_below" title="Run this cell, select below">
143 <li id="run_cell_select_below" title="Run this cell, select below">
144 <a href="#">Run and Select Below</a></li>
144 <a href="#">Run and Select Below</a></li>
145 <li id="run_cell_insert_below" title="Run this cell, insert below">
145 <li id="run_cell_insert_below" title="Run this cell, insert below">
146 <a href="#">Run and Insert Below</a></li>
146 <a href="#">Run and Insert Below</a></li>
147 <li id="run_all_cells" title="Run all cells in the notebook">
147 <li id="run_all_cells" title="Run all cells in the notebook">
148 <a href="#">Run All</a></li>
148 <a href="#">Run All</a></li>
149 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
149 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
150 <a href="#">Run All Above</a></li>
150 <a href="#">Run All Above</a></li>
151 <li id="run_all_cells_below" title="Run this cell and all cells below it">
151 <li id="run_all_cells_below" title="Run this cell and all cells below it">
152 <a href="#">Run All Below</a></li>
152 <a href="#">Run All Below</a></li>
153 <li class="divider"></li>
153 <li class="divider"></li>
154 <li id="change_cell_type" class="dropdown-submenu"
154 <li id="change_cell_type" class="dropdown-submenu"
155 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
155 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
156 <a href="#">Cell Type</a>
156 <a href="#">Cell Type</a>
157 <ul class="dropdown-menu">
157 <ul class="dropdown-menu">
158 <li id="to_code"
158 <li id="to_code"
159 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
159 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
160 <a href="#">Code</a></li>
160 <a href="#">Code</a></li>
161 <li id="to_markdown"
161 <li id="to_markdown"
162 title="Contents will be rendered as HTML and serve as explanatory text">
162 title="Contents will be rendered as HTML and serve as explanatory text">
163 <a href="#">Markdown</a></li>
163 <a href="#">Markdown</a></li>
164 <li id="to_raw"
164 <li id="to_raw"
165 title="Contents will pass through nbconvert unmodified">
165 title="Contents will pass through nbconvert unmodified">
166 <a href="#">Raw NBConvert</a></li>
166 <a href="#">Raw NBConvert</a></li>
167 <li id="to_heading1"><a href="#">Heading 1</a></li>
167 <li id="to_heading1"><a href="#">Heading 1</a></li>
168 <li id="to_heading2"><a href="#">Heading 2</a></li>
168 <li id="to_heading2"><a href="#">Heading 2</a></li>
169 <li id="to_heading3"><a href="#">Heading 3</a></li>
169 <li id="to_heading3"><a href="#">Heading 3</a></li>
170 <li id="to_heading4"><a href="#">Heading 4</a></li>
170 <li id="to_heading4"><a href="#">Heading 4</a></li>
171 <li id="to_heading5"><a href="#">Heading 5</a></li>
171 <li id="to_heading5"><a href="#">Heading 5</a></li>
172 <li id="to_heading6"><a href="#">Heading 6</a></li>
172 <li id="to_heading6"><a href="#">Heading 6</a></li>
173 </ul>
173 </ul>
174 </li>
174 </li>
175 <li class="divider"></li>
175 <li class="divider"></li>
176 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
176 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
177 <ul class="dropdown-menu">
177 <ul class="dropdown-menu">
178 <li id="toggle_current_output"
178 <li id="toggle_current_output"
179 title="Hide/Show the output of the current cell">
179 title="Hide/Show the output of the current cell">
180 <a href="#">Toggle</a>
180 <a href="#">Toggle</a>
181 </li>
181 </li>
182 <li id="toggle_current_output_scroll"
182 <li id="toggle_current_output_scroll"
183 title="Scroll the output of the current cell">
183 title="Scroll the output of the current cell">
184 <a href="#">Toggle Scrolling</a>
184 <a href="#">Toggle Scrolling</a>
185 </li>
185 </li>
186 <li id="clear_current_output"
186 <li id="clear_current_output"
187 title="Clear the output of the current cell">
187 title="Clear the output of the current cell">
188 <a href="#">Clear</a>
188 <a href="#">Clear</a>
189 </li>
189 </li>
190 </ul>
190 </ul>
191 </li>
191 </li>
192 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
192 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
193 <ul class="dropdown-menu">
193 <ul class="dropdown-menu">
194 <li id="toggle_all_output"
194 <li id="toggle_all_output"
195 title="Hide/Show the output of all cells">
195 title="Hide/Show the output of all cells">
196 <a href="#">Toggle</a>
196 <a href="#">Toggle</a>
197 </li>
197 </li>
198 <li id="toggle_all_output_scroll"
198 <li id="toggle_all_output_scroll"
199 title="Scroll the output of all cells">
199 title="Scroll the output of all cells">
200 <a href="#">Toggle Scrolling</a>
200 <a href="#">Toggle Scrolling</a>
201 </li>
201 </li>
202 <li id="clear_all_output"
202 <li id="clear_all_output"
203 title="Clear the output of all cells">
203 title="Clear the output of all cells">
204 <a href="#">Clear</a>
204 <a href="#">Clear</a>
205 </li>
205 </li>
206 </ul>
206 </ul>
207 </li>
207 </li>
208 </ul>
208 </ul>
209 </li>
209 </li>
210 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
210 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
211 <ul id="kernel_menu" class="dropdown-menu">
211 <ul id="kernel_menu" class="dropdown-menu">
212 <li id="int_kernel"
212 <li id="int_kernel"
213 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
213 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
214 <a href="#">Interrupt</a></li>
214 <a href="#">Interrupt</a></li>
215 <li id="restart_kernel"
215 <li id="restart_kernel"
216 title="Restart the Kernel">
216 title="Restart the Kernel">
217 <a href="#">Restart</a></li>
217 <a href="#">Restart</a></li>
218 </ul>
218 </ul>
219 </li>
219 </li>
220 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
220 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
221 <ul id="help_menu" class="dropdown-menu">
221 <ul id="help_menu" class="dropdown-menu">
222 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
222 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
223 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
223 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
224 <li class="divider"></li>
224 <li class="divider"></li>
225 {% set
225 {% set
226 sections = (
226 sections = (
227 (
227 (
228 ("http://ipython.org/documentation.html","IPython Help",True),
228 ("http://ipython.org/documentation.html","IPython Help",True),
229 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
229 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
230 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
230 ("http://ipython.org/ipython-doc/2/notebook/notebook.html","Notebook Help",True),
231 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
231 ("http://ipython.org/ipython-doc/2/notebook/cm_keyboard.html","Editor Shortcuts",True),
232 ),(
232 ),(
233 ("http://docs.python.org","Python",True),
233 ("http://docs.python.org","Python",True),
234 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
234 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
235 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
235 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
236 ("http://matplotlib.org/contents.html","Matplotlib",True),
236 ("http://matplotlib.org/contents.html","Matplotlib",True),
237 ("http://docs.sympy.org/dev/index.html","SymPy",True),
237 ("http://docs.sympy.org/latest/index.html","SymPy",True),
238 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
238 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
239 )
239 )
240 )
240 )
241 %}
241 %}
242
242
243 {% for helplinks in sections %}
243 {% for helplinks in sections %}
244 {% for link in helplinks %}
244 {% for link in helplinks %}
245 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
245 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
246 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
246 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
247 {{link[1]}}
247 {{link[1]}}
248 </a></li>
248 </a></li>
249 {% endfor %}
249 {% endfor %}
250 {% if not loop.last %}
250 {% if not loop.last %}
251 <li class="divider"></li>
251 <li class="divider"></li>
252 {% endif %}
252 {% endif %}
253 {% endfor %}
253 {% endfor %}
254 </li>
254 </li>
255 </ul>
255 </ul>
256 </li>
256 </li>
257 </ul>
257 </ul>
258 <div id="kernel_indicator" class="indicator_area pull-right">
258 <div id="kernel_indicator" class="indicator_area pull-right">
259 <i id="kernel_indicator_icon"></i>
259 <i id="kernel_indicator_icon"></i>
260 </div>
260 </div>
261 <div id="modal_indicator" class="indicator_area pull-right">
261 <div id="modal_indicator" class="indicator_area pull-right">
262 <i id="modal_indicator_icon"></i>
262 <i id="modal_indicator_icon"></i>
263 </div>
263 </div>
264 <div id="notification_area"></div>
264 <div id="notification_area"></div>
265 </div>
265 </div>
266 </div>
266 </div>
267 </div>
267 </div>
268 </div>
268 </div>
269 <div id="maintoolbar" class="navbar">
269 <div id="maintoolbar" class="navbar">
270 <div class="toolbar-inner navbar-inner navbar-nobg">
270 <div class="toolbar-inner navbar-inner navbar-nobg">
271 <div id="maintoolbar-container" class="container"></div>
271 <div id="maintoolbar-container" class="container"></div>
272 </div>
272 </div>
273 </div>
273 </div>
274 </div>
274 </div>
275
275
276 <div id="ipython-main-app">
276 <div id="ipython-main-app">
277
277
278 <div id="notebook_panel">
278 <div id="notebook_panel">
279 <div id="notebook"></div>
279 <div id="notebook"></div>
280 <div id="pager_splitter"></div>
280 <div id="pager_splitter"></div>
281 <div id="pager">
281 <div id="pager">
282 <div id='pager_button_area'>
282 <div id='pager_button_area'>
283 </div>
283 </div>
284 <div id="pager-container" class="container"></div>
284 <div id="pager-container" class="container"></div>
285 </div>
285 </div>
286 </div>
286 </div>
287
287
288 </div>
288 </div>
289 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
289 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
290
290
291
291
292 {% endblock %}
292 {% endblock %}
293
293
294
294
295 {% block script %}
295 {% block script %}
296
296
297 {{super()}}
297 {{super()}}
298
298
299 <script src="{{ static_url("components/google-caja/html-css-sanitizer-minified.js") }}" charset="utf-8"></script>
299 <script src="{{ static_url("components/google-caja/html-css-sanitizer-minified.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
301 <script type="text/javascript">
301 <script type="text/javascript">
302 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
302 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
303 </script>
303 </script>
304 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
313 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
313 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
314 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
314 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
315 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
315 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
316 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
316 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
317 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
317 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
318 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
318 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
319
319
320 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
320 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
321
321
322 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
322 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
323
323
324 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
324 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("base/js/keyboard.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("base/js/keyboard.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("base/js/security.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("base/js/security.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
344 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
344 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
345 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
345 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
346 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
346 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
347 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
347 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
348 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
348 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
350 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
350 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
351 <script src="{{ static_url("notebook/js/tour.js") }}" type="text/javascript" charset="utf-8"></script>
351 <script src="{{ static_url("notebook/js/tour.js") }}" type="text/javascript" charset="utf-8"></script>
352
352
353 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
353 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
354 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
354 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
355
355
356 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
356 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
357
357
358 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
358 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
359 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
359 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
360 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
360 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
361
361
362 {% endblock %}
362 {% endblock %}
@@ -1,21 +1,21 b''
1 //
1 //
2 // Check for errors with up and down arrow presses in an empty notebook.
2 // Check for errors with up and down arrow presses in an empty notebook.
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 var result = this.evaluate(function() {
5 var result = this.evaluate(function() {
6 var ncells = IPython.notebook.ncells();
6 var ncells = IPython.notebook.ncells();
7 var i;
7 var i;
8
8
9 // Delete all cells.
9 // Delete all cells.
10 for (i = 0; i < ncells; i++) {
10 for (i = 0; i < ncells; i++) {
11 IPython.notebook.delete_cell();
11 IPython.notebook.delete_cell();
12 }
12 }
13
13
14 // Simulate the "up arrow" and "down arrow" keys.
15 //
16 IPython.keyboard.trigger_keydown('up');
17 IPython.keyboard.trigger_keydown('down');
18 return true;
14 return true;
19 });
15 });
16
17 // Simulate the "up arrow" and "down arrow" keys.
18 this.trigger_keydown('up');
19 this.trigger_keydown('down');
20 this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
20 this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
21 });
21 });
@@ -1,71 +1,78 b''
1 //
1 //
2 // Test code cell execution.
2 // Test code cell execution.
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 this.evaluate(function () {
5 this.evaluate(function () {
6 var cell = IPython.notebook.get_cell(0);
6 var cell = IPython.notebook.get_cell(0);
7 cell.set_text('a=10; print(a)');
7 cell.set_text('a=10; print(a)');
8 cell.execute();
8 cell.execute();
9 });
9 });
10
10
11 this.wait_for_output(0);
11 this.wait_for_output(0);
12
12
13 // refactor this into just a get_output(0)
13 // refactor this into just a get_output(0)
14 this.then(function () {
14 this.then(function () {
15 var result = this.get_output_cell(0);
15 var result = this.get_output_cell(0);
16 this.test.assertEquals(result.text, '10\n', 'cell execute (using js)');
16 this.test.assertEquals(result.text, '10\n', 'cell execute (using js)');
17 });
17 });
18
18
19
19
20 // do it again with the keyboard shortcut
20 // do it again with the keyboard shortcut
21 this.thenEvaluate(function () {
21 this.thenEvaluate(function () {
22 var cell = IPython.notebook.get_cell(0);
22 var cell = IPython.notebook.get_cell(0);
23 cell.set_text('a=11; print(a)');
23 cell.set_text('a=11; print(a)');
24 cell.clear_output();
24 cell.clear_output();
25 IPython.keyboard.trigger_keydown('shift-enter');
25 });
26
27 this.then(function(){
28
29 this.trigger_keydown('shift-enter');
26 });
30 });
27
31
28 this.wait_for_output(0);
32 this.wait_for_output(0);
29
33
30 this.then(function () {
34 this.then(function () {
31 var result = this.get_output_cell(0);
35 var result = this.get_output_cell(0);
32 var num_cells = this.get_cells_length();
36 var num_cells = this.get_cells_length();
33 this.test.assertEquals(result.text, '11\n', 'cell execute (using ctrl-enter)');
37 this.test.assertEquals(result.text, '11\n', 'cell execute (using ctrl-enter)');
34 this.test.assertEquals(num_cells, 2, 'shift-enter adds a new cell at the bottom')
38 this.test.assertEquals(num_cells, 2, 'shift-enter adds a new cell at the bottom')
35 });
39 });
36
40
37 // do it again with the keyboard shortcut
41 // do it again with the keyboard shortcut
38 this.thenEvaluate(function () {
42 this.thenEvaluate(function () {
39 IPython.notebook.select(1);
43 IPython.notebook.select(1);
40 IPython.notebook.delete_cell();
44 IPython.notebook.delete_cell();
41 var cell = IPython.notebook.get_cell(0);
45 var cell = IPython.notebook.get_cell(0);
42 cell.set_text('a=12; print(a)');
46 cell.set_text('a=12; print(a)');
43 cell.clear_output();
47 cell.clear_output();
44 IPython.keyboard.trigger_keydown('ctrl-enter');
48 });
49
50 this.then(function(){
51 this.trigger_keydown('ctrl-enter');
45 });
52 });
46
53
47 this.wait_for_output(0);
54 this.wait_for_output(0);
48
55
49 this.then(function () {
56 this.then(function () {
50 var result = this.get_output_cell(0);
57 var result = this.get_output_cell(0);
51 var num_cells = this.get_cells_length();
58 var num_cells = this.get_cells_length();
52 this.test.assertEquals(result.text, '12\n', 'cell execute (using shift-enter)');
59 this.test.assertEquals(result.text, '12\n', 'cell execute (using shift-enter)');
53 this.test.assertEquals(num_cells, 1, 'ctrl-enter adds no new cell at the bottom')
60 this.test.assertEquals(num_cells, 1, 'ctrl-enter adds no new cell at the bottom')
54 });
61 });
55
62
56 // press the "play" triangle button in the toolbar
63 // press the "play" triangle button in the toolbar
57 this.thenEvaluate(function () {
64 this.thenEvaluate(function () {
58 var cell = IPython.notebook.get_cell(0);
65 var cell = IPython.notebook.get_cell(0);
59 IPython.notebook.select(0);
66 IPython.notebook.select(0);
60 cell.clear_output();
67 cell.clear_output();
61 cell.set_text('a=13; print(a)');
68 cell.set_text('a=13; print(a)');
62 $('#run_b').click();
69 $('#run_b').click();
63 });
70 });
64
71
65 this.wait_for_output(0);
72 this.wait_for_output(0);
66
73
67 this.then(function () {
74 this.then(function () {
68 var result = this.get_output_cell(0);
75 var result = this.get_output_cell(0);
69 this.test.assertEquals(result.text, '13\n', 'cell execute (using "play" toolbar button)')
76 this.test.assertEquals(result.text, '13\n', 'cell execute (using "play" toolbar button)')
70 });
77 });
71 });
78 });
@@ -1,44 +1,44 b''
1 //
1 //
2 // Test kernel interrupt
2 // Test kernel interrupt
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 this.evaluate(function () {
5 this.evaluate(function () {
6 var cell = IPython.notebook.get_cell(0);
6 var cell = IPython.notebook.get_cell(0);
7 cell.set_text(
7 cell.set_text(
8 'import time'+
8 'import time'+
9 '\nfor x in range(3):'+
9 '\nfor x in range(3):'+
10 '\n time.sleep(1)'
10 '\n time.sleep(1)'
11 );
11 );
12 cell.execute();
12 cell.execute();
13 });
13 });
14
14
15 this.wait_for_busy();
15 this.wait_for_busy();
16
16
17 // interrupt using menu item (Kernel -> Interrupt)
17 // interrupt using menu item (Kernel -> Interrupt)
18 this.thenClick('li#int_kernel');
18 this.thenClick('li#int_kernel');
19
19
20 this.wait_for_output(0);
20 this.wait_for_output(0);
21
21
22 this.then(function () {
22 this.then(function () {
23 var result = this.get_output_cell(0);
23 var result = this.get_output_cell(0);
24 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (mouseclick)');
24 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (mouseclick)');
25 });
25 });
26
26
27 // run cell 0 again, now interrupting using keyboard shortcut
27 // run cell 0 again, now interrupting using keyboard shortcut
28 this.thenEvaluate(function () {
28 this.thenEvaluate(function () {
29 cell.clear_output();
29 cell.clear_output();
30 cell.execute();
30 cell.execute();
31 });
31 });
32
32
33 // interrupt using Ctrl-M I keyboard shortcut
33 // interrupt using Ctrl-M I keyboard shortcut
34 this.thenEvaluate( function() {
34 this.then(function(){
35 IPython.keyboard.trigger_keydown('i');
35 this.trigger_keydown('i');
36 });
36 });
37
37
38 this.wait_for_output(0);
38 this.wait_for_output(0);
39
39
40 this.then(function () {
40 this.then(function () {
41 var result = this.get_output_cell(0);
41 var result = this.get_output_cell(0);
42 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (shortcut)');
42 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (shortcut)');
43 });
43 });
44 });
44 });
@@ -1,38 +1,43 b''
1 //
1 //
2 // Test merging two notebook cells.
2 // Test merging two notebook cells.
3 //
3 //
4 casper.notebook_test(function() {
4 casper.notebook_test(function() {
5 var output = this.evaluate(function () {
5 var that = this;
6 // Fill in test data.
6 var set_cells_text = function () {
7 IPython.notebook.command_mode();
7 that.evaluate(function() {
8 var set_cell_text = function () {
9 var cell_one = IPython.notebook.get_selected_cell();
8 var cell_one = IPython.notebook.get_selected_cell();
10 cell_one.set_text('a = 5');
9 cell_one.set_text('a = 5');
11
10 });
12 IPython.keyboard.trigger_keydown('b');
11
12 that.trigger_keydown('b');
13
14 that.evaluate(function() {
13 var cell_two = IPython.notebook.get_selected_cell();
15 var cell_two = IPython.notebook.get_selected_cell();
14 cell_two.set_text('print(a)');
16 cell_two.set_text('print(a)');
15 };
17 });
18 };
19
20 this.evaluate(function () {
21 IPython.notebook.command_mode();
22 });
16
23
17 // merge_cell_above()
24 // merge_cell_above()
18 set_cell_text();
25 set_cells_text();
26 var output_above = this.evaluate(function () {
19 IPython.notebook.merge_cell_above();
27 IPython.notebook.merge_cell_above();
20 var merged_above = IPython.notebook.get_selected_cell();
28 return IPython.notebook.get_selected_cell().get_text();
29 });
21
30
22 // merge_cell_below()
31 // merge_cell_below()
23 set_cell_text();
32 set_cells_text();
33 var output_below = this.evaluate(function() {
24 IPython.notebook.select(0);
34 IPython.notebook.select(0);
25 IPython.notebook.merge_cell_below();
35 IPython.notebook.merge_cell_below();
26 var merged_below = IPython.notebook.get_selected_cell();
36 return IPython.notebook.get_selected_cell().get_text();
27
28 return {
29 above: merged_above.get_text(),
30 below: merged_below.get_text()
31 };
32 });
37 });
33
38
34 this.test.assertEquals(output.above, 'a = 5\nprint(a)',
39 this.test.assertEquals(output_above, 'a = 5\nprint(a)',
35 'Successful merge_cell_above().');
40 'Successful merge_cell_above().');
36 this.test.assertEquals(output.below, 'a = 5\nprint(a)',
41 this.test.assertEquals(output_below, 'a = 5\nprint(a)',
37 'Successful merge_cell_below().');
42 'Successful merge_cell_below().');
38 });
43 });
@@ -1,292 +1,478 b''
1 //
1 //
2 // Utility functions for the HTML notebook's CasperJS tests.
2 // Utility functions for the HTML notebook's CasperJS tests.
3 //
3 //
4
4
5 // Get the URL of a notebook server on which to run tests.
6 casper.get_notebook_server = function () {
5 casper.get_notebook_server = function () {
7 port = casper.cli.get("port")
6 // Get the URL of a notebook server on which to run tests.
7 port = casper.cli.get("port");
8 port = (typeof port === 'undefined') ? '8888' : port;
8 port = (typeof port === 'undefined') ? '8888' : port;
9 return 'http://127.0.0.1:' + port
9 return 'http://127.0.0.1:' + port;
10 };
10 };
11
11
12 // Create and open a new notebook.
13 casper.open_new_notebook = function () {
12 casper.open_new_notebook = function () {
13 // Create and open a new notebook.
14 var baseUrl = this.get_notebook_server();
14 var baseUrl = this.get_notebook_server();
15 this.start(baseUrl);
15 this.start(baseUrl);
16 this.thenClick('button#new_notebook');
16 this.thenClick('button#new_notebook');
17 this.waitForPopup('');
17 this.waitForPopup('');
18
18
19 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
19 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
20 this.then(function () {
20 this.then(function () {
21 this.open(this.popups[0].url);
21 this.open(this.popups[0].url);
22 });
22 });
23
23
24 // Make sure the kernel has started
24 // Make sure the kernel has started
25 this.waitFor( this.kernel_running );
25 this.waitFor( this.kernel_running );
26 // track the IPython busy/idle state
26 // track the IPython busy/idle state
27 this.thenEvaluate(function () {
27 this.thenEvaluate(function () {
28 $([IPython.events]).on('status_idle.Kernel',function () {
28 $([IPython.events]).on('status_idle.Kernel',function () {
29 IPython._status = 'idle';
29 IPython._status = 'idle';
30 });
30 });
31 $([IPython.events]).on('status_busy.Kernel',function () {
31 $([IPython.events]).on('status_busy.Kernel',function () {
32 IPython._status = 'busy';
32 IPython._status = 'busy';
33 });
33 });
34 });
34 });
35 };
35 };
36
36
37 // Return whether or not the kernel is running.
38 casper.kernel_running = function kernel_running() {
37 casper.kernel_running = function kernel_running() {
38 // Return whether or not the kernel is running.
39 return this.evaluate(function kernel_running() {
39 return this.evaluate(function kernel_running() {
40 return IPython.notebook.kernel.running;
40 return IPython.notebook.kernel.running;
41 });
41 });
42 };
42 };
43
43
44 // Shut down the current notebook's kernel.
45 casper.shutdown_current_kernel = function () {
44 casper.shutdown_current_kernel = function () {
45 // Shut down the current notebook's kernel.
46 this.thenEvaluate(function() {
46 this.thenEvaluate(function() {
47 IPython.notebook.kernel.kill();
47 IPython.notebook.kernel.kill();
48 });
48 });
49 // We close the page right after this so we need to give it time to complete.
49 // We close the page right after this so we need to give it time to complete.
50 this.wait(1000);
50 this.wait(1000);
51 };
51 };
52
52
53 // Delete created notebook.
54 casper.delete_current_notebook = function () {
53 casper.delete_current_notebook = function () {
54 // Delete created notebook.
55
55 // For some unknown reason, this doesn't work?!?
56 // For some unknown reason, this doesn't work?!?
56 this.thenEvaluate(function() {
57 this.thenEvaluate(function() {
57 IPython.notebook.delete();
58 IPython.notebook.delete();
58 });
59 });
59 };
60 };
60
61
61 casper.wait_for_busy = function () {
62 casper.wait_for_busy = function () {
63 // Waits for the notebook to enter a busy state.
62 this.waitFor(function () {
64 this.waitFor(function () {
63 return this.evaluate(function () {
65 return this.evaluate(function () {
64 return IPython._status == 'busy';
66 return IPython._status == 'busy';
65 });
67 });
66 });
68 });
67 };
69 };
68
70
69 casper.wait_for_idle = function () {
71 casper.wait_for_idle = function () {
72 // Waits for the notebook to idle.
70 this.waitFor(function () {
73 this.waitFor(function () {
71 return this.evaluate(function () {
74 return this.evaluate(function () {
72 return IPython._status == 'idle';
75 return IPython._status == 'idle';
73 });
76 });
74 });
77 });
75 };
78 };
76
79
77 // wait for the nth output in a given cell
78 casper.wait_for_output = function (cell_num, out_num) {
80 casper.wait_for_output = function (cell_num, out_num) {
81 // wait for the nth output in a given cell
79 this.wait_for_idle();
82 this.wait_for_idle();
80 out_num = out_num || 0;
83 out_num = out_num || 0;
81 this.then(function() {
84 this.then(function() {
82 this.waitFor(function (c, o) {
85 this.waitFor(function (c, o) {
83 return this.evaluate(function get_output(c, o) {
86 return this.evaluate(function get_output(c, o) {
84 var cell = IPython.notebook.get_cell(c);
87 var cell = IPython.notebook.get_cell(c);
85 return cell.output_area.outputs.length > o;
88 return cell.output_area.outputs.length > o;
86 },
89 },
87 // pass parameter from the test suite js to the browser code js
90 // pass parameter from the test suite js to the browser code js
88 {c : cell_num, o : out_num});
91 {c : cell_num, o : out_num});
89 });
92 });
90 },
93 },
91 function then() { },
94 function then() { },
92 function timeout() {
95 function timeout() {
93 this.echo("wait_for_output timed out!");
96 this.echo("wait_for_output timed out!");
94 });
97 });
95 };
98 };
96
99
97 // wait for a widget msg que to reach 0
98 //
99 // Parameters
100 // ----------
101 // widget_info : object
102 // Object which contains info related to the widget. The model_id property
103 // is used to identify the widget.
104 casper.wait_for_widget = function (widget_info) {
100 casper.wait_for_widget = function (widget_info) {
101 // wait for a widget msg que to reach 0
102 //
103 // Parameters
104 // ----------
105 // widget_info : object
106 // Object which contains info related to the widget. The model_id property
107 // is used to identify the widget.
105 this.waitFor(function () {
108 this.waitFor(function () {
106 var pending = this.evaluate(function (m) {
109 var pending = this.evaluate(function (m) {
107 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
110 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
108 }, {m: widget_info.model_id});
111 }, {m: widget_info.model_id});
109
112
110 if (pending == 0) {
113 if (pending === 0) {
111 return true;
114 return true;
112 } else {
115 } else {
113 return false;
116 return false;
114 }
117 }
115 });
118 });
116 }
119 };
117
120
118 // return an output of a given cell
119 casper.get_output_cell = function (cell_num, out_num) {
121 casper.get_output_cell = function (cell_num, out_num) {
122 // return an output of a given cell
120 out_num = out_num || 0;
123 out_num = out_num || 0;
121 var result = casper.evaluate(function (c, o) {
124 var result = casper.evaluate(function (c, o) {
122 var cell = IPython.notebook.get_cell(c);
125 var cell = IPython.notebook.get_cell(c);
123 return cell.output_area.outputs[o];
126 return cell.output_area.outputs[o];
124 },
127 },
125 {c : cell_num, o : out_num});
128 {c : cell_num, o : out_num});
126 if (!result) {
129 if (!result) {
127 var num_outputs = casper.evaluate(function (c) {
130 var num_outputs = casper.evaluate(function (c) {
128 var cell = IPython.notebook.get_cell(c);
131 var cell = IPython.notebook.get_cell(c);
129 return cell.output_area.outputs.length;
132 return cell.output_area.outputs.length;
130 },
133 },
131 {c : cell_num});
134 {c : cell_num});
132 this.test.assertTrue(false,
135 this.test.assertTrue(false,
133 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
136 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
134 );
137 );
135 } else {
138 } else {
136 return result;
139 return result;
137 }
140 }
138 };
141 };
139
142
140 // return the number of cells in the notebook
141 casper.get_cells_length = function () {
143 casper.get_cells_length = function () {
144 // return the number of cells in the notebook
142 var result = casper.evaluate(function () {
145 var result = casper.evaluate(function () {
143 return IPython.notebook.get_cells().length;
146 return IPython.notebook.get_cells().length;
144 })
147 });
145 return result;
148 return result;
146 };
149 };
147
150
148 // Set the text content of a cell.
149 casper.set_cell_text = function(index, text){
151 casper.set_cell_text = function(index, text){
152 // Set the text content of a cell.
150 this.evaluate(function (index, text) {
153 this.evaluate(function (index, text) {
151 var cell = IPython.notebook.get_cell(index);
154 var cell = IPython.notebook.get_cell(index);
152 cell.set_text(text);
155 cell.set_text(text);
153 }, index, text);
156 }, index, text);
154 };
157 };
155
158
156 // Inserts a cell at the bottom of the notebook
159 casper.get_cell_text = function(index){
157 // Returns the new cell's index.
160 // Get the text content of a cell.
161 return this.evaluate(function (index) {
162 var cell = IPython.notebook.get_cell(index);
163 return cell.get_text();
164 }, index);
165 };
166
158 casper.insert_cell_at_bottom = function(cell_type){
167 casper.insert_cell_at_bottom = function(cell_type){
168 // Inserts a cell at the bottom of the notebook
169 // Returns the new cell's index.
159 cell_type = cell_type || 'code';
170 cell_type = cell_type || 'code';
160
171
161 return this.evaluate(function (cell_type) {
172 return this.evaluate(function (cell_type) {
162 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
173 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
163 return IPython.notebook.find_cell_index(cell);
174 return IPython.notebook.find_cell_index(cell);
164 }, cell_type);
175 }, cell_type);
165 };
176 };
166
177
167 // Insert a cell at the bottom of the notebook and set the cells text.
168 // Returns the new cell's index.
169 casper.append_cell = function(text, cell_type) {
178 casper.append_cell = function(text, cell_type) {
179 // Insert a cell at the bottom of the notebook and set the cells text.
180 // Returns the new cell's index.
170 var index = this.insert_cell_at_bottom(cell_type);
181 var index = this.insert_cell_at_bottom(cell_type);
171 if (text !== undefined) {
182 if (text !== undefined) {
172 this.set_cell_text(index, text);
183 this.set_cell_text(index, text);
173 }
184 }
174 return index;
185 return index;
175 };
186 };
176
187
177 // Asynchronously executes a cell by index.
178 // Returns the cell's index.
179 casper.execute_cell = function(index){
188 casper.execute_cell = function(index){
189 // Asynchronously executes a cell by index.
190 // Returns the cell's index.
180 var that = this;
191 var that = this;
181 this.then(function(){
192 this.then(function(){
182 that.evaluate(function (index) {
193 that.evaluate(function (index) {
183 var cell = IPython.notebook.get_cell(index);
194 var cell = IPython.notebook.get_cell(index);
184 cell.execute();
195 cell.execute();
185 }, index);
196 }, index);
186 });
197 });
187 return index;
198 return index;
188 };
199 };
189
200
190 // Synchronously executes a cell by index.
191 // Optionally accepts a then_callback parameter. then_callback will get called
192 // when the cell has finished executing.
193 // Returns the cell's index.
194 casper.execute_cell_then = function(index, then_callback) {
201 casper.execute_cell_then = function(index, then_callback) {
202 // Synchronously executes a cell by index.
203 // Optionally accepts a then_callback parameter. then_callback will get called
204 // when the cell has finished executing.
205 // Returns the cell's index.
195 var return_val = this.execute_cell(index);
206 var return_val = this.execute_cell(index);
196
207
197 this.wait_for_idle();
208 this.wait_for_idle();
198
209
199 var that = this;
210 var that = this;
200 this.then(function(){
211 this.then(function(){
201 if (then_callback!==undefined) {
212 if (then_callback!==undefined) {
202 then_callback.apply(that, [index]);
213 then_callback.apply(that, [index]);
203 }
214 }
204 });
215 });
205
216
206 return return_val;
217 return return_val;
207 };
218 };
208
219
209 // Utility function that allows us to easily check if an element exists
210 // within a cell. Uses JQuery selector to look for the element.
211 casper.cell_element_exists = function(index, selector){
220 casper.cell_element_exists = function(index, selector){
221 // Utility function that allows us to easily check if an element exists
222 // within a cell. Uses JQuery selector to look for the element.
212 return casper.evaluate(function (index, selector) {
223 return casper.evaluate(function (index, selector) {
213 var $cell = IPython.notebook.get_cell(index).element;
224 var $cell = IPython.notebook.get_cell(index).element;
214 return $cell.find(selector).length > 0;
225 return $cell.find(selector).length > 0;
215 }, index, selector);
226 }, index, selector);
216 };
227 };
217
228
218 // Utility function that allows us to execute a jQuery function on an
219 // element within a cell.
220 casper.cell_element_function = function(index, selector, function_name, function_args){
229 casper.cell_element_function = function(index, selector, function_name, function_args){
230 // Utility function that allows us to execute a jQuery function on an
231 // element within a cell.
221 return casper.evaluate(function (index, selector, function_name, function_args) {
232 return casper.evaluate(function (index, selector, function_name, function_args) {
222 var $cell = IPython.notebook.get_cell(index).element;
233 var $cell = IPython.notebook.get_cell(index).element;
223 var $el = $cell.find(selector);
234 var $el = $cell.find(selector);
224 return $el[function_name].apply($el, function_args);
235 return $el[function_name].apply($el, function_args);
225 }, index, selector, function_name, function_args);
236 }, index, selector, function_name, function_args);
226 };
237 };
227
238
228 // Wrap a notebook test to reduce boilerplate.
239 casper.validate_notebook_state = function(message, mode, cell_index) {
240 // Validate the entire dual mode state of the notebook. Make sure no more than
241 // one cell is selected, focused, in edit mode, etc...
242
243 // General tests.
244 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
245 message + '; keyboard and notebook modes match');
246 // Is the selected cell the only cell that is selected?
247 if (cell_index!==undefined) {
248 this.test.assert(this.is_only_cell_selected(cell_index),
249 message + '; cell ' + cell_index + ' is the only cell selected');
250 }
251
252 // Mode specific tests.
253 if (mode==='command') {
254 // Are the notebook and keyboard manager in command mode?
255 this.test.assertEquals(this.get_keyboard_mode(), 'command',
256 message + '; in command mode');
257 // Make sure there isn't a single cell in edit mode.
258 this.test.assert(this.is_only_cell_edit(null),
259 message + '; all cells in command mode');
260 this.test.assert(this.is_cell_editor_focused(null),
261 message + '; no cell editors are focused while in command mode');
262
263 } else if (mode==='edit') {
264 // Are the notebook and keyboard manager in edit mode?
265 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
266 message + '; in edit mode');
267 if (cell_index!==undefined) {
268 // Is the specified cell the only cell in edit mode?
269 this.test.assert(this.is_only_cell_edit(cell_index),
270 message + '; cell ' + cell_index + ' is the only cell in edit mode');
271 // Is the specified cell the only cell with a focused code mirror?
272 this.test.assert(this.is_cell_editor_focused(cell_index),
273 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
274 }
275
276 } else {
277 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
278 }
279 };
280
281 casper.select_cell = function(index) {
282 // Select a cell in the notebook.
283 this.evaluate(function (i) {
284 IPython.notebook.select(i);
285 }, {i: index});
286 };
287
288 casper.click_cell_editor = function(index) {
289 // Emulate a click on a cell's editor.
290
291 // Code Mirror does not play nicely with emulated brower events.
292 // Instead of trying to emulate a click, here we run code similar to
293 // the code used in Code Mirror that handles the mousedown event on a
294 // region of codemirror that the user can focus.
295 this.evaluate(function (i) {
296 var cm = IPython.notebook.get_cell(i).code_mirror;
297 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
298 cm.display.input.focus();
299 }, {i: index});
300 };
301
302 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
303 // Set the Code Mirror instance cursor's location.
304 this.evaluate(function (i, l, c) {
305 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
306 }, {i: index, l: line_index, c: char_index});
307 };
308
309 casper.focus_notebook = function() {
310 // Focus the notebook div.
311 this.evaluate(function (){
312 $('#notebook').focus();
313 }, {});
314 };
315
316 casper.trigger_keydown = function() {
317 // Emulate a keydown in the notebook.
318 for (var i = 0; i < arguments.length; i++) {
319 this.evaluate(function (k) {
320 var element = $(document);
321 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
322 element.trigger(event);
323 }, {k: arguments[i]});
324 }
325 };
326
327 casper.get_keyboard_mode = function() {
328 // Get the mode of the keyboard manager.
329 return this.evaluate(function() {
330 return IPython.keyboard_manager.mode;
331 }, {});
332 };
333
334 casper.get_notebook_mode = function() {
335 // Get the mode of the notebook.
336 return this.evaluate(function() {
337 return IPython.notebook.mode;
338 }, {});
339 };
340
341 casper.get_cell = function(index) {
342 // Get a single cell.
343 //
344 // Note: Handles to DOM elements stored in the cell will be useless once in
345 // CasperJS context.
346 return this.evaluate(function(i) {
347 var cell = IPython.notebook.get_cell(i);
348 if (cell) {
349 return cell;
350 }
351 return null;
352 }, {i : index});
353 };
354
355 casper.is_cell_editor_focused = function(index) {
356 // Make sure a cell's editor is the only editor focused on the page.
357 return this.evaluate(function(i) {
358 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
359 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
360 if (i === null) {
361 return focused_textarea.length === 0;
362 } else {
363 var cell = IPython.notebook.get_cell(i);
364 if (cell) {
365 return cell.code_mirror.getInputField() == focused_textarea[0];
366 }
367 }
368 return false;
369 }, {i : index});
370 };
371
372 casper.is_only_cell_selected = function(index) {
373 // Check if a cell is the only cell selected.
374 // Pass null as the index to check if no cells are selected.
375 return this.is_only_cell_on(index, 'selected', 'unselected');
376 };
377
378 casper.is_only_cell_edit = function(index) {
379 // Check if a cell is the only cell in edit mode.
380 // Pass null as the index to check if all of the cells are in command mode.
381 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
382 };
383
384 casper.is_only_cell_on = function(i, on_class, off_class) {
385 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
386 // All of the other cells are checked for the `off_class` DOM class.
387 // Pass null as the index to check if all of the cells have the `off_class`.
388 var cells_length = this.get_cells_length();
389 for (var j = 0; j < cells_length; j++) {
390 if (j === i) {
391 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
392 return false;
393 }
394 } else {
395 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
396 return false;
397 }
398 }
399 }
400 return true;
401 };
402
403 casper.cell_has_class = function(index, classes) {
404 // Check if a cell has a class.
405 return this.evaluate(function(i, c) {
406 var cell = IPython.notebook.get_cell(i);
407 if (cell) {
408 return cell.element.hasClass(c);
409 }
410 return false;
411 }, {i : index, c: classes});
412 };
413
229 casper.notebook_test = function(test) {
414 casper.notebook_test = function(test) {
415 // Wrap a notebook test to reduce boilerplate.
230 this.open_new_notebook();
416 this.open_new_notebook();
231 this.then(test);
417 this.then(test);
232
418
233 // Kill the kernel and delete the notebook.
419 // Kill the kernel and delete the notebook.
234 this.shutdown_current_kernel();
420 this.shutdown_current_kernel();
235 // This is still broken but shouldn't be a problem for now.
421 // This is still broken but shouldn't be a problem for now.
236 // this.delete_current_notebook();
422 // this.delete_current_notebook();
237
423
238 // This is required to clean up the page we just finished with. If we don't call this
424 // This is required to clean up the page we just finished with. If we don't call this
239 // casperjs will leak file descriptors of all the open WebSockets in that page. We
425 // casperjs will leak file descriptors of all the open WebSockets in that page. We
240 // have to set this.page=null so that next time casper.start runs, it will create a
426 // have to set this.page=null so that next time casper.start runs, it will create a
241 // new page from scratch.
427 // new page from scratch.
242 this.then(function () {
428 this.then(function () {
243 this.page.close();
429 this.page.close();
244 this.page = null;
430 this.page = null;
245 });
431 });
246
432
247 // Run the browser automation.
433 // Run the browser automation.
248 this.run(function() {
434 this.run(function() {
249 this.test.done();
435 this.test.done();
250 });
436 });
251 };
437 };
252
438
253 casper.wait_for_dashboard = function () {
439 casper.wait_for_dashboard = function () {
254 // Wait for the dashboard list to load.
440 // Wait for the dashboard list to load.
255 casper.waitForSelector('.list_item');
441 casper.waitForSelector('.list_item');
256 }
442 };
257
443
258 casper.open_dashboard = function () {
444 casper.open_dashboard = function () {
259 // Start casper by opening the dashboard page.
445 // Start casper by opening the dashboard page.
260 var baseUrl = this.get_notebook_server();
446 var baseUrl = this.get_notebook_server();
261 this.start(baseUrl);
447 this.start(baseUrl);
262 this.wait_for_dashboard();
448 this.wait_for_dashboard();
263 }
449 };
264
450
265 casper.dashboard_test = function (test) {
451 casper.dashboard_test = function (test) {
266 // Open the dashboard page and run a test.
452 // Open the dashboard page and run a test.
267 this.open_dashboard();
453 this.open_dashboard();
268 this.then(test);
454 this.then(test);
269
455
270 this.then(function () {
456 this.then(function () {
271 this.page.close();
457 this.page.close();
272 this.page = null;
458 this.page = null;
273 });
459 });
274
460
275 // Run the browser automation.
461 // Run the browser automation.
276 this.run(function() {
462 this.run(function() {
277 this.test.done();
463 this.test.done();
278 });
464 });
279 }
465 };
280
466
281 casper.options.waitTimeout=10000
467 casper.options.waitTimeout=10000;
282 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
468 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
283 this.echo("Timeout for " + casper.get_notebook_server());
469 this.echo("Timeout for " + casper.get_notebook_server());
284 this.echo("Is the notebook server running?");
470 this.echo("Is the notebook server running?");
285 });
471 });
286
472
287 // Pass `console.log` calls from page JS to casper.
473 casper.print_log = function () {
288 casper.printLog = function () {
474 // Pass `console.log` calls from page JS to casper.
289 this.on('remote.message', function(msg) {
475 this.on('remote.message', function(msg) {
290 this.echo('Remote message caught: ' + msg);
476 this.echo('Remote message caught: ' + msg);
291 });
477 });
292 };
478 };
@@ -1,42 +1,65 b''
1 """Tests for RSTExporter"""
1 """Tests for RSTExporter"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import io
16
17 from IPython.nbformat import current
18
15 from .base import ExportersTestsBase
19 from .base import ExportersTestsBase
16 from ..rst import RSTExporter
20 from ..rst import RSTExporter
17 from IPython.testing.decorators import onlyif_cmds_exist
21 from IPython.testing.decorators import onlyif_cmds_exist
18
22
19 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
20 # Class
24 # Class
21 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
22
26
23 class TestRSTExporter(ExportersTestsBase):
27 class TestRSTExporter(ExportersTestsBase):
24 """Tests for RSTExporter"""
28 """Tests for RSTExporter"""
25
29
26 exporter_class = RSTExporter
30 exporter_class = RSTExporter
27 should_include_raw = ['rst']
31 should_include_raw = ['rst']
28
32
29 def test_constructor(self):
33 def test_constructor(self):
30 """
34 """
31 Can a RSTExporter be constructed?
35 Can a RSTExporter be constructed?
32 """
36 """
33 RSTExporter()
37 RSTExporter()
34
38
35
39
36 @onlyif_cmds_exist('pandoc')
40 @onlyif_cmds_exist('pandoc')
37 def test_export(self):
41 def test_export(self):
38 """
42 """
39 Can a RSTExporter export something?
43 Can a RSTExporter export something?
40 """
44 """
41 (output, resources) = RSTExporter().from_filename(self._get_notebook())
45 (output, resources) = RSTExporter().from_filename(self._get_notebook())
42 assert len(output) > 0
46 assert len(output) > 0
47
48 @onlyif_cmds_exist('pandoc')
49 def test_empty_code_cell(self):
50 """No empty code cells in rst"""
51 nbname = self._get_notebook()
52 with io.open(nbname, encoding='utf8') as f:
53 nb = current.read(f, 'json')
54
55 exporter = self.exporter_class()
56
57 (output, resources) = exporter.from_notebook_node(nb)
58 # add an empty code cell
59 nb.worksheets[0].cells.append(
60 current.new_code_cell(input="")
61 )
62 (output2, resources) = exporter.from_notebook_node(nb)
63 # adding an empty code cell shouldn't change output
64 self.assertEqual(output.strip(), output2.strip())
65
@@ -1,80 +1,80 b''
1 {%- extends 'display_priority.tpl' -%}
1 {%- extends 'display_priority.tpl' -%}
2
2
3
3
4 {% block in_prompt %}
4 {% block in_prompt %}
5 {% endblock in_prompt %}
5 {% endblock in_prompt %}
6
6
7 {% block output_prompt %}
7 {% block output_prompt %}
8 {% endblock output_prompt %}
8 {% endblock output_prompt %}
9
9
10 {% block input %}
10 {% block input %}
11 {%- if not cell.input.isspace() -%}
11 {%- if cell.input.strip() -%}
12 .. code:: python
12 .. code:: python
13
13
14 {{ cell.input | indent}}
14 {{ cell.input | indent}}
15 {%- endif -%}
15 {%- endif -%}
16 {% endblock input %}
16 {% endblock input %}
17
17
18 {% block pyerr %}
18 {% block pyerr %}
19 ::
19 ::
20
20
21 {{ super() }}
21 {{ super() }}
22 {% endblock pyerr %}
22 {% endblock pyerr %}
23
23
24 {% block traceback_line %}
24 {% block traceback_line %}
25 {{ line | indent | strip_ansi }}
25 {{ line | indent | strip_ansi }}
26 {% endblock traceback_line %}
26 {% endblock traceback_line %}
27
27
28 {% block pyout %}
28 {% block pyout %}
29 {% block data_priority scoped %}
29 {% block data_priority scoped %}
30 {{ super() }}
30 {{ super() }}
31 {% endblock %}
31 {% endblock %}
32 {% endblock pyout %}
32 {% endblock pyout %}
33
33
34 {% block stream %}
34 {% block stream %}
35 .. parsed-literal::
35 .. parsed-literal::
36
36
37 {{ output.text | indent }}
37 {{ output.text | indent }}
38 {% endblock stream %}
38 {% endblock stream %}
39
39
40 {% block data_svg %}
40 {% block data_svg %}
41 .. image:: {{ output.svg_filename|urlencode }}
41 .. image:: {{ output.svg_filename|urlencode }}
42 {% endblock data_svg %}
42 {% endblock data_svg %}
43
43
44 {% block data_png %}
44 {% block data_png %}
45 .. image:: {{ output.png_filename|urlencode }}
45 .. image:: {{ output.png_filename|urlencode }}
46 {% endblock data_png %}
46 {% endblock data_png %}
47
47
48 {% block data_jpg %}
48 {% block data_jpg %}
49 .. image:: {{ output.jpeg_filename|urlencode }}
49 .. image:: {{ output.jpeg_filename|urlencode }}
50 {% endblock data_jpg %}
50 {% endblock data_jpg %}
51
51
52 {% block data_latex %}
52 {% block data_latex %}
53 .. math::
53 .. math::
54
54
55 {{ output.latex | strip_dollars | indent }}
55 {{ output.latex | strip_dollars | indent }}
56 {% endblock data_latex %}
56 {% endblock data_latex %}
57
57
58 {% block data_text scoped %}
58 {% block data_text scoped %}
59 .. parsed-literal::
59 .. parsed-literal::
60
60
61 {{ output.text | indent }}
61 {{ output.text | indent }}
62 {% endblock data_text %}
62 {% endblock data_text %}
63
63
64 {% block data_html scoped %}
64 {% block data_html scoped %}
65 .. raw:: html
65 .. raw:: html
66
66
67 {{ output.html | indent }}
67 {{ output.html | indent }}
68 {% endblock data_html %}
68 {% endblock data_html %}
69
69
70 {% block markdowncell scoped %}
70 {% block markdowncell scoped %}
71 {{ cell.source | markdown2rst }}
71 {{ cell.source | markdown2rst }}
72 {% endblock markdowncell %}
72 {% endblock markdowncell %}
73
73
74 {% block headingcell scoped %}
74 {% block headingcell scoped %}
75 {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | markdown2rst }}
75 {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | markdown2rst }}
76 {% endblock headingcell %}
76 {% endblock headingcell %}
77
77
78 {% block unknowncell scoped %}
78 {% block unknowncell scoped %}
79 unknown type {{cell.type}}
79 unknown type {{cell.type}}
80 {% endblock unknowncell %}
80 {% endblock unknowncell %}
@@ -1,551 +1,555 b''
1 """Tests for parallel client.py
1 """Tests for parallel client.py
2
2
3 Authors:
3 Authors:
4
4
5 * Min RK
5 * Min RK
6 """
6 """
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 from __future__ import division
19 from __future__ import division
20
20
21 import time
21 import time
22 from datetime import datetime
22 from datetime import datetime
23
23
24 import zmq
24 import zmq
25
25
26 from IPython import parallel
26 from IPython import parallel
27 from IPython.parallel.client import client as clientmod
27 from IPython.parallel.client import client as clientmod
28 from IPython.parallel import error
28 from IPython.parallel import error
29 from IPython.parallel import AsyncResult, AsyncHubResult
29 from IPython.parallel import AsyncResult, AsyncHubResult
30 from IPython.parallel import LoadBalancedView, DirectView
30 from IPython.parallel import LoadBalancedView, DirectView
31
31
32 from .clienttest import ClusterTestCase, segfault, wait, add_engines
32 from .clienttest import ClusterTestCase, segfault, wait, add_engines
33
33
34 def setup():
34 def setup():
35 add_engines(4, total=True)
35 add_engines(4, total=True)
36
36
37 class TestClient(ClusterTestCase):
37 class TestClient(ClusterTestCase):
38
38
39 def test_ids(self):
39 def test_ids(self):
40 n = len(self.client.ids)
40 n = len(self.client.ids)
41 self.add_engines(2)
41 self.add_engines(2)
42 self.assertEqual(len(self.client.ids), n+2)
42 self.assertEqual(len(self.client.ids), n+2)
43
43
44 def test_view_indexing(self):
44 def test_view_indexing(self):
45 """test index access for views"""
45 """test index access for views"""
46 self.minimum_engines(4)
46 self.minimum_engines(4)
47 targets = self.client._build_targets('all')[-1]
47 targets = self.client._build_targets('all')[-1]
48 v = self.client[:]
48 v = self.client[:]
49 self.assertEqual(v.targets, targets)
49 self.assertEqual(v.targets, targets)
50 t = self.client.ids[2]
50 t = self.client.ids[2]
51 v = self.client[t]
51 v = self.client[t]
52 self.assertTrue(isinstance(v, DirectView))
52 self.assertTrue(isinstance(v, DirectView))
53 self.assertEqual(v.targets, t)
53 self.assertEqual(v.targets, t)
54 t = self.client.ids[2:4]
54 t = self.client.ids[2:4]
55 v = self.client[t]
55 v = self.client[t]
56 self.assertTrue(isinstance(v, DirectView))
56 self.assertTrue(isinstance(v, DirectView))
57 self.assertEqual(v.targets, t)
57 self.assertEqual(v.targets, t)
58 v = self.client[::2]
58 v = self.client[::2]
59 self.assertTrue(isinstance(v, DirectView))
59 self.assertTrue(isinstance(v, DirectView))
60 self.assertEqual(v.targets, targets[::2])
60 self.assertEqual(v.targets, targets[::2])
61 v = self.client[1::3]
61 v = self.client[1::3]
62 self.assertTrue(isinstance(v, DirectView))
62 self.assertTrue(isinstance(v, DirectView))
63 self.assertEqual(v.targets, targets[1::3])
63 self.assertEqual(v.targets, targets[1::3])
64 v = self.client[:-3]
64 v = self.client[:-3]
65 self.assertTrue(isinstance(v, DirectView))
65 self.assertTrue(isinstance(v, DirectView))
66 self.assertEqual(v.targets, targets[:-3])
66 self.assertEqual(v.targets, targets[:-3])
67 v = self.client[-1]
67 v = self.client[-1]
68 self.assertTrue(isinstance(v, DirectView))
68 self.assertTrue(isinstance(v, DirectView))
69 self.assertEqual(v.targets, targets[-1])
69 self.assertEqual(v.targets, targets[-1])
70 self.assertRaises(TypeError, lambda : self.client[None])
70 self.assertRaises(TypeError, lambda : self.client[None])
71
71
72 def test_lbview_targets(self):
72 def test_lbview_targets(self):
73 """test load_balanced_view targets"""
73 """test load_balanced_view targets"""
74 v = self.client.load_balanced_view()
74 v = self.client.load_balanced_view()
75 self.assertEqual(v.targets, None)
75 self.assertEqual(v.targets, None)
76 v = self.client.load_balanced_view(-1)
76 v = self.client.load_balanced_view(-1)
77 self.assertEqual(v.targets, [self.client.ids[-1]])
77 self.assertEqual(v.targets, [self.client.ids[-1]])
78 v = self.client.load_balanced_view('all')
78 v = self.client.load_balanced_view('all')
79 self.assertEqual(v.targets, None)
79 self.assertEqual(v.targets, None)
80
80
81 def test_dview_targets(self):
81 def test_dview_targets(self):
82 """test direct_view targets"""
82 """test direct_view targets"""
83 v = self.client.direct_view()
83 v = self.client.direct_view()
84 self.assertEqual(v.targets, 'all')
84 self.assertEqual(v.targets, 'all')
85 v = self.client.direct_view('all')
85 v = self.client.direct_view('all')
86 self.assertEqual(v.targets, 'all')
86 self.assertEqual(v.targets, 'all')
87 v = self.client.direct_view(-1)
87 v = self.client.direct_view(-1)
88 self.assertEqual(v.targets, self.client.ids[-1])
88 self.assertEqual(v.targets, self.client.ids[-1])
89
89
90 def test_lazy_all_targets(self):
90 def test_lazy_all_targets(self):
91 """test lazy evaluation of rc.direct_view('all')"""
91 """test lazy evaluation of rc.direct_view('all')"""
92 v = self.client.direct_view()
92 v = self.client.direct_view()
93 self.assertEqual(v.targets, 'all')
93 self.assertEqual(v.targets, 'all')
94
94
95 def double(x):
95 def double(x):
96 return x*2
96 return x*2
97 seq = list(range(100))
97 seq = list(range(100))
98 ref = [ double(x) for x in seq ]
98 ref = [ double(x) for x in seq ]
99
99
100 # add some engines, which should be used
100 # add some engines, which should be used
101 self.add_engines(1)
101 self.add_engines(1)
102 n1 = len(self.client.ids)
102 n1 = len(self.client.ids)
103
103
104 # simple apply
104 # simple apply
105 r = v.apply_sync(lambda : 1)
105 r = v.apply_sync(lambda : 1)
106 self.assertEqual(r, [1] * n1)
106 self.assertEqual(r, [1] * n1)
107
107
108 # map goes through remotefunction
108 # map goes through remotefunction
109 r = v.map_sync(double, seq)
109 r = v.map_sync(double, seq)
110 self.assertEqual(r, ref)
110 self.assertEqual(r, ref)
111
111
112 # add a couple more engines, and try again
112 # add a couple more engines, and try again
113 self.add_engines(2)
113 self.add_engines(2)
114 n2 = len(self.client.ids)
114 n2 = len(self.client.ids)
115 self.assertNotEqual(n2, n1)
115 self.assertNotEqual(n2, n1)
116
116
117 # apply
117 # apply
118 r = v.apply_sync(lambda : 1)
118 r = v.apply_sync(lambda : 1)
119 self.assertEqual(r, [1] * n2)
119 self.assertEqual(r, [1] * n2)
120
120
121 # map
121 # map
122 r = v.map_sync(double, seq)
122 r = v.map_sync(double, seq)
123 self.assertEqual(r, ref)
123 self.assertEqual(r, ref)
124
124
125 def test_targets(self):
125 def test_targets(self):
126 """test various valid targets arguments"""
126 """test various valid targets arguments"""
127 build = self.client._build_targets
127 build = self.client._build_targets
128 ids = self.client.ids
128 ids = self.client.ids
129 idents,targets = build(None)
129 idents,targets = build(None)
130 self.assertEqual(ids, targets)
130 self.assertEqual(ids, targets)
131
131
132 def test_clear(self):
132 def test_clear(self):
133 """test clear behavior"""
133 """test clear behavior"""
134 self.minimum_engines(2)
134 self.minimum_engines(2)
135 v = self.client[:]
135 v = self.client[:]
136 v.block=True
136 v.block=True
137 v.push(dict(a=5))
137 v.push(dict(a=5))
138 v.pull('a')
138 v.pull('a')
139 id0 = self.client.ids[-1]
139 id0 = self.client.ids[-1]
140 self.client.clear(targets=id0, block=True)
140 self.client.clear(targets=id0, block=True)
141 a = self.client[:-1].get('a')
141 a = self.client[:-1].get('a')
142 self.assertRaisesRemote(NameError, self.client[id0].get, 'a')
142 self.assertRaisesRemote(NameError, self.client[id0].get, 'a')
143 self.client.clear(block=True)
143 self.client.clear(block=True)
144 for i in self.client.ids:
144 for i in self.client.ids:
145 self.assertRaisesRemote(NameError, self.client[i].get, 'a')
145 self.assertRaisesRemote(NameError, self.client[i].get, 'a')
146
146
147 def test_get_result(self):
147 def test_get_result(self):
148 """test getting results from the Hub."""
148 """test getting results from the Hub."""
149 c = clientmod.Client(profile='iptest')
149 c = clientmod.Client(profile='iptest')
150 t = c.ids[-1]
150 t = c.ids[-1]
151 ar = c[t].apply_async(wait, 1)
151 ar = c[t].apply_async(wait, 1)
152 # give the monitor time to notice the message
152 # give the monitor time to notice the message
153 time.sleep(.25)
153 time.sleep(.25)
154 ahr = self.client.get_result(ar.msg_ids[0])
154 ahr = self.client.get_result(ar.msg_ids[0])
155 self.assertTrue(isinstance(ahr, AsyncHubResult))
155 self.assertTrue(isinstance(ahr, AsyncHubResult))
156 self.assertEqual(ahr.get(), ar.get())
156 self.assertEqual(ahr.get(), ar.get())
157 ar2 = self.client.get_result(ar.msg_ids[0])
157 ar2 = self.client.get_result(ar.msg_ids[0])
158 self.assertFalse(isinstance(ar2, AsyncHubResult))
158 self.assertFalse(isinstance(ar2, AsyncHubResult))
159 c.close()
159 c.close()
160
160
161 def test_get_execute_result(self):
161 def test_get_execute_result(self):
162 """test getting execute results from the Hub."""
162 """test getting execute results from the Hub."""
163 c = clientmod.Client(profile='iptest')
163 c = clientmod.Client(profile='iptest')
164 t = c.ids[-1]
164 t = c.ids[-1]
165 cell = '\n'.join([
165 cell = '\n'.join([
166 'import time',
166 'import time',
167 'time.sleep(0.25)',
167 'time.sleep(0.25)',
168 '5'
168 '5'
169 ])
169 ])
170 ar = c[t].execute("import time; time.sleep(1)", silent=False)
170 ar = c[t].execute("import time; time.sleep(1)", silent=False)
171 # give the monitor time to notice the message
171 # give the monitor time to notice the message
172 time.sleep(.25)
172 time.sleep(.25)
173 ahr = self.client.get_result(ar.msg_ids[0])
173 ahr = self.client.get_result(ar.msg_ids[0])
174 self.assertTrue(isinstance(ahr, AsyncHubResult))
174 self.assertTrue(isinstance(ahr, AsyncHubResult))
175 self.assertEqual(ahr.get().pyout, ar.get().pyout)
175 self.assertEqual(ahr.get().pyout, ar.get().pyout)
176 ar2 = self.client.get_result(ar.msg_ids[0])
176 ar2 = self.client.get_result(ar.msg_ids[0])
177 self.assertFalse(isinstance(ar2, AsyncHubResult))
177 self.assertFalse(isinstance(ar2, AsyncHubResult))
178 c.close()
178 c.close()
179
179
180 def test_ids_list(self):
180 def test_ids_list(self):
181 """test client.ids"""
181 """test client.ids"""
182 ids = self.client.ids
182 ids = self.client.ids
183 self.assertEqual(ids, self.client._ids)
183 self.assertEqual(ids, self.client._ids)
184 self.assertFalse(ids is self.client._ids)
184 self.assertFalse(ids is self.client._ids)
185 ids.remove(ids[-1])
185 ids.remove(ids[-1])
186 self.assertNotEqual(ids, self.client._ids)
186 self.assertNotEqual(ids, self.client._ids)
187
187
188 def test_queue_status(self):
188 def test_queue_status(self):
189 ids = self.client.ids
189 ids = self.client.ids
190 id0 = ids[0]
190 id0 = ids[0]
191 qs = self.client.queue_status(targets=id0)
191 qs = self.client.queue_status(targets=id0)
192 self.assertTrue(isinstance(qs, dict))
192 self.assertTrue(isinstance(qs, dict))
193 self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
193 self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
194 allqs = self.client.queue_status()
194 allqs = self.client.queue_status()
195 self.assertTrue(isinstance(allqs, dict))
195 self.assertTrue(isinstance(allqs, dict))
196 intkeys = list(allqs.keys())
196 intkeys = list(allqs.keys())
197 intkeys.remove('unassigned')
197 intkeys.remove('unassigned')
198 print("intkeys", intkeys)
198 print("intkeys", intkeys)
199 intkeys = sorted(intkeys)
199 intkeys = sorted(intkeys)
200 ids = self.client.ids
200 ids = self.client.ids
201 print("client.ids", ids)
201 print("client.ids", ids)
202 ids = sorted(self.client.ids)
202 ids = sorted(self.client.ids)
203 self.assertEqual(intkeys, ids)
203 self.assertEqual(intkeys, ids)
204 unassigned = allqs.pop('unassigned')
204 unassigned = allqs.pop('unassigned')
205 for eid,qs in allqs.items():
205 for eid,qs in allqs.items():
206 self.assertTrue(isinstance(qs, dict))
206 self.assertTrue(isinstance(qs, dict))
207 self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
207 self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
208
208
209 def test_shutdown(self):
209 def test_shutdown(self):
210 ids = self.client.ids
210 ids = self.client.ids
211 id0 = ids[0]
211 id0 = ids[0]
212 self.client.shutdown(id0, block=True)
212 self.client.shutdown(id0, block=True)
213 while id0 in self.client.ids:
213 while id0 in self.client.ids:
214 time.sleep(0.1)
214 time.sleep(0.1)
215 self.client.spin()
215 self.client.spin()
216
216
217 self.assertRaises(IndexError, lambda : self.client[id0])
217 self.assertRaises(IndexError, lambda : self.client[id0])
218
218
219 def test_result_status(self):
219 def test_result_status(self):
220 pass
220 pass
221 # to be written
221 # to be written
222
222
223 def test_db_query_dt(self):
223 def test_db_query_dt(self):
224 """test db query by date"""
224 """test db query by date"""
225 hist = self.client.hub_history()
225 hist = self.client.hub_history()
226 middle = self.client.db_query({'msg_id' : hist[len(hist)//2]})[0]
226 middle = self.client.db_query({'msg_id' : hist[len(hist)//2]})[0]
227 tic = middle['submitted']
227 tic = middle['submitted']
228 before = self.client.db_query({'submitted' : {'$lt' : tic}})
228 before = self.client.db_query({'submitted' : {'$lt' : tic}})
229 after = self.client.db_query({'submitted' : {'$gte' : tic}})
229 after = self.client.db_query({'submitted' : {'$gte' : tic}})
230 self.assertEqual(len(before)+len(after),len(hist))
230 self.assertEqual(len(before)+len(after),len(hist))
231 for b in before:
231 for b in before:
232 self.assertTrue(b['submitted'] < tic)
232 self.assertTrue(b['submitted'] < tic)
233 for a in after:
233 for a in after:
234 self.assertTrue(a['submitted'] >= tic)
234 self.assertTrue(a['submitted'] >= tic)
235 same = self.client.db_query({'submitted' : tic})
235 same = self.client.db_query({'submitted' : tic})
236 for s in same:
236 for s in same:
237 self.assertTrue(s['submitted'] == tic)
237 self.assertTrue(s['submitted'] == tic)
238
238
239 def test_db_query_keys(self):
239 def test_db_query_keys(self):
240 """test extracting subset of record keys"""
240 """test extracting subset of record keys"""
241 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
241 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
242 for rec in found:
242 for rec in found:
243 self.assertEqual(set(rec.keys()), set(['msg_id', 'submitted', 'completed']))
243 self.assertEqual(set(rec.keys()), set(['msg_id', 'submitted', 'completed']))
244
244
245 def test_db_query_default_keys(self):
245 def test_db_query_default_keys(self):
246 """default db_query excludes buffers"""
246 """default db_query excludes buffers"""
247 found = self.client.db_query({'msg_id': {'$ne' : ''}})
247 found = self.client.db_query({'msg_id': {'$ne' : ''}})
248 for rec in found:
248 for rec in found:
249 keys = set(rec.keys())
249 keys = set(rec.keys())
250 self.assertFalse('buffers' in keys, "'buffers' should not be in: %s" % keys)
250 self.assertFalse('buffers' in keys, "'buffers' should not be in: %s" % keys)
251 self.assertFalse('result_buffers' in keys, "'result_buffers' should not be in: %s" % keys)
251 self.assertFalse('result_buffers' in keys, "'result_buffers' should not be in: %s" % keys)
252
252
253 def test_db_query_msg_id(self):
253 def test_db_query_msg_id(self):
254 """ensure msg_id is always in db queries"""
254 """ensure msg_id is always in db queries"""
255 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
255 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
256 for rec in found:
256 for rec in found:
257 self.assertTrue('msg_id' in rec.keys())
257 self.assertTrue('msg_id' in rec.keys())
258 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted'])
258 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted'])
259 for rec in found:
259 for rec in found:
260 self.assertTrue('msg_id' in rec.keys())
260 self.assertTrue('msg_id' in rec.keys())
261 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['msg_id'])
261 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['msg_id'])
262 for rec in found:
262 for rec in found:
263 self.assertTrue('msg_id' in rec.keys())
263 self.assertTrue('msg_id' in rec.keys())
264
264
265 def test_db_query_get_result(self):
265 def test_db_query_get_result(self):
266 """pop in db_query shouldn't pop from result itself"""
266 """pop in db_query shouldn't pop from result itself"""
267 self.client[:].apply_sync(lambda : 1)
267 self.client[:].apply_sync(lambda : 1)
268 found = self.client.db_query({'msg_id': {'$ne' : ''}})
268 found = self.client.db_query({'msg_id': {'$ne' : ''}})
269 rc2 = clientmod.Client(profile='iptest')
269 rc2 = clientmod.Client(profile='iptest')
270 # If this bug is not fixed, this call will hang:
270 # If this bug is not fixed, this call will hang:
271 ar = rc2.get_result(self.client.history[-1])
271 ar = rc2.get_result(self.client.history[-1])
272 ar.wait(2)
272 ar.wait(2)
273 self.assertTrue(ar.ready())
273 self.assertTrue(ar.ready())
274 ar.get()
274 ar.get()
275 rc2.close()
275 rc2.close()
276
276
277 def test_db_query_in(self):
277 def test_db_query_in(self):
278 """test db query with '$in','$nin' operators"""
278 """test db query with '$in','$nin' operators"""
279 hist = self.client.hub_history()
279 hist = self.client.hub_history()
280 even = hist[::2]
280 even = hist[::2]
281 odd = hist[1::2]
281 odd = hist[1::2]
282 recs = self.client.db_query({ 'msg_id' : {'$in' : even}})
282 recs = self.client.db_query({ 'msg_id' : {'$in' : even}})
283 found = [ r['msg_id'] for r in recs ]
283 found = [ r['msg_id'] for r in recs ]
284 self.assertEqual(set(even), set(found))
284 self.assertEqual(set(even), set(found))
285 recs = self.client.db_query({ 'msg_id' : {'$nin' : even}})
285 recs = self.client.db_query({ 'msg_id' : {'$nin' : even}})
286 found = [ r['msg_id'] for r in recs ]
286 found = [ r['msg_id'] for r in recs ]
287 self.assertEqual(set(odd), set(found))
287 self.assertEqual(set(odd), set(found))
288
288
289 def test_hub_history(self):
289 def test_hub_history(self):
290 hist = self.client.hub_history()
290 hist = self.client.hub_history()
291 recs = self.client.db_query({ 'msg_id' : {"$ne":''}})
291 recs = self.client.db_query({ 'msg_id' : {"$ne":''}})
292 recdict = {}
292 recdict = {}
293 for rec in recs:
293 for rec in recs:
294 recdict[rec['msg_id']] = rec
294 recdict[rec['msg_id']] = rec
295
295
296 latest = datetime(1984,1,1)
296 latest = datetime(1984,1,1)
297 for msg_id in hist:
297 for msg_id in hist:
298 rec = recdict[msg_id]
298 rec = recdict[msg_id]
299 newt = rec['submitted']
299 newt = rec['submitted']
300 self.assertTrue(newt >= latest)
300 self.assertTrue(newt >= latest)
301 latest = newt
301 latest = newt
302 ar = self.client[-1].apply_async(lambda : 1)
302 ar = self.client[-1].apply_async(lambda : 1)
303 ar.get()
303 ar.get()
304 time.sleep(0.25)
304 time.sleep(0.25)
305 self.assertEqual(self.client.hub_history()[-1:],ar.msg_ids)
305 self.assertEqual(self.client.hub_history()[-1:],ar.msg_ids)
306
306
307 def _wait_for_idle(self):
307 def _wait_for_idle(self):
308 """wait for the cluster to become idle, according to the everyone."""
308 """wait for the cluster to become idle, according to the everyone."""
309 rc = self.client
309 rc = self.client
310
310
311 # step 0. wait for local results
311 # step 0. wait for local results
312 # this should be sufficient 99% of the time.
312 # this should be sufficient 99% of the time.
313 rc.wait(timeout=5)
313 rc.wait(timeout=5)
314
314
315 # step 1. wait for all requests to be noticed
315 # step 1. wait for all requests to be noticed
316 # timeout 5s, polling every 100ms
316 # timeout 5s, polling every 100ms
317 msg_ids = set(rc.history)
317 msg_ids = set(rc.history)
318 hub_hist = rc.hub_history()
318 hub_hist = rc.hub_history()
319 for i in range(50):
319 for i in range(50):
320 if msg_ids.difference(hub_hist):
320 if msg_ids.difference(hub_hist):
321 time.sleep(0.1)
321 time.sleep(0.1)
322 hub_hist = rc.hub_history()
322 hub_hist = rc.hub_history()
323 else:
323 else:
324 break
324 break
325
325
326 self.assertEqual(len(msg_ids.difference(hub_hist)), 0)
326 self.assertEqual(len(msg_ids.difference(hub_hist)), 0)
327
327
328 # step 2. wait for all requests to be done
328 # step 2. wait for all requests to be done
329 # timeout 5s, polling every 100ms
329 # timeout 5s, polling every 100ms
330 qs = rc.queue_status()
330 qs = rc.queue_status()
331 for i in range(50):
331 for i in range(50):
332 if qs['unassigned'] or any(qs[eid]['tasks'] + qs[eid]['queue'] for eid in qs if eid != 'unassigned'):
332 if qs['unassigned'] or any(qs[eid]['tasks'] + qs[eid]['queue'] for eid in qs if eid != 'unassigned'):
333 time.sleep(0.1)
333 time.sleep(0.1)
334 qs = rc.queue_status()
334 qs = rc.queue_status()
335 else:
335 else:
336 break
336 break
337
337
338 # ensure Hub up to date:
338 # ensure Hub up to date:
339 self.assertEqual(qs['unassigned'], 0)
339 self.assertEqual(qs['unassigned'], 0)
340 for eid in [ eid for eid in qs if eid != 'unassigned' ]:
340 for eid in [ eid for eid in qs if eid != 'unassigned' ]:
341 self.assertEqual(qs[eid]['tasks'], 0)
341 self.assertEqual(qs[eid]['tasks'], 0)
342 self.assertEqual(qs[eid]['queue'], 0)
342 self.assertEqual(qs[eid]['queue'], 0)
343
343
344
344
345 def test_resubmit(self):
345 def test_resubmit(self):
346 def f():
346 def f():
347 import random
347 import random
348 return random.random()
348 return random.random()
349 v = self.client.load_balanced_view()
349 v = self.client.load_balanced_view()
350 ar = v.apply_async(f)
350 ar = v.apply_async(f)
351 r1 = ar.get(1)
351 r1 = ar.get(1)
352 # give the Hub a chance to notice:
352 # give the Hub a chance to notice:
353 self._wait_for_idle()
353 self._wait_for_idle()
354 ahr = self.client.resubmit(ar.msg_ids)
354 ahr = self.client.resubmit(ar.msg_ids)
355 r2 = ahr.get(1)
355 r2 = ahr.get(1)
356 self.assertFalse(r1 == r2)
356 self.assertFalse(r1 == r2)
357
357
358 def test_resubmit_chain(self):
358 def test_resubmit_chain(self):
359 """resubmit resubmitted tasks"""
359 """resubmit resubmitted tasks"""
360 v = self.client.load_balanced_view()
360 v = self.client.load_balanced_view()
361 ar = v.apply_async(lambda x: x, 'x'*1024)
361 ar = v.apply_async(lambda x: x, 'x'*1024)
362 ar.get()
362 ar.get()
363 self._wait_for_idle()
363 self._wait_for_idle()
364 ars = [ar]
364 ars = [ar]
365
365
366 for i in range(10):
366 for i in range(10):
367 ar = ars[-1]
367 ar = ars[-1]
368 ar2 = self.client.resubmit(ar.msg_ids)
368 ar2 = self.client.resubmit(ar.msg_ids)
369
369
370 [ ar.get() for ar in ars ]
370 [ ar.get() for ar in ars ]
371
371
372 def test_resubmit_header(self):
372 def test_resubmit_header(self):
373 """resubmit shouldn't clobber the whole header"""
373 """resubmit shouldn't clobber the whole header"""
374 def f():
374 def f():
375 import random
375 import random
376 return random.random()
376 return random.random()
377 v = self.client.load_balanced_view()
377 v = self.client.load_balanced_view()
378 v.retries = 1
378 v.retries = 1
379 ar = v.apply_async(f)
379 ar = v.apply_async(f)
380 r1 = ar.get(1)
380 r1 = ar.get(1)
381 # give the Hub a chance to notice:
381 # give the Hub a chance to notice:
382 self._wait_for_idle()
382 self._wait_for_idle()
383 ahr = self.client.resubmit(ar.msg_ids)
383 ahr = self.client.resubmit(ar.msg_ids)
384 ahr.get(1)
384 ahr.get(1)
385 time.sleep(0.5)
385 time.sleep(0.5)
386 records = self.client.db_query({'msg_id': {'$in': ar.msg_ids + ahr.msg_ids}}, keys='header')
386 records = self.client.db_query({'msg_id': {'$in': ar.msg_ids + ahr.msg_ids}}, keys='header')
387 h1,h2 = [ r['header'] for r in records ]
387 h1,h2 = [ r['header'] for r in records ]
388 for key in set(h1.keys()).union(set(h2.keys())):
388 for key in set(h1.keys()).union(set(h2.keys())):
389 if key in ('msg_id', 'date'):
389 if key in ('msg_id', 'date'):
390 self.assertNotEqual(h1[key], h2[key])
390 self.assertNotEqual(h1[key], h2[key])
391 else:
391 else:
392 self.assertEqual(h1[key], h2[key])
392 self.assertEqual(h1[key], h2[key])
393
393
394 def test_resubmit_aborted(self):
394 def test_resubmit_aborted(self):
395 def f():
395 def f():
396 import random
396 import random
397 return random.random()
397 return random.random()
398 v = self.client.load_balanced_view()
398 v = self.client.load_balanced_view()
399 # restrict to one engine, so we can put a sleep
399 # restrict to one engine, so we can put a sleep
400 # ahead of the task, so it will get aborted
400 # ahead of the task, so it will get aborted
401 eid = self.client.ids[-1]
401 eid = self.client.ids[-1]
402 v.targets = [eid]
402 v.targets = [eid]
403 sleep = v.apply_async(time.sleep, 0.5)
403 sleep = v.apply_async(time.sleep, 0.5)
404 ar = v.apply_async(f)
404 ar = v.apply_async(f)
405 ar.abort()
405 ar.abort()
406 self.assertRaises(error.TaskAborted, ar.get)
406 self.assertRaises(error.TaskAborted, ar.get)
407 # Give the Hub a chance to get up to date:
407 # Give the Hub a chance to get up to date:
408 self._wait_for_idle()
408 self._wait_for_idle()
409 ahr = self.client.resubmit(ar.msg_ids)
409 ahr = self.client.resubmit(ar.msg_ids)
410 r2 = ahr.get(1)
410 r2 = ahr.get(1)
411
411
412 def test_resubmit_inflight(self):
412 def test_resubmit_inflight(self):
413 """resubmit of inflight task"""
413 """resubmit of inflight task"""
414 v = self.client.load_balanced_view()
414 v = self.client.load_balanced_view()
415 ar = v.apply_async(time.sleep,1)
415 ar = v.apply_async(time.sleep,1)
416 # give the message a chance to arrive
416 # give the message a chance to arrive
417 time.sleep(0.2)
417 time.sleep(0.2)
418 ahr = self.client.resubmit(ar.msg_ids)
418 ahr = self.client.resubmit(ar.msg_ids)
419 ar.get(2)
419 ar.get(2)
420 ahr.get(2)
420 ahr.get(2)
421
421
422 def test_resubmit_badkey(self):
422 def test_resubmit_badkey(self):
423 """ensure KeyError on resubmit of nonexistant task"""
423 """ensure KeyError on resubmit of nonexistant task"""
424 self.assertRaisesRemote(KeyError, self.client.resubmit, ['invalid'])
424 self.assertRaisesRemote(KeyError, self.client.resubmit, ['invalid'])
425
425
426 def test_purge_hub_results(self):
426 def test_purge_hub_results(self):
427 # ensure there are some tasks
427 # ensure there are some tasks
428 for i in range(5):
428 for i in range(5):
429 self.client[:].apply_sync(lambda : 1)
429 self.client[:].apply_sync(lambda : 1)
430 # Wait for the Hub to realise the result is done:
430 # Wait for the Hub to realise the result is done:
431 # This prevents a race condition, where we
431 # This prevents a race condition, where we
432 # might purge a result the Hub still thinks is pending.
432 # might purge a result the Hub still thinks is pending.
433 self._wait_for_idle()
433 self._wait_for_idle()
434 rc2 = clientmod.Client(profile='iptest')
434 rc2 = clientmod.Client(profile='iptest')
435 hist = self.client.hub_history()
435 hist = self.client.hub_history()
436 ahr = rc2.get_result([hist[-1]])
436 ahr = rc2.get_result([hist[-1]])
437 ahr.wait(10)
437 ahr.wait(10)
438 self.client.purge_hub_results(hist[-1])
438 self.client.purge_hub_results(hist[-1])
439 newhist = self.client.hub_history()
439 newhist = self.client.hub_history()
440 self.assertEqual(len(newhist)+1,len(hist))
440 self.assertEqual(len(newhist)+1,len(hist))
441 rc2.spin()
441 rc2.spin()
442 rc2.close()
442 rc2.close()
443
443
444 def test_purge_local_results(self):
444 def test_purge_local_results(self):
445 # ensure there are some tasks
445 # ensure there are some tasks
446 res = []
446 res = []
447 for i in range(5):
447 for i in range(5):
448 res.append(self.client[:].apply_async(lambda : 1))
448 res.append(self.client[:].apply_async(lambda : 1))
449 self._wait_for_idle()
449 self._wait_for_idle()
450 self.client.wait(10) # wait for the results to come back
450 self.client.wait(10) # wait for the results to come back
451 before = len(self.client.results)
451 before = len(self.client.results)
452 self.assertEqual(len(self.client.metadata),before)
452 self.assertEqual(len(self.client.metadata),before)
453 self.client.purge_local_results(res[-1])
453 self.client.purge_local_results(res[-1])
454 self.assertEqual(len(self.client.results),before-len(res[-1]), msg="Not removed from results")
454 self.assertEqual(len(self.client.results),before-len(res[-1]), msg="Not removed from results")
455 self.assertEqual(len(self.client.metadata),before-len(res[-1]), msg="Not removed from metadata")
455 self.assertEqual(len(self.client.metadata),before-len(res[-1]), msg="Not removed from metadata")
456
456
457 def test_purge_local_results_outstanding(self):
457 def test_purge_local_results_outstanding(self):
458 v = self.client[-1]
458 v = self.client[-1]
459 ar = v.apply_async(lambda : 1)
459 ar = v.apply_async(lambda : 1)
460 msg_id = ar.msg_ids[0]
460 msg_id = ar.msg_ids[0]
461 ar.get()
461 ar.get()
462 self._wait_for_idle()
462 self._wait_for_idle()
463 ar2 = v.apply_async(time.sleep, 1)
463 ar2 = v.apply_async(time.sleep, 1)
464 self.assertIn(msg_id, self.client.results)
464 self.assertIn(msg_id, self.client.results)
465 self.assertIn(msg_id, self.client.metadata)
465 self.assertIn(msg_id, self.client.metadata)
466 self.client.purge_local_results(ar)
466 self.client.purge_local_results(ar)
467 self.assertNotIn(msg_id, self.client.results)
467 self.assertNotIn(msg_id, self.client.results)
468 self.assertNotIn(msg_id, self.client.metadata)
468 self.assertNotIn(msg_id, self.client.metadata)
469 with self.assertRaises(RuntimeError):
469 with self.assertRaises(RuntimeError):
470 self.client.purge_local_results(ar2)
470 self.client.purge_local_results(ar2)
471 ar2.get()
471 ar2.get()
472 self.client.purge_local_results(ar2)
472 self.client.purge_local_results(ar2)
473
473
474 def test_purge_all_local_results_outstanding(self):
474 def test_purge_all_local_results_outstanding(self):
475 v = self.client[-1]
475 v = self.client[-1]
476 ar = v.apply_async(time.sleep, 1)
476 ar = v.apply_async(time.sleep, 1)
477 with self.assertRaises(RuntimeError):
477 with self.assertRaises(RuntimeError):
478 self.client.purge_local_results('all')
478 self.client.purge_local_results('all')
479 ar.get()
479 ar.get()
480 self.client.purge_local_results('all')
480 self.client.purge_local_results('all')
481
481
482 def test_purge_all_hub_results(self):
482 def test_purge_all_hub_results(self):
483 self.client.purge_hub_results('all')
483 self.client.purge_hub_results('all')
484 hist = self.client.hub_history()
484 hist = self.client.hub_history()
485 self.assertEqual(len(hist), 0)
485 self.assertEqual(len(hist), 0)
486
486
487 def test_purge_all_local_results(self):
487 def test_purge_all_local_results(self):
488 self.client.purge_local_results('all')
488 self.client.purge_local_results('all')
489 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
489 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
490 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
490 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
491
491
492 def test_purge_all_results(self):
492 def test_purge_all_results(self):
493 # ensure there are some tasks
493 # ensure there are some tasks
494 for i in range(5):
494 for i in range(5):
495 self.client[:].apply_sync(lambda : 1)
495 self.client[:].apply_sync(lambda : 1)
496 self.client.wait(10)
496 self.client.wait(10)
497 self._wait_for_idle()
497 self._wait_for_idle()
498 self.client.purge_results('all')
498 self.client.purge_results('all')
499 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
499 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
500 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
500 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
501 hist = self.client.hub_history()
501 hist = self.client.hub_history()
502 self.assertEqual(len(hist), 0, msg="hub history not empty")
502 self.assertEqual(len(hist), 0, msg="hub history not empty")
503
503
504 def test_purge_everything(self):
504 def test_purge_everything(self):
505 # ensure there are some tasks
505 # ensure there are some tasks
506 for i in range(5):
506 for i in range(5):
507 self.client[:].apply_sync(lambda : 1)
507 self.client[:].apply_sync(lambda : 1)
508 self.client.wait(10)
508 self.client.wait(10)
509 self._wait_for_idle()
509 self._wait_for_idle()
510 self.client.purge_everything()
510 self.client.purge_everything()
511 # The client results
511 # The client results
512 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
512 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
513 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
513 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
514 # The client "bookkeeping"
514 # The client "bookkeeping"
515 self.assertEqual(len(self.client.session.digest_history), 0, msg="session digest not empty")
515 self.assertEqual(len(self.client.session.digest_history), 0, msg="session digest not empty")
516 self.assertEqual(len(self.client.history), 0, msg="client history not empty")
516 self.assertEqual(len(self.client.history), 0, msg="client history not empty")
517 # the hub results
517 # the hub results
518 hist = self.client.hub_history()
518 hist = self.client.hub_history()
519 self.assertEqual(len(hist), 0, msg="hub history not empty")
519 self.assertEqual(len(hist), 0, msg="hub history not empty")
520
520
521
521
522 def test_spin_thread(self):
522 def test_spin_thread(self):
523 self.client.spin_thread(0.01)
523 self.client.spin_thread(0.01)
524 ar = self.client[-1].apply_async(lambda : 1)
524 ar = self.client[-1].apply_async(lambda : 1)
525 time.sleep(0.1)
525 md = self.client.metadata[ar.msg_ids[0]]
526 self.assertTrue(ar.wall_time < 0.1,
526 # 3s timeout, 100ms poll
527 "spin should have kept wall_time < 0.1, but got %f" % ar.wall_time
527 for i in range(30):
528 )
528 time.sleep(0.1)
529 if md['received'] is not None:
530 break
531 self.assertIsInstance(md['received'], datetime)
529
532
530 def test_stop_spin_thread(self):
533 def test_stop_spin_thread(self):
531 self.client.spin_thread(0.01)
534 self.client.spin_thread(0.01)
532 self.client.stop_spin_thread()
535 self.client.stop_spin_thread()
533 ar = self.client[-1].apply_async(lambda : 1)
536 ar = self.client[-1].apply_async(lambda : 1)
534 time.sleep(0.15)
537 md = self.client.metadata[ar.msg_ids[0]]
535 self.assertTrue(ar.wall_time > 0.1,
538 # 500ms timeout, 100ms poll
536 "Shouldn't be spinning, but got wall_time=%f" % ar.wall_time
539 for i in range(5):
537 )
540 time.sleep(0.1)
541 self.assertIsNone(md['received'], None)
538
542
539 def test_activate(self):
543 def test_activate(self):
540 ip = get_ipython()
544 ip = get_ipython()
541 magics = ip.magics_manager.magics
545 magics = ip.magics_manager.magics
542 self.assertTrue('px' in magics['line'])
546 self.assertTrue('px' in magics['line'])
543 self.assertTrue('px' in magics['cell'])
547 self.assertTrue('px' in magics['cell'])
544 v0 = self.client.activate(-1, '0')
548 v0 = self.client.activate(-1, '0')
545 self.assertTrue('px0' in magics['line'])
549 self.assertTrue('px0' in magics['line'])
546 self.assertTrue('px0' in magics['cell'])
550 self.assertTrue('px0' in magics['cell'])
547 self.assertEqual(v0.targets, self.client.ids[-1])
551 self.assertEqual(v0.targets, self.client.ids[-1])
548 v0 = self.client.activate('all', 'all')
552 v0 = self.client.activate('all', 'all')
549 self.assertTrue('pxall' in magics['line'])
553 self.assertTrue('pxall' in magics['line'])
550 self.assertTrue('pxall' in magics['cell'])
554 self.assertTrue('pxall' in magics['cell'])
551 self.assertEqual(v0.targets, 'all')
555 self.assertEqual(v0.targets, 'all')
@@ -1,171 +1,170 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for getting information about IPython and the system it's running in.
3 Utilities for getting information about IPython and the system it's running in.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18 import platform
18 import platform
19 import pprint
19 import pprint
20 import sys
20 import sys
21 import subprocess
21 import subprocess
22
22
23 from IPython.core import release
23 from IPython.core import release
24 from IPython.utils import py3compat, _sysinfo, encoding
24 from IPython.utils import py3compat, _sysinfo, encoding
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Code
27 # Code
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 def pkg_commit_hash(pkg_path):
30 def pkg_commit_hash(pkg_path):
31 """Get short form of commit hash given directory `pkg_path`
31 """Get short form of commit hash given directory `pkg_path`
32
32
33 We get the commit hash from (in order of preference):
33 We get the commit hash from (in order of preference):
34
34
35 * IPython.utils._sysinfo.commit
35 * IPython.utils._sysinfo.commit
36 * git output, if we are in a git repository
36 * git output, if we are in a git repository
37
37
38 If these fail, we return a not-found placeholder tuple
38 If these fail, we return a not-found placeholder tuple
39
39
40 Parameters
40 Parameters
41 ----------
41 ----------
42 pkg_path : str
42 pkg_path : str
43 directory containing package
43 directory containing package
44 only used for getting commit from active repo
44 only used for getting commit from active repo
45
45
46 Returns
46 Returns
47 -------
47 -------
48 hash_from : str
48 hash_from : str
49 Where we got the hash from - description
49 Where we got the hash from - description
50 hash_str : str
50 hash_str : str
51 short form of hash
51 short form of hash
52 """
52 """
53 # Try and get commit from written commit text file
53 # Try and get commit from written commit text file
54 if _sysinfo.commit:
54 if _sysinfo.commit:
55 return "installation", _sysinfo.commit
55 return "installation", _sysinfo.commit
56
56
57 # maybe we are in a repository
57 # maybe we are in a repository
58 proc = subprocess.Popen('git rev-parse --short HEAD',
58 proc = subprocess.Popen('git rev-parse --short HEAD',
59 stdout=subprocess.PIPE,
59 stdout=subprocess.PIPE,
60 stderr=subprocess.PIPE,
60 stderr=subprocess.PIPE,
61 cwd=pkg_path, shell=True)
61 cwd=pkg_path, shell=True)
62 repo_commit, _ = proc.communicate()
62 repo_commit, _ = proc.communicate()
63 if repo_commit:
63 if repo_commit:
64 return 'repository', repo_commit.strip()
64 return 'repository', repo_commit.strip()
65 return '(none found)', '<not found>'
65 return '(none found)', '<not found>'
66
66
67
67
68 def pkg_info(pkg_path):
68 def pkg_info(pkg_path):
69 """Return dict describing the context of this package
69 """Return dict describing the context of this package
70
70
71 Parameters
71 Parameters
72 ----------
72 ----------
73 pkg_path : str
73 pkg_path : str
74 path containing __init__.py for package
74 path containing __init__.py for package
75
75
76 Returns
76 Returns
77 -------
77 -------
78 context : dict
78 context : dict
79 with named parameters of interest
79 with named parameters of interest
80 """
80 """
81 src, hsh = pkg_commit_hash(pkg_path)
81 src, hsh = pkg_commit_hash(pkg_path)
82 return dict(
82 return dict(
83 ipython_version=release.version,
83 ipython_version=release.version,
84 ipython_path=pkg_path,
84 ipython_path=pkg_path,
85 codename=release.codename,
86 commit_source=src,
85 commit_source=src,
87 commit_hash=hsh,
86 commit_hash=hsh,
88 sys_version=sys.version,
87 sys_version=sys.version,
89 sys_executable=sys.executable,
88 sys_executable=sys.executable,
90 sys_platform=sys.platform,
89 sys_platform=sys.platform,
91 platform=platform.platform(),
90 platform=platform.platform(),
92 os_name=os.name,
91 os_name=os.name,
93 default_encoding=encoding.DEFAULT_ENCODING,
92 default_encoding=encoding.DEFAULT_ENCODING,
94 )
93 )
95
94
96 def get_sys_info():
95 def get_sys_info():
97 """Return useful information about IPython and the system, as a dict."""
96 """Return useful information about IPython and the system, as a dict."""
98 p = os.path
97 p = os.path
99 path = p.dirname(p.abspath(p.join(__file__, '..')))
98 path = p.dirname(p.abspath(p.join(__file__, '..')))
100 return pkg_info(path)
99 return pkg_info(path)
101
100
102 @py3compat.doctest_refactor_print
101 @py3compat.doctest_refactor_print
103 def sys_info():
102 def sys_info():
104 """Return useful information about IPython and the system, as a string.
103 """Return useful information about IPython and the system, as a string.
105
104
106 Examples
105 Examples
107 --------
106 --------
108 ::
107 ::
109
108
110 In [2]: print sys_info()
109 In [2]: print sys_info()
111 {'commit_hash': '144fdae', # random
110 {'commit_hash': '144fdae', # random
112 'commit_source': 'repository',
111 'commit_source': 'repository',
113 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
112 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
114 'ipython_version': '0.11.dev',
113 'ipython_version': '0.11.dev',
115 'os_name': 'posix',
114 'os_name': 'posix',
116 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
115 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
117 'sys_executable': '/usr/bin/python',
116 'sys_executable': '/usr/bin/python',
118 'sys_platform': 'linux2',
117 'sys_platform': 'linux2',
119 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
118 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
120 """
119 """
121 return pprint.pformat(get_sys_info())
120 return pprint.pformat(get_sys_info())
122
121
123 def _num_cpus_unix():
122 def _num_cpus_unix():
124 """Return the number of active CPUs on a Unix system."""
123 """Return the number of active CPUs on a Unix system."""
125 return os.sysconf("SC_NPROCESSORS_ONLN")
124 return os.sysconf("SC_NPROCESSORS_ONLN")
126
125
127
126
128 def _num_cpus_darwin():
127 def _num_cpus_darwin():
129 """Return the number of active CPUs on a Darwin system."""
128 """Return the number of active CPUs on a Darwin system."""
130 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
129 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
131 return p.stdout.read()
130 return p.stdout.read()
132
131
133
132
134 def _num_cpus_windows():
133 def _num_cpus_windows():
135 """Return the number of active CPUs on a Windows system."""
134 """Return the number of active CPUs on a Windows system."""
136 return os.environ.get("NUMBER_OF_PROCESSORS")
135 return os.environ.get("NUMBER_OF_PROCESSORS")
137
136
138
137
139 def num_cpus():
138 def num_cpus():
140 """Return the effective number of CPUs in the system as an integer.
139 """Return the effective number of CPUs in the system as an integer.
141
140
142 This cross-platform function makes an attempt at finding the total number of
141 This cross-platform function makes an attempt at finding the total number of
143 available CPUs in the system, as returned by various underlying system and
142 available CPUs in the system, as returned by various underlying system and
144 python calls.
143 python calls.
145
144
146 If it can't find a sensible answer, it returns 1 (though an error *may* make
145 If it can't find a sensible answer, it returns 1 (though an error *may* make
147 it return a large positive number that's actually incorrect).
146 it return a large positive number that's actually incorrect).
148 """
147 """
149
148
150 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
149 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
151 # for the names of the keys we needed to look up for this function. This
150 # for the names of the keys we needed to look up for this function. This
152 # code was inspired by their equivalent function.
151 # code was inspired by their equivalent function.
153
152
154 ncpufuncs = {'Linux':_num_cpus_unix,
153 ncpufuncs = {'Linux':_num_cpus_unix,
155 'Darwin':_num_cpus_darwin,
154 'Darwin':_num_cpus_darwin,
156 'Windows':_num_cpus_windows,
155 'Windows':_num_cpus_windows,
157 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
156 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
158 # See http://bugs.python.org/issue1082 for details.
157 # See http://bugs.python.org/issue1082 for details.
159 'Microsoft':_num_cpus_windows,
158 'Microsoft':_num_cpus_windows,
160 }
159 }
161
160
162 ncpufunc = ncpufuncs.get(platform.system(),
161 ncpufunc = ncpufuncs.get(platform.system(),
163 # default to unix version (Solaris, AIX, etc)
162 # default to unix version (Solaris, AIX, etc)
164 _num_cpus_unix)
163 _num_cpus_unix)
165
164
166 try:
165 try:
167 ncpus = max(1,int(ncpufunc()))
166 ncpus = max(1,int(ncpufunc()))
168 except:
167 except:
169 ncpus = 1
168 ncpus = 1
170 return ncpus
169 return ncpus
171
170
@@ -1,38 +1,38 b''
1 include README.rst
1 include README.rst
2 include COPYING.txt
2 include COPYING.rst
3 include setupbase.py
3 include setupbase.py
4 include setupegg.py
4 include setupegg.py
5
5
6 graft setupext
6 graft setupext
7
7
8 graft scripts
8 graft scripts
9
9
10 # Load main dir but exclude things we don't want in the distro
10 # Load main dir but exclude things we don't want in the distro
11 graft IPython
11 graft IPython
12 prune IPython/html/static/mathjax
12 prune IPython/html/static/mathjax
13
13
14 # Include some specific files and data resources we need
14 # Include some specific files and data resources we need
15 include IPython/.git_commit_info.ini
15 include IPython/.git_commit_info.ini
16 include IPython/qt/console/resources/icon/IPythonConsole.svg
16 include IPython/qt/console/resources/icon/IPythonConsole.svg
17
17
18 # Documentation
18 # Documentation
19 graft docs
19 graft docs
20 exclude docs/\#*
20 exclude docs/\#*
21 exclude docs/man/*.1.gz
21 exclude docs/man/*.1.gz
22
22
23 # Examples
23 # Examples
24 graft examples
24 graft examples
25
25
26 # docs subdirs we want to skip
26 # docs subdirs we want to skip
27 prune docs/build
27 prune docs/build
28 prune docs/gh-pages
28 prune docs/gh-pages
29 prune docs/dist
29 prune docs/dist
30
30
31 # Patterns to exclude from any directory
31 # Patterns to exclude from any directory
32 global-exclude *~
32 global-exclude *~
33 global-exclude *.flc
33 global-exclude *.flc
34 global-exclude *.pyc
34 global-exclude *.pyc
35 global-exclude *.pyo
35 global-exclude *.pyo
36 global-exclude .dircopy.log
36 global-exclude .dircopy.log
37 global-exclude .git
37 global-exclude .git
38 global-exclude .ipynb_checkpoints
38 global-exclude .ipynb_checkpoints
@@ -1,9 +1,9 b''
1 <html>
1 <html>
2 <head>
2 <head>
3 <meta http-equiv="Refresh" content="0; url=notebook.html" />
3 <meta http-equiv="Refresh" content="0; url=../notebook/index.html" />
4 <title>Notebook page has move</title>
4 <title>Notebook docs have moved</title>
5 </head>
5 </head>
6 <body>
6 <body>
7 <p>The notebook page has moved to <a href="notebook.html">this link</a>.</p>
7 <p>The notebook docs have moved <a href="../notebook/index.html">here</a>.</p>
8 </body>
8 </body>
9 </html>
9 </html>
@@ -1,246 +1,248 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 #
2 #
3 # IPython documentation build configuration file.
3 # IPython documentation build configuration file.
4
4
5 # NOTE: This file has been edited manually from the auto-generated one from
5 # NOTE: This file has been edited manually from the auto-generated one from
6 # sphinx. Do NOT delete and re-generate. If any changes from sphinx are
6 # sphinx. Do NOT delete and re-generate. If any changes from sphinx are
7 # needed, generate a scratch one and merge by hand any new fields needed.
7 # needed, generate a scratch one and merge by hand any new fields needed.
8
8
9 #
9 #
10 # This file is execfile()d with the current directory set to its containing dir.
10 # This file is execfile()d with the current directory set to its containing dir.
11 #
11 #
12 # The contents of this file are pickled, so don't put values in the namespace
12 # The contents of this file are pickled, so don't put values in the namespace
13 # that aren't pickleable (module imports are okay, they're removed automatically).
13 # that aren't pickleable (module imports are okay, they're removed automatically).
14 #
14 #
15 # All configuration values have a default value; values that are commented out
15 # All configuration values have a default value; values that are commented out
16 # serve to show the default value.
16 # serve to show the default value.
17
17
18 import sys, os
18 import sys, os
19
19
20 ON_RTD = os.environ.get('READTHEDOCS', None) == 'True'
20 ON_RTD = os.environ.get('READTHEDOCS', None) == 'True'
21
21
22 if ON_RTD:
22 if ON_RTD:
23 # Mock the presence of matplotlib, which we don't have on RTD
23 # Mock the presence of matplotlib, which we don't have on RTD
24 # see
24 # see
25 # http://read-the-docs.readthedocs.org/en/latest/faq.html
25 # http://read-the-docs.readthedocs.org/en/latest/faq.html
26 tags.add('rtd')
26 tags.add('rtd')
27
27
28 # If your extensions are in another directory, add it here. If the directory
28 # If your extensions are in another directory, add it here. If the directory
29 # is relative to the documentation root, use os.path.abspath to make it
29 # is relative to the documentation root, use os.path.abspath to make it
30 # absolute, like shown here.
30 # absolute, like shown here.
31 sys.path.insert(0, os.path.abspath('../sphinxext'))
31 sys.path.insert(0, os.path.abspath('../sphinxext'))
32
32
33 # We load the ipython release info into a dict by explicit execution
33 # We load the ipython release info into a dict by explicit execution
34 iprelease = {}
34 iprelease = {}
35 execfile('../../IPython/core/release.py',iprelease)
35 execfile('../../IPython/core/release.py',iprelease)
36
36
37 # General configuration
37 # General configuration
38 # ---------------------
38 # ---------------------
39
39
40 # Add any Sphinx extension module names here, as strings. They can be extensions
40 # Add any Sphinx extension module names here, as strings. They can be extensions
41 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
41 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
42 extensions = [
42 extensions = [
43 'matplotlib.sphinxext.mathmpl',
43 'matplotlib.sphinxext.mathmpl',
44 'matplotlib.sphinxext.only_directives',
44 'matplotlib.sphinxext.only_directives',
45 'matplotlib.sphinxext.plot_directive',
45 'matplotlib.sphinxext.plot_directive',
46 'sphinx.ext.autodoc',
46 'sphinx.ext.autodoc',
47 'sphinx.ext.autosummary',
47 'sphinx.ext.autosummary',
48 'sphinx.ext.doctest',
48 'sphinx.ext.doctest',
49 'sphinx.ext.inheritance_diagram',
49 'sphinx.ext.inheritance_diagram',
50 'sphinx.ext.intersphinx',
50 'sphinx.ext.intersphinx',
51 'IPython.sphinxext.ipython_console_highlighting',
51 'IPython.sphinxext.ipython_console_highlighting',
52 'IPython.sphinxext.ipython_directive',
52 'IPython.sphinxext.ipython_directive',
53 'numpydoc', # to preprocess docstrings
53 'numpydoc', # to preprocess docstrings
54 'github', # for easy GitHub links
54 'github', # for easy GitHub links
55 ]
55 ]
56
56
57 if ON_RTD:
57 if ON_RTD:
58 # Remove extensions not currently supported on RTD
58 # Remove extensions not currently supported on RTD
59 extensions.remove('matplotlib.sphinxext.only_directives')
59 extensions.remove('matplotlib.sphinxext.only_directives')
60 extensions.remove('matplotlib.sphinxext.mathmpl')
60 extensions.remove('matplotlib.sphinxext.mathmpl')
61 extensions.remove('matplotlib.sphinxext.plot_directive')
61 extensions.remove('matplotlib.sphinxext.plot_directive')
62 extensions.remove('IPython.sphinxext.ipython_directive')
62 extensions.remove('IPython.sphinxext.ipython_directive')
63 extensions.remove('IPython.sphinxext.ipython_console_highlighting')
63 extensions.remove('IPython.sphinxext.ipython_console_highlighting')
64
64
65 # Add any paths that contain templates here, relative to this directory.
65 # Add any paths that contain templates here, relative to this directory.
66 templates_path = ['_templates']
66 templates_path = ['_templates']
67
67
68 # The suffix of source filenames.
68 # The suffix of source filenames.
69 source_suffix = '.rst'
69 source_suffix = '.rst'
70
70
71 if iprelease['_version_extra']:
71 if iprelease['_version_extra']:
72 rst_prolog = """
72 rst_prolog = """
73 .. note::
73 .. note::
74
74
75 This documentation is for a development version of IPython. There may be
75 This documentation is for a development version of IPython. There may be
76 significant differences from the latest stable release (1.2.1).
76 significant differences from the latest stable release (1.2.1).
77
77
78 """
78 """
79
79
80 # The master toctree document.
80 # The master toctree document.
81 master_doc = 'index'
81 master_doc = 'index'
82
82
83 # General substitutions.
83 # General substitutions.
84 project = 'IPython'
84 project = 'IPython'
85 copyright = '2008, The IPython Development Team'
85 copyright = '2008, The IPython Development Team'
86
86
87 # ghissue config
87 # ghissue config
88 github_project_url = "https://github.com/ipython/ipython"
88 github_project_url = "https://github.com/ipython/ipython"
89
89
90 # numpydoc config
90 # numpydoc config
91 numpydoc_show_class_members = False # Otherwise Sphinx emits thousands of warnings
91 numpydoc_show_class_members = False # Otherwise Sphinx emits thousands of warnings
92 numpydoc_class_members_toctree = False
92 numpydoc_class_members_toctree = False
93
93
94 # The default replacements for |version| and |release|, also used in various
94 # The default replacements for |version| and |release|, also used in various
95 # other places throughout the built documents.
95 # other places throughout the built documents.
96 #
96 #
97 # The full version, including alpha/beta/rc tags.
97 # The full version, including alpha/beta/rc tags.
98 codename = iprelease['codename']
98 release = "%s" % iprelease['version']
99 release = "%s: %s" % (iprelease['version'], codename)
100 # Just the X.Y.Z part, no '-dev'
99 # Just the X.Y.Z part, no '-dev'
101 version = iprelease['version'].split('-', 1)[0]
100 version = iprelease['version'].split('-', 1)[0]
102
101
103
102
104 # There are two options for replacing |today|: either, you set today to some
103 # There are two options for replacing |today|: either, you set today to some
105 # non-false value, then it is used:
104 # non-false value, then it is used:
106 #today = ''
105 #today = ''
107 # Else, today_fmt is used as the format for a strftime call.
106 # Else, today_fmt is used as the format for a strftime call.
108 today_fmt = '%B %d, %Y'
107 today_fmt = '%B %d, %Y'
109
108
110 # List of documents that shouldn't be included in the build.
109 # List of documents that shouldn't be included in the build.
111 #unused_docs = []
110 #unused_docs = []
112
111
113 # List of directories, relative to source directories, that shouldn't be searched
112 # List of directories, relative to source directories, that shouldn't be searched
114 # for source files.
113 # for source files.
115 exclude_dirs = ['attic']
114 exclude_dirs = ['attic']
116
115
117 # If true, '()' will be appended to :func: etc. cross-reference text.
116 # If true, '()' will be appended to :func: etc. cross-reference text.
118 #add_function_parentheses = True
117 #add_function_parentheses = True
119
118
120 # If true, the current module name will be prepended to all description
119 # If true, the current module name will be prepended to all description
121 # unit titles (such as .. function::).
120 # unit titles (such as .. function::).
122 #add_module_names = True
121 #add_module_names = True
123
122
124 # If true, sectionauthor and moduleauthor directives will be shown in the
123 # If true, sectionauthor and moduleauthor directives will be shown in the
125 # output. They are ignored by default.
124 # output. They are ignored by default.
126 #show_authors = False
125 #show_authors = False
127
126
128 # The name of the Pygments (syntax highlighting) style to use.
127 # The name of the Pygments (syntax highlighting) style to use.
129 pygments_style = 'sphinx'
128 pygments_style = 'sphinx'
130
129
131
130
132 # Options for HTML output
131 # Options for HTML output
133 # -----------------------
132 # -----------------------
134
133
135 # The style sheet to use for HTML and HTML Help pages. A file of that name
134 # The style sheet to use for HTML and HTML Help pages. A file of that name
136 # must exist either in Sphinx' static/ path, or in one of the custom paths
135 # must exist either in Sphinx' static/ path, or in one of the custom paths
137 # given in html_static_path.
136 # given in html_static_path.
138 html_style = 'default.css'
137 html_style = 'default.css'
139
138
140 # The name for this set of Sphinx documents. If None, it defaults to
139 # The name for this set of Sphinx documents. If None, it defaults to
141 # "<project> v<release> documentation".
140 # "<project> v<release> documentation".
142 #html_title = None
141 #html_title = None
143
142
144 # The name of an image file (within the static path) to place at the top of
143 # The name of an image file (within the static path) to place at the top of
145 # the sidebar.
144 # the sidebar.
146 #html_logo = None
145 #html_logo = None
147
146
148 # Add any paths that contain custom static files (such as style sheets) here,
147 # Add any paths that contain custom static files (such as style sheets) here,
149 # relative to this directory. They are copied after the builtin static files,
148 # relative to this directory. They are copied after the builtin static files,
150 # so a file named "default.css" will overwrite the builtin "default.css".
149 # so a file named "default.css" will overwrite the builtin "default.css".
151 html_static_path = ['_static']
150 html_static_path = ['_static']
152
151
153 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
152 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
154 # using the given strftime format.
153 # using the given strftime format.
155 html_last_updated_fmt = '%b %d, %Y'
154 html_last_updated_fmt = '%b %d, %Y'
156
155
157 # If true, SmartyPants will be used to convert quotes and dashes to
156 # If true, SmartyPants will be used to convert quotes and dashes to
158 # typographically correct entities.
157 # typographically correct entities.
159 #html_use_smartypants = True
158 #html_use_smartypants = True
160
159
161 # Custom sidebar templates, maps document names to template names.
160 # Custom sidebar templates, maps document names to template names.
162 #html_sidebars = {}
161 #html_sidebars = {}
163
162
164 # Additional templates that should be rendered to pages, maps page names to
163 # Additional templates that should be rendered to pages, maps page names to
165 # template names.
164 # template names.
166 html_additional_pages = {
165 html_additional_pages = {
167 'interactive/htmlnotebook': 'htmlnotebook.html',
166 'interactive/htmlnotebook': 'notebook_redirect.html',
167 'interactive/notebook': 'notebook_redirect.html',
168 'interactive/nbconvert': 'notebook_redirect.html',
169 'interactive/public_server': 'notebook_redirect.html',
168 }
170 }
169
171
170 # If false, no module index is generated.
172 # If false, no module index is generated.
171 #html_use_modindex = True
173 #html_use_modindex = True
172
174
173 # If true, the reST sources are included in the HTML build as _sources/<name>.
175 # If true, the reST sources are included in the HTML build as _sources/<name>.
174 #html_copy_source = True
176 #html_copy_source = True
175
177
176 # If true, an OpenSearch description file will be output, and all pages will
178 # If true, an OpenSearch description file will be output, and all pages will
177 # contain a <link> tag referring to it. The value of this option must be the
179 # contain a <link> tag referring to it. The value of this option must be the
178 # base URL from which the finished HTML is served.
180 # base URL from which the finished HTML is served.
179 #html_use_opensearch = ''
181 #html_use_opensearch = ''
180
182
181 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
183 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
182 #html_file_suffix = ''
184 #html_file_suffix = ''
183
185
184 # Output file base name for HTML help builder.
186 # Output file base name for HTML help builder.
185 htmlhelp_basename = 'ipythondoc'
187 htmlhelp_basename = 'ipythondoc'
186
188
187 intersphinx_mapping = {'python': ('http://docs.python.org/2/', None)}
189 intersphinx_mapping = {'python': ('http://docs.python.org/2/', None)}
188
190
189 # Options for LaTeX output
191 # Options for LaTeX output
190 # ------------------------
192 # ------------------------
191
193
192 # The paper size ('letter' or 'a4').
194 # The paper size ('letter' or 'a4').
193 latex_paper_size = 'letter'
195 latex_paper_size = 'letter'
194
196
195 # The font size ('10pt', '11pt' or '12pt').
197 # The font size ('10pt', '11pt' or '12pt').
196 latex_font_size = '11pt'
198 latex_font_size = '11pt'
197
199
198 # Grouping the document tree into LaTeX files. List of tuples
200 # Grouping the document tree into LaTeX files. List of tuples
199 # (source start file, target name, title, author, document class [howto/manual]).
201 # (source start file, target name, title, author, document class [howto/manual]).
200
202
201 latex_documents = [
203 latex_documents = [
202 ('index', 'ipython.tex', 'IPython Documentation',
204 ('index', 'ipython.tex', 'IPython Documentation',
203 ur"""The IPython Development Team""", 'manual', True),
205 ur"""The IPython Development Team""", 'manual', True),
204 ('parallel/winhpc_index', 'winhpc_whitepaper.tex',
206 ('parallel/winhpc_index', 'winhpc_whitepaper.tex',
205 'Using IPython on Windows HPC Server 2008',
207 'Using IPython on Windows HPC Server 2008',
206 ur"Brian E. Granger", 'manual', True)
208 ur"Brian E. Granger", 'manual', True)
207 ]
209 ]
208
210
209 # The name of an image file (relative to this directory) to place at the top of
211 # The name of an image file (relative to this directory) to place at the top of
210 # the title page.
212 # the title page.
211 #latex_logo = None
213 #latex_logo = None
212
214
213 # For "manual" documents, if this is true, then toplevel headings are parts,
215 # For "manual" documents, if this is true, then toplevel headings are parts,
214 # not chapters.
216 # not chapters.
215 #latex_use_parts = False
217 #latex_use_parts = False
216
218
217 # Additional stuff for the LaTeX preamble.
219 # Additional stuff for the LaTeX preamble.
218 #latex_preamble = ''
220 #latex_preamble = ''
219
221
220 # Documents to append as an appendix to all manuals.
222 # Documents to append as an appendix to all manuals.
221 #latex_appendices = []
223 #latex_appendices = []
222
224
223 # If false, no module index is generated.
225 # If false, no module index is generated.
224 latex_use_modindex = True
226 latex_use_modindex = True
225
227
226
228
227 # Options for texinfo output
229 # Options for texinfo output
228 # --------------------------
230 # --------------------------
229
231
230 texinfo_documents = [
232 texinfo_documents = [
231 (master_doc, 'ipython', 'IPython Documentation',
233 (master_doc, 'ipython', 'IPython Documentation',
232 'The IPython Development Team',
234 'The IPython Development Team',
233 'IPython',
235 'IPython',
234 'IPython Documentation',
236 'IPython Documentation',
235 'Programming',
237 'Programming',
236 1),
238 1),
237 ]
239 ]
238
240
239 modindex_common_prefix = ['IPython.']
241 modindex_common_prefix = ['IPython.']
240
242
241
243
242 # Cleanup
244 # Cleanup
243 # -------
245 # -------
244 # delete release info to avoid pickling errors from sphinx
246 # delete release info to avoid pickling errors from sphinx
245
247
246 del iprelease
248 del iprelease
@@ -1,7 +1,12 b''
1 .. _extensions_octavemagic:
1 .. _extensions_octavemagic:
2
2
3 ===========
3 ===========
4 octavemagic
4 octavemagic
5 ===========
5 ===========
6
6
7 .. note::
8
9 The octavemagic extension has been moved to `oct2py <http://blink1073.github.io/oct2py/docs/>`_
10 as :mod:`oct2py.ipython`.
11
7 .. automodule:: IPython.extensions.octavemagic
12 .. automodule:: IPython.extensions.octavemagic
@@ -1,7 +1,12 b''
1 .. _extensions_rmagic:
1 .. _extensions_rmagic:
2
2
3 ===========
3 ===========
4 rmagic
4 rmagic
5 ===========
5 ===========
6
6
7 .. note::
8
9 The rmagic extension has been moved to `rpy2 <http://rpy.sourceforge.net/rpy2.html>`_
10 as :mod:`rpy2.interactive.ipython`.
11
7 .. automodule:: IPython.extensions.rmagic
12 .. automodule:: IPython.extensions.rmagic
@@ -1,29 +1,27 b''
1 .. _developer_guide:
1 .. _developer_guide:
2
2
3 =========================
3 =========================
4 IPython developer's guide
4 IPython developer's guide
5 =========================
5 =========================
6
6
7 This are two categories of developer focused documentation:
7 This are two categories of developer focused documentation:
8
8
9 1. Documentation for developers of *IPython itself*.
9 1. Documentation for developers of *IPython itself*.
10 2. Documentation for developers of third party tools and libraries
10 2. Documentation for developers of third party tools and libraries
11 that use IPython.
11 that use IPython.
12
12
13 This part of our documentation only contains information in the second category.
13 This part of our documentation only contains information in the second category.
14
14
15 Developers interested in working on IPython itself should consult
15 Developers interested in working on IPython itself should consult
16 our `developer information <https://github.com/ipython/ipython/wiki/Dev:-Index>`_
16 our `developer information <https://github.com/ipython/ipython/wiki/Dev:-Index>`_
17 on the IPython GitHub wiki.
17 on the IPython GitHub wiki.
18
18
19 .. toctree::
19 .. toctree::
20 :maxdepth: 1
20 :maxdepth: 1
21
21
22
23 gitwash/index
24 messaging
22 messaging
25 parallel_messages
23 parallel_messages
26 parallel_connections
24 parallel_connections
27 pycompat
25 pycompat
28 config
26 config
29 inputhook_app
27 inputhook_app
@@ -1,38 +1,39 b''
1 =====================
1 =====================
2 IPython Documentation
2 IPython Documentation
3 =====================
3 =====================
4
4
5 .. htmlonly::
5 .. htmlonly::
6
6
7 :Release: |release|
7 :Release: |release|
8 :Date: |today|
8 :Date: |today|
9
9
10 .. only:: not rtd
10 .. only:: not rtd
11
11
12 Welcome to the official IPython documentation.
12 Welcome to the official IPython documentation.
13
13
14 .. only:: rtd
14 .. only:: rtd
15
15
16 This is a partial copy of IPython documentation, please visit `IPython official documentation <http://ipython.org/documentation.html>`_.
16 This is a partial copy of IPython documentation, please visit `IPython official documentation <http://ipython.org/documentation.html>`_.
17
17
18 Contents
18 Contents
19 ========
19 ========
20
20
21 .. toctree::
21 .. toctree::
22 :maxdepth: 1
22 :maxdepth: 1
23
23
24 overview
24 overview
25 whatsnew/index
25 whatsnew/index
26 install/index
26 install/index
27 interactive/index
27 interactive/index
28 notebook/index
28 parallel/index
29 parallel/index
29 config/index
30 config/index
30 development/index
31 development/index
31 api/index
32 api/index
32 about/index
33 about/index
33
34
34 .. htmlonly::
35 .. htmlonly::
35 * :ref:`genindex`
36 * :ref:`genindex`
36 * :ref:`modindex`
37 * :ref:`modindex`
37 * :ref:`search`
38 * :ref:`search`
38
39
@@ -1,18 +1,16 b''
1 ==================================
1 ==================================
2 Using IPython for interactive work
2 Using IPython for interactive work
3 ==================================
3 ==================================
4
4
5 .. toctree::
5 .. toctree::
6 :maxdepth: 2
6 :maxdepth: 2
7
7
8 tutorial
8 tutorial
9 tips
9 tips
10 reference
10 reference
11 shell
11 shell
12 qtconsole
12 qtconsole
13 notebook
14 cm_keyboard
15 nbconvert
16 public_server
17
13
14 .. seealso::
18
15
16 :doc:`/notebook/index`
1 NO CONTENT: file renamed from docs/source/interactive/cm_keyboard.rst to docs/source/notebook/cm_keyboard.rst
NO CONTENT: file renamed from docs/source/interactive/cm_keyboard.rst to docs/source/notebook/cm_keyboard.rst
1 NO CONTENT: file renamed from docs/source/interactive/nbconvert.rst to docs/source/notebook/nbconvert.rst
NO CONTENT: file renamed from docs/source/interactive/nbconvert.rst to docs/source/notebook/nbconvert.rst
1 NO CONTENT: file renamed from docs/source/interactive/notebook.rst to docs/source/notebook/notebook.rst
NO CONTENT: file renamed from docs/source/interactive/notebook.rst to docs/source/notebook/notebook.rst
@@ -1,159 +1,159 b''
1 .. _working_remotely:
1 .. _working_remotely:
2
2
3 Running a notebook server
3 Running a notebook server
4 =========================
4 =========================
5
5
6
6
7 The :ref:`IPython notebook <htmlnotebook>` web-application is based on a
7 The :ref:`IPython notebook <htmlnotebook>` web-application is based on a
8 server-client structure. This server uses a :ref:`two-process kernel
8 server-client structure. This server uses a :ref:`two-process kernel
9 architecture <ipythonzmq>` based on ZeroMQ_, as well as Tornado_ for serving
9 architecture <ipythonzmq>` based on ZeroMQ_, as well as Tornado_ for serving
10 HTTP requests. By default, a notebook server runs on http://127.0.0.1:8888/
10 HTTP requests. By default, a notebook server runs on http://127.0.0.1:8888/
11 and is accessible only from `localhost`. This document describes how you can
11 and is accessible only from `localhost`. This document describes how you can
12 :ref:`secure a notebook server <notebook_security>` and how to :ref:`run it on
12 :ref:`secure a notebook server <notebook_security>` and how to :ref:`run it on
13 a public interface <notebook_public_server>`.
13 a public interface <notebook_public_server>`.
14
14
15 .. _ZeroMQ: http://zeromq.org
15 .. _ZeroMQ: http://zeromq.org
16
16
17 .. _Tornado: http://www.tornadoweb.org
17 .. _Tornado: http://www.tornadoweb.org
18
18
19
19
20 .. _notebook_security:
20 .. _notebook_security:
21
21
22 Notebook security
22 Securing a notebook server
23 -----------------
23 --------------------------
24
24
25 You can protect your notebook server with a simple single password by
25 You can protect your notebook server with a simple single password by
26 setting the :attr:`NotebookApp.password` configurable. You can prepare a
26 setting the :attr:`NotebookApp.password` configurable. You can prepare a
27 hashed password using the function :func:`IPython.lib.security.passwd`:
27 hashed password using the function :func:`IPython.lib.security.passwd`:
28
28
29 .. sourcecode:: ipython
29 .. sourcecode:: ipython
30
30
31 In [1]: from IPython.lib import passwd
31 In [1]: from IPython.lib import passwd
32 In [2]: passwd()
32 In [2]: passwd()
33 Enter password:
33 Enter password:
34 Verify password:
34 Verify password:
35 Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
35 Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
36
36
37 .. note::
37 .. note::
38
38
39 :func:`~IPython.lib.security.passwd` can also take the password as a string
39 :func:`~IPython.lib.security.passwd` can also take the password as a string
40 argument. **Do not** pass it as an argument inside an IPython session, as it
40 argument. **Do not** pass it as an argument inside an IPython session, as it
41 will be saved in your input history.
41 will be saved in your input history.
42
42
43 You can then add this to your :file:`ipython_notebook_config.py`, e.g.::
43 You can then add this to your :file:`ipython_notebook_config.py`, e.g.::
44
44
45 # Password to use for web authentication
45 # Password to use for web authentication
46 c = get_config()
46 c = get_config()
47 c.NotebookApp.password =
47 c.NotebookApp.password =
48 u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
48 u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
49
49
50 When using a password, it is a good idea to also use SSL, so that your
50 When using a password, it is a good idea to also use SSL, so that your
51 password is not sent unencrypted by your browser. You can start the notebook
51 password is not sent unencrypted by your browser. You can start the notebook
52 to communicate via a secure protocol mode using a self-signed certificate with
52 to communicate via a secure protocol mode using a self-signed certificate with
53 the command::
53 the command::
54
54
55 $ ipython notebook --certfile=mycert.pem
55 $ ipython notebook --certfile=mycert.pem
56
56
57 .. note::
57 .. note::
58
58
59 A self-signed certificate can be generated with ``openssl``. For example,
59 A self-signed certificate can be generated with ``openssl``. For example,
60 the following command will create a certificate valid for 365 days with
60 the following command will create a certificate valid for 365 days with
61 both the key and certificate data written to the same file::
61 both the key and certificate data written to the same file::
62
62
63 $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
63 $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
64
64
65 Your browser will warn you of a dangerous certificate because it is
65 Your browser will warn you of a dangerous certificate because it is
66 self-signed. If you want to have a fully compliant certificate that will not
66 self-signed. If you want to have a fully compliant certificate that will not
67 raise warnings, it is possible (but rather involved) to obtain one,
67 raise warnings, it is possible (but rather involved) to obtain one,
68 as explained in detail in `this tutorial`__.
68 as explained in detail in `this tutorial`__.
69
69
70 .. __: http://arstechnica.com/security/news/2009/12/how-to-get-set-with-a-secure-sertificate-for-free.ars
70 .. __: http://arstechnica.com/security/news/2009/12/how-to-get-set-with-a-secure-sertificate-for-free.ars
71
71
72 Keep in mind that when you enable SSL support, you will need to access the
72 Keep in mind that when you enable SSL support, you will need to access the
73 notebook server over ``https://``, not over plain ``http://``. The startup
73 notebook server over ``https://``, not over plain ``http://``. The startup
74 message from the server prints this, but it is easy to overlook and think the
74 message from the server prints this, but it is easy to overlook and think the
75 server is for some reason non-responsive.
75 server is for some reason non-responsive.
76
76
77
77
78 .. _notebook_public_server:
78 .. _notebook_public_server:
79
79
80 Running a public notebook server
80 Running a public notebook server
81 --------------------------------
81 --------------------------------
82
82
83 If you want to access your notebook server remotely via a web browser,
83 If you want to access your notebook server remotely via a web browser,
84 you can do the following.
84 you can do the following.
85
85
86 Start by creating a certificate file and a hashed password, as explained
86 Start by creating a certificate file and a hashed password, as explained
87 above. Then create a custom profile for the notebook, with the following
87 above. Then create a custom profile for the notebook, with the following
88 command line, type::
88 command line, type::
89
89
90 $ ipython profile create nbserver
90 $ ipython profile create nbserver
91
91
92 In the profile directory just created, edit the file
92 In the profile directory just created, edit the file
93 ``ipython_notebook_config.py``. By default, the file has all fields
93 ``ipython_notebook_config.py``. By default, the file has all fields
94 commented; the minimum set you need to uncomment and edit is the following::
94 commented; the minimum set you need to uncomment and edit is the following::
95
95
96 c = get_config()
96 c = get_config()
97
97
98 # Kernel config
98 # Kernel config
99 c.IPKernelApp.pylab = 'inline' # if you want plotting support always
99 c.IPKernelApp.pylab = 'inline' # if you want plotting support always
100
100
101 # Notebook config
101 # Notebook config
102 c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
102 c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
103 c.NotebookApp.ip = '*'
103 c.NotebookApp.ip = '*'
104 c.NotebookApp.open_browser = False
104 c.NotebookApp.open_browser = False
105 c.NotebookApp.password = u'sha1:bcd259ccf...[your hashed password here]'
105 c.NotebookApp.password = u'sha1:bcd259ccf...[your hashed password here]'
106 # It is a good idea to put it on a known, fixed port
106 # It is a good idea to put it on a known, fixed port
107 c.NotebookApp.port = 9999
107 c.NotebookApp.port = 9999
108
108
109 You can then start the notebook and access it later by pointing your browser
109 You can then start the notebook and access it later by pointing your browser
110 to ``https://your.host.com:9999`` with ``ipython notebook
110 to ``https://your.host.com:9999`` with ``ipython notebook
111 --profile=nbserver``.
111 --profile=nbserver``.
112
112
113 Running with a different URL prefix
113 Running with a different URL prefix
114 -----------------------------------
114 -----------------------------------
115
115
116 The notebook dashboard (the landing page with an overview
116 The notebook dashboard (the landing page with an overview
117 of the notebooks in your working directory) typically lives at the URL
117 of the notebooks in your working directory) typically lives at the URL
118 ``http://localhost:8888/``. If you prefer that it lives, together with the
118 ``http://localhost:8888/``. If you prefer that it lives, together with the
119 rest of the notebook, under a sub-directory,
119 rest of the notebook, under a sub-directory,
120 e.g. ``http://localhost:8888/ipython/``, you can do so with
120 e.g. ``http://localhost:8888/ipython/``, you can do so with
121 configuration options like the following (see above for instructions about
121 configuration options like the following (see above for instructions about
122 modifying ``ipython_notebook_config.py``)::
122 modifying ``ipython_notebook_config.py``)::
123
123
124 c.NotebookApp.base_url = '/ipython/'
124 c.NotebookApp.base_url = '/ipython/'
125 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
125 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
126
126
127 Using a different notebook store
127 Using a different notebook store
128 --------------------------------
128 --------------------------------
129
129
130 By default, the notebook server stores the notebook documents that it saves as
130 By default, the notebook server stores the notebook documents that it saves as
131 files in the working directory of the notebook server, also known as the
131 files in the working directory of the notebook server, also known as the
132 ``notebook_dir``. This logic is implemented in the
132 ``notebook_dir``. This logic is implemented in the
133 :class:`FileNotebookManager` class. However, the server can be configured to
133 :class:`FileNotebookManager` class. However, the server can be configured to
134 use a different notebook manager class, which can
134 use a different notebook manager class, which can
135 store the notebooks in a different format.
135 store the notebooks in a different format.
136
136
137 The bookstore_ package currently allows users to store notebooks on Rackspace
137 The bookstore_ package currently allows users to store notebooks on Rackspace
138 CloudFiles or OpenStack Swift based object stores.
138 CloudFiles or OpenStack Swift based object stores.
139
139
140 Writing a notebook manager is as simple as extending the base class
140 Writing a notebook manager is as simple as extending the base class
141 :class:`NotebookManager`. The simple_notebook_manager_ provides a great example
141 :class:`NotebookManager`. The simple_notebook_manager_ provides a great example
142 of an in memory notebook manager, created solely for the purpose of
142 of an in memory notebook manager, created solely for the purpose of
143 illustrating the notebook manager API.
143 illustrating the notebook manager API.
144
144
145 .. _bookstore: https://github.com/rgbkrk/bookstore
145 .. _bookstore: https://github.com/rgbkrk/bookstore
146
146
147 .. _simple_notebook_manager: https://github.com/khinsen/simple_notebook_manager
147 .. _simple_notebook_manager: https://github.com/khinsen/simple_notebook_manager
148
148
149 Known issues
149 Known issues
150 ------------
150 ------------
151
151
152 When behind a proxy, especially if your system or browser is set to autodetect
152 When behind a proxy, especially if your system or browser is set to autodetect
153 the proxy, the notebook web application might fail to connect to the server's
153 the proxy, the notebook web application might fail to connect to the server's
154 websockets, and present you with a warning at startup. In this case, you need
154 websockets, and present you with a warning at startup. In this case, you need
155 to configure your system not to use the proxy for the server's address.
155 to configure your system not to use the proxy for the server's address.
156
156
157 For example, in Firefox, go to the Preferences panel, Advanced section,
157 For example, in Firefox, go to the Preferences panel, Advanced section,
158 Network tab, click 'Settings...', and add the address of the notebook server
158 Network tab, click 'Settings...', and add the address of the notebook server
159 to the 'No proxy for' field.
159 to the 'No proxy for' field.
@@ -1,177 +1,177 b''
1 .. _dag_dependencies:
1 .. _dag_dependencies:
2
2
3 ================
3 ================
4 DAG Dependencies
4 DAG Dependencies
5 ================
5 ================
6
6
7 Often, parallel workflow is described in terms of a `Directed Acyclic Graph
7 Often, parallel workflow is described in terms of a `Directed Acyclic Graph
8 <http://en.wikipedia.org/wiki/Directed_acyclic_graph>`_ or DAG. A popular library
8 <http://en.wikipedia.org/wiki/Directed_acyclic_graph>`_ or DAG. A popular library
9 for working with Graphs is NetworkX_. Here, we will walk through a demo mapping
9 for working with Graphs is NetworkX_. Here, we will walk through a demo mapping
10 a nx DAG to task dependencies.
10 a nx DAG to task dependencies.
11
11
12 The full script that runs this demo can be found in
12 The full script that runs this demo can be found in
13 :file:`examples/parallel/dagdeps.py`.
13 :file:`examples/parallel/dagdeps.py`.
14
14
15 Why are DAGs good for task dependencies?
15 Why are DAGs good for task dependencies?
16 ----------------------------------------
16 ----------------------------------------
17
17
18 The 'G' in DAG is 'Graph'. A Graph is a collection of **nodes** and **edges** that connect
18 The 'G' in DAG is 'Graph'. A Graph is a collection of **nodes** and **edges** that connect
19 the nodes. For our purposes, each node would be a task, and each edge would be a
19 the nodes. For our purposes, each node would be a task, and each edge would be a
20 dependency. The 'D' in DAG stands for 'Directed'. This means that each edge has a
20 dependency. The 'D' in DAG stands for 'Directed'. This means that each edge has a
21 direction associated with it. So we can interpret the edge (a,b) as meaning that b depends
21 direction associated with it. So we can interpret the edge (a,b) as meaning that b depends
22 on a, whereas the edge (b,a) would mean a depends on b. The 'A' is 'Acyclic', meaning that
22 on a, whereas the edge (b,a) would mean a depends on b. The 'A' is 'Acyclic', meaning that
23 there must not be any closed loops in the graph. This is important for dependencies,
23 there must not be any closed loops in the graph. This is important for dependencies,
24 because if a loop were closed, then a task could ultimately depend on itself, and never be
24 because if a loop were closed, then a task could ultimately depend on itself, and never be
25 able to run. If your workflow can be described as a DAG, then it is impossible for your
25 able to run. If your workflow can be described as a DAG, then it is impossible for your
26 dependencies to cause a deadlock.
26 dependencies to cause a deadlock.
27
27
28 A Sample DAG
28 A Sample DAG
29 ------------
29 ------------
30
30
31 Here, we have a very simple 5-node DAG:
31 Here, we have a very simple 5-node DAG:
32
32
33 .. figure:: figs/simpledag.*
33 .. figure:: figs/simpledag.*
34 :width: 600px
34 :width: 600px
35
35
36 With NetworkX, an arrow is just a fattened bit on the edge. Here, we can see that task 0
36 With NetworkX, an arrow is just a fattened bit on the edge. Here, we can see that task 0
37 depends on nothing, and can run immediately. 1 and 2 depend on 0; 3 depends on
37 depends on nothing, and can run immediately. 1 and 2 depend on 0; 3 depends on
38 1 and 2; and 4 depends only on 1.
38 1 and 2; and 4 depends only on 1.
39
39
40 A possible sequence of events for this workflow:
40 A possible sequence of events for this workflow:
41
41
42 0. Task 0 can run right away
42 0. Task 0 can run right away
43 1. 0 finishes, so 1,2 can start
43 1. 0 finishes, so 1,2 can start
44 2. 1 finishes, 3 is still waiting on 2, but 4 can start right away
44 2. 1 finishes, 3 is still waiting on 2, but 4 can start right away
45 3. 2 finishes, and 3 can finally start
45 3. 2 finishes, and 3 can finally start
46
46
47
47
48 Further, taking failures into account, assuming all dependencies are run with the default
48 Further, taking failures into account, assuming all dependencies are run with the default
49 `success=True,failure=False`, the following cases would occur for each node's failure:
49 `success=True,failure=False`, the following cases would occur for each node's failure:
50
50
51 0. fails: all other tasks fail as Impossible
51 0. fails: all other tasks fail as Impossible
52 1. 2 can still succeed, but 3,4 are unreachable
52 1. 2 can still succeed, but 3,4 are unreachable
53 2. 3 becomes unreachable, but 4 is unaffected
53 2. 3 becomes unreachable, but 4 is unaffected
54 3. and 4. are terminal, and can have no effect on other nodes
54 3. and 4. are terminal, and can have no effect on other nodes
55
55
56 The code to generate the simple DAG:
56 The code to generate the simple DAG:
57
57
58 .. sourcecode:: python
58 .. sourcecode:: python
59
59
60 import networkx as nx
60 import networkx as nx
61
61
62 G = nx.DiGraph()
62 G = nx.DiGraph()
63
63
64 # add 5 nodes, labeled 0-4:
64 # add 5 nodes, labeled 0-4:
65 map(G.add_node, range(5))
65 map(G.add_node, range(5))
66 # 1,2 depend on 0:
66 # 1,2 depend on 0:
67 G.add_edge(0,1)
67 G.add_edge(0,1)
68 G.add_edge(0,2)
68 G.add_edge(0,2)
69 # 3 depends on 1,2
69 # 3 depends on 1,2
70 G.add_edge(1,3)
70 G.add_edge(1,3)
71 G.add_edge(2,3)
71 G.add_edge(2,3)
72 # 4 depends on 1
72 # 4 depends on 1
73 G.add_edge(1,4)
73 G.add_edge(1,4)
74
74
75 # now draw the graph:
75 # now draw the graph:
76 pos = { 0 : (0,0), 1 : (1,1), 2 : (-1,1),
76 pos = { 0 : (0,0), 1 : (1,1), 2 : (-1,1),
77 3 : (0,2), 4 : (2,2)}
77 3 : (0,2), 4 : (2,2)}
78 nx.draw(G, pos, edge_color='r')
78 nx.draw(G, pos, edge_color='r')
79
79
80
80
81 For demonstration purposes, we have a function that generates a random DAG with a given
81 For demonstration purposes, we have a function that generates a random DAG with a given
82 number of nodes and edges.
82 number of nodes and edges.
83
83
84 .. literalinclude:: ../../../examples/parallel/dagdeps.py
84 .. literalinclude:: ../../../examples/parallel/dagdeps.py
85 :language: python
85 :language: python
86 :lines: 20-36
86 :lines: 20-36
87
87
88 So first, we start with a graph of 32 nodes, with 128 edges:
88 So first, we start with a graph of 32 nodes, with 128 edges:
89
89
90 .. sourcecode:: ipython
90 .. sourcecode:: ipython
91
91
92 In [2]: G = random_dag(32,128)
92 In [2]: G = random_dag(32,128)
93
93
94 Now, we need to build our dict of jobs corresponding to the nodes on the graph:
94 Now, we need to build our dict of jobs corresponding to the nodes on the graph:
95
95
96 .. sourcecode:: ipython
96 .. sourcecode:: ipython
97
97
98 In [3]: jobs = {}
98 In [3]: jobs = {}
99
99
100 # in reality, each job would presumably be different
100 # in reality, each job would presumably be different
101 # randomwait is just a function that sleeps for a random interval
101 # randomwait is just a function that sleeps for a random interval
102 In [4]: for node in G:
102 In [4]: for node in G:
103 ...: jobs[node] = randomwait
103 ...: jobs[node] = randomwait
104
104
105 Once we have a dict of jobs matching the nodes on the graph, we can start submitting jobs,
105 Once we have a dict of jobs matching the nodes on the graph, we can start submitting jobs,
106 and linking up the dependencies. Since we don't know a job's msg_id until it is submitted,
106 and linking up the dependencies. Since we don't know a job's msg_id until it is submitted,
107 which is necessary for building dependencies, it is critical that we don't submit any jobs
107 which is necessary for building dependencies, it is critical that we don't submit any jobs
108 before other jobs it may depend on. Fortunately, NetworkX provides a
108 before other jobs it may depend on. Fortunately, NetworkX provides a
109 :meth:`topological_sort` method which ensures exactly this. It presents an iterable, that
109 :meth:`topological_sort` method which ensures exactly this. It presents an iterable, that
110 guarantees that when you arrive at a node, you have already visited all the nodes it
110 guarantees that when you arrive at a node, you have already visited all the nodes it
111 on which it depends:
111 on which it depends:
112
112
113 .. sourcecode:: ipython
113 .. sourcecode:: ipython
114
114
115 In [5]: rc = Client()
115 In [5]: rc = Client()
116 In [5]: view = rc.load_balanced_view()
116 In [5]: view = rc.load_balanced_view()
117
117
118 In [6]: results = {}
118 In [6]: results = {}
119
119
120 In [7]: for node in G.topological_sort():
120 In [7]: for node in nx.topological_sort(G):
121 ...: # get list of AsyncResult objects from nodes
121 ...: # get list of AsyncResult objects from nodes
122 ...: # leading into this one as dependencies
122 ...: # leading into this one as dependencies
123 ...: deps = [ results[n] for n in G.predecessors(node) ]
123 ...: deps = [ results[n] for n in G.predecessors(node) ]
124 ...: # submit and store AsyncResult object
124 ...: # submit and store AsyncResult object
125 ...: with view.temp_flags(after=deps, block=False):
125 ...: with view.temp_flags(after=deps, block=False):
126 ...: results[node] = view.apply_with_flags(jobs[node])
126 ...: results[node] = view.apply_with_flags(jobs[node])
127
127
128
128
129 Now that we have submitted all the jobs, we can wait for the results:
129 Now that we have submitted all the jobs, we can wait for the results:
130
130
131 .. sourcecode:: ipython
131 .. sourcecode:: ipython
132
132
133 In [8]: view.wait(results.values())
133 In [8]: view.wait(results.values())
134
134
135 Now, at least we know that all the jobs ran and did not fail (``r.get()`` would have
135 Now, at least we know that all the jobs ran and did not fail (``r.get()`` would have
136 raised an error if a task failed). But we don't know that the ordering was properly
136 raised an error if a task failed). But we don't know that the ordering was properly
137 respected. For this, we can use the :attr:`metadata` attribute of each AsyncResult.
137 respected. For this, we can use the :attr:`metadata` attribute of each AsyncResult.
138
138
139 These objects store a variety of metadata about each task, including various timestamps.
139 These objects store a variety of metadata about each task, including various timestamps.
140 We can validate that the dependencies were respected by checking that each task was
140 We can validate that the dependencies were respected by checking that each task was
141 started after all of its predecessors were completed:
141 started after all of its predecessors were completed:
142
142
143 .. literalinclude:: ../../../examples/parallel/dagdeps.py
143 .. literalinclude:: ../../../examples/parallel/dagdeps.py
144 :language: python
144 :language: python
145 :lines: 64-70
145 :lines: 64-70
146
146
147 We can also validate the graph visually. By drawing the graph with each node's x-position
147 We can also validate the graph visually. By drawing the graph with each node's x-position
148 as its start time, all arrows must be pointing to the right if dependencies were respected.
148 as its start time, all arrows must be pointing to the right if dependencies were respected.
149 For spreading, the y-position will be the runtime of the task, so long tasks
149 For spreading, the y-position will be the runtime of the task, so long tasks
150 will be at the top, and quick, small tasks will be at the bottom.
150 will be at the top, and quick, small tasks will be at the bottom.
151
151
152 .. sourcecode:: ipython
152 .. sourcecode:: ipython
153
153
154 In [10]: from matplotlib.dates import date2num
154 In [10]: from matplotlib.dates import date2num
155
155
156 In [11]: from matplotlib.cm import gist_rainbow
156 In [11]: from matplotlib.cm import gist_rainbow
157
157
158 In [12]: pos = {}; colors = {}
158 In [12]: pos = {}; colors = {}
159
159
160 In [12]: for node in G:
160 In [12]: for node in G:
161 ....: md = results[node].metadata
161 ....: md = results[node].metadata
162 ....: start = date2num(md.started)
162 ....: start = date2num(md.started)
163 ....: runtime = date2num(md.completed) - start
163 ....: runtime = date2num(md.completed) - start
164 ....: pos[node] = (start, runtime)
164 ....: pos[node] = (start, runtime)
165 ....: colors[node] = md.engine_id
165 ....: colors[node] = md.engine_id
166
166
167 In [13]: nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(),
167 In [13]: nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(),
168 ....: cmap=gist_rainbow)
168 ....: cmap=gist_rainbow)
169
169
170 .. figure:: figs/dagdeps.*
170 .. figure:: figs/dagdeps.*
171 :width: 600px
171 :width: 600px
172
172
173 Time started on x, runtime on y, and color-coded by engine-id (in this case there
173 Time started on x, runtime on y, and color-coded by engine-id (in this case there
174 were four engines). Edges denote dependencies.
174 were four engines). Edges denote dependencies.
175
175
176
176
177 .. _NetworkX: http://networkx.lanl.gov/
177 .. _NetworkX: http://networkx.lanl.gov/
@@ -1,308 +1,315 b''
1 =====================
1 =====================
2 Development version
2 Development version
3 =====================
3 =====================
4
4
5 This document describes in-flight development work.
5 This document describes in-flight development work.
6
6
7 .. warning::
7 .. warning::
8
8
9 Please do not edit this file by hand (doing so will likely cause merge
9 Please do not edit this file by hand (doing so will likely cause merge
10 conflicts for other Pull Requests). Instead, create a new file in the
10 conflicts for other Pull Requests). Instead, create a new file in the
11 `docs/source/whatsnew/pr` folder
11 `docs/source/whatsnew/pr` folder
12
12
13 Select Notebook Name When Renaming a Notebook
13 Select Notebook Name When Renaming a Notebook
14 ---------------------------------------------
14 ---------------------------------------------
15
15
16 The default notebook name is Untitled. It's unlikely you want to keep this name
16 The default notebook name is Untitled. It's unlikely you want to keep this name
17 or part of it when naming your notebook. Instead, IPython will select the text
17 or part of it when naming your notebook. Instead, IPython will select the text
18 in the input field so the user can easily type over the name and change it.
18 in the input field so the user can easily type over the name and change it.
19
19
20 clear_output changes
20 clear_output changes
21 --------------------
21 --------------------
22
22
23 * There is no longer a 500ms delay when calling ``clear_output``.
23 * There is no longer a 500ms delay when calling ``clear_output``.
24 * The ability to clear stderr and stdout individually was removed.
24 * The ability to clear stderr and stdout individually was removed.
25 * A new ``wait`` flag that prevents ``clear_output`` from being executed until new
25 * A new ``wait`` flag that prevents ``clear_output`` from being executed until new
26 output is available. This eliminates animation flickering by allowing the
26 output is available. This eliminates animation flickering by allowing the
27 user to double buffer the output.
27 user to double buffer the output.
28 * The output div height is remembered when the ``wait=True`` flag is used.
28 * The output div height is remembered when the ``wait=True`` flag is used.
29
29
30 Extending Configurable Containers
30 Extending Configurable Containers
31 ---------------------------------
31 ---------------------------------
32
32
33 Some configurable traits are containers (list, dict, set)
33 Some configurable traits are containers (list, dict, set)
34 Config objects now support calling ``extend``, ``update``, ``insert``, etc.
34 Config objects now support calling ``extend``, ``update``, ``insert``, etc.
35 on traits in config files, which will ultimately result in calling
35 on traits in config files, which will ultimately result in calling
36 those methods on the original object.
36 those methods on the original object.
37
37
38 The effect being that you can now add to containers without having to copy/paste
38 The effect being that you can now add to containers without having to copy/paste
39 the initial value::
39 the initial value::
40
40
41 c = get_config()
41 c = get_config()
42 c.InlineBackend.rc.update({ 'figure.figsize' : (6, 4) })
42 c.InlineBackend.rc.update({ 'figure.figsize' : (6, 4) })
43
43
44 Single codebase Python 3 support
44 Single codebase Python 3 support
45 --------------------------------
45 --------------------------------
46
46
47 IPython previously supported Python 3 by running 2to3 during setup. We
47 IPython previously supported Python 3 by running 2to3 during setup. We
48 have now switched to a single codebase which runs natively on Python 2.7
48 have now switched to a single codebase which runs natively on Python 2.7
49 and 3.3.
49 and 3.3.
50
50
51 For notes on how to maintain this, see :doc:`/development/pycompat`.
51 For notes on how to maintain this, see :doc:`/development/pycompat`.
52
52
53 changes to hidden namespace on startup
53 changes to hidden namespace on startup
54 --------------------------------------
54 --------------------------------------
55
55
56 Previously, all names declared in code run at startup
56 Previously, all names declared in code run at startup
57 (startup files, ``ipython -i script.py``, etc.)
57 (startup files, ``ipython -i script.py``, etc.)
58 were added to the hidden namespace, which hides the names from tools like ``%whos``.
58 were added to the hidden namespace, which hides the names from tools like ``%whos``.
59 There are two changes to this behavior:
59 There are two changes to this behavior:
60
60
61 1. Scripts run on the command-line ``ipython -i script.py``now behave the same as if they were
61 1. Scripts run on the command-line ``ipython -i script.py``now behave the same as if they were
62 passed to ``%run``, so their variables are never hidden.
62 passed to ``%run``, so their variables are never hidden.
63 2. A boolean config flag ``InteractiveShellApp.hide_initial_ns`` has been added to optionally
63 2. A boolean config flag ``InteractiveShellApp.hide_initial_ns`` has been added to optionally
64 disable the hidden behavior altogether. The default behavior is unchanged.
64 disable the hidden behavior altogether. The default behavior is unchanged.
65
65
66 Using dill to expand serialization support
66 Using dill to expand serialization support
67 ------------------------------------------
67 ------------------------------------------
68
68
69 adds :func:`~IPython.utils.pickleutil.use_dill` for allowing
69 adds :func:`~IPython.utils.pickleutil.use_dill` for allowing
70 dill to extend serialization support in :mod:`IPython.parallel` (closures, etc.).
70 dill to extend serialization support in :mod:`IPython.parallel` (closures, etc.).
71 Also adds :meth:`DirectView.use_dill` convenience method for enabling dill
71 Also adds :meth:`DirectView.use_dill` convenience method for enabling dill
72 locally and on all engines with one call.
72 locally and on all engines with one call.
73
73
74 New IPython Console Lexer
74 New IPython Console Lexer
75 -------------------------
75 -------------------------
76
76
77 The IPython console lexer has been rewritten and now supports tracebacks
77 The IPython console lexer has been rewritten and now supports tracebacks
78 and customized input/output prompts. An entire suite of lexers is now
78 and customized input/output prompts. An entire suite of lexers is now
79 available at :mod:`IPython.nbconvert.utils.lexers`. These include:
79 available at :mod:`IPython.nbconvert.utils.lexers`. These include:
80
80
81 IPythonLexer & IPython3Lexer
81 IPythonLexer & IPython3Lexer
82 Lexers for pure IPython (python + magic/shell commands)
82 Lexers for pure IPython (python + magic/shell commands)
83
83
84 IPythonPartialTracebackLexer & IPythonTracebackLexer
84 IPythonPartialTracebackLexer & IPythonTracebackLexer
85 Supports 2.x and 3.x via the keyword `python3`. The partial traceback
85 Supports 2.x and 3.x via the keyword `python3`. The partial traceback
86 lexer reads everything but the Python code appearing in a traceback.
86 lexer reads everything but the Python code appearing in a traceback.
87 The full lexer combines the partial lexer with an IPython lexer.
87 The full lexer combines the partial lexer with an IPython lexer.
88
88
89 IPythonConsoleLexer
89 IPythonConsoleLexer
90 A lexer for IPython console sessions, with support for tracebacks.
90 A lexer for IPython console sessions, with support for tracebacks.
91 Supports 2.x and 3.x via the keyword `python3`.
91 Supports 2.x and 3.x via the keyword `python3`.
92
92
93 IPyLexer
93 IPyLexer
94 A friendly lexer which examines the first line of text and from it,
94 A friendly lexer which examines the first line of text and from it,
95 decides whether to use an IPython lexer or an IPython console lexer.
95 decides whether to use an IPython lexer or an IPython console lexer.
96 Supports 2.x and 3.x via the keyword `python3`.
96 Supports 2.x and 3.x via the keyword `python3`.
97
97
98 Previously, the :class:`IPythonConsoleLexer` class was available at
98 Previously, the :class:`IPythonConsoleLexer` class was available at
99 :mod:`IPython.sphinxext.ipython_console_hightlight`. It was inserted
99 :mod:`IPython.sphinxext.ipython_console_hightlight`. It was inserted
100 into Pygments' list of available lexers under the name `ipython`. It should
100 into Pygments' list of available lexers under the name `ipython`. It should
101 be mentioned that this name is inaccurate, since an IPython console session
101 be mentioned that this name is inaccurate, since an IPython console session
102 is not the same as IPython code (which itself is a superset of the Python
102 is not the same as IPython code (which itself is a superset of the Python
103 language).
103 language).
104
104
105 Now, the Sphinx extension inserts two console lexers into Pygments' list of
105 Now, the Sphinx extension inserts two console lexers into Pygments' list of
106 available lexers. Both are IPyLexer instances under the names: `ipython` and
106 available lexers. Both are IPyLexer instances under the names: `ipython` and
107 `ipython3`. Although the names can be confusing (as mentioned above), their
107 `ipython3`. Although the names can be confusing (as mentioned above), their
108 continued use is, in part, to maintain backwards compatibility and to
108 continued use is, in part, to maintain backwards compatibility and to
109 aid typical usage. If a project needs to make Pygments aware of more than just
109 aid typical usage. If a project needs to make Pygments aware of more than just
110 the IPyLexer class, then one should not make the IPyLexer class available under
110 the IPyLexer class, then one should not make the IPyLexer class available under
111 the name `ipython` and use `ipy` or some other non-conflicting value.
111 the name `ipython` and use `ipy` or some other non-conflicting value.
112
112
113 Code blocks such as:
113 Code blocks such as:
114
114
115 .. code-block:: rst
115 .. code-block:: rst
116
116
117 .. code-block:: ipython
117 .. code-block:: ipython
118
118
119 In [1]: 2**2
119 In [1]: 2**2
120 Out[1]: 4
120 Out[1]: 4
121
121
122 will continue to work as before, but now, they will also properly highlight
122 will continue to work as before, but now, they will also properly highlight
123 tracebacks. For pure IPython code, the same lexer will also work:
123 tracebacks. For pure IPython code, the same lexer will also work:
124
124
125 .. code-block:: rst
125 .. code-block:: rst
126
126
127 .. code-block:: ipython
127 .. code-block:: ipython
128
128
129 x = ''.join(map(str, range(10)))
129 x = ''.join(map(str, range(10)))
130 !echo $x
130 !echo $x
131
131
132 Since the first line of the block did not begin with a standard IPython console
132 Since the first line of the block did not begin with a standard IPython console
133 prompt, the entire block is assumed to consist of IPython code instead.
133 prompt, the entire block is assumed to consist of IPython code instead.
134
134
135 DisplayFormatter changes
135 DisplayFormatter changes
136 ------------------------
136 ------------------------
137
137
138 There was no official way to query or remove callbacks in the Formatter API.
138 There was no official way to query or remove callbacks in the Formatter API.
139 To remedy this, the following methods are added to :class:`BaseFormatter`:
139 To remedy this, the following methods are added to :class:`BaseFormatter`:
140
140
141 - ``lookup(instance)`` - return appropriate callback or a given object
141 - ``lookup(instance)`` - return appropriate callback or a given object
142 - ``lookup_by_type(type_or_str)`` - return appropriate callback for a given type or ``'mod.name'`` type string
142 - ``lookup_by_type(type_or_str)`` - return appropriate callback for a given type or ``'mod.name'`` type string
143 - ``pop(type_or_str)`` - remove a type (by type or string).
143 - ``pop(type_or_str)`` - remove a type (by type or string).
144 Pass a second argument to avoid KeyError (like dict).
144 Pass a second argument to avoid KeyError (like dict).
145
145
146 All of the above methods raise a KeyError if no match is found.
146 All of the above methods raise a KeyError if no match is found.
147
147
148 And the following methods are changed:
148 And the following methods are changed:
149
149
150 - ``for_type(type_or_str)`` - behaves the same as before, only adding support for ``'mod.name'``
150 - ``for_type(type_or_str)`` - behaves the same as before, only adding support for ``'mod.name'``
151 type strings in addition to plain types. This removes the need for ``for_type_by_name()``,
151 type strings in addition to plain types. This removes the need for ``for_type_by_name()``,
152 but it remains for backward compatibility.
152 but it remains for backward compatibility.
153
153
154 Notebook Widgets
154 Notebook Widgets
155 ----------------
155 ----------------
156
156
157 Available in the new `IPython.html.widgets` namespace, widgets provide an easy
157 Available in the new `IPython.html.widgets` namespace, widgets provide an easy
158 way for IPython notebook users to display GUI controls in the IPython notebook.
158 way for IPython notebook users to display GUI controls in the IPython notebook.
159 IPython comes with bundle of built-in widgets and also the ability for users
159 IPython comes with bundle of built-in widgets and also the ability for users
160 to define their own widgets. A widget is displayed in the front-end using
160 to define their own widgets. A widget is displayed in the front-end using
161 using a view. For example, a FloatRangeWidget can be displayed using a
161 using a view. For example, a FloatRangeWidget can be displayed using a
162 FloatSliderView (which is the default if no view is specified when displaying
162 FloatSliderView (which is the default if no view is specified when displaying
163 the widget). IPython also comes with a bundle of views and the ability for the
163 the widget). IPython also comes with a bundle of views and the ability for the
164 user to define custom views. One widget can be displayed multiple times, in on
164 user to define custom views. One widget can be displayed multiple times, in on
165 or more cells, using one or more views. All views will automatically remain in
165 or more cells, using one or more views. All views will automatically remain in
166 sync with the widget which is accessible in the back-end.
166 sync with the widget which is accessible in the back-end.
167
167
168 The widget layer provides an MVC-like architecture on top of the comm layer.
168 The widget layer provides an MVC-like architecture on top of the comm layer.
169 It's useful for widgets that can be expressed via a list of properties.
169 It's useful for widgets that can be expressed via a list of properties.
170 Widgets work by synchronizing IPython traitlet models in the back-end with
170 Widgets work by synchronizing IPython traitlet models in the back-end with
171 backbone models in the front-end. The widget layer automatically handles
171 backbone models in the front-end. The widget layer automatically handles
172
172
173 * delta compression (only sending the state information that has changed)
173 * delta compression (only sending the state information that has changed)
174 * wiring the message callbacks to the correct cells automatically
174 * wiring the message callbacks to the correct cells automatically
175 * inter-view synchronization (handled by backbone)
175 * inter-view synchronization (handled by backbone)
176 * message throttling (to avoid flooding the kernel)
176 * message throttling (to avoid flooding the kernel)
177 * parent/child relationships between views (which one can override to specify custom parent/child relationships)
177 * parent/child relationships between views (which one can override to specify custom parent/child relationships)
178 * ability to manipulate the widget view's DOM from python using CSS, $().addClass, and $().removeClass methods
178 * ability to manipulate the widget view's DOM from python using CSS, $().addClass, and $().removeClass methods
179
179
180 Signing Notebooks
180 Signing Notebooks
181 -----------------
181 -----------------
182
182
183 To prevent untrusted code from executing on users' behalf when notebooks open,
183 To prevent untrusted code from executing on users' behalf when notebooks open,
184 we have added a signature to the notebook, stored in metadata.
184 we have added a signature to the notebook, stored in metadata.
185
185
186 For more information, see :ref:`signing_notebooks`.
186 For more information, see :ref:`signing_notebooks`.
187
187
188 Dashboard "Running" Tab
188 Dashboard "Running" Tab
189 -----------------------
189 -----------------------
190
190
191 The dashboard now has a "Running" tab which shows all of the running
191 The dashboard now has a "Running" tab which shows all of the running
192 notebooks.
192 notebooks.
193
193
194 Interactive Notebook Tour
195 -------------------------
196
197 Familiarize yourself with the updated notebook user interface, including an
198 explanation of Edit and Command modes, by going through the short guided tour
199 which can be started from the Help menu.
200
194 Other changes
201 Other changes
195 -------------
202 -------------
196
203
197 * `%%capture` cell magic now captures the rich display output, not just
204 * `%%capture` cell magic now captures the rich display output, not just
198 stdout/stderr
205 stdout/stderr
199
206
200 * In notebook, Showing tooltip on tab has been disables to avoid conflict with
207 * In notebook, Showing tooltip on tab has been disables to avoid conflict with
201 completion, Shift-Tab could still be used to invoke tooltip when inside
208 completion, Shift-Tab could still be used to invoke tooltip when inside
202 function signature and/or on selection.
209 function signature and/or on selection.
203
210
204 * ``object_info_request`` as been replaced by ``object_info`` for consistency in the javascript API.
211 * ``object_info_request`` as been replaced by ``object_info`` for consistency in the javascript API.
205 ``object_info`` as a simpler interface to register callback that is incompatible with ``object_info_request``.
212 ``object_info`` as a simpler interface to register callback that is incompatible with ``object_info_request``.
206
213
207 * Previous versions of IPython on Linux would use the XDG config directory,
214 * Previous versions of IPython on Linux would use the XDG config directory,
208 creating :file:`~/.config/ipython` by default. We have decided to go
215 creating :file:`~/.config/ipython` by default. We have decided to go
209 back to :file:`~/.ipython` for consistency among systems. IPython will
216 back to :file:`~/.ipython` for consistency among systems. IPython will
210 issue a warning if it finds the XDG location, and will move it to the new
217 issue a warning if it finds the XDG location, and will move it to the new
211 location if there isn't already a directory there.
218 location if there isn't already a directory there.
212
219
213 * Equations, images and tables are now centered in Markdown cells.
220 * Equations, images and tables are now centered in Markdown cells.
214 * Multiline equations are now centered in output areas; single line equations
221 * Multiline equations are now centered in output areas; single line equations
215 remain left justified.
222 remain left justified.
216
223
217 * IPython config objects can be loaded from and serialized to JSON.
224 * IPython config objects can be loaded from and serialized to JSON.
218 JSON config file have the same base name as their ``.py`` counterpart,
225 JSON config file have the same base name as their ``.py`` counterpart,
219 and will be loaded with higher priority if found.
226 and will be loaded with higher priority if found.
220
227
221 * bash completion updated with support for all ipython subcommands and flags, including nbconvert
228 * bash completion updated with support for all ipython subcommands and flags, including nbconvert
222
229
223 * ``ipython history trim``: added ``--keep=<N>`` as an alias for the more verbose
230 * ``ipython history trim``: added ``--keep=<N>`` as an alias for the more verbose
224 ``--HistoryTrim.keep=<N>``
231 ``--HistoryTrim.keep=<N>``
225 * new ``ipython history clear`` subcommand, which is the same as the newly supported
232 * new ``ipython history clear`` subcommand, which is the same as the newly supported
226 ``ipython history trim --keep=0``
233 ``ipython history trim --keep=0``
227
234
228 * You can now run notebooks in an interactive session via ``%run notebook.ipynb``.
235 * You can now run notebooks in an interactive session via ``%run notebook.ipynb``.
229
236
230 * Print preview is back in the notebook menus, along with options to
237 * Print preview is back in the notebook menus, along with options to
231 download the open notebook in various formats. This is powered by
238 download the open notebook in various formats. This is powered by
232 nbconvert.
239 nbconvert.
233
240
234 * :exc:`~IPython.nbconvert.utils.pandoc.PandocMissing` exceptions will be
241 * :exc:`~IPython.nbconvert.utils.pandoc.PandocMissing` exceptions will be
235 raised if Pandoc is unavailable, and warnings will be printed if the version
242 raised if Pandoc is unavailable, and warnings will be printed if the version
236 found is too old. The recommended Pandoc version for use with nbconvert is
243 found is too old. The recommended Pandoc version for use with nbconvert is
237 1.12.1.
244 1.12.1.
238
245
239 * The InlineBackend.figure_format flag now supports JPEG output if PIL/Pillow is available.
246 * The InlineBackend.figure_format flag now supports JPEG output if PIL/Pillow is available.
240
247
241 * Input transformers (see :doc:`/config/inputtransforms`) may now raise
248 * Input transformers (see :doc:`/config/inputtransforms`) may now raise
242 :exc:`SyntaxError` if they determine that input is invalid. The input
249 :exc:`SyntaxError` if they determine that input is invalid. The input
243 transformation machinery in IPython will handle displaying the exception to
250 transformation machinery in IPython will handle displaying the exception to
244 the user and resetting state.
251 the user and resetting state.
245
252
246 * Calling ``container.show()`` on javascript display is deprecated and will
253 * Calling ``container.show()`` on javascript display is deprecated and will
247 trigger errors on future IPython notebook versions. ``container`` now show
254 trigger errors on future IPython notebook versions. ``container`` now show
248 itself as soon as non-empty
255 itself as soon as non-empty
249
256
250 * Added ``InlineBackend.print_figure_kwargs`` to allow passing keyword arguments
257 * Added ``InlineBackend.print_figure_kwargs`` to allow passing keyword arguments
251 to matplotlib's ``Canvas.print_figure``. This can be used to change the value of
258 to matplotlib's ``Canvas.print_figure``. This can be used to change the value of
252 ``bbox_inches``, which is 'tight' by default, or set the quality of JPEG figures.
259 ``bbox_inches``, which is 'tight' by default, or set the quality of JPEG figures.
253
260
254 * A new callback system has been introduced. For details, see :doc:`/config/callbacks`.
261 * A new callback system has been introduced. For details, see :doc:`/config/callbacks`.
255
262
256 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
263 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
257
264
258 Backwards incompatible changes
265 Backwards incompatible changes
259 ------------------------------
266 ------------------------------
260
267
261 * Python 2.6 and 3.2 are no longer supported: the minimum required
268 * Python 2.6 and 3.2 are no longer supported: the minimum required
262 Python versions are now 2.7 and 3.3.
269 Python versions are now 2.7 and 3.3.
263 * The Transformer classes have been renamed to Preprocessor in nbconvert and
270 * The Transformer classes have been renamed to Preprocessor in nbconvert and
264 their `call` methods for them have been renamed to `preprocess`.
271 their `call` methods for them have been renamed to `preprocess`.
265 * The `call` methods of nbconvert post-processsors have been renamed to
272 * The `call` methods of nbconvert post-processsors have been renamed to
266 `postprocess`.
273 `postprocess`.
267
274
268 * The module ``IPython.core.fakemodule`` has been removed.
275 * The module ``IPython.core.fakemodule`` has been removed.
269
276
270 * The alias system has been reimplemented to use magic functions. There should be little
277 * The alias system has been reimplemented to use magic functions. There should be little
271 visible difference while automagics are enabled, as they are by default, but parts of the
278 visible difference while automagics are enabled, as they are by default, but parts of the
272 :class:`~IPython.core.alias.AliasManager` API have been removed.
279 :class:`~IPython.core.alias.AliasManager` API have been removed.
273
280
274 * We fixed an issue with switching between matplotlib inline and GUI backends,
281 * We fixed an issue with switching between matplotlib inline and GUI backends,
275 but the fix requires matplotlib 1.1 or newer. So from now on, we consider
282 but the fix requires matplotlib 1.1 or newer. So from now on, we consider
276 matplotlib 1.1 to be the minimally supported version for IPython. Older
283 matplotlib 1.1 to be the minimally supported version for IPython. Older
277 versions for the most part will work, but we make no guarantees about it.
284 versions for the most part will work, but we make no guarantees about it.
278
285
279 * The :command:`pycolor` command has been removed. We recommend the much more capable
286 * The :command:`pycolor` command has been removed. We recommend the much more capable
280 :command:`pygmentize` command from the `Pygments <http://pygments.org/>`_ project.
287 :command:`pygmentize` command from the `Pygments <http://pygments.org/>`_ project.
281 If you need to keep the exact output of :command:`pycolor`, you can still use
288 If you need to keep the exact output of :command:`pycolor`, you can still use
282 ``python -m IPython.utils.PyColorize foo.py``.
289 ``python -m IPython.utils.PyColorize foo.py``.
283
290
284 * :mod:`IPython.lib.irunner` and its command-line entry point have been removed.
291 * :mod:`IPython.lib.irunner` and its command-line entry point have been removed.
285 It had fallen out of use long ago.
292 It had fallen out of use long ago.
286
293
287 * The ``input_prefilter`` hook has been removed, as it was never
294 * The ``input_prefilter`` hook has been removed, as it was never
288 actually used by the code. The input transformer system offers much
295 actually used by the code. The input transformer system offers much
289 more powerful APIs to work with input code. See
296 more powerful APIs to work with input code. See
290 :doc:`/config/inputtransforms` for details.
297 :doc:`/config/inputtransforms` for details.
291
298
292 * :class:`IPython.core.inputsplitter.IPythonInputSplitter` no longer has a method
299 * :class:`IPython.core.inputsplitter.IPythonInputSplitter` no longer has a method
293 ``source_raw_reset()``, but gains :meth:`~IPython.core.inputsplitter.IPythonInputSplitter.raw_reset`
300 ``source_raw_reset()``, but gains :meth:`~IPython.core.inputsplitter.IPythonInputSplitter.raw_reset`
294 instead. Use of ``source_raw_reset`` can be replaced with::
301 instead. Use of ``source_raw_reset`` can be replaced with::
295
302
296 raw = isp.source_raw
303 raw = isp.source_raw
297 transformed = isp.source_reset()
304 transformed = isp.source_reset()
298
305
299 * The Azure notebook manager was removed as it was no longer compatible with the notebook storage scheme
306 * The Azure notebook manager was removed as it was no longer compatible with the notebook storage scheme
300
307
301 .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT.
308 .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT.
302
309
303 Simplifying configurable URLs
310 Simplifying configurable URLs
304 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
311 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
305
312
306 - base_kernel_url configurable is removed
313 - base_kernel_url configurable is removed
307 - websocket_url configurable is removed
314 - websocket_url configurable is removed
308 - base_project_url is renamed to base_url (base_project_url is kept as a deprecated alias, for now)
315 - base_project_url is renamed to base_url (base_project_url is kept as a deprecated alias, for now)
@@ -1,343 +1,343 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 """Setup script for IPython.
3 """Setup script for IPython.
4
4
5 Under Posix environments it works like a typical setup.py script.
5 Under Posix environments it works like a typical setup.py script.
6 Under Windows, the command sdist is not supported, since IPython
6 Under Windows, the command sdist is not supported, since IPython
7 requires utilities which are not available under Windows."""
7 requires utilities which are not available under Windows."""
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (c) 2008-2011, IPython Development Team.
10 # Copyright (c) 2008-2011, IPython Development Team.
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.txt, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Minimal Python version sanity check
21 # Minimal Python version sanity check
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 from __future__ import print_function
23 from __future__ import print_function
24
24
25 import sys
25 import sys
26
26
27 # This check is also made in IPython/__init__, don't forget to update both when
27 # This check is also made in IPython/__init__, don't forget to update both when
28 # changing Python version requirements.
28 # changing Python version requirements.
29 if sys.version_info[:2] < (2,7):
29 if sys.version_info[:2] < (2,7):
30 error = "ERROR: IPython requires Python Version 2.7 or above."
30 error = "ERROR: IPython requires Python Version 2.7 or above."
31 print(error, file=sys.stderr)
31 print(error, file=sys.stderr)
32 sys.exit(1)
32 sys.exit(1)
33
33
34 PY3 = (sys.version_info[0] >= 3)
34 PY3 = (sys.version_info[0] >= 3)
35
35
36 # At least we're on the python version we need, move on.
36 # At least we're on the python version we need, move on.
37
37
38 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
39 # Imports
39 # Imports
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41
41
42 # Stdlib imports
42 # Stdlib imports
43 import os
43 import os
44 import shutil
44 import shutil
45
45
46 from glob import glob
46 from glob import glob
47
47
48 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
48 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
49 # update it when the contents of directories change.
49 # update it when the contents of directories change.
50 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
50 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
51
51
52 from distutils.core import setup
52 from distutils.core import setup
53
53
54 # Our own imports
54 # Our own imports
55 from setupbase import target_update
55 from setupbase import target_update
56
56
57 from setupbase import (
57 from setupbase import (
58 setup_args,
58 setup_args,
59 find_packages,
59 find_packages,
60 find_package_data,
60 find_package_data,
61 check_package_data_first,
61 check_package_data_first,
62 find_entry_points,
62 find_entry_points,
63 build_scripts_entrypt,
63 build_scripts_entrypt,
64 find_data_files,
64 find_data_files,
65 check_for_dependencies,
65 check_for_dependencies,
66 git_prebuild,
66 git_prebuild,
67 check_submodule_status,
67 check_submodule_status,
68 update_submodules,
68 update_submodules,
69 require_submodules,
69 require_submodules,
70 UpdateSubmodules,
70 UpdateSubmodules,
71 get_bdist_wheel,
71 get_bdist_wheel,
72 CompileCSS,
72 CompileCSS,
73 JavascriptVersion,
73 JavascriptVersion,
74 install_symlinked,
74 install_symlinked,
75 install_lib_symlink,
75 install_lib_symlink,
76 install_scripts_for_symlink,
76 install_scripts_for_symlink,
77 unsymlink,
77 unsymlink,
78 )
78 )
79 from setupext import setupext
79 from setupext import setupext
80
80
81 isfile = os.path.isfile
81 isfile = os.path.isfile
82 pjoin = os.path.join
82 pjoin = os.path.join
83
83
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 # Function definitions
85 # Function definitions
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87
87
88 def cleanup():
88 def cleanup():
89 """Clean up the junk left around by the build process"""
89 """Clean up the junk left around by the build process"""
90 if "develop" not in sys.argv and "egg_info" not in sys.argv:
90 if "develop" not in sys.argv and "egg_info" not in sys.argv:
91 try:
91 try:
92 shutil.rmtree('ipython.egg-info')
92 shutil.rmtree('ipython.egg-info')
93 except:
93 except:
94 try:
94 try:
95 os.unlink('ipython.egg-info')
95 os.unlink('ipython.egg-info')
96 except:
96 except:
97 pass
97 pass
98
98
99 #-------------------------------------------------------------------------------
99 #-------------------------------------------------------------------------------
100 # Handle OS specific things
100 # Handle OS specific things
101 #-------------------------------------------------------------------------------
101 #-------------------------------------------------------------------------------
102
102
103 if os.name in ('nt','dos'):
103 if os.name in ('nt','dos'):
104 os_name = 'windows'
104 os_name = 'windows'
105 else:
105 else:
106 os_name = os.name
106 os_name = os.name
107
107
108 # Under Windows, 'sdist' has not been supported. Now that the docs build with
108 # Under Windows, 'sdist' has not been supported. Now that the docs build with
109 # Sphinx it might work, but let's not turn it on until someone confirms that it
109 # Sphinx it might work, but let's not turn it on until someone confirms that it
110 # actually works.
110 # actually works.
111 if os_name == 'windows' and 'sdist' in sys.argv:
111 if os_name == 'windows' and 'sdist' in sys.argv:
112 print('The sdist command is not available under Windows. Exiting.')
112 print('The sdist command is not available under Windows. Exiting.')
113 sys.exit(1)
113 sys.exit(1)
114
114
115 #-------------------------------------------------------------------------------
115 #-------------------------------------------------------------------------------
116 # Make sure we aren't trying to run without submodules
116 # Make sure we aren't trying to run without submodules
117 #-------------------------------------------------------------------------------
117 #-------------------------------------------------------------------------------
118 here = os.path.abspath(os.path.dirname(__file__))
118 here = os.path.abspath(os.path.dirname(__file__))
119
119
120 def require_clean_submodules():
120 def require_clean_submodules():
121 """Check on git submodules before distutils can do anything
121 """Check on git submodules before distutils can do anything
122
122
123 Since distutils cannot be trusted to update the tree
123 Since distutils cannot be trusted to update the tree
124 after everything has been set in motion,
124 after everything has been set in motion,
125 this is not a distutils command.
125 this is not a distutils command.
126 """
126 """
127 # PACKAGERS: Add a return here to skip checks for git submodules
127 # PACKAGERS: Add a return here to skip checks for git submodules
128
128
129 # don't do anything if nothing is actually supposed to happen
129 # don't do anything if nothing is actually supposed to happen
130 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
130 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
131 if do_nothing in sys.argv:
131 if do_nothing in sys.argv:
132 return
132 return
133
133
134 status = check_submodule_status(here)
134 status = check_submodule_status(here)
135
135
136 if status == "missing":
136 if status == "missing":
137 print("checking out submodules for the first time")
137 print("checking out submodules for the first time")
138 update_submodules(here)
138 update_submodules(here)
139 elif status == "unclean":
139 elif status == "unclean":
140 print('\n'.join([
140 print('\n'.join([
141 "Cannot build / install IPython with unclean submodules",
141 "Cannot build / install IPython with unclean submodules",
142 "Please update submodules with",
142 "Please update submodules with",
143 " python setup.py submodule",
143 " python setup.py submodule",
144 "or",
144 "or",
145 " git submodule update",
145 " git submodule update",
146 "or commit any submodule changes you have made."
146 "or commit any submodule changes you have made."
147 ]))
147 ]))
148 sys.exit(1)
148 sys.exit(1)
149
149
150 require_clean_submodules()
150 require_clean_submodules()
151
151
152 #-------------------------------------------------------------------------------
152 #-------------------------------------------------------------------------------
153 # Things related to the IPython documentation
153 # Things related to the IPython documentation
154 #-------------------------------------------------------------------------------
154 #-------------------------------------------------------------------------------
155
155
156 # update the manuals when building a source dist
156 # update the manuals when building a source dist
157 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
157 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
158
158
159 # List of things to be updated. Each entry is a triplet of args for
159 # List of things to be updated. Each entry is a triplet of args for
160 # target_update()
160 # target_update()
161 to_update = [
161 to_update = [
162 # FIXME - Disabled for now: we need to redo an automatic way
162 # FIXME - Disabled for now: we need to redo an automatic way
163 # of generating the magic info inside the rst.
163 # of generating the magic info inside the rst.
164 #('docs/magic.tex',
164 #('docs/magic.tex',
165 #['IPython/Magic.py'],
165 #['IPython/Magic.py'],
166 #"cd doc && ./update_magic.sh" ),
166 #"cd doc && ./update_magic.sh" ),
167
167
168 ('docs/man/ipcluster.1.gz',
168 ('docs/man/ipcluster.1.gz',
169 ['docs/man/ipcluster.1'],
169 ['docs/man/ipcluster.1'],
170 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
170 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
171
171
172 ('docs/man/ipcontroller.1.gz',
172 ('docs/man/ipcontroller.1.gz',
173 ['docs/man/ipcontroller.1'],
173 ['docs/man/ipcontroller.1'],
174 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
174 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
175
175
176 ('docs/man/ipengine.1.gz',
176 ('docs/man/ipengine.1.gz',
177 ['docs/man/ipengine.1'],
177 ['docs/man/ipengine.1'],
178 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
178 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
179
179
180 ('docs/man/ipython.1.gz',
180 ('docs/man/ipython.1.gz',
181 ['docs/man/ipython.1'],
181 ['docs/man/ipython.1'],
182 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
182 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
183
183
184 ]
184 ]
185
185
186
186
187 [ target_update(*t) for t in to_update ]
187 [ target_update(*t) for t in to_update ]
188
188
189 #---------------------------------------------------------------------------
189 #---------------------------------------------------------------------------
190 # Find all the packages, package data, and data_files
190 # Find all the packages, package data, and data_files
191 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
192
192
193 packages = find_packages()
193 packages = find_packages()
194 package_data = find_package_data()
194 package_data = find_package_data()
195
195
196 data_files = find_data_files()
196 data_files = find_data_files()
197
197
198 setup_args['packages'] = packages
198 setup_args['packages'] = packages
199 setup_args['package_data'] = package_data
199 setup_args['package_data'] = package_data
200 setup_args['data_files'] = data_files
200 setup_args['data_files'] = data_files
201
201
202 #---------------------------------------------------------------------------
202 #---------------------------------------------------------------------------
203 # custom distutils commands
203 # custom distutils commands
204 #---------------------------------------------------------------------------
204 #---------------------------------------------------------------------------
205 # imports here, so they are after setuptools import if there was one
205 # imports here, so they are after setuptools import if there was one
206 from distutils.command.sdist import sdist
206 from distutils.command.sdist import sdist
207 from distutils.command.upload import upload
207 from distutils.command.upload import upload
208
208
209 class UploadWindowsInstallers(upload):
209 class UploadWindowsInstallers(upload):
210
210
211 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
211 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
212 user_options = upload.user_options + [
212 user_options = upload.user_options + [
213 ('files=', 'f', 'exe file (or glob) to upload')
213 ('files=', 'f', 'exe file (or glob) to upload')
214 ]
214 ]
215 def initialize_options(self):
215 def initialize_options(self):
216 upload.initialize_options(self)
216 upload.initialize_options(self)
217 meta = self.distribution.metadata
217 meta = self.distribution.metadata
218 base = '{name}-{version}'.format(
218 base = '{name}-{version}'.format(
219 name=meta.get_name(),
219 name=meta.get_name(),
220 version=meta.get_version()
220 version=meta.get_version()
221 )
221 )
222 self.files = os.path.join('dist', '%s.*.exe' % base)
222 self.files = os.path.join('dist', '%s.*.exe' % base)
223
223
224 def run(self):
224 def run(self):
225 for dist_file in glob(self.files):
225 for dist_file in glob(self.files):
226 self.upload_file('bdist_wininst', 'any', dist_file)
226 self.upload_file('bdist_wininst', 'any', dist_file)
227
227
228 setup_args['cmdclass'] = {
228 setup_args['cmdclass'] = {
229 'build_py': check_package_data_first(git_prebuild('IPython')),
229 'build_py': check_package_data_first(git_prebuild('IPython')),
230 'sdist' : git_prebuild('IPython', sdist),
230 'sdist' : git_prebuild('IPython', sdist),
231 'upload_wininst' : UploadWindowsInstallers,
231 'upload_wininst' : UploadWindowsInstallers,
232 'submodule' : UpdateSubmodules,
232 'submodule' : UpdateSubmodules,
233 'css' : CompileCSS,
233 'css' : CompileCSS,
234 'symlink': install_symlinked,
234 'symlink': install_symlinked,
235 'install_lib_symlink': install_lib_symlink,
235 'install_lib_symlink': install_lib_symlink,
236 'install_scripts_sym': install_scripts_for_symlink,
236 'install_scripts_sym': install_scripts_for_symlink,
237 'unsymlink': unsymlink,
237 'unsymlink': unsymlink,
238 'jsversion' : JavascriptVersion,
238 'jsversion' : JavascriptVersion,
239 }
239 }
240
240
241 #---------------------------------------------------------------------------
241 #---------------------------------------------------------------------------
242 # Handle scripts, dependencies, and setuptools specific things
242 # Handle scripts, dependencies, and setuptools specific things
243 #---------------------------------------------------------------------------
243 #---------------------------------------------------------------------------
244
244
245 # For some commands, use setuptools. Note that we do NOT list install here!
245 # For some commands, use setuptools. Note that we do NOT list install here!
246 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
246 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
247 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
247 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
248 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
248 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
249 'egg_info', 'easy_install', 'upload', 'install_egg_info',
249 'egg_info', 'easy_install', 'upload', 'install_egg_info',
250 ))
250 ))
251 if sys.platform == 'win32':
251 if sys.platform == 'win32':
252 # Depend on setuptools for install on *Windows only*
252 # Depend on setuptools for install on *Windows only*
253 # If we get script-installation working without setuptools,
253 # If we get script-installation working without setuptools,
254 # then we can back off, but until then use it.
254 # then we can back off, but until then use it.
255 # See Issue #369 on GitHub for more
255 # See Issue #369 on GitHub for more
256 needs_setuptools.add('install')
256 needs_setuptools.add('install')
257
257
258 if len(needs_setuptools.intersection(sys.argv)) > 0:
258 if len(needs_setuptools.intersection(sys.argv)) > 0:
259 import setuptools
259 import setuptools
260
260
261 # This dict is used for passing extra arguments that are setuptools
261 # This dict is used for passing extra arguments that are setuptools
262 # specific to setup
262 # specific to setup
263 setuptools_extra_args = {}
263 setuptools_extra_args = {}
264
264
265 # setuptools requirements
265 # setuptools requirements
266
266
267 extras_require = dict(
267 extras_require = dict(
268 parallel = ['pyzmq>=2.1.11'],
268 parallel = ['pyzmq>=2.1.11'],
269 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
269 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
270 zmq = ['pyzmq>=2.1.11'],
270 zmq = ['pyzmq>=2.1.11'],
271 doc = ['Sphinx>=1.1', 'numpydoc'],
271 doc = ['Sphinx>=1.1', 'numpydoc'],
272 test = ['nose>=0.10.1'],
272 test = ['nose>=0.10.1'],
273 notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'],
273 notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'],
274 nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
274 nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
275 )
275 )
276 if sys.version_info < (3, 3):
276 if sys.version_info < (3, 3):
277 extras_require['test'].append('mock')
277 extras_require['test'].append('mock')
278
278
279 everything = set()
279 everything = set()
280 for deps in extras_require.values():
280 for deps in extras_require.values():
281 everything.update(deps)
281 everything.update(deps)
282 extras_require['all'] = everything
282 extras_require['all'] = everything
283
283
284 install_requires = []
284 install_requires = []
285 if sys.platform == 'darwin':
285 if sys.platform == 'darwin':
286 if any(arg.startswith('bdist') for arg in sys.argv) or not setupext.check_for_readline():
286 if any(arg.startswith('bdist') for arg in sys.argv) or not setupext.check_for_readline():
287 install_requires.append('gnureadline')
287 install_requires.append('gnureadline')
288 elif sys.platform.startswith('win'):
288 elif sys.platform.startswith('win'):
289 # Pyreadline has unicode and Python 3 fixes in 2.0
289 # Pyreadline has unicode and Python 3 fixes in 2.0
290 install_requires.append('pyreadline>=2.0')
290 install_requires.append('pyreadline>=2.0')
291
291
292 if 'setuptools' in sys.modules:
292 if 'setuptools' in sys.modules:
293 # setup.py develop should check for submodules
293 # setup.py develop should check for submodules
294 from setuptools.command.develop import develop
294 from setuptools.command.develop import develop
295 setup_args['cmdclass']['develop'] = require_submodules(develop)
295 setup_args['cmdclass']['develop'] = require_submodules(develop)
296 setup_args['cmdclass']['bdist_wheel'] = get_bdist_wheel()
296 setup_args['cmdclass']['bdist_wheel'] = get_bdist_wheel()
297
297
298 setuptools_extra_args['zip_safe'] = False
298 setuptools_extra_args['zip_safe'] = False
299 setuptools_extra_args['entry_points'] = {'console_scripts':find_entry_points()}
299 setuptools_extra_args['entry_points'] = {'console_scripts':find_entry_points()}
300 setup_args['extras_require'] = extras_require
300 setup_args['extras_require'] = extras_require
301 requires = setup_args['install_requires'] = install_requires
301 requires = setup_args['install_requires'] = install_requires
302
302
303 # Script to be run by the windows binary installer after the default setup
303 # Script to be run by the windows binary installer after the default setup
304 # routine, to add shortcuts and similar windows-only things. Windows
304 # routine, to add shortcuts and similar windows-only things. Windows
305 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
305 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
306 # doesn't find them.
306 # doesn't find them.
307 if 'bdist_wininst' in sys.argv:
307 if 'bdist_wininst' in sys.argv:
308 if len(sys.argv) > 2 and \
308 if len(sys.argv) > 2 and \
309 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
309 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
310 print >> sys.stderr, "ERROR: bdist_wininst must be run alone. Exiting."
310 print >> sys.stderr, "ERROR: bdist_wininst must be run alone. Exiting."
311 sys.exit(1)
311 sys.exit(1)
312 setup_args['data_files'].append(
312 setup_args['data_files'].append(
313 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
313 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
314 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
314 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
315 setup_args['options'] = {"bdist_wininst":
315 setup_args['options'] = {"bdist_wininst":
316 {"install_script":
316 {"install_script":
317 "ipython_win_post_install.py"}}
317 "ipython_win_post_install.py"}}
318
318
319 else:
319 else:
320 # If we are installing without setuptools, call this function which will
320 # If we are installing without setuptools, call this function which will
321 # check for dependencies an inform the user what is needed. This is
321 # check for dependencies an inform the user what is needed. This is
322 # just to make life easy for users.
322 # just to make life easy for users.
323 for install_cmd in ('install', 'symlink'):
323 for install_cmd in ('install', 'symlink'):
324 if install_cmd in sys.argv:
324 if install_cmd in sys.argv:
325 check_for_dependencies()
325 check_for_dependencies()
326 break
326 break
327 # scripts has to be a non-empty list, or install_scripts isn't called
327 # scripts has to be a non-empty list, or install_scripts isn't called
328 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
328 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
329
329
330 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
330 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
331
331
332 #---------------------------------------------------------------------------
332 #---------------------------------------------------------------------------
333 # Do the actual setup now
333 # Do the actual setup now
334 #---------------------------------------------------------------------------
334 #---------------------------------------------------------------------------
335
335
336 setup_args.update(setuptools_extra_args)
336 setup_args.update(setuptools_extra_args)
337
337
338 def main():
338 def main():
339 setup(**setup_args)
339 setup(**setup_args)
340 cleanup()
340 cleanup()
341
341
342 if __name__ == '__main__':
342 if __name__ == '__main__':
343 main()
343 main()
@@ -1,703 +1,704 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11 from __future__ import print_function
11 from __future__ import print_function
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import errno
23 import errno
24 import os
24 import os
25 import sys
25 import sys
26
26
27 from distutils.command.build_py import build_py
27 from distutils.command.build_py import build_py
28 from distutils.command.build_scripts import build_scripts
28 from distutils.command.build_scripts import build_scripts
29 from distutils.command.install import install
29 from distutils.command.install import install
30 from distutils.command.install_scripts import install_scripts
30 from distutils.command.install_scripts import install_scripts
31 from distutils.cmd import Command
31 from distutils.cmd import Command
32 from fnmatch import fnmatch
32 from fnmatch import fnmatch
33 from glob import glob
33 from glob import glob
34 from subprocess import call
34 from subprocess import call
35
35
36 from setupext import install_data_ext
36 from setupext import install_data_ext
37
37
38 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
39 # Useful globals and utility functions
39 # Useful globals and utility functions
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41
41
42 # A few handy globals
42 # A few handy globals
43 isfile = os.path.isfile
43 isfile = os.path.isfile
44 pjoin = os.path.join
44 pjoin = os.path.join
45 repo_root = os.path.dirname(os.path.abspath(__file__))
45 repo_root = os.path.dirname(os.path.abspath(__file__))
46
46
47 def oscmd(s):
47 def oscmd(s):
48 print(">", s)
48 print(">", s)
49 os.system(s)
49 os.system(s)
50
50
51 # Py3 compatibility hacks, without assuming IPython itself is installed with
51 # Py3 compatibility hacks, without assuming IPython itself is installed with
52 # the full py3compat machinery.
52 # the full py3compat machinery.
53
53
54 try:
54 try:
55 execfile
55 execfile
56 except NameError:
56 except NameError:
57 def execfile(fname, globs, locs=None):
57 def execfile(fname, globs, locs=None):
58 locs = locs or globs
58 locs = locs or globs
59 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
59 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
60
60
61 # A little utility we'll need below, since glob() does NOT allow you to do
61 # A little utility we'll need below, since glob() does NOT allow you to do
62 # exclusion on multiple endings!
62 # exclusion on multiple endings!
63 def file_doesnt_endwith(test,endings):
63 def file_doesnt_endwith(test,endings):
64 """Return true if test is a file and its name does NOT end with any
64 """Return true if test is a file and its name does NOT end with any
65 of the strings listed in endings."""
65 of the strings listed in endings."""
66 if not isfile(test):
66 if not isfile(test):
67 return False
67 return False
68 for e in endings:
68 for e in endings:
69 if test.endswith(e):
69 if test.endswith(e):
70 return False
70 return False
71 return True
71 return True
72
72
73 #---------------------------------------------------------------------------
73 #---------------------------------------------------------------------------
74 # Basic project information
74 # Basic project information
75 #---------------------------------------------------------------------------
75 #---------------------------------------------------------------------------
76
76
77 # release.py contains version, authors, license, url, keywords, etc.
77 # release.py contains version, authors, license, url, keywords, etc.
78 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
78 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
79
79
80 # Create a dict with the basic information
80 # Create a dict with the basic information
81 # This dict is eventually passed to setup after additional keys are added.
81 # This dict is eventually passed to setup after additional keys are added.
82 setup_args = dict(
82 setup_args = dict(
83 name = name,
83 name = name,
84 version = version,
84 version = version,
85 description = description,
85 description = description,
86 long_description = long_description,
86 long_description = long_description,
87 author = author,
87 author = author,
88 author_email = author_email,
88 author_email = author_email,
89 url = url,
89 url = url,
90 download_url = download_url,
90 download_url = download_url,
91 license = license,
91 license = license,
92 platforms = platforms,
92 platforms = platforms,
93 keywords = keywords,
93 keywords = keywords,
94 classifiers = classifiers,
94 classifiers = classifiers,
95 cmdclass = {'install_data': install_data_ext},
95 cmdclass = {'install_data': install_data_ext},
96 )
96 )
97
97
98
98
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100 # Find packages
100 # Find packages
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102
102
103 def find_packages():
103 def find_packages():
104 """
104 """
105 Find all of IPython's packages.
105 Find all of IPython's packages.
106 """
106 """
107 excludes = ['deathrow', 'quarantine']
107 excludes = ['deathrow', 'quarantine']
108 packages = []
108 packages = []
109 for dir,subdirs,files in os.walk('IPython'):
109 for dir,subdirs,files in os.walk('IPython'):
110 package = dir.replace(os.path.sep, '.')
110 package = dir.replace(os.path.sep, '.')
111 if any(package.startswith('IPython.'+exc) for exc in excludes):
111 if any(package.startswith('IPython.'+exc) for exc in excludes):
112 # package is to be excluded (e.g. deathrow)
112 # package is to be excluded (e.g. deathrow)
113 continue
113 continue
114 if '__init__.py' not in files:
114 if '__init__.py' not in files:
115 # not a package
115 # not a package
116 continue
116 continue
117 packages.append(package)
117 packages.append(package)
118 return packages
118 return packages
119
119
120 #---------------------------------------------------------------------------
120 #---------------------------------------------------------------------------
121 # Find package data
121 # Find package data
122 #---------------------------------------------------------------------------
122 #---------------------------------------------------------------------------
123
123
124 def find_package_data():
124 def find_package_data():
125 """
125 """
126 Find IPython's package_data.
126 Find IPython's package_data.
127 """
127 """
128 # This is not enough for these things to appear in an sdist.
128 # This is not enough for these things to appear in an sdist.
129 # We need to muck with the MANIFEST to get this to work
129 # We need to muck with the MANIFEST to get this to work
130
130
131 # exclude components and less from the walk;
131 # exclude components and less from the walk;
132 # we will build the components separately
132 # we will build the components separately
133 excludes = [
133 excludes = [
134 pjoin('static', 'components'),
134 pjoin('static', 'components'),
135 pjoin('static', '*', 'less'),
135 pjoin('static', '*', 'less'),
136 ]
136 ]
137
137
138 # walk notebook resources:
138 # walk notebook resources:
139 cwd = os.getcwd()
139 cwd = os.getcwd()
140 os.chdir(os.path.join('IPython', 'html'))
140 os.chdir(os.path.join('IPython', 'html'))
141 static_data = []
141 static_data = []
142 for parent, dirs, files in os.walk('static'):
142 for parent, dirs, files in os.walk('static'):
143 if any(fnmatch(parent, pat) for pat in excludes):
143 if any(fnmatch(parent, pat) for pat in excludes):
144 # prevent descending into subdirs
144 # prevent descending into subdirs
145 dirs[:] = []
145 dirs[:] = []
146 continue
146 continue
147 for f in files:
147 for f in files:
148 static_data.append(pjoin(parent, f))
148 static_data.append(pjoin(parent, f))
149
149
150 components = pjoin("static", "components")
150 components = pjoin("static", "components")
151 # select the components we actually need to install
151 # select the components we actually need to install
152 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
152 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
153 static_data.extend([
153 static_data.extend([
154 pjoin(components, "backbone", "backbone-min.js"),
154 pjoin(components, "backbone", "backbone-min.js"),
155 pjoin(components, "bootstrap", "bootstrap", "js", "bootstrap.min.js"),
155 pjoin(components, "bootstrap", "bootstrap", "js", "bootstrap.min.js"),
156 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
156 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
157 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
157 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
158 pjoin(components, "font-awesome", "font", "*.*"),
158 pjoin(components, "font-awesome", "font", "*.*"),
159 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
159 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
160 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
160 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
161 pjoin(components, "jquery", "jquery.min.js"),
161 pjoin(components, "jquery", "jquery.min.js"),
162 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
162 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
163 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
163 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
164 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
164 pjoin(components, "marked", "lib", "marked.js"),
165 pjoin(components, "marked", "lib", "marked.js"),
165 pjoin(components, "requirejs", "require.js"),
166 pjoin(components, "requirejs", "require.js"),
166 pjoin(components, "underscore", "underscore-min.js"),
167 pjoin(components, "underscore", "underscore-min.js"),
167 ])
168 ])
168
169
169 # Ship all of Codemirror's CSS and JS
170 # Ship all of Codemirror's CSS and JS
170 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 for f in files:
172 for f in files:
172 if f.endswith(('.js', '.css')):
173 if f.endswith(('.js', '.css')):
173 static_data.append(pjoin(parent, f))
174 static_data.append(pjoin(parent, f))
174
175
175 os.chdir(os.path.join('tests',))
176 os.chdir(os.path.join('tests',))
176 js_tests = glob('*.js') + glob('*/*.js')
177 js_tests = glob('*.js') + glob('*/*.js')
177
178
178 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 for dirpath, _, _ in os.walk('templates')]
181 for dirpath, _, _ in os.walk('templates')]
181
182
182 os.chdir(cwd)
183 os.chdir(cwd)
183
184
184 package_data = {
185 package_data = {
185 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.core.tests' : ['*.png', '*.jpg'],
187 'IPython.core.tests' : ['*.png', '*.jpg'],
187 'IPython.lib.tests' : ['*.wav'],
188 'IPython.lib.tests' : ['*.wav'],
188 'IPython.testing.plugin' : ['*.txt'],
189 'IPython.testing.plugin' : ['*.txt'],
189 'IPython.html' : ['templates/*'] + static_data,
190 'IPython.html' : ['templates/*'] + static_data,
190 'IPython.html.tests' : js_tests,
191 'IPython.html.tests' : js_tests,
191 'IPython.qt.console' : ['resources/icon/*.svg'],
192 'IPython.qt.console' : ['resources/icon/*.svg'],
192 'IPython.nbconvert' : nbconvert_templates +
193 'IPython.nbconvert' : nbconvert_templates +
193 ['tests/files/*.*', 'exporters/tests/files/*.*'],
194 ['tests/files/*.*', 'exporters/tests/files/*.*'],
194 'IPython.nbconvert.filters' : ['marked.js'],
195 'IPython.nbconvert.filters' : ['marked.js'],
195 'IPython.nbformat' : ['tests/*.ipynb']
196 'IPython.nbformat' : ['tests/*.ipynb']
196 }
197 }
197
198
198 return package_data
199 return package_data
199
200
200
201
201 def check_package_data(package_data):
202 def check_package_data(package_data):
202 """verify that package_data globs make sense"""
203 """verify that package_data globs make sense"""
203 print("checking package data")
204 print("checking package data")
204 for pkg, data in package_data.items():
205 for pkg, data in package_data.items():
205 pkg_root = pjoin(*pkg.split('.'))
206 pkg_root = pjoin(*pkg.split('.'))
206 for d in data:
207 for d in data:
207 path = pjoin(pkg_root, d)
208 path = pjoin(pkg_root, d)
208 if '*' in path:
209 if '*' in path:
209 assert len(glob(path)) > 0, "No files match pattern %s" % path
210 assert len(glob(path)) > 0, "No files match pattern %s" % path
210 else:
211 else:
211 assert os.path.exists(path), "Missing package data: %s" % path
212 assert os.path.exists(path), "Missing package data: %s" % path
212
213
213
214
214 def check_package_data_first(command):
215 def check_package_data_first(command):
215 """decorator for checking package_data before running a given command
216 """decorator for checking package_data before running a given command
216
217
217 Probably only needs to wrap build_py
218 Probably only needs to wrap build_py
218 """
219 """
219 class DecoratedCommand(command):
220 class DecoratedCommand(command):
220 def run(self):
221 def run(self):
221 check_package_data(self.package_data)
222 check_package_data(self.package_data)
222 command.run(self)
223 command.run(self)
223 return DecoratedCommand
224 return DecoratedCommand
224
225
225
226
226 #---------------------------------------------------------------------------
227 #---------------------------------------------------------------------------
227 # Find data files
228 # Find data files
228 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
229
230
230 def make_dir_struct(tag,base,out_base):
231 def make_dir_struct(tag,base,out_base):
231 """Make the directory structure of all files below a starting dir.
232 """Make the directory structure of all files below a starting dir.
232
233
233 This is just a convenience routine to help build a nested directory
234 This is just a convenience routine to help build a nested directory
234 hierarchy because distutils is too stupid to do this by itself.
235 hierarchy because distutils is too stupid to do this by itself.
235
236
236 XXX - this needs a proper docstring!
237 XXX - this needs a proper docstring!
237 """
238 """
238
239
239 # we'll use these a lot below
240 # we'll use these a lot below
240 lbase = len(base)
241 lbase = len(base)
241 pathsep = os.path.sep
242 pathsep = os.path.sep
242 lpathsep = len(pathsep)
243 lpathsep = len(pathsep)
243
244
244 out = []
245 out = []
245 for (dirpath,dirnames,filenames) in os.walk(base):
246 for (dirpath,dirnames,filenames) in os.walk(base):
246 # we need to strip out the dirpath from the base to map it to the
247 # we need to strip out the dirpath from the base to map it to the
247 # output (installation) path. This requires possibly stripping the
248 # output (installation) path. This requires possibly stripping the
248 # path separator, because otherwise pjoin will not work correctly
249 # path separator, because otherwise pjoin will not work correctly
249 # (pjoin('foo/','/bar') returns '/bar').
250 # (pjoin('foo/','/bar') returns '/bar').
250
251
251 dp_eff = dirpath[lbase:]
252 dp_eff = dirpath[lbase:]
252 if dp_eff.startswith(pathsep):
253 if dp_eff.startswith(pathsep):
253 dp_eff = dp_eff[lpathsep:]
254 dp_eff = dp_eff[lpathsep:]
254 # The output path must be anchored at the out_base marker
255 # The output path must be anchored at the out_base marker
255 out_path = pjoin(out_base,dp_eff)
256 out_path = pjoin(out_base,dp_eff)
256 # Now we can generate the final filenames. Since os.walk only produces
257 # Now we can generate the final filenames. Since os.walk only produces
257 # filenames, we must join back with the dirpath to get full valid file
258 # filenames, we must join back with the dirpath to get full valid file
258 # paths:
259 # paths:
259 pfiles = [pjoin(dirpath,f) for f in filenames]
260 pfiles = [pjoin(dirpath,f) for f in filenames]
260 # Finally, generate the entry we need, which is a pari of (output
261 # Finally, generate the entry we need, which is a pari of (output
261 # path, files) for use as a data_files parameter in install_data.
262 # path, files) for use as a data_files parameter in install_data.
262 out.append((out_path, pfiles))
263 out.append((out_path, pfiles))
263
264
264 return out
265 return out
265
266
266
267
267 def find_data_files():
268 def find_data_files():
268 """
269 """
269 Find IPython's data_files.
270 Find IPython's data_files.
270
271
271 Just man pages at this point.
272 Just man pages at this point.
272 """
273 """
273
274
274 manpagebase = pjoin('share', 'man', 'man1')
275 manpagebase = pjoin('share', 'man', 'man1')
275
276
276 # Simple file lists can be made by hand
277 # Simple file lists can be made by hand
277 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
278 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
278 if not manpages:
279 if not manpages:
279 # When running from a source tree, the manpages aren't gzipped
280 # When running from a source tree, the manpages aren't gzipped
280 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
281 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
281
282
282 # And assemble the entire output list
283 # And assemble the entire output list
283 data_files = [ (manpagebase, manpages) ]
284 data_files = [ (manpagebase, manpages) ]
284
285
285 return data_files
286 return data_files
286
287
287
288
288 def make_man_update_target(manpage):
289 def make_man_update_target(manpage):
289 """Return a target_update-compliant tuple for the given manpage.
290 """Return a target_update-compliant tuple for the given manpage.
290
291
291 Parameters
292 Parameters
292 ----------
293 ----------
293 manpage : string
294 manpage : string
294 Name of the manpage, must include the section number (trailing number).
295 Name of the manpage, must include the section number (trailing number).
295
296
296 Example
297 Example
297 -------
298 -------
298
299
299 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
300 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
300 ('docs/man/ipython.1.gz',
301 ('docs/man/ipython.1.gz',
301 ['docs/man/ipython.1'],
302 ['docs/man/ipython.1'],
302 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
303 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
303 """
304 """
304 man_dir = pjoin('docs', 'man')
305 man_dir = pjoin('docs', 'man')
305 manpage_gz = manpage + '.gz'
306 manpage_gz = manpage + '.gz'
306 manpath = pjoin(man_dir, manpage)
307 manpath = pjoin(man_dir, manpage)
307 manpath_gz = pjoin(man_dir, manpage_gz)
308 manpath_gz = pjoin(man_dir, manpage_gz)
308 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
309 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
309 locals() )
310 locals() )
310 return (manpath_gz, [manpath], gz_cmd)
311 return (manpath_gz, [manpath], gz_cmd)
311
312
312 # The two functions below are copied from IPython.utils.path, so we don't need
313 # The two functions below are copied from IPython.utils.path, so we don't need
313 # to import IPython during setup, which fails on Python 3.
314 # to import IPython during setup, which fails on Python 3.
314
315
315 def target_outdated(target,deps):
316 def target_outdated(target,deps):
316 """Determine whether a target is out of date.
317 """Determine whether a target is out of date.
317
318
318 target_outdated(target,deps) -> 1/0
319 target_outdated(target,deps) -> 1/0
319
320
320 deps: list of filenames which MUST exist.
321 deps: list of filenames which MUST exist.
321 target: single filename which may or may not exist.
322 target: single filename which may or may not exist.
322
323
323 If target doesn't exist or is older than any file listed in deps, return
324 If target doesn't exist or is older than any file listed in deps, return
324 true, otherwise return false.
325 true, otherwise return false.
325 """
326 """
326 try:
327 try:
327 target_time = os.path.getmtime(target)
328 target_time = os.path.getmtime(target)
328 except os.error:
329 except os.error:
329 return 1
330 return 1
330 for dep in deps:
331 for dep in deps:
331 dep_time = os.path.getmtime(dep)
332 dep_time = os.path.getmtime(dep)
332 if dep_time > target_time:
333 if dep_time > target_time:
333 #print "For target",target,"Dep failed:",dep # dbg
334 #print "For target",target,"Dep failed:",dep # dbg
334 #print "times (dep,tar):",dep_time,target_time # dbg
335 #print "times (dep,tar):",dep_time,target_time # dbg
335 return 1
336 return 1
336 return 0
337 return 0
337
338
338
339
339 def target_update(target,deps,cmd):
340 def target_update(target,deps,cmd):
340 """Update a target with a given command given a list of dependencies.
341 """Update a target with a given command given a list of dependencies.
341
342
342 target_update(target,deps,cmd) -> runs cmd if target is outdated.
343 target_update(target,deps,cmd) -> runs cmd if target is outdated.
343
344
344 This is just a wrapper around target_outdated() which calls the given
345 This is just a wrapper around target_outdated() which calls the given
345 command if target is outdated."""
346 command if target is outdated."""
346
347
347 if target_outdated(target,deps):
348 if target_outdated(target,deps):
348 os.system(cmd)
349 os.system(cmd)
349
350
350 #---------------------------------------------------------------------------
351 #---------------------------------------------------------------------------
351 # Find scripts
352 # Find scripts
352 #---------------------------------------------------------------------------
353 #---------------------------------------------------------------------------
353
354
354 def find_entry_points():
355 def find_entry_points():
355 """Find IPython's scripts.
356 """Find IPython's scripts.
356
357
357 if entry_points is True:
358 if entry_points is True:
358 return setuptools entry_point-style definitions
359 return setuptools entry_point-style definitions
359 else:
360 else:
360 return file paths of plain scripts [default]
361 return file paths of plain scripts [default]
361
362
362 suffix is appended to script names if entry_points is True, so that the
363 suffix is appended to script names if entry_points is True, so that the
363 Python 3 scripts get named "ipython3" etc.
364 Python 3 scripts get named "ipython3" etc.
364 """
365 """
365 ep = [
366 ep = [
366 'ipython%s = IPython:start_ipython',
367 'ipython%s = IPython:start_ipython',
367 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
368 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
368 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
369 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
369 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
370 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
370 'iptest%s = IPython.testing.iptestcontroller:main',
371 'iptest%s = IPython.testing.iptestcontroller:main',
371 ]
372 ]
372 suffix = str(sys.version_info[0])
373 suffix = str(sys.version_info[0])
373 return [e % '' for e in ep] + [e % suffix for e in ep]
374 return [e % '' for e in ep] + [e % suffix for e in ep]
374
375
375 script_src = """#!{executable}
376 script_src = """#!{executable}
376 # This script was automatically generated by setup.py
377 # This script was automatically generated by setup.py
377 if __name__ == '__main__':
378 if __name__ == '__main__':
378 from {mod} import {func}
379 from {mod} import {func}
379 {func}()
380 {func}()
380 """
381 """
381
382
382 class build_scripts_entrypt(build_scripts):
383 class build_scripts_entrypt(build_scripts):
383 def run(self):
384 def run(self):
384 self.mkpath(self.build_dir)
385 self.mkpath(self.build_dir)
385 outfiles = []
386 outfiles = []
386 for script in find_entry_points():
387 for script in find_entry_points():
387 name, entrypt = script.split('=')
388 name, entrypt = script.split('=')
388 name = name.strip()
389 name = name.strip()
389 entrypt = entrypt.strip()
390 entrypt = entrypt.strip()
390 outfile = os.path.join(self.build_dir, name)
391 outfile = os.path.join(self.build_dir, name)
391 outfiles.append(outfile)
392 outfiles.append(outfile)
392 print('Writing script to', outfile)
393 print('Writing script to', outfile)
393
394
394 mod, func = entrypt.split(':')
395 mod, func = entrypt.split(':')
395 with open(outfile, 'w') as f:
396 with open(outfile, 'w') as f:
396 f.write(script_src.format(executable=sys.executable,
397 f.write(script_src.format(executable=sys.executable,
397 mod=mod, func=func))
398 mod=mod, func=func))
398
399
399 return outfiles, outfiles
400 return outfiles, outfiles
400
401
401 class install_lib_symlink(Command):
402 class install_lib_symlink(Command):
402 user_options = [
403 user_options = [
403 ('install-dir=', 'd', "directory to install to"),
404 ('install-dir=', 'd', "directory to install to"),
404 ]
405 ]
405
406
406 def initialize_options(self):
407 def initialize_options(self):
407 self.install_dir = None
408 self.install_dir = None
408
409
409 def finalize_options(self):
410 def finalize_options(self):
410 self.set_undefined_options('symlink',
411 self.set_undefined_options('symlink',
411 ('install_lib', 'install_dir'),
412 ('install_lib', 'install_dir'),
412 )
413 )
413
414
414 def run(self):
415 def run(self):
415 if sys.platform == 'win32':
416 if sys.platform == 'win32':
416 raise Exception("This doesn't work on Windows.")
417 raise Exception("This doesn't work on Windows.")
417 pkg = os.path.join(os.getcwd(), 'IPython')
418 pkg = os.path.join(os.getcwd(), 'IPython')
418 dest = os.path.join(self.install_dir, 'IPython')
419 dest = os.path.join(self.install_dir, 'IPython')
419 if os.path.islink(dest):
420 if os.path.islink(dest):
420 print('removing existing symlink at %s' % dest)
421 print('removing existing symlink at %s' % dest)
421 os.unlink(dest)
422 os.unlink(dest)
422 print('symlinking %s -> %s' % (pkg, dest))
423 print('symlinking %s -> %s' % (pkg, dest))
423 os.symlink(pkg, dest)
424 os.symlink(pkg, dest)
424
425
425 class unsymlink(install):
426 class unsymlink(install):
426 def run(self):
427 def run(self):
427 dest = os.path.join(self.install_lib, 'IPython')
428 dest = os.path.join(self.install_lib, 'IPython')
428 if os.path.islink(dest):
429 if os.path.islink(dest):
429 print('removing symlink at %s' % dest)
430 print('removing symlink at %s' % dest)
430 os.unlink(dest)
431 os.unlink(dest)
431 else:
432 else:
432 print('No symlink exists at %s' % dest)
433 print('No symlink exists at %s' % dest)
433
434
434 class install_symlinked(install):
435 class install_symlinked(install):
435 def run(self):
436 def run(self):
436 if sys.platform == 'win32':
437 if sys.platform == 'win32':
437 raise Exception("This doesn't work on Windows.")
438 raise Exception("This doesn't work on Windows.")
438
439
439 # Run all sub-commands (at least those that need to be run)
440 # Run all sub-commands (at least those that need to be run)
440 for cmd_name in self.get_sub_commands():
441 for cmd_name in self.get_sub_commands():
441 self.run_command(cmd_name)
442 self.run_command(cmd_name)
442
443
443 # 'sub_commands': a list of commands this command might have to run to
444 # 'sub_commands': a list of commands this command might have to run to
444 # get its work done. See cmd.py for more info.
445 # get its work done. See cmd.py for more info.
445 sub_commands = [('install_lib_symlink', lambda self:True),
446 sub_commands = [('install_lib_symlink', lambda self:True),
446 ('install_scripts_sym', lambda self:True),
447 ('install_scripts_sym', lambda self:True),
447 ]
448 ]
448
449
449 class install_scripts_for_symlink(install_scripts):
450 class install_scripts_for_symlink(install_scripts):
450 """Redefined to get options from 'symlink' instead of 'install'.
451 """Redefined to get options from 'symlink' instead of 'install'.
451
452
452 I love distutils almost as much as I love setuptools.
453 I love distutils almost as much as I love setuptools.
453 """
454 """
454 def finalize_options(self):
455 def finalize_options(self):
455 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
456 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
456 self.set_undefined_options('symlink',
457 self.set_undefined_options('symlink',
457 ('install_scripts', 'install_dir'),
458 ('install_scripts', 'install_dir'),
458 ('force', 'force'),
459 ('force', 'force'),
459 ('skip_build', 'skip_build'),
460 ('skip_build', 'skip_build'),
460 )
461 )
461
462
462 #---------------------------------------------------------------------------
463 #---------------------------------------------------------------------------
463 # Verify all dependencies
464 # Verify all dependencies
464 #---------------------------------------------------------------------------
465 #---------------------------------------------------------------------------
465
466
466 def check_for_dependencies():
467 def check_for_dependencies():
467 """Check for IPython's dependencies.
468 """Check for IPython's dependencies.
468
469
469 This function should NOT be called if running under setuptools!
470 This function should NOT be called if running under setuptools!
470 """
471 """
471 from setupext.setupext import (
472 from setupext.setupext import (
472 print_line, print_raw, print_status,
473 print_line, print_raw, print_status,
473 check_for_sphinx, check_for_pygments,
474 check_for_sphinx, check_for_pygments,
474 check_for_nose, check_for_pexpect,
475 check_for_nose, check_for_pexpect,
475 check_for_pyzmq, check_for_readline,
476 check_for_pyzmq, check_for_readline,
476 check_for_jinja2, check_for_tornado
477 check_for_jinja2, check_for_tornado
477 )
478 )
478 print_line()
479 print_line()
479 print_raw("BUILDING IPYTHON")
480 print_raw("BUILDING IPYTHON")
480 print_status('python', sys.version)
481 print_status('python', sys.version)
481 print_status('platform', sys.platform)
482 print_status('platform', sys.platform)
482 if sys.platform == 'win32':
483 if sys.platform == 'win32':
483 print_status('Windows version', sys.getwindowsversion())
484 print_status('Windows version', sys.getwindowsversion())
484
485
485 print_raw("")
486 print_raw("")
486 print_raw("OPTIONAL DEPENDENCIES")
487 print_raw("OPTIONAL DEPENDENCIES")
487
488
488 check_for_sphinx()
489 check_for_sphinx()
489 check_for_pygments()
490 check_for_pygments()
490 check_for_nose()
491 check_for_nose()
491 if os.name == 'posix':
492 if os.name == 'posix':
492 check_for_pexpect()
493 check_for_pexpect()
493 check_for_pyzmq()
494 check_for_pyzmq()
494 check_for_tornado()
495 check_for_tornado()
495 check_for_readline()
496 check_for_readline()
496 check_for_jinja2()
497 check_for_jinja2()
497
498
498 #---------------------------------------------------------------------------
499 #---------------------------------------------------------------------------
499 # VCS related
500 # VCS related
500 #---------------------------------------------------------------------------
501 #---------------------------------------------------------------------------
501
502
502 # utils.submodule has checks for submodule status
503 # utils.submodule has checks for submodule status
503 execfile(pjoin('IPython','utils','submodule.py'), globals())
504 execfile(pjoin('IPython','utils','submodule.py'), globals())
504
505
505 class UpdateSubmodules(Command):
506 class UpdateSubmodules(Command):
506 """Update git submodules
507 """Update git submodules
507
508
508 IPython's external javascript dependencies live in a separate repo.
509 IPython's external javascript dependencies live in a separate repo.
509 """
510 """
510 description = "Update git submodules"
511 description = "Update git submodules"
511 user_options = []
512 user_options = []
512
513
513 def initialize_options(self):
514 def initialize_options(self):
514 pass
515 pass
515
516
516 def finalize_options(self):
517 def finalize_options(self):
517 pass
518 pass
518
519
519 def run(self):
520 def run(self):
520 failure = False
521 failure = False
521 try:
522 try:
522 self.spawn('git submodule init'.split())
523 self.spawn('git submodule init'.split())
523 self.spawn('git submodule update --recursive'.split())
524 self.spawn('git submodule update --recursive'.split())
524 except Exception as e:
525 except Exception as e:
525 failure = e
526 failure = e
526 print(e)
527 print(e)
527
528
528 if not check_submodule_status(repo_root) == 'clean':
529 if not check_submodule_status(repo_root) == 'clean':
529 print("submodules could not be checked out")
530 print("submodules could not be checked out")
530 sys.exit(1)
531 sys.exit(1)
531
532
532
533
533 def git_prebuild(pkg_dir, build_cmd=build_py):
534 def git_prebuild(pkg_dir, build_cmd=build_py):
534 """Return extended build or sdist command class for recording commit
535 """Return extended build or sdist command class for recording commit
535
536
536 records git commit in IPython.utils._sysinfo.commit
537 records git commit in IPython.utils._sysinfo.commit
537
538
538 for use in IPython.utils.sysinfo.sys_info() calls after installation.
539 for use in IPython.utils.sysinfo.sys_info() calls after installation.
539
540
540 Also ensures that submodules exist prior to running
541 Also ensures that submodules exist prior to running
541 """
542 """
542
543
543 class MyBuildPy(build_cmd):
544 class MyBuildPy(build_cmd):
544 ''' Subclass to write commit data into installation tree '''
545 ''' Subclass to write commit data into installation tree '''
545 def run(self):
546 def run(self):
546 build_cmd.run(self)
547 build_cmd.run(self)
547 # this one will only fire for build commands
548 # this one will only fire for build commands
548 if hasattr(self, 'build_lib'):
549 if hasattr(self, 'build_lib'):
549 self._record_commit(self.build_lib)
550 self._record_commit(self.build_lib)
550
551
551 def make_release_tree(self, base_dir, files):
552 def make_release_tree(self, base_dir, files):
552 # this one will fire for sdist
553 # this one will fire for sdist
553 build_cmd.make_release_tree(self, base_dir, files)
554 build_cmd.make_release_tree(self, base_dir, files)
554 self._record_commit(base_dir)
555 self._record_commit(base_dir)
555
556
556 def _record_commit(self, base_dir):
557 def _record_commit(self, base_dir):
557 import subprocess
558 import subprocess
558 proc = subprocess.Popen('git rev-parse --short HEAD',
559 proc = subprocess.Popen('git rev-parse --short HEAD',
559 stdout=subprocess.PIPE,
560 stdout=subprocess.PIPE,
560 stderr=subprocess.PIPE,
561 stderr=subprocess.PIPE,
561 shell=True)
562 shell=True)
562 repo_commit, _ = proc.communicate()
563 repo_commit, _ = proc.communicate()
563 repo_commit = repo_commit.strip().decode("ascii")
564 repo_commit = repo_commit.strip().decode("ascii")
564
565
565 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
566 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
566 if os.path.isfile(out_pth) and not repo_commit:
567 if os.path.isfile(out_pth) and not repo_commit:
567 # nothing to write, don't clobber
568 # nothing to write, don't clobber
568 return
569 return
569
570
570 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
571 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
571
572
572 # remove to avoid overwriting original via hard link
573 # remove to avoid overwriting original via hard link
573 try:
574 try:
574 os.remove(out_pth)
575 os.remove(out_pth)
575 except (IOError, OSError):
576 except (IOError, OSError):
576 pass
577 pass
577 with open(out_pth, 'w') as out_file:
578 with open(out_pth, 'w') as out_file:
578 out_file.writelines([
579 out_file.writelines([
579 '# GENERATED BY setup.py\n',
580 '# GENERATED BY setup.py\n',
580 'commit = "%s"\n' % repo_commit,
581 'commit = "%s"\n' % repo_commit,
581 ])
582 ])
582 return require_submodules(MyBuildPy)
583 return require_submodules(MyBuildPy)
583
584
584
585
585 def require_submodules(command):
586 def require_submodules(command):
586 """decorator for instructing a command to check for submodules before running"""
587 """decorator for instructing a command to check for submodules before running"""
587 class DecoratedCommand(command):
588 class DecoratedCommand(command):
588 def run(self):
589 def run(self):
589 if not check_submodule_status(repo_root) == 'clean':
590 if not check_submodule_status(repo_root) == 'clean':
590 print("submodules missing! Run `setup.py submodule` and try again")
591 print("submodules missing! Run `setup.py submodule` and try again")
591 sys.exit(1)
592 sys.exit(1)
592 command.run(self)
593 command.run(self)
593 return DecoratedCommand
594 return DecoratedCommand
594
595
595 #---------------------------------------------------------------------------
596 #---------------------------------------------------------------------------
596 # bdist related
597 # bdist related
597 #---------------------------------------------------------------------------
598 #---------------------------------------------------------------------------
598
599
599 def get_bdist_wheel():
600 def get_bdist_wheel():
600 """Construct bdist_wheel command for building wheels
601 """Construct bdist_wheel command for building wheels
601
602
602 Constructs py2-none-any tag, instead of py2.7-none-any
603 Constructs py2-none-any tag, instead of py2.7-none-any
603 """
604 """
604 class RequiresWheel(Command):
605 class RequiresWheel(Command):
605 description = "Dummy command for missing bdist_wheel"
606 description = "Dummy command for missing bdist_wheel"
606 user_options = []
607 user_options = []
607
608
608 def initialize_options(self):
609 def initialize_options(self):
609 pass
610 pass
610
611
611 def finalize_options(self):
612 def finalize_options(self):
612 pass
613 pass
613
614
614 def run(self):
615 def run(self):
615 print("bdist_wheel requires the wheel package")
616 print("bdist_wheel requires the wheel package")
616 sys.exit(1)
617 sys.exit(1)
617
618
618 if 'setuptools' not in sys.modules:
619 if 'setuptools' not in sys.modules:
619 return RequiresWheel
620 return RequiresWheel
620 else:
621 else:
621 try:
622 try:
622 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
623 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
623 except ImportError:
624 except ImportError:
624 return RequiresWheel
625 return RequiresWheel
625
626
626 class bdist_wheel_tag(bdist_wheel):
627 class bdist_wheel_tag(bdist_wheel):
627
628
628 def get_tag(self):
629 def get_tag(self):
629 return ('py%i' % sys.version_info[0], 'none', 'any')
630 return ('py%i' % sys.version_info[0], 'none', 'any')
630
631
631 def add_requirements(self, metadata_path):
632 def add_requirements(self, metadata_path):
632 """transform platform-dependent requirements"""
633 """transform platform-dependent requirements"""
633 pkg_info = read_pkg_info(metadata_path)
634 pkg_info = read_pkg_info(metadata_path)
634 # pkg_info is an email.Message object (?!)
635 # pkg_info is an email.Message object (?!)
635 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
636 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
636 # and transform them to conditionals
637 # and transform them to conditionals
637 requires = pkg_info.get_all('Requires-Dist')
638 requires = pkg_info.get_all('Requires-Dist')
638 del pkg_info['Requires-Dist']
639 del pkg_info['Requires-Dist']
639 def _remove_startswith(lis, prefix):
640 def _remove_startswith(lis, prefix):
640 """like list.remove, but with startswith instead of =="""
641 """like list.remove, but with startswith instead of =="""
641 found = False
642 found = False
642 for idx, item in enumerate(lis):
643 for idx, item in enumerate(lis):
643 if item.startswith(prefix):
644 if item.startswith(prefix):
644 found = True
645 found = True
645 break
646 break
646 if found:
647 if found:
647 lis.pop(idx)
648 lis.pop(idx)
648
649
649 for pkg in ("gnureadline", "pyreadline", "mock"):
650 for pkg in ("gnureadline", "pyreadline", "mock"):
650 _remove_startswith(requires, pkg)
651 _remove_startswith(requires, pkg)
651 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
652 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
652 requires.append("pyreadline (>=2.0); sys.platform == 'win32' and platform.python_implementation == 'CPython'")
653 requires.append("pyreadline (>=2.0); sys.platform == 'win32' and platform.python_implementation == 'CPython'")
653 requires.append("mock; extra == 'test' and python_version < '3.3'")
654 requires.append("mock; extra == 'test' and python_version < '3.3'")
654 for r in requires:
655 for r in requires:
655 pkg_info['Requires-Dist'] = r
656 pkg_info['Requires-Dist'] = r
656 write_pkg_info(metadata_path, pkg_info)
657 write_pkg_info(metadata_path, pkg_info)
657
658
658 return bdist_wheel_tag
659 return bdist_wheel_tag
659
660
660 #---------------------------------------------------------------------------
661 #---------------------------------------------------------------------------
661 # Notebook related
662 # Notebook related
662 #---------------------------------------------------------------------------
663 #---------------------------------------------------------------------------
663
664
664 class CompileCSS(Command):
665 class CompileCSS(Command):
665 """Recompile Notebook CSS
666 """Recompile Notebook CSS
666
667
667 Regenerate the compiled CSS from LESS sources.
668 Regenerate the compiled CSS from LESS sources.
668
669
669 Requires various dev dependencies, such as fabric and lessc.
670 Requires various dev dependencies, such as fabric and lessc.
670 """
671 """
671 description = "Recompile Notebook CSS"
672 description = "Recompile Notebook CSS"
672 user_options = []
673 user_options = []
673
674
674 def initialize_options(self):
675 def initialize_options(self):
675 pass
676 pass
676
677
677 def finalize_options(self):
678 def finalize_options(self):
678 pass
679 pass
679
680
680 def run(self):
681 def run(self):
681 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
682 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
682
683
683 class JavascriptVersion(Command):
684 class JavascriptVersion(Command):
684 """write the javascript version to notebook javascript"""
685 """write the javascript version to notebook javascript"""
685 description = "Write IPython version to javascript"
686 description = "Write IPython version to javascript"
686 user_options = []
687 user_options = []
687
688
688 def initialize_options(self):
689 def initialize_options(self):
689 pass
690 pass
690
691
691 def finalize_options(self):
692 def finalize_options(self):
692 pass
693 pass
693
694
694 def run(self):
695 def run(self):
695 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
696 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
696 with open(nsfile) as f:
697 with open(nsfile) as f:
697 lines = f.readlines()
698 lines = f.readlines()
698 with open(nsfile, 'w') as f:
699 with open(nsfile, 'w') as f:
699 for line in lines:
700 for line in lines:
700 if line.startswith("IPython.version"):
701 if line.startswith("IPython.version"):
701 line = 'IPython.version = "{0}";\n'.format(version)
702 line = 'IPython.version = "{0}";\n'.format(version)
702 f.write(line)
703 f.write(line)
703
704
@@ -1,276 +1,276 b''
1 """Functions for Github API requests."""
1 """Functions for Github API requests."""
2 from __future__ import print_function
2 from __future__ import print_function
3
3
4 try:
4 try:
5 input = raw_input
5 input = raw_input
6 except NameError:
6 except NameError:
7 pass
7 pass
8
8
9 import os
9 import os
10 import re
10 import re
11 import sys
11 import sys
12
12
13 import requests
13 import requests
14 import getpass
14 import getpass
15 import json
15 import json
16
16
17 try:
17 try:
18 import requests_cache
18 import requests_cache
19 except ImportError:
19 except ImportError:
20 print("no cache")
20 print("no cache")
21 else:
21 else:
22 requests_cache.install_cache("gh_api")
22 requests_cache.install_cache("gh_api")
23
23
24 # Keyring stores passwords by a 'username', but we're not storing a username and
24 # Keyring stores passwords by a 'username', but we're not storing a username and
25 # password
25 # password
26 fake_username = 'ipython_tools'
26 fake_username = 'ipython_tools'
27
27
28 class Obj(dict):
28 class Obj(dict):
29 """Dictionary with attribute access to names."""
29 """Dictionary with attribute access to names."""
30 def __getattr__(self, name):
30 def __getattr__(self, name):
31 try:
31 try:
32 return self[name]
32 return self[name]
33 except KeyError:
33 except KeyError:
34 raise AttributeError(name)
34 raise AttributeError(name)
35
35
36 def __setattr__(self, name, val):
36 def __setattr__(self, name, val):
37 self[name] = val
37 self[name] = val
38
38
39 token = None
39 token = None
40 def get_auth_token():
40 def get_auth_token():
41 global token
41 global token
42
42
43 if token is not None:
43 if token is not None:
44 return token
44 return token
45
45
46 import keyring
46 import keyring
47 token = keyring.get_password('github', fake_username)
47 token = keyring.get_password('github', fake_username)
48 if token is not None:
48 if token is not None:
49 return token
49 return token
50
50
51 print("Please enter your github username and password. These are not "
51 print("Please enter your github username and password. These are not "
52 "stored, only used to get an oAuth token. You can revoke this at "
52 "stored, only used to get an oAuth token. You can revoke this at "
53 "any time on Github.")
53 "any time on Github.")
54 user = input("Username: ")
54 user = input("Username: ")
55 pw = getpass.getpass("Password: ")
55 pw = getpass.getpass("Password: ")
56
56
57 auth_request = {
57 auth_request = {
58 "scopes": [
58 "scopes": [
59 "public_repo",
59 "public_repo",
60 "gist"
60 "gist"
61 ],
61 ],
62 "note": "IPython tools",
62 "note": "IPython tools",
63 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
63 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
64 }
64 }
65 response = requests.post('https://api.github.com/authorizations',
65 response = requests.post('https://api.github.com/authorizations',
66 auth=(user, pw), data=json.dumps(auth_request))
66 auth=(user, pw), data=json.dumps(auth_request))
67 response.raise_for_status()
67 response.raise_for_status()
68 token = json.loads(response.text)['token']
68 token = json.loads(response.text)['token']
69 keyring.set_password('github', fake_username, token)
69 keyring.set_password('github', fake_username, token)
70 return token
70 return token
71
71
72 def make_auth_header():
72 def make_auth_header():
73 return {'Authorization': 'token ' + get_auth_token()}
73 return {'Authorization': 'token ' + get_auth_token()}
74
74
75 def post_issue_comment(project, num, body):
75 def post_issue_comment(project, num, body):
76 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
76 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
77 payload = json.dumps({'body': body})
77 payload = json.dumps({'body': body})
78 requests.post(url, data=payload, headers=make_auth_header())
78 requests.post(url, data=payload, headers=make_auth_header())
79
79
80 def post_gist(content, description='', filename='file', auth=False):
80 def post_gist(content, description='', filename='file', auth=False):
81 """Post some text to a Gist, and return the URL."""
81 """Post some text to a Gist, and return the URL."""
82 post_data = json.dumps({
82 post_data = json.dumps({
83 "description": description,
83 "description": description,
84 "public": True,
84 "public": True,
85 "files": {
85 "files": {
86 filename: {
86 filename: {
87 "content": content
87 "content": content
88 }
88 }
89 }
89 }
90 }).encode('utf-8')
90 }).encode('utf-8')
91
91
92 headers = make_auth_header() if auth else {}
92 headers = make_auth_header() if auth else {}
93 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
93 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
94 response.raise_for_status()
94 response.raise_for_status()
95 response_data = json.loads(response.text)
95 response_data = json.loads(response.text)
96 return response_data['html_url']
96 return response_data['html_url']
97
97
98 def get_pull_request(project, num, auth=False):
98 def get_pull_request(project, num, auth=False):
99 """get pull request info by number
99 """get pull request info by number
100 """
100 """
101 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
101 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
102 if auth:
102 if auth:
103 header = make_auth_header()
103 header = make_auth_header()
104 else:
104 else:
105 header = None
105 header = None
106 response = requests.get(url, headers=header)
106 response = requests.get(url, headers=header)
107 response.raise_for_status()
107 response.raise_for_status()
108 return json.loads(response.text, object_hook=Obj)
108 return json.loads(response.text, object_hook=Obj)
109
109
110 def get_pull_request_files(project, num, auth=False):
110 def get_pull_request_files(project, num, auth=False):
111 """get list of files in a pull request"""
111 """get list of files in a pull request"""
112 url = "https://api.github.com/repos/{project}/pulls/{num}/files".format(project=project, num=num)
112 url = "https://api.github.com/repos/{project}/pulls/{num}/files".format(project=project, num=num)
113 if auth:
113 if auth:
114 header = make_auth_header()
114 header = make_auth_header()
115 else:
115 else:
116 header = None
116 header = None
117 return get_paged_request(url, headers=header)
117 return get_paged_request(url, headers=header)
118
118
119 element_pat = re.compile(r'<(.+?)>')
119 element_pat = re.compile(r'<(.+?)>')
120 rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')
120 rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')
121
121
122 def get_paged_request(url, headers=None, **params):
122 def get_paged_request(url, headers=None, **params):
123 """get a full list, handling APIv3's paging"""
123 """get a full list, handling APIv3's paging"""
124 results = []
124 results = []
125 params.setdefault("per_page", 100)
125 params.setdefault("per_page", 100)
126 while True:
126 while True:
127 print("fetching %s with %s" % (url, params), file=sys.stderr)
127 print("fetching %s with %s" % (url, params), file=sys.stderr)
128 response = requests.get(url, headers=headers, params=params)
128 response = requests.get(url, headers=headers, params=params)
129 response.raise_for_status()
129 response.raise_for_status()
130 results.extend(response.json())
130 results.extend(response.json())
131 if 'next' in response.links:
131 if 'next' in response.links:
132 url = response.links['next']['url']
132 url = response.links['next']['url']
133 else:
133 else:
134 break
134 break
135 return results
135 return results
136
136
137 def get_pulls_list(project, auth=False, **params):
137 def get_pulls_list(project, auth=False, **params):
138 """get pull request list"""
138 """get pull request list"""
139 params.setdefault("state", "closed")
139 params.setdefault("state", "closed")
140 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
140 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
141 if auth:
141 if auth:
142 headers = make_auth_header()
142 headers = make_auth_header()
143 else:
143 else:
144 headers = None
144 headers = None
145 pages = get_paged_request(url, headers=headers, params=params)
145 pages = get_paged_request(url, headers=headers, **params)
146 return pages
146 return pages
147
147
148 def get_issues_list(project, auth=False, **params):
148 def get_issues_list(project, auth=False, **params):
149 """get issues list"""
149 """get issues list"""
150 params.setdefault("state", "closed")
150 params.setdefault("state", "closed")
151 url = "https://api.github.com/repos/{project}/issues".format(project=project)
151 url = "https://api.github.com/repos/{project}/issues".format(project=project)
152 if auth:
152 if auth:
153 headers = make_auth_header()
153 headers = make_auth_header()
154 else:
154 else:
155 headers = None
155 headers = None
156 pages = get_paged_request(url, headers=headers, **params)
156 pages = get_paged_request(url, headers=headers, **params)
157 return pages
157 return pages
158
158
159 def get_milestones(project, auth=False, **params):
159 def get_milestones(project, auth=False, **params):
160 url = "https://api.github.com/repos/{project}/milestones".format(project=project)
160 url = "https://api.github.com/repos/{project}/milestones".format(project=project)
161 if auth:
161 if auth:
162 headers = make_auth_header()
162 headers = make_auth_header()
163 else:
163 else:
164 headers = None
164 headers = None
165 milestones = get_paged_request(url, headers=headers, **params)
165 milestones = get_paged_request(url, headers=headers, **params)
166 return milestones
166 return milestones
167
167
168 def get_milestone_id(project, milestone, auth=False, **params):
168 def get_milestone_id(project, milestone, auth=False, **params):
169 milestones = get_milestones(project, auth=auth, **params)
169 milestones = get_milestones(project, auth=auth, **params)
170 for mstone in milestones:
170 for mstone in milestones:
171 if mstone['title'] == milestone:
171 if mstone['title'] == milestone:
172 return mstone['number']
172 return mstone['number']
173 else:
173 else:
174 raise ValueError("milestone %s not found" % milestone)
174 raise ValueError("milestone %s not found" % milestone)
175
175
176 def is_pull_request(issue):
176 def is_pull_request(issue):
177 """Return True if the given issue is a pull request."""
177 """Return True if the given issue is a pull request."""
178 return bool(issue.get('pull_request', {}).get('html_url', None))
178 return bool(issue.get('pull_request', {}).get('html_url', None))
179
179
180 # encode_multipart_formdata is from urllib3.filepost
180 # encode_multipart_formdata is from urllib3.filepost
181 # The only change is to iter_fields, to enforce S3's required key ordering
181 # The only change is to iter_fields, to enforce S3's required key ordering
182
182
183 def iter_fields(fields):
183 def iter_fields(fields):
184 fields = fields.copy()
184 fields = fields.copy()
185 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
185 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
186 'Policy', 'Signature', 'Content-Type', 'file'):
186 'Policy', 'Signature', 'Content-Type', 'file'):
187 yield (key, fields.pop(key))
187 yield (key, fields.pop(key))
188 for (k,v) in fields.items():
188 for (k,v) in fields.items():
189 yield k,v
189 yield k,v
190
190
191 def encode_multipart_formdata(fields, boundary=None):
191 def encode_multipart_formdata(fields, boundary=None):
192 """
192 """
193 Encode a dictionary of ``fields`` using the multipart/form-data mime format.
193 Encode a dictionary of ``fields`` using the multipart/form-data mime format.
194
194
195 :param fields:
195 :param fields:
196 Dictionary of fields or list of (key, value) field tuples. The key is
196 Dictionary of fields or list of (key, value) field tuples. The key is
197 treated as the field name, and the value as the body of the form-data
197 treated as the field name, and the value as the body of the form-data
198 bytes. If the value is a tuple of two elements, then the first element
198 bytes. If the value is a tuple of two elements, then the first element
199 is treated as the filename of the form-data section.
199 is treated as the filename of the form-data section.
200
200
201 Field names and filenames must be unicode.
201 Field names and filenames must be unicode.
202
202
203 :param boundary:
203 :param boundary:
204 If not specified, then a random boundary will be generated using
204 If not specified, then a random boundary will be generated using
205 :func:`mimetools.choose_boundary`.
205 :func:`mimetools.choose_boundary`.
206 """
206 """
207 # copy requests imports in here:
207 # copy requests imports in here:
208 from io import BytesIO
208 from io import BytesIO
209 from requests.packages.urllib3.filepost import (
209 from requests.packages.urllib3.filepost import (
210 choose_boundary, six, writer, b, get_content_type
210 choose_boundary, six, writer, b, get_content_type
211 )
211 )
212 body = BytesIO()
212 body = BytesIO()
213 if boundary is None:
213 if boundary is None:
214 boundary = choose_boundary()
214 boundary = choose_boundary()
215
215
216 for fieldname, value in iter_fields(fields):
216 for fieldname, value in iter_fields(fields):
217 body.write(b('--%s\r\n' % (boundary)))
217 body.write(b('--%s\r\n' % (boundary)))
218
218
219 if isinstance(value, tuple):
219 if isinstance(value, tuple):
220 filename, data = value
220 filename, data = value
221 writer(body).write('Content-Disposition: form-data; name="%s"; '
221 writer(body).write('Content-Disposition: form-data; name="%s"; '
222 'filename="%s"\r\n' % (fieldname, filename))
222 'filename="%s"\r\n' % (fieldname, filename))
223 body.write(b('Content-Type: %s\r\n\r\n' %
223 body.write(b('Content-Type: %s\r\n\r\n' %
224 (get_content_type(filename))))
224 (get_content_type(filename))))
225 else:
225 else:
226 data = value
226 data = value
227 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
227 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
228 % (fieldname))
228 % (fieldname))
229 body.write(b'Content-Type: text/plain\r\n\r\n')
229 body.write(b'Content-Type: text/plain\r\n\r\n')
230
230
231 if isinstance(data, int):
231 if isinstance(data, int):
232 data = str(data) # Backwards compatibility
232 data = str(data) # Backwards compatibility
233 if isinstance(data, six.text_type):
233 if isinstance(data, six.text_type):
234 writer(body).write(data)
234 writer(body).write(data)
235 else:
235 else:
236 body.write(data)
236 body.write(data)
237
237
238 body.write(b'\r\n')
238 body.write(b'\r\n')
239
239
240 body.write(b('--%s--\r\n' % (boundary)))
240 body.write(b('--%s--\r\n' % (boundary)))
241
241
242 content_type = b('multipart/form-data; boundary=%s' % boundary)
242 content_type = b('multipart/form-data; boundary=%s' % boundary)
243
243
244 return body.getvalue(), content_type
244 return body.getvalue(), content_type
245
245
246
246
247 def post_download(project, filename, name=None, description=""):
247 def post_download(project, filename, name=None, description=""):
248 """Upload a file to the GitHub downloads area"""
248 """Upload a file to the GitHub downloads area"""
249 if name is None:
249 if name is None:
250 name = os.path.basename(filename)
250 name = os.path.basename(filename)
251 with open(filename, 'rb') as f:
251 with open(filename, 'rb') as f:
252 filedata = f.read()
252 filedata = f.read()
253
253
254 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
254 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
255
255
256 payload = json.dumps(dict(name=name, size=len(filedata),
256 payload = json.dumps(dict(name=name, size=len(filedata),
257 description=description))
257 description=description))
258 response = requests.post(url, data=payload, headers=make_auth_header())
258 response = requests.post(url, data=payload, headers=make_auth_header())
259 response.raise_for_status()
259 response.raise_for_status()
260 reply = json.loads(response.content)
260 reply = json.loads(response.content)
261 s3_url = reply['s3_url']
261 s3_url = reply['s3_url']
262
262
263 fields = dict(
263 fields = dict(
264 key=reply['path'],
264 key=reply['path'],
265 acl=reply['acl'],
265 acl=reply['acl'],
266 success_action_status=201,
266 success_action_status=201,
267 Filename=reply['name'],
267 Filename=reply['name'],
268 AWSAccessKeyId=reply['accesskeyid'],
268 AWSAccessKeyId=reply['accesskeyid'],
269 Policy=reply['policy'],
269 Policy=reply['policy'],
270 Signature=reply['signature'],
270 Signature=reply['signature'],
271 file=(reply['name'], filedata),
271 file=(reply['name'], filedata),
272 )
272 )
273 fields['Content-Type'] = reply['mime_type']
273 fields['Content-Type'] = reply['mime_type']
274 data, content_type = encode_multipart_formdata(fields)
274 data, content_type = encode_multipart_formdata(fields)
275 s3r = requests.post(s3_url, data=data, headers={'Content-Type': content_type})
275 s3r = requests.post(s3_url, data=data, headers={'Content-Type': content_type})
276 return s3r
276 return s3r
@@ -1,194 +1,209 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Simple tools to query github.com and gather stats about issues.
2 """Simple tools to query github.com and gather stats about issues.
3
4 To generate a report for IPython 2.0, run:
5
6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
3 """
7 """
4 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
5 # Imports
9 # Imports
6 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
7
11
8 from __future__ import print_function
12 from __future__ import print_function
9
13
14 import codecs
10 import json
15 import json
11 import re
16 import re
12 import sys
17 import sys
13
18
19 from argparse import ArgumentParser
14 from datetime import datetime, timedelta
20 from datetime import datetime, timedelta
15 from subprocess import check_output
21 from subprocess import check_output
16 from gh_api import get_paged_request, make_auth_header, get_pull_request, is_pull_request
22 from gh_api import (
17
23 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
24 get_milestone_id, get_issues_list,
25 )
18 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
19 # Globals
27 # Globals
20 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
21
29
22 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
30 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
23 PER_PAGE = 100
31 PER_PAGE = 100
24
32
25 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
26 # Functions
34 # Functions
27 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
28
36
29 def get_issues(project="ipython/ipython", state="closed", pulls=False):
30 """Get a list of the issues from the Github API."""
31 which = 'pulls' if pulls else 'issues'
32 url = "https://api.github.com/repos/%s/%s?state=%s&per_page=%i" % (project, which, state, PER_PAGE)
33 return get_paged_request(url, headers=make_auth_header())
34
35 def round_hour(dt):
37 def round_hour(dt):
36 return dt.replace(minute=0,second=0,microsecond=0)
38 return dt.replace(minute=0,second=0,microsecond=0)
37
39
38 def _parse_datetime(s):
40 def _parse_datetime(s):
39 """Parse dates in the format returned by the Github API."""
41 """Parse dates in the format returned by the Github API."""
40 if s:
42 if s:
41 return datetime.strptime(s, ISO8601)
43 return datetime.strptime(s, ISO8601)
42 else:
44 else:
43 return datetime.fromtimestamp(0)
45 return datetime.fromtimestamp(0)
44
46
45
46 def issues2dict(issues):
47 def issues2dict(issues):
47 """Convert a list of issues to a dict, keyed by issue number."""
48 """Convert a list of issues to a dict, keyed by issue number."""
48 idict = {}
49 idict = {}
49 for i in issues:
50 for i in issues:
50 idict[i['number']] = i
51 idict[i['number']] = i
51 return idict
52 return idict
52
53
53 def split_pulls(all_issues, project="ipython/ipython"):
54 def split_pulls(all_issues, project="ipython/ipython"):
54 """split a list of closed issues into non-PR Issues and Pull Requests"""
55 """split a list of closed issues into non-PR Issues and Pull Requests"""
55 pulls = []
56 pulls = []
56 issues = []
57 issues = []
57 for i in all_issues:
58 for i in all_issues:
58 if is_pull_request(i):
59 if is_pull_request(i):
59 pull = get_pull_request(project, i['number'], auth=True)
60 pull = get_pull_request(project, i['number'], auth=True)
60 pulls.append(pull)
61 pulls.append(pull)
61 else:
62 else:
62 issues.append(i)
63 issues.append(i)
63 return issues, pulls
64 return issues, pulls
64
65
65
66
66
67 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
67 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
68 """Get all issues closed since a particular point in time. period
68 """Get all issues closed since a particular point in time. period
69 can either be a datetime object, or a timedelta object. In the
69 can either be a datetime object, or a timedelta object. In the
70 latter case, it is used as a time before the present.
70 latter case, it is used as a time before the present.
71 """
71 """
72
72
73 which = 'pulls' if pulls else 'issues'
73 which = 'pulls' if pulls else 'issues'
74
74
75 if isinstance(period, timedelta):
75 if isinstance(period, timedelta):
76 since = round_hour(datetime.utcnow() - period)
76 since = round_hour(datetime.utcnow() - period)
77 else:
77 else:
78 since = period
78 since = period
79 url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, since.strftime(ISO8601), PER_PAGE)
79 url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, since.strftime(ISO8601), PER_PAGE)
80 allclosed = get_paged_request(url, headers=make_auth_header())
80 allclosed = get_paged_request(url, headers=make_auth_header())
81
81
82 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
82 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
83 if pulls:
83 if pulls:
84 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
84 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
85 # filter out PRs not against master (backports)
85 # filter out PRs not against master (backports)
86 filtered = [ i for i in filtered if i['base']['ref'] == 'master' ]
86 filtered = [ i for i in filtered if i['base']['ref'] == 'master' ]
87 else:
87 else:
88 filtered = [ i for i in filtered if not is_pull_request(i) ]
88 filtered = [ i for i in filtered if not is_pull_request(i) ]
89
89
90 return filtered
90 return filtered
91
91
92
92
93 def sorted_by_field(issues, field='closed_at', reverse=False):
93 def sorted_by_field(issues, field='closed_at', reverse=False):
94 """Return a list of issues sorted by closing date date."""
94 """Return a list of issues sorted by closing date date."""
95 return sorted(issues, key = lambda i:i[field], reverse=reverse)
95 return sorted(issues, key = lambda i:i[field], reverse=reverse)
96
96
97
97
98 def report(issues, show_urls=False):
98 def report(issues, show_urls=False):
99 """Summary report about a list of issues, printing number and title.
99 """Summary report about a list of issues, printing number and title.
100 """
100 """
101 # titles may have unicode in them, so we must encode everything below
101 # titles may have unicode in them, so we must encode everything below
102 if show_urls:
102 if show_urls:
103 for i in issues:
103 for i in issues:
104 role = 'ghpull' if 'merged_at' in i else 'ghissue'
104 role = 'ghpull' if 'merged_at' in i else 'ghissue'
105 print('* :%s:`%d`: %s' % (role, i['number'],
105 print('* :%s:`%d`: %s' % (role, i['number'],
106 i['title'].encode('utf-8')))
106 i['title'].encode('utf-8')))
107 else:
107 else:
108 for i in issues:
108 for i in issues:
109 print('* %d: %s' % (i['number'], i['title'].encode('utf-8')))
109 print('* %d: %s' % (i['number'], i['title'].encode('utf-8')))
110
110
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112 # Main script
112 # Main script
113 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
114
114
115 if __name__ == "__main__":
115 if __name__ == "__main__":
116 # deal with unicode
116 # deal with unicode
117 import codecs
118 sys.stdout = codecs.getwriter('utf8')(sys.stdout)
117 sys.stdout = codecs.getwriter('utf8')(sys.stdout)
119
118
120 # Whether to add reST urls for all issues in printout.
119 # Whether to add reST urls for all issues in printout.
121 show_urls = True
120 show_urls = True
122
123 # By default, search one month back
124 tag = None
125 if len(sys.argv) > 1:
126 try:
127 days = int(sys.argv[1])
128 except:
129 tag = sys.argv[1]
130 else:
131 tag = check_output(['git', 'describe', '--abbrev=0']).strip()
132
121
133 if tag:
122 parser = ArgumentParser()
123 parser.add_argument('--since-tag', type=str,
124 help="The git tag to use for the starting point (typically the last major release)."
125 )
126 parser.add_argument('--milestone', type=str,
127 help="The GitHub milestone to use for filtering issues [optional]."
128 )
129 parser.add_argument('--days', type=int,
130 help="The number of days of data to summarize (use this or --since-tag)."
131 )
132
133 opts = parser.parse_args()
134 tag = opts.since_tag
135
136 # set `since` from days or git tag
137 if opts.days:
138 since = datetime.utcnow() - timedelta(days=opts.days)
139 else:
140 if not tag:
141 tag = check_output(['git', 'describe', '--abbrev=0']).strip()
134 cmd = ['git', 'log', '-1', '--format=%ai', tag]
142 cmd = ['git', 'log', '-1', '--format=%ai', tag]
135 tagday, tz = check_output(cmd).strip().rsplit(' ', 1)
143 tagday, tz = check_output(cmd).strip().rsplit(' ', 1)
136 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
144 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
137 h = int(tz[1:3])
145 h = int(tz[1:3])
138 m = int(tz[3:])
146 m = int(tz[3:])
139 td = timedelta(hours=h, minutes=m)
147 td = timedelta(hours=h, minutes=m)
140 if tz[0] == '-':
148 if tz[0] == '-':
141 since += td
149 since += td
142 else:
150 else:
143 since -= td
151 since -= td
144 else:
145 since = datetime.utcnow() - timedelta(days=days)
146
152
147 since = round_hour(since)
153 since = round_hour(since)
148
154
149 print("fetching GitHub stats since %s (tag: %s)" % (since, tag), file=sys.stderr)
155 milestone = opts.milestone
150 # turn off to play interactively without redownloading, use %run -i
156
151 if 1:
157 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
158 if milestone:
159 milestone_id = get_milestone_id("ipython/ipython", milestone,
160 auth=True)
161 issues = get_issues_list("ipython/ipython",
162 milestone=milestone_id,
163 state='closed',
164 auth=True,
165 )
166 else:
152 issues = issues_closed_since(since, pulls=False)
167 issues = issues_closed_since(since, pulls=False)
153 pulls = issues_closed_since(since, pulls=True)
168 pulls = issues_closed_since(since, pulls=True)
154
169
155 # For regular reports, it's nice to show them in reverse chronological order
170 # For regular reports, it's nice to show them in reverse chronological order
156 issues = sorted_by_field(issues, reverse=True)
171 issues = sorted_by_field(issues, reverse=True)
157 pulls = sorted_by_field(pulls, reverse=True)
172 pulls = sorted_by_field(pulls, reverse=True)
158
173
159 n_issues, n_pulls = map(len, (issues, pulls))
174 n_issues, n_pulls = map(len, (issues, pulls))
160 n_total = n_issues + n_pulls
175 n_total = n_issues + n_pulls
161
176
162 # Print summary report we can directly include into release notes.
177 # Print summary report we can directly include into release notes.
163
178
164 print()
179 print()
165 since_day = since.strftime("%Y/%m/%d")
180 since_day = since.strftime("%Y/%m/%d")
166 today = datetime.today().strftime("%Y/%m/%d")
181 today = datetime.today().strftime("%Y/%m/%d")
167 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
182 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
168 print()
183 print()
169 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
184 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
170 print()
185 print()
171 if tag:
186 if tag:
172 # print git info, in addition to GitHub info:
187 # print git info, in addition to GitHub info:
173 since_tag = tag+'..'
188 since_tag = tag+'..'
174 cmd = ['git', 'log', '--oneline', since_tag]
189 cmd = ['git', 'log', '--oneline', since_tag]
175 ncommits = len(check_output(cmd).splitlines())
190 ncommits = len(check_output(cmd).splitlines())
176
191
177 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
192 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
178 all_authors = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
193 all_authors = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
179 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
194 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
180 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
195 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
181 print()
196 print()
182 print('\n'.join(unique_authors))
197 print('\n'.join(unique_authors))
183 print()
198 print()
184
199
185 print()
200 print()
186 print("We closed a total of %d issues, %d pull requests and %d regular issues;\n"
201 print("We closed a total of %d issues, %d pull requests and %d regular issues;\n"
187 "this is the full list (generated with the script \n"
202 "this is the full list (generated with the script \n"
188 ":file:`tools/github_stats.py`):" % (n_total, n_pulls, n_issues))
203 ":file:`tools/github_stats.py`):" % (n_total, n_pulls, n_issues))
189 print()
204 print()
190 print('Pull Requests (%d):\n' % n_pulls)
205 print('Pull Requests (%d):\n' % n_pulls)
191 report(pulls, show_urls)
206 report(pulls, show_urls)
192 print()
207 print()
193 print('Issues (%d):\n' % n_issues)
208 print('Issues (%d):\n' % n_issues)
194 report(issues, show_urls)
209 report(issues, show_urls)
@@ -1,24 +0,0 b''
1 //
2 // Check for errors with up and down arrow presses in a non-empty notebook.
3 //
4 casper.notebook_test(function () {
5 var result = this.evaluate(function() {
6 IPython.notebook.command_mode();
7 pos0 = IPython.notebook.get_selected_index();
8 IPython.keyboard.trigger_keydown('b');
9 pos1 = IPython.notebook.get_selected_index();
10 IPython.keyboard.trigger_keydown('b');
11 pos2 = IPython.notebook.get_selected_index();
12 // Simulate the "up arrow" and "down arrow" keys.
13 IPython.keyboard.trigger_keydown('up');
14 pos3 = IPython.notebook.get_selected_index();
15 IPython.keyboard.trigger_keydown('down');
16 pos4 = IPython.notebook.get_selected_index();
17 return pos0 == 0 &&
18 pos1 == 1 &&
19 pos2 == 2 &&
20 pos3 == 1 &&
21 pos4 == 2;
22 });
23 this.test.assertTrue(result, 'Up/down arrow okay in non-empty notebook.');
24 });
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
@@ -1,89 +0,0 b''
1 .. _configure-git:
2
3 ===============
4 Configure git
5 ===============
6
7 .. _git-config-basic:
8
9 Overview
10 ========
11
12 ::
13
14 git config --global user.email you@yourdomain.example.com
15 git config --global user.name "Your Name Comes Here"
16
17
18 In detail
19 =========
20
21 This is to tell git_ who you are, for labeling any changes you make to
22 the code. The simplest way to do this is from the command line::
23
24 git config --global user.email you@yourdomain.example.com
25 git config --global user.name "Your Name Comes Here"
26
27 This will write the settings into your git configuration file - a file
28 called ``.gitconfig`` in your home directory.
29
30 Advanced git configuration
31 ==========================
32
33 You might well benefit from some aliases to common commands.
34
35 For example, you might well want to be able to shorten ``git checkout`` to ``git co``.
36
37 The easiest way to do this, is to create a ``.gitconfig`` file in your
38 home directory, with contents like this::
39
40 [core]
41 editor = emacs
42 [user]
43 email = you@yourdomain.example.com
44 name = Your Name Comes Here
45 [alias]
46 st = status
47 stat = status
48 co = checkout
49 [color]
50 diff = auto
51 status = true
52
53 (of course you'll need to set your email and name, and may want to set
54 your editor). If you prefer, you can do the same thing from the command
55 line::
56
57 git config --global core.editor emacs
58 git config --global user.email you@yourdomain.example.com
59 git config --global user.name "Your Name Comes Here"
60 git config --global alias.st status
61 git config --global alias.stat status
62 git config --global alias.co checkout
63 git config --global color.diff auto
64 git config --global color.status true
65
66 These commands will write to your user's git configuration file
67 ``~/.gitconfig``.
68
69 To set up on another computer, you can copy your ``~/.gitconfig`` file,
70 or run the commands above.
71
72 Other configuration recommended by Yarik
73 ========================================
74
75 In your ``~/.gitconfig`` file alias section::
76
77 wdiff = diff --color-words
78
79 so that ``git wdiff`` gives a nicely formatted output of the diff.
80
81 To enforce summaries when doing merges(``~/.gitconfig`` file again)::
82
83 [merge]
84 summary = true
85
86
87 .. include:: git_links.txt
88
89
@@ -1,232 +0,0 b''
1 .. _development-workflow:
2
3 ====================
4 Development workflow
5 ====================
6
7 You already have your own forked copy of the ipython_ repository, by
8 following :ref:`forking`, :ref:`set-up-fork`, and you have configured
9 git_ by following :ref:`configure-git`.
10
11 Workflow summary
12 ================
13
14 * Keep your ``master`` branch clean of edits that have not been merged
15 to the main ipython_ development repo. Your ``master`` then will follow
16 the main ipython_ repository.
17 * Start a new *feature branch* for each set of edits that you do.
18 * If you can avoid it, try not to merge other branches into your feature
19 branch while you are working.
20 * Ask for review!
21
22 This way of working really helps to keep work well organized, and in
23 keeping history as clear as possible.
24
25 See - for example - `linux git workflow`_.
26
27 Making a new feature branch
28 ===========================
29
30 ::
31
32 git branch my-new-feature
33 git checkout my-new-feature
34
35 Generally, you will want to keep this also on your public github_ fork
36 of ipython_. To do this, you `git push`_ this new branch up to your github_
37 repo. Generally (if you followed the instructions in these pages, and
38 by default), git will have a link to your github_ repo, called
39 ``origin``. You push up to your own repo on github_ with::
40
41 git push origin my-new-feature
42
43 From now on git_ will know that ``my-new-feature`` is related to the
44 ``my-new-feature`` branch in the github_ repo.
45
46 The editing workflow
47 ====================
48
49 Overview
50 --------
51
52 ::
53
54 # hack hack
55 git add my_new_file
56 git commit -am 'NF - some message'
57 git push
58
59 In more detail
60 --------------
61
62 #. Make some changes
63 #. See which files have changed with ``git status`` (see `git status`_).
64 You'll see a listing like this one::
65
66 # On branch ny-new-feature
67 # Changed but not updated:
68 # (use "git add <file>..." to update what will be committed)
69 # (use "git checkout -- <file>..." to discard changes in working directory)
70 #
71 # modified: README
72 #
73 # Untracked files:
74 # (use "git add <file>..." to include in what will be committed)
75 #
76 # INSTALL
77 no changes added to commit (use "git add" and/or "git commit -a")
78
79 #. Check what the actual changes are with ``git diff`` (`git diff`_).
80 #. Add any new files to version control ``git add new_file_name`` (see
81 `git add`_).
82 #. To commit all modified files into the local copy of your repo,, do
83 ``git commit -am 'A commit message'``. Note the ``-am`` options to
84 ``commit``. The ``m`` flag just signals that you're going to type a
85 message on the command line. The ``a`` flag - you can just take on
86 faith - or see `why the -a flag?`_. See also the `git commit`_ manual
87 page.
88 #. To push the changes up to your forked repo on github_, do a ``git
89 push`` (see `git push`).
90
91 Asking for code review
92 ======================
93
94 #. Go to your repo URL - e.g. ``http://github.com/your-user-name/ipython``.
95 #. Click on the *Branch list* button:
96
97 .. image:: branch_list.png
98
99 #. Click on the *Compare* button for your feature branch - here ``my-new-feature``:
100
101 .. image:: branch_list_compare.png
102
103 #. If asked, select the *base* and *comparison* branch names you want to
104 compare. Usually these will be ``master`` and ``my-new-feature``
105 (where that is your feature branch name).
106 #. At this point you should get a nice summary of the changes. Copy the
107 URL for this, and post it to the `ipython mailing list`_, asking for
108 review. The URL will look something like:
109 ``http://github.com/your-user-name/ipython/compare/master...my-new-feature``.
110 There's an example at
111 http://github.com/matthew-brett/nipy/compare/master...find-install-data
112 See: http://github.com/blog/612-introducing-github-compare-view for
113 more detail.
114
115 The generated comparison, is between your feature branch
116 ``my-new-feature``, and the place in ``master`` from which you branched
117 ``my-new-feature``. In other words, you can keep updating ``master``
118 without interfering with the output from the comparison. More detail?
119 Note the three dots in the URL above (``master...my-new-feature``).
120
121 Asking for your changes to be merged with the main repo
122 =======================================================
123
124 When you are ready to ask for the merge of your code:
125
126 #. Go to the URL of your forked repo, say
127 ``http://github.com/your-user-name/ipython.git``.
128 #. Click on the 'Pull request' button:
129
130 .. image:: pull_button.png
131
132 Enter a message; we suggest you select only ``ipython`` as the
133 recipient. The message will go to the `ipython mailing list`_. Please
134 feel free to add others from the list as you like.
135
136 Merging from trunk
137 ==================
138
139 This updates your code from the upstream `ipython github`_ repo.
140
141 Overview
142 --------
143
144 ::
145
146 # go to your master branch
147 git checkout master
148 # pull changes from github
149 git fetch upstream
150 # merge from upstream
151 git merge upstream/master
152
153 In detail
154 ---------
155
156 We suggest that you do this only for your ``master`` branch, and leave
157 your 'feature' branches unmerged, to keep their history as clean as
158 possible. This makes code review easier::
159
160 git checkout master
161
162 Make sure you have done :ref:`linking-to-upstream`.
163
164 Merge the upstream code into your current development by first pulling
165 the upstream repo to a copy on your local machine::
166
167 git fetch upstream
168
169 then merging into your current branch::
170
171 git merge upstream/master
172
173 Deleting a branch on github_
174 ============================
175
176 ::
177
178 git checkout master
179 # delete branch locally
180 git branch -D my-unwanted-branch
181 # delete branch on github
182 git push origin :my-unwanted-branch
183
184 (Note the colon ``:`` before ``test-branch``. See also:
185 http://github.com/guides/remove-a-remote-branch
186
187 Several people sharing a single repository
188 ==========================================
189
190 If you want to work on some stuff with other people, where you are all
191 committing into the same repository, or even the same branch, then just
192 share it via github_.
193
194 First fork ipython into your account, as from :ref:`forking`.
195
196 Then, go to your forked repository github page, say
197 ``http://github.com/your-user-name/ipython``
198
199 Click on the 'Admin' button, and add anyone else to the repo as a
200 collaborator:
201
202 .. image:: pull_button.png
203
204 Now all those people can do::
205
206 git clone git@githhub.com:your-user-name/ipython.git
207
208 Remember that links starting with ``git@`` use the ssh protocol and are
209 read-write; links starting with ``git://`` are read-only.
210
211 Your collaborators can then commit directly into that repo with the
212 usual::
213
214 git commit -am 'ENH - much better code'
215 git push origin master # pushes directly into your repo
216
217 Exploring your repository
218 =========================
219
220 To see a graphical representation of the repository branches and
221 commits::
222
223 gitk --all
224
225 To see a linear list of commits for this branch::
226
227 git log
228
229 You can also look at the `network graph visualizer`_ for your github_
230 repo.
231
232 .. include:: git_links.txt
@@ -1,36 +0,0 b''
1 .. _following-latest:
2
3 =============================
4 Following the latest source
5 =============================
6
7 These are the instructions if you just want to follow the latest
8 *ipython* source, but you don't need to do any development for now.
9
10 The steps are:
11
12 * :ref:`install-git`
13 * get local copy of the git repository from github_
14 * update local copy from time to time
15
16 Get the local copy of the code
17 ==============================
18
19 From the command line::
20
21 git clone git://github.com/ipython/ipython.git
22
23 You now have a copy of the code tree in the new ``ipython`` directory.
24
25 Updating the code
26 =================
27
28 From time to time you may want to pull down the latest code. Do this with::
29
30 cd ipython
31 git pull
32
33 The tree in ``ipython`` will now have the latest changes from the initial
34 repository.
35
36 .. include:: git_links.txt
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
@@ -1,33 +0,0 b''
1 .. _forking:
2
3 ==========================================
4 Making your own copy (fork) of ipython
5 ==========================================
6
7 You need to do this only once. The instructions here are very similar
8 to the instructions at http://help.github.com/forking/ - please see that
9 page for more detail. We're repeating some of it here just to give the
10 specifics for the ipython_ project, and to suggest some default names.
11
12 Set up and configure a github_ account
13 ======================================
14
15 If you don't have a github_ account, go to the github_ page, and make one.
16
17 You then need to configure your account to allow write access - see the
18 ``Generating SSH keys`` help on `github help`_.
19
20 Create your own forked copy of ipython_
21 =========================================
22
23 #. Log into your github_ account.
24 #. Go to the ipython_ github home at `ipython github`_.
25 #. Click on the *fork* button:
26
27 .. image:: forking_button.png
28
29 Now, after a short pause and some 'Hardcore forking action', you
30 should find yourself at the home page for your own forked copy of ipython_.
31
32 .. include:: git_links.txt
33
@@ -1,16 +0,0 b''
1 .. _git-development:
2
3 =====================
4 Git for development
5 =====================
6
7 Contents:
8
9 .. toctree::
10 :maxdepth: 2
11
12 forking_hell
13 set_up_fork
14 configure_git
15 development_workflow
16
@@ -1,26 +0,0 b''
1 .. _install-git:
2
3 =============
4 Install git
5 =============
6
7 Overview
8 ========
9
10 ================ =============
11 Debian / Ubuntu ``sudo apt-get install git-core``
12 Fedora ``sudo yum install git-core``
13 Windows Download and install msysGit_
14 OS X Use the git-osx-installer_
15 ================ =============
16
17 In detail
18 =========
19
20 See the git_ page for the most recent information.
21
22 Have a look at the github_ install help pages available from `github help`_
23
24 There are good instructions here: http://book.git-scm.com/2_installing_git.html
25
26 .. include:: git_links.txt
@@ -1,67 +0,0 b''
1 .. This (-*- rst -*-) format file contains commonly used link targets
2 and name substitutions. It may be included in many files,
3 therefore it should only contain link targets and name
4 substitutions. Try grepping for "^\.\. _" to find plausible
5 candidates for this list.
6
7 .. NOTE: reST targets are
8 __not_case_sensitive__, so only one target definition is needed for
9 nipy, NIPY, Nipy, etc...
10
11 .. PROJECTNAME placeholders
12 .. _PROJECTNAME: http://neuroimaging.scipy.org
13 .. _`PROJECTNAME github`: http://github.com/nipy
14 .. _`PROJECTNAME mailing list`: http://projects.scipy.org/mailman/listinfo/nipy-devel
15
16 .. nipy
17 .. _nipy: http://nipy.org/nipy
18 .. _`nipy github`: http://github.com/nipy/nipy
19 .. _`nipy mailing list`: http://mail.scipy.org/mailman/listinfo/nipy-devel
20
21 .. ipython
22 .. _ipython: http://ipython.org
23 .. _`ipython github`: http://github.com/ipython/ipython
24 .. _`ipython mailing list`: http://mail.scipy.org/mailman/listinfo/IPython-dev
25
26 .. nipy
27 .. _dipy: http://nipy.org/dipy
28 .. _`dipy github`: http://github.com/Garyfallidis/dipy
29 .. _`dipy mailing list`: http://mail.scipy.org/mailman/listinfo/nipy-devel
30
31 .. git stuff
32 .. _git: http://git-scm.com/
33 .. _github: http://github.com
34 .. _github help: http://help.github.com
35 .. _msysgit: http://code.google.com/p/msysgit/downloads/list
36 .. _git-osx-installer: http://code.google.com/p/git-osx-installer/downloads/list
37 .. _subversion: http://subversion.tigris.org/
38 .. _git cheat sheet: http://github.com/guides/git-cheat-sheet
39 .. _pro git book: http://progit.org/
40 .. _git svn crash course: http://git-scm.com/course/svn.html
41 .. _learn.github: http://learn.github.com/
42 .. _network graph visualizer: http://github.com/blog/39-say-hello-to-the-network-graph-visualizer
43 .. _git user manual: http://www.kernel.org/pub/software/scm/git/docs/user-manual.html
44 .. _git tutorial: http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html
45 .. _git community book: http://book.git-scm.com/
46 .. _git ready: http://www.gitready.com/
47 .. _git casts: http://www.gitcasts.com/
48 .. _Fernando's git page: http://www.fperez.org/py4science/git.html
49 .. _git magic: http://www-cs-students.stanford.edu/~blynn/gitmagic/index.html
50 .. _git concepts: http://www.eecs.harvard.edu/~cduan/technical/git/
51 .. _git clone: http://www.kernel.org/pub/software/scm/git/docs/git-clone.html
52 .. _git checkout: http://www.kernel.org/pub/software/scm/git/docs/git-checkout.html
53 .. _git commit: http://www.kernel.org/pub/software/scm/git/docs/git-commit.html
54 .. _git push: http://www.kernel.org/pub/software/scm/git/docs/git-push.html
55 .. _git pull: http://www.kernel.org/pub/software/scm/git/docs/git-pull.html
56 .. _git add: http://www.kernel.org/pub/software/scm/git/docs/git-add.html
57 .. _git status: http://www.kernel.org/pub/software/scm/git/docs/git-status.html
58 .. _git diff: http://www.kernel.org/pub/software/scm/git/docs/git-diff.html
59 .. _git log: http://www.kernel.org/pub/software/scm/git/docs/git-log.html
60 .. _git branch: http://www.kernel.org/pub/software/scm/git/docs/git-branch.html
61 .. _git remote: http://www.kernel.org/pub/software/scm/git/docs/git-remote.html
62 .. _git config: http://www.kernel.org/pub/software/scm/git/docs/git-config.html
63 .. _why the -a flag?: http://www.gitready.com/beginner/2009/01/18/the-staging-area.html
64 .. _git staging area: http://www.gitready.com/beginner/2009/01/18/the-staging-area.html
65 .. _git management: http://kerneltrap.org/Linux/Git_Management
66 .. _linux git workflow: http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html
67 .. _git parable: http://tom.preston-werner.com/2009/05/19/the-git-parable.html
@@ -1,57 +0,0 b''
1 .. _git-resources:
2
3 ================
4 git_ resources
5 ================
6
7 Tutorials and summaries
8 =======================
9
10 * `github help`_ has an excellent series of how-to guides.
11 * `learn.github`_ has an excellent series of tutorials
12 * The `pro git book`_ is a good in-depth book on git.
13 * A `git cheat sheet`_ is a page giving summaries of common commands.
14 * The `git user manual`_
15 * The `git tutorial`_
16 * The `git community book`_
17 * `git ready`_ - a nice series of tutorials
18 * `git casts`_ - video snippets giving git how-tos.
19 * `git magic`_ - extended introduction with intermediate detail
20 * Fernando Perez' git page - `Fernando's git page`_ - many links and tips
21 * A good but technical page on `git concepts`_
22 * Th `git parable`_ is an easy read explaining the concepts behind git.
23 * `git svn crash course`_: git_ for those of us used to subversion_
24
25 Advanced git workflow
26 =====================
27
28 There are many ways of working with git_; here are some posts on the
29 rules of thumb that other projects have come up with:
30
31 * Linus Torvalds on `git management`_
32 * Linus Torvalds on `linux git workflow`_ . Summary; use the git tools
33 to make the history of your edits as clean as possible; merge from
34 upstream edits as little as possible in branches where you are doing
35 active development.
36
37 Manual pages online
38 ===================
39
40 You can get these on your own machine with (e.g) ``git help push`` or
41 (same thing) ``git push --help``, but, for convenience, here are the
42 online manual pages for some common commands:
43
44 * `git add`_
45 * `git branch`_
46 * `git checkout`_
47 * `git clone`_
48 * `git commit`_
49 * `git config`_
50 * `git diff`_
51 * `git log`_
52 * `git pull`_
53 * `git push`_
54 * `git remote`_
55 * `git status`_
56
57 .. include:: git_links.txt
@@ -1,30 +0,0 b''
1 .. _using-git:
2
3 Working with IPython source code
4 ================================
5
6 These pages describe a git_ and github_ workflow for the IPython_ project.
7
8 There are several different workflows here, for different ways of
9 working with IPython.
10
11 This is not a comprehensive git_ reference, it's just a workflow for our
12 own project. It's tailored to the github_ hosting service. You may well
13 find better or quicker ways of getting stuff done with git, but these
14 should get you started.
15
16 For general resources for learning git_ see :ref:`git-resources`.
17
18
19 Contents:
20
21 .. toctree::
22 :maxdepth: 2
23
24 git_install
25 following_latest
26 patching
27 git_development
28 git_resources
29
30 .. include:: git_links.txt
@@ -1,123 +0,0 b''
1 ================
2 Making a patch
3 ================
4
5 You've discovered a bug or something else you want to change in ipython_ - excellent!
6
7 You've worked out a way to fix it - even better!
8
9 You want to tell us about it - best of all!
10
11 The easiest way is to make a *patch* or set of patches. Here we explain
12 how. Making a patch is the simplest and quickest, but if you're going
13 to be doing anything more than simple quick things, please consider
14 following the :ref:`git-development` model instead.
15
16 .. _making-patches:
17
18 Making patches
19 ==============
20
21 Overview
22 --------
23
24 ::
25
26 # tell git who you are
27 git config --global user.email you@yourdomain.example.com
28 git config --global user.name "Your Name Comes Here"
29 # get the repository if you don't have it
30 git clone git://github.com/ipython/ipython.git
31 # make a branch for your patching
32 cd ipython
33 git branch the-fix-im-thinking-of
34 git checkout the-fix-im-thinking-of
35 # hack, hack, hack
36 # Tell git about any new files you've made
37 git add somewhere/tests/test_my_bug.py
38 # commit work in progress as you go
39 git commit -am 'BF - added tests for Funny bug'
40 # hack hack, hack
41 git commit -am 'BF - added fix for Funny bug'
42 # make the patch files
43 git format-patch -M -C master
44
45 Then, send the generated patch files to the `ipython mailing list`_ - where we will thank you warmly.
46
47 In detail
48 ---------
49
50 #. Tell git_ who you are so it can label the commits you've made::
51
52 git config --global user.email you@yourdomain.example.com
53 git config --global user.name "Your Name Comes Here"
54
55 #. If you don't already have one, clone a copy of the ipython_ repository::
56
57 git clone git://github.com/ipython/ipython.git
58 cd ipython
59
60 #. Make a 'feature branch'. This will be where you work on your bug
61 fix. It's nice and safe and leaves you with access to an unmodified
62 copy of the code in the main branch::
63
64 git branch the-fix-im-thinking-of
65 git checkout the-fix-im-thinking-of
66
67 #. Do some edits, and commit them as you go::
68
69 # hack, hack, hack
70 # Tell git about any new files you've made
71 git add somewhere/tests/test_my_bug.py
72 # commit work in progress as you go
73 git commit -am 'BF - added tests for Funny bug'
74 # hack hack, hack
75 git commit -am 'BF - added fix for Funny bug'
76
77 Note the ``-am`` options to ``commit``. The ``m`` flag just signals
78 that you're going to type a message on the command line. The ``a``
79 flag - you can just take on faith - or see `why the -a flag?`_.
80
81 #. When you have finished, check you have committed all your changes::
82
83 git status
84
85 #. Finally, make your commits into patches. You want all the commits
86 since you branched from the ``master`` branch::
87
88 git format-patch -M -C master
89
90 You will now have several files named for the commits::
91
92 0001-BF-added-tests-for-Funny-bug.patch
93 0002-BF-added-fix-for-Funny-bug.patch
94
95 Send these files to the `ipython mailing list`_.
96
97 When you are done, to switch back to the main copy of the code, just
98 return to the ``master`` branch::
99
100 git checkout master
101
102 Moving from patching to development
103 ===================================
104
105 If you find you have done some patches, and you have one or more feature
106 branches, you will probably want to switch to development mode. You can
107 do this with the repository you have.
108
109 Fork the ipython_ repository on github_ - :ref:`forking`. Then::
110
111 # checkout and refresh master branch from main repo
112 git checkout master
113 git pull origin master
114 # rename pointer to main repository to 'upstream'
115 git remote rename origin upstream
116 # point your repo to default read / write to your fork on github
117 git remote add origin git@github.com:your-user-name/ipython.git
118 # push up any branches you've made and want to keep
119 git push origin the-fix-im-thinking-of
120
121 Then you can, if you want, follow the :ref:`development-workflow`.
122
123 .. include:: git_links.txt
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
@@ -1,68 +0,0 b''
1 .. _set-up-fork:
2
3 ==================
4 Set up your fork
5 ==================
6
7 First you follow the instructions for :ref:`forking`.
8
9 Overview
10 ========
11
12 ::
13
14 git clone git@github.com:your-user-name/ipython.git
15 cd ipython
16 git remote add upstream git://github.com/ipython/ipython.git
17
18 In detail
19 =========
20
21 Clone your fork
22 ---------------
23
24 #. Clone your fork to the local computer with ``git clone
25 git@github.com:your-user-name/ipython.git``
26 #. Investigate. Change directory to your new repo: ``cd ipython``. Then
27 ``git branch -a`` to show you all branches. You'll get something
28 like::
29
30 * master
31 remotes/origin/master
32
33 This tells you that you are currently on the ``master`` branch, and
34 that you also have a ``remote`` connection to ``origin/master``.
35 What remote repository is ``remote/origin``? Try ``git remote -v`` to
36 see the URLs for the remote. They will point to your github_ fork.
37
38 Now you want to connect to the upstream `ipython github`_ repository, so
39 you can merge in changes from trunk.
40
41 .. _linking-to-upstream:
42
43 Linking your repository to the upstream repo
44 --------------------------------------------
45
46 ::
47
48 cd ipython
49 git remote add upstream git://github.com/ipython/ipython.git
50
51 ``upstream`` here is just the arbitrary name we're using to refer to the
52 main ipython_ repository at `ipython github`_.
53
54 Note that we've used ``git://`` for the URL rather than ``git@``. The
55 ``git://`` URL is read only. This means we that we can't accidentally
56 (or deliberately) write to the upstream repo, and we are only going to
57 use it to merge into our own code.
58
59 Just for your own satisfaction, show yourself that you now have a new
60 'remote', with ``git remote -v show``, giving you something like::
61
62 upstream git://github.com/ipython/ipython.git (fetch)
63 upstream git://github.com/ipython/ipython.git (push)
64 origin git@github.com:your-user-name/ipython.git (fetch)
65 origin git@github.com:your-user-name/ipython.git (push)
66
67 .. include:: git_links.txt
68
@@ -1,6 +0,0 b''
1 Interactive Notebook Tour
2 -------------------------
3
4 Familiarize yourself with the updated notebook user interface, including an
5 explanation of Edit and Command modes, by going through the short guided tour
6 which can be started from the Help menu.
General Comments 0
You need to be logged in to leave comments. Login now