##// END OF EJS Templates
Add rsvp to setupbase
Jonathan Frederic -
Show More
@@ -1,740 +1,747
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 casper.get_notebook_server = function () {
4 casper.get_notebook_server = function () {
5 // Get the URL of a notebook server on which to run tests.
5 // Get the URL of a notebook server on which to run tests.
6 var port = casper.cli.get("port");
6 var port = casper.cli.get("port");
7 port = (typeof port === 'undefined') ? '8888' : port;
7 port = (typeof port === 'undefined') ? '8888' : port;
8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
9 };
9 };
10
10
11 casper.open_new_notebook = function () {
11 casper.open_new_notebook = function () {
12 // Create and open a new notebook.
12 // Create and open a new notebook.
13 var baseUrl = this.get_notebook_server();
13 var baseUrl = this.get_notebook_server();
14 this.start(baseUrl);
14 this.start(baseUrl);
15 this.waitFor(this.page_loaded);
15 this.waitFor(this.page_loaded);
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 this.waitFor(this.page_loaded);
23 this.waitFor(this.page_loaded);
24
24
25 // Hook the log and error methods of the console, forcing them to
25 // Hook the log and error methods of the console, forcing them to
26 // serialize their arguments before printing. This allows the
26 // serialize their arguments before printing. This allows the
27 // Objects to cross into the phantom/slimer regime for display.
27 // Objects to cross into the phantom/slimer regime for display.
28 this.thenEvaluate(function(){
28 this.thenEvaluate(function(){
29 var serialize_arguments = function(f, context) {
29 var serialize_arguments = function(f, context) {
30 return function() {
30 return function() {
31 var pretty_arguments = [];
31 var pretty_arguments = [];
32 for (var i = 0; i < arguments.length; i++) {
32 for (var i = 0; i < arguments.length; i++) {
33 var value = arguments[i];
33 var value = arguments[i];
34 if (value instanceof Object) {
34 if (value instanceof Object) {
35 pretty_arguments.push(JSON.stringify(value, null, ' '));
35 var name = value.name || 'Object';
36 // Print a JSON string representation of the object.
37 // If we don't do this, [Object object] gets printed
38 // by casper, which is useless. The long regular
39 // expression reduces the verbosity of the JSON.
40 pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
41 .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
42 .replace(/\n(\s+)?\n/g, '\n'));
36 } else {
43 } else {
37 pretty_arguments.push(value);
44 pretty_arguments.push(value);
38 }
45 }
39 }
46 }
40 f.apply(context, pretty_arguments);
47 f.apply(context, pretty_arguments);
41 };
48 };
42 };
49 };
43 console.log = serialize_arguments(console.log, console);
50 console.log = serialize_arguments(console.log, console);
44 console.error = serialize_arguments(console.error, console);
51 console.error = serialize_arguments(console.error, console);
45 });
52 });
46
53
47 // Make sure the kernel has started
54 // Make sure the kernel has started
48 this.waitFor(this.kernel_running);
55 this.waitFor(this.kernel_running);
49 // track the IPython busy/idle state
56 // track the IPython busy/idle state
50 this.thenEvaluate(function () {
57 this.thenEvaluate(function () {
51 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
58 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
52
59
53 events.on('kernel_idle.Kernel',function () {
60 events.on('kernel_idle.Kernel',function () {
54 IPython._status = 'idle';
61 IPython._status = 'idle';
55 });
62 });
56 events.on('kernel_busy.Kernel',function () {
63 events.on('kernel_busy.Kernel',function () {
57 IPython._status = 'busy';
64 IPython._status = 'busy';
58 });
65 });
59 });
66 });
60 });
67 });
61
68
62 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
69 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
63 // sure the notebook has actually been loaded into the IPython namespace
70 // sure the notebook has actually been loaded into the IPython namespace
64 // before running any tests.
71 // before running any tests.
65 this.waitFor(function() {
72 this.waitFor(function() {
66 return this.evaluate(function () {
73 return this.evaluate(function () {
67 return IPython.notebook;
74 return IPython.notebook;
68 });
75 });
69 });
76 });
70 };
77 };
71
78
72 casper.page_loaded = function() {
79 casper.page_loaded = function() {
73 // Return whether or not the kernel is running.
80 // Return whether or not the kernel is running.
74 return this.evaluate(function() {
81 return this.evaluate(function() {
75 return typeof IPython !== "undefined" &&
82 return typeof IPython !== "undefined" &&
76 IPython.page !== undefined;
83 IPython.page !== undefined;
77 });
84 });
78 };
85 };
79
86
80 casper.kernel_running = function() {
87 casper.kernel_running = function() {
81 // Return whether or not the kernel is running.
88 // Return whether or not the kernel is running.
82 return this.evaluate(function() {
89 return this.evaluate(function() {
83 return IPython &&
90 return IPython &&
84 IPython.notebook &&
91 IPython.notebook &&
85 IPython.notebook.kernel &&
92 IPython.notebook.kernel &&
86 IPython.notebook.kernel.is_connected();
93 IPython.notebook.kernel.is_connected();
87 });
94 });
88 };
95 };
89
96
90 casper.kernel_disconnected = function() {
97 casper.kernel_disconnected = function() {
91 return this.evaluate(function() {
98 return this.evaluate(function() {
92 return IPython.notebook.kernel.is_fully_disconnected();
99 return IPython.notebook.kernel.is_fully_disconnected();
93 });
100 });
94 };
101 };
95
102
96 casper.wait_for_kernel_ready = function () {
103 casper.wait_for_kernel_ready = function () {
97 this.waitFor(this.kernel_running);
104 this.waitFor(this.kernel_running);
98 this.thenEvaluate(function () {
105 this.thenEvaluate(function () {
99 IPython._kernel_ready = false;
106 IPython._kernel_ready = false;
100 IPython.notebook.kernel.kernel_info(
107 IPython.notebook.kernel.kernel_info(
101 function () {
108 function () {
102 IPython._kernel_ready = true;
109 IPython._kernel_ready = true;
103 });
110 });
104 });
111 });
105 this.waitFor(function () {
112 this.waitFor(function () {
106 return this.evaluate(function () {
113 return this.evaluate(function () {
107 return IPython._kernel_ready;
114 return IPython._kernel_ready;
108 });
115 });
109 });
116 });
110 };
117 };
111
118
112 casper.shutdown_current_kernel = function () {
119 casper.shutdown_current_kernel = function () {
113 // Shut down the current notebook's kernel.
120 // Shut down the current notebook's kernel.
114 this.thenEvaluate(function() {
121 this.thenEvaluate(function() {
115 IPython.notebook.session.delete();
122 IPython.notebook.session.delete();
116 });
123 });
117 // We close the page right after this so we need to give it time to complete.
124 // We close the page right after this so we need to give it time to complete.
118 this.wait(1000);
125 this.wait(1000);
119 };
126 };
120
127
121 casper.delete_current_notebook = function () {
128 casper.delete_current_notebook = function () {
122 // Delete created notebook.
129 // Delete created notebook.
123
130
124 // For some unknown reason, this doesn't work?!?
131 // For some unknown reason, this doesn't work?!?
125 this.thenEvaluate(function() {
132 this.thenEvaluate(function() {
126 IPython.notebook.delete();
133 IPython.notebook.delete();
127 });
134 });
128 };
135 };
129
136
130 casper.wait_for_busy = function () {
137 casper.wait_for_busy = function () {
131 // Waits for the notebook to enter a busy state.
138 // Waits for the notebook to enter a busy state.
132 this.waitFor(function () {
139 this.waitFor(function () {
133 return this.evaluate(function () {
140 return this.evaluate(function () {
134 return IPython._status == 'busy';
141 return IPython._status == 'busy';
135 });
142 });
136 });
143 });
137 };
144 };
138
145
139 casper.wait_for_idle = function () {
146 casper.wait_for_idle = function () {
140 // Waits for the notebook to idle.
147 // Waits for the notebook to idle.
141 this.waitFor(function () {
148 this.waitFor(function () {
142 return this.evaluate(function () {
149 return this.evaluate(function () {
143 return IPython._status == 'idle';
150 return IPython._status == 'idle';
144 });
151 });
145 });
152 });
146 };
153 };
147
154
148 casper.wait_for_output = function (cell_num, out_num) {
155 casper.wait_for_output = function (cell_num, out_num) {
149 // wait for the nth output in a given cell
156 // wait for the nth output in a given cell
150 this.wait_for_idle();
157 this.wait_for_idle();
151 out_num = out_num || 0;
158 out_num = out_num || 0;
152 this.then(function() {
159 this.then(function() {
153 this.waitFor(function (c, o) {
160 this.waitFor(function (c, o) {
154 return this.evaluate(function get_output(c, o) {
161 return this.evaluate(function get_output(c, o) {
155 var cell = IPython.notebook.get_cell(c);
162 var cell = IPython.notebook.get_cell(c);
156 return cell.output_area.outputs.length > o;
163 return cell.output_area.outputs.length > o;
157 },
164 },
158 // pass parameter from the test suite js to the browser code js
165 // pass parameter from the test suite js to the browser code js
159 {c : cell_num, o : out_num});
166 {c : cell_num, o : out_num});
160 });
167 });
161 },
168 },
162 function then() { },
169 function then() { },
163 function timeout() {
170 function timeout() {
164 this.echo("wait_for_output timed out!");
171 this.echo("wait_for_output timed out!");
165 });
172 });
166 };
173 };
167
174
168 casper.wait_for_widget = function (widget_info) {
175 casper.wait_for_widget = function (widget_info) {
169 // wait for a widget msg que to reach 0
176 // wait for a widget msg que to reach 0
170 //
177 //
171 // Parameters
178 // Parameters
172 // ----------
179 // ----------
173 // widget_info : object
180 // widget_info : object
174 // Object which contains info related to the widget. The model_id property
181 // Object which contains info related to the widget. The model_id property
175 // is used to identify the widget.
182 // is used to identify the widget.
176 this.waitFor(function () {
183 this.waitFor(function () {
177 var pending = this.evaluate(function (m) {
184 var pending = this.evaluate(function (m) {
178 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
185 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
179 }, {m: widget_info.model_id});
186 }, {m: widget_info.model_id});
180
187
181 if (pending === 0) {
188 if (pending === 0) {
182 return true;
189 return true;
183 } else {
190 } else {
184 return false;
191 return false;
185 }
192 }
186 });
193 });
187 };
194 };
188
195
189 casper.get_output_cell = function (cell_num, out_num) {
196 casper.get_output_cell = function (cell_num, out_num) {
190 // return an output of a given cell
197 // return an output of a given cell
191 out_num = out_num || 0;
198 out_num = out_num || 0;
192 var result = casper.evaluate(function (c, o) {
199 var result = casper.evaluate(function (c, o) {
193 var cell = IPython.notebook.get_cell(c);
200 var cell = IPython.notebook.get_cell(c);
194 return cell.output_area.outputs[o];
201 return cell.output_area.outputs[o];
195 },
202 },
196 {c : cell_num, o : out_num});
203 {c : cell_num, o : out_num});
197 if (!result) {
204 if (!result) {
198 var num_outputs = casper.evaluate(function (c) {
205 var num_outputs = casper.evaluate(function (c) {
199 var cell = IPython.notebook.get_cell(c);
206 var cell = IPython.notebook.get_cell(c);
200 return cell.output_area.outputs.length;
207 return cell.output_area.outputs.length;
201 },
208 },
202 {c : cell_num});
209 {c : cell_num});
203 this.test.assertTrue(false,
210 this.test.assertTrue(false,
204 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
211 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
205 );
212 );
206 } else {
213 } else {
207 return result;
214 return result;
208 }
215 }
209 };
216 };
210
217
211 casper.get_cells_length = function () {
218 casper.get_cells_length = function () {
212 // return the number of cells in the notebook
219 // return the number of cells in the notebook
213 var result = casper.evaluate(function () {
220 var result = casper.evaluate(function () {
214 return IPython.notebook.get_cells().length;
221 return IPython.notebook.get_cells().length;
215 });
222 });
216 return result;
223 return result;
217 };
224 };
218
225
219 casper.set_cell_text = function(index, text){
226 casper.set_cell_text = function(index, text){
220 // Set the text content of a cell.
227 // Set the text content of a cell.
221 this.evaluate(function (index, text) {
228 this.evaluate(function (index, text) {
222 var cell = IPython.notebook.get_cell(index);
229 var cell = IPython.notebook.get_cell(index);
223 cell.set_text(text);
230 cell.set_text(text);
224 }, index, text);
231 }, index, text);
225 };
232 };
226
233
227 casper.get_cell_text = function(index){
234 casper.get_cell_text = function(index){
228 // Get the text content of a cell.
235 // Get the text content of a cell.
229 return this.evaluate(function (index) {
236 return this.evaluate(function (index) {
230 var cell = IPython.notebook.get_cell(index);
237 var cell = IPython.notebook.get_cell(index);
231 return cell.get_text();
238 return cell.get_text();
232 }, index);
239 }, index);
233 };
240 };
234
241
235 casper.insert_cell_at_bottom = function(cell_type){
242 casper.insert_cell_at_bottom = function(cell_type){
236 // Inserts a cell at the bottom of the notebook
243 // Inserts a cell at the bottom of the notebook
237 // Returns the new cell's index.
244 // Returns the new cell's index.
238 return this.evaluate(function (cell_type) {
245 return this.evaluate(function (cell_type) {
239 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
246 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
240 return IPython.notebook.find_cell_index(cell);
247 return IPython.notebook.find_cell_index(cell);
241 }, cell_type);
248 }, cell_type);
242 };
249 };
243
250
244 casper.append_cell = function(text, cell_type) {
251 casper.append_cell = function(text, cell_type) {
245 // Insert a cell at the bottom of the notebook and set the cells text.
252 // Insert a cell at the bottom of the notebook and set the cells text.
246 // Returns the new cell's index.
253 // Returns the new cell's index.
247 var index = this.insert_cell_at_bottom(cell_type);
254 var index = this.insert_cell_at_bottom(cell_type);
248 if (text !== undefined) {
255 if (text !== undefined) {
249 this.set_cell_text(index, text);
256 this.set_cell_text(index, text);
250 }
257 }
251 return index;
258 return index;
252 };
259 };
253
260
254 casper.execute_cell = function(index, expect_failure){
261 casper.execute_cell = function(index, expect_failure){
255 // Asynchronously executes a cell by index.
262 // Asynchronously executes a cell by index.
256 // Returns the cell's index.
263 // Returns the cell's index.
257
264
258 if (expect_failure === undefined) expect_failure = false;
265 if (expect_failure === undefined) expect_failure = false;
259 var that = this;
266 var that = this;
260 this.then(function(){
267 this.then(function(){
261 that.evaluate(function (index) {
268 that.evaluate(function (index) {
262 var cell = IPython.notebook.get_cell(index);
269 var cell = IPython.notebook.get_cell(index);
263 cell.execute();
270 cell.execute();
264 }, index);
271 }, index);
265 });
272 });
266 this.wait_for_idle();
273 this.wait_for_idle();
267
274
268 this.then(function () {
275 this.then(function () {
269 var error = that.evaluate(function (index) {
276 var error = that.evaluate(function (index) {
270 var cell = IPython.notebook.get_cell(index);
277 var cell = IPython.notebook.get_cell(index);
271 var outputs = cell.output_area.outputs;
278 var outputs = cell.output_area.outputs;
272 for (var i = 0; i < outputs.length; i++) {
279 for (var i = 0; i < outputs.length; i++) {
273 if (outputs[i].output_type == 'error') {
280 if (outputs[i].output_type == 'error') {
274 return outputs[i];
281 return outputs[i];
275 }
282 }
276 }
283 }
277 return false;
284 return false;
278 }, index);
285 }, index);
279 if (error === null) {
286 if (error === null) {
280 this.test.fail("Failed to check for error output");
287 this.test.fail("Failed to check for error output");
281 }
288 }
282 if (expect_failure && error === false) {
289 if (expect_failure && error === false) {
283 this.test.fail("Expected error while running cell");
290 this.test.fail("Expected error while running cell");
284 } else if (!expect_failure && error !== false) {
291 } else if (!expect_failure && error !== false) {
285 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
292 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
286 }
293 }
287 });
294 });
288 return index;
295 return index;
289 };
296 };
290
297
291 casper.execute_cell_then = function(index, then_callback, expect_failure) {
298 casper.execute_cell_then = function(index, then_callback, expect_failure) {
292 // Synchronously executes a cell by index.
299 // Synchronously executes a cell by index.
293 // Optionally accepts a then_callback parameter. then_callback will get called
300 // Optionally accepts a then_callback parameter. then_callback will get called
294 // when the cell has finished executing.
301 // when the cell has finished executing.
295 // Returns the cell's index.
302 // Returns the cell's index.
296 var return_val = this.execute_cell(index, expect_failure);
303 var return_val = this.execute_cell(index, expect_failure);
297
304
298 this.wait_for_idle();
305 this.wait_for_idle();
299
306
300 var that = this;
307 var that = this;
301 this.then(function(){
308 this.then(function(){
302 if (then_callback!==undefined) {
309 if (then_callback!==undefined) {
303 then_callback.apply(that, [index]);
310 then_callback.apply(that, [index]);
304 }
311 }
305 });
312 });
306
313
307 return return_val;
314 return return_val;
308 };
315 };
309
316
310 casper.cell_element_exists = function(index, selector){
317 casper.cell_element_exists = function(index, selector){
311 // Utility function that allows us to easily check if an element exists
318 // Utility function that allows us to easily check if an element exists
312 // within a cell. Uses JQuery selector to look for the element.
319 // within a cell. Uses JQuery selector to look for the element.
313 return casper.evaluate(function (index, selector) {
320 return casper.evaluate(function (index, selector) {
314 var $cell = IPython.notebook.get_cell(index).element;
321 var $cell = IPython.notebook.get_cell(index).element;
315 return $cell.find(selector).length > 0;
322 return $cell.find(selector).length > 0;
316 }, index, selector);
323 }, index, selector);
317 };
324 };
318
325
319 casper.cell_element_function = function(index, selector, function_name, function_args){
326 casper.cell_element_function = function(index, selector, function_name, function_args){
320 // Utility function that allows us to execute a jQuery function on an
327 // Utility function that allows us to execute a jQuery function on an
321 // element within a cell.
328 // element within a cell.
322 return casper.evaluate(function (index, selector, function_name, function_args) {
329 return casper.evaluate(function (index, selector, function_name, function_args) {
323 var $cell = IPython.notebook.get_cell(index).element;
330 var $cell = IPython.notebook.get_cell(index).element;
324 var $el = $cell.find(selector);
331 var $el = $cell.find(selector);
325 return $el[function_name].apply($el, function_args);
332 return $el[function_name].apply($el, function_args);
326 }, index, selector, function_name, function_args);
333 }, index, selector, function_name, function_args);
327 };
334 };
328
335
329 casper.validate_notebook_state = function(message, mode, cell_index) {
336 casper.validate_notebook_state = function(message, mode, cell_index) {
330 // Validate the entire dual mode state of the notebook. Make sure no more than
337 // Validate the entire dual mode state of the notebook. Make sure no more than
331 // one cell is selected, focused, in edit mode, etc...
338 // one cell is selected, focused, in edit mode, etc...
332
339
333 // General tests.
340 // General tests.
334 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
341 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
335 message + '; keyboard and notebook modes match');
342 message + '; keyboard and notebook modes match');
336 // Is the selected cell the only cell that is selected?
343 // Is the selected cell the only cell that is selected?
337 if (cell_index!==undefined) {
344 if (cell_index!==undefined) {
338 this.test.assert(this.is_only_cell_selected(cell_index),
345 this.test.assert(this.is_only_cell_selected(cell_index),
339 message + '; cell ' + cell_index + ' is the only cell selected');
346 message + '; cell ' + cell_index + ' is the only cell selected');
340 }
347 }
341
348
342 // Mode specific tests.
349 // Mode specific tests.
343 if (mode==='command') {
350 if (mode==='command') {
344 // Are the notebook and keyboard manager in command mode?
351 // Are the notebook and keyboard manager in command mode?
345 this.test.assertEquals(this.get_keyboard_mode(), 'command',
352 this.test.assertEquals(this.get_keyboard_mode(), 'command',
346 message + '; in command mode');
353 message + '; in command mode');
347 // Make sure there isn't a single cell in edit mode.
354 // Make sure there isn't a single cell in edit mode.
348 this.test.assert(this.is_only_cell_edit(null),
355 this.test.assert(this.is_only_cell_edit(null),
349 message + '; all cells in command mode');
356 message + '; all cells in command mode');
350 this.test.assert(this.is_cell_editor_focused(null),
357 this.test.assert(this.is_cell_editor_focused(null),
351 message + '; no cell editors are focused while in command mode');
358 message + '; no cell editors are focused while in command mode');
352
359
353 } else if (mode==='edit') {
360 } else if (mode==='edit') {
354 // Are the notebook and keyboard manager in edit mode?
361 // Are the notebook and keyboard manager in edit mode?
355 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
362 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
356 message + '; in edit mode');
363 message + '; in edit mode');
357 if (cell_index!==undefined) {
364 if (cell_index!==undefined) {
358 // Is the specified cell the only cell in edit mode?
365 // Is the specified cell the only cell in edit mode?
359 this.test.assert(this.is_only_cell_edit(cell_index),
366 this.test.assert(this.is_only_cell_edit(cell_index),
360 message + '; cell ' + cell_index + ' is the only cell in edit mode');
367 message + '; cell ' + cell_index + ' is the only cell in edit mode');
361 // Is the specified cell the only cell with a focused code mirror?
368 // Is the specified cell the only cell with a focused code mirror?
362 this.test.assert(this.is_cell_editor_focused(cell_index),
369 this.test.assert(this.is_cell_editor_focused(cell_index),
363 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
370 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
364 }
371 }
365
372
366 } else {
373 } else {
367 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
374 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
368 }
375 }
369 };
376 };
370
377
371 casper.select_cell = function(index) {
378 casper.select_cell = function(index) {
372 // Select a cell in the notebook.
379 // Select a cell in the notebook.
373 this.evaluate(function (i) {
380 this.evaluate(function (i) {
374 IPython.notebook.select(i);
381 IPython.notebook.select(i);
375 }, {i: index});
382 }, {i: index});
376 };
383 };
377
384
378 casper.click_cell_editor = function(index) {
385 casper.click_cell_editor = function(index) {
379 // Emulate a click on a cell's editor.
386 // Emulate a click on a cell's editor.
380
387
381 // Code Mirror does not play nicely with emulated brower events.
388 // Code Mirror does not play nicely with emulated brower events.
382 // Instead of trying to emulate a click, here we run code similar to
389 // Instead of trying to emulate a click, here we run code similar to
383 // the code used in Code Mirror that handles the mousedown event on a
390 // the code used in Code Mirror that handles the mousedown event on a
384 // region of codemirror that the user can focus.
391 // region of codemirror that the user can focus.
385 this.evaluate(function (i) {
392 this.evaluate(function (i) {
386 var cm = IPython.notebook.get_cell(i).code_mirror;
393 var cm = IPython.notebook.get_cell(i).code_mirror;
387 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
394 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
388 cm.display.input.focus();
395 cm.display.input.focus();
389 }, {i: index});
396 }, {i: index});
390 };
397 };
391
398
392 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
399 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
393 // Set the Code Mirror instance cursor's location.
400 // Set the Code Mirror instance cursor's location.
394 this.evaluate(function (i, l, c) {
401 this.evaluate(function (i, l, c) {
395 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
402 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
396 }, {i: index, l: line_index, c: char_index});
403 }, {i: index, l: line_index, c: char_index});
397 };
404 };
398
405
399 casper.focus_notebook = function() {
406 casper.focus_notebook = function() {
400 // Focus the notebook div.
407 // Focus the notebook div.
401 this.evaluate(function (){
408 this.evaluate(function (){
402 $('#notebook').focus();
409 $('#notebook').focus();
403 }, {});
410 }, {});
404 };
411 };
405
412
406 casper.trigger_keydown = function() {
413 casper.trigger_keydown = function() {
407 // Emulate a keydown in the notebook.
414 // Emulate a keydown in the notebook.
408 for (var i = 0; i < arguments.length; i++) {
415 for (var i = 0; i < arguments.length; i++) {
409 this.evaluate(function (k) {
416 this.evaluate(function (k) {
410 var element = $(document);
417 var element = $(document);
411 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
418 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
412 element.trigger(event);
419 element.trigger(event);
413 }, {k: arguments[i]});
420 }, {k: arguments[i]});
414 }
421 }
415 };
422 };
416
423
417 casper.get_keyboard_mode = function() {
424 casper.get_keyboard_mode = function() {
418 // Get the mode of the keyboard manager.
425 // Get the mode of the keyboard manager.
419 return this.evaluate(function() {
426 return this.evaluate(function() {
420 return IPython.keyboard_manager.mode;
427 return IPython.keyboard_manager.mode;
421 }, {});
428 }, {});
422 };
429 };
423
430
424 casper.get_notebook_mode = function() {
431 casper.get_notebook_mode = function() {
425 // Get the mode of the notebook.
432 // Get the mode of the notebook.
426 return this.evaluate(function() {
433 return this.evaluate(function() {
427 return IPython.notebook.mode;
434 return IPython.notebook.mode;
428 }, {});
435 }, {});
429 };
436 };
430
437
431 casper.get_cell = function(index) {
438 casper.get_cell = function(index) {
432 // Get a single cell.
439 // Get a single cell.
433 //
440 //
434 // Note: Handles to DOM elements stored in the cell will be useless once in
441 // Note: Handles to DOM elements stored in the cell will be useless once in
435 // CasperJS context.
442 // CasperJS context.
436 return this.evaluate(function(i) {
443 return this.evaluate(function(i) {
437 var cell = IPython.notebook.get_cell(i);
444 var cell = IPython.notebook.get_cell(i);
438 if (cell) {
445 if (cell) {
439 return cell;
446 return cell;
440 }
447 }
441 return null;
448 return null;
442 }, {i : index});
449 }, {i : index});
443 };
450 };
444
451
445 casper.is_cell_editor_focused = function(index) {
452 casper.is_cell_editor_focused = function(index) {
446 // Make sure a cell's editor is the only editor focused on the page.
453 // Make sure a cell's editor is the only editor focused on the page.
447 return this.evaluate(function(i) {
454 return this.evaluate(function(i) {
448 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
455 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
449 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
456 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
450 if (i === null) {
457 if (i === null) {
451 return focused_textarea.length === 0;
458 return focused_textarea.length === 0;
452 } else {
459 } else {
453 var cell = IPython.notebook.get_cell(i);
460 var cell = IPython.notebook.get_cell(i);
454 if (cell) {
461 if (cell) {
455 return cell.code_mirror.getInputField() == focused_textarea[0];
462 return cell.code_mirror.getInputField() == focused_textarea[0];
456 }
463 }
457 }
464 }
458 return false;
465 return false;
459 }, {i : index});
466 }, {i : index});
460 };
467 };
461
468
462 casper.is_only_cell_selected = function(index) {
469 casper.is_only_cell_selected = function(index) {
463 // Check if a cell is the only cell selected.
470 // Check if a cell is the only cell selected.
464 // Pass null as the index to check if no cells are selected.
471 // Pass null as the index to check if no cells are selected.
465 return this.is_only_cell_on(index, 'selected', 'unselected');
472 return this.is_only_cell_on(index, 'selected', 'unselected');
466 };
473 };
467
474
468 casper.is_only_cell_edit = function(index) {
475 casper.is_only_cell_edit = function(index) {
469 // Check if a cell is the only cell in edit mode.
476 // Check if a cell is the only cell in edit mode.
470 // Pass null as the index to check if all of the cells are in command mode.
477 // Pass null as the index to check if all of the cells are in command mode.
471 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
478 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
472 };
479 };
473
480
474 casper.is_only_cell_on = function(i, on_class, off_class) {
481 casper.is_only_cell_on = function(i, on_class, off_class) {
475 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
482 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
476 // All of the other cells are checked for the `off_class` DOM class.
483 // All of the other cells are checked for the `off_class` DOM class.
477 // Pass null as the index to check if all of the cells have the `off_class`.
484 // Pass null as the index to check if all of the cells have the `off_class`.
478 var cells_length = this.get_cells_length();
485 var cells_length = this.get_cells_length();
479 for (var j = 0; j < cells_length; j++) {
486 for (var j = 0; j < cells_length; j++) {
480 if (j === i) {
487 if (j === i) {
481 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
488 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
482 return false;
489 return false;
483 }
490 }
484 } else {
491 } else {
485 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
492 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
486 return false;
493 return false;
487 }
494 }
488 }
495 }
489 }
496 }
490 return true;
497 return true;
491 };
498 };
492
499
493 casper.cell_has_class = function(index, classes) {
500 casper.cell_has_class = function(index, classes) {
494 // Check if a cell has a class.
501 // Check if a cell has a class.
495 return this.evaluate(function(i, c) {
502 return this.evaluate(function(i, c) {
496 var cell = IPython.notebook.get_cell(i);
503 var cell = IPython.notebook.get_cell(i);
497 if (cell) {
504 if (cell) {
498 return cell.element.hasClass(c);
505 return cell.element.hasClass(c);
499 }
506 }
500 return false;
507 return false;
501 }, {i : index, c: classes});
508 }, {i : index, c: classes});
502 };
509 };
503
510
504 casper.is_cell_rendered = function (index) {
511 casper.is_cell_rendered = function (index) {
505 return this.evaluate(function(i) {
512 return this.evaluate(function(i) {
506 return !!IPython.notebook.get_cell(i).rendered;
513 return !!IPython.notebook.get_cell(i).rendered;
507 }, {i:index});
514 }, {i:index});
508 };
515 };
509
516
510 casper.assert_colors_equal = function (hex_color, local_color, msg) {
517 casper.assert_colors_equal = function (hex_color, local_color, msg) {
511 // Tests to see if two colors are equal.
518 // Tests to see if two colors are equal.
512 //
519 //
513 // Parameters
520 // Parameters
514 // hex_color: string
521 // hex_color: string
515 // Hexadecimal color code, with or without preceeding hash character.
522 // Hexadecimal color code, with or without preceeding hash character.
516 // local_color: string
523 // local_color: string
517 // Local color representation. Can either be hexadecimal (default for
524 // Local color representation. Can either be hexadecimal (default for
518 // phantom) or rgb (default for slimer).
525 // phantom) or rgb (default for slimer).
519
526
520 // Remove parentheses, hashes, semi-colons, and space characters.
527 // Remove parentheses, hashes, semi-colons, and space characters.
521 hex_color = hex_color.replace(/[\(\); #]/, '');
528 hex_color = hex_color.replace(/[\(\); #]/, '');
522 local_color = local_color.replace(/[\(\); #]/, '');
529 local_color = local_color.replace(/[\(\); #]/, '');
523
530
524 // If the local color is rgb, clean it up and replace
531 // If the local color is rgb, clean it up and replace
525 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
532 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
526 components = local_color.substr(3).split(',');
533 components = local_color.substr(3).split(',');
527 local_color = '';
534 local_color = '';
528 for (var i = 0; i < components.length; i++) {
535 for (var i = 0; i < components.length; i++) {
529 var part = parseInt(components[i]).toString(16);
536 var part = parseInt(components[i]).toString(16);
530 while (part.length < 2) part = '0' + part;
537 while (part.length < 2) part = '0' + part;
531 local_color += part;
538 local_color += part;
532 }
539 }
533 }
540 }
534
541
535 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
542 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
536 };
543 };
537
544
538 casper.notebook_test = function(test) {
545 casper.notebook_test = function(test) {
539 // Wrap a notebook test to reduce boilerplate.
546 // Wrap a notebook test to reduce boilerplate.
540 this.open_new_notebook();
547 this.open_new_notebook();
541
548
542 // Echo whether or not we are running this test using SlimerJS
549 // Echo whether or not we are running this test using SlimerJS
543 if (this.evaluate(function(){
550 if (this.evaluate(function(){
544 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
551 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
545 })) {
552 })) {
546 console.log('This test is running in SlimerJS.');
553 console.log('This test is running in SlimerJS.');
547 this.slimerjs = true;
554 this.slimerjs = true;
548 }
555 }
549
556
550 // Make sure to remove the onbeforeunload callback. This callback is
557 // Make sure to remove the onbeforeunload callback. This callback is
551 // responsible for the "Are you sure you want to quit?" type messages.
558 // responsible for the "Are you sure you want to quit?" type messages.
552 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
559 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
553 this.then(function(){
560 this.then(function(){
554 this.evaluate(function(){
561 this.evaluate(function(){
555 window.onbeforeunload = function(){};
562 window.onbeforeunload = function(){};
556 });
563 });
557 });
564 });
558
565
559 this.then(test);
566 this.then(test);
560
567
561 // Kill the kernel and delete the notebook.
568 // Kill the kernel and delete the notebook.
562 this.shutdown_current_kernel();
569 this.shutdown_current_kernel();
563 // This is still broken but shouldn't be a problem for now.
570 // This is still broken but shouldn't be a problem for now.
564 // this.delete_current_notebook();
571 // this.delete_current_notebook();
565
572
566 // This is required to clean up the page we just finished with. If we don't call this
573 // This is required to clean up the page we just finished with. If we don't call this
567 // casperjs will leak file descriptors of all the open WebSockets in that page. We
574 // casperjs will leak file descriptors of all the open WebSockets in that page. We
568 // have to set this.page=null so that next time casper.start runs, it will create a
575 // have to set this.page=null so that next time casper.start runs, it will create a
569 // new page from scratch.
576 // new page from scratch.
570 this.then(function () {
577 this.then(function () {
571 this.page.close();
578 this.page.close();
572 this.page = null;
579 this.page = null;
573 });
580 });
574
581
575 // Run the browser automation.
582 // Run the browser automation.
576 this.run(function() {
583 this.run(function() {
577 this.test.done();
584 this.test.done();
578 });
585 });
579 };
586 };
580
587
581 casper.wait_for_dashboard = function () {
588 casper.wait_for_dashboard = function () {
582 // Wait for the dashboard list to load.
589 // Wait for the dashboard list to load.
583 casper.waitForSelector('.list_item');
590 casper.waitForSelector('.list_item');
584 };
591 };
585
592
586 casper.open_dashboard = function () {
593 casper.open_dashboard = function () {
587 // Start casper by opening the dashboard page.
594 // Start casper by opening the dashboard page.
588 var baseUrl = this.get_notebook_server();
595 var baseUrl = this.get_notebook_server();
589 this.start(baseUrl);
596 this.start(baseUrl);
590 this.waitFor(this.page_loaded);
597 this.waitFor(this.page_loaded);
591 this.wait_for_dashboard();
598 this.wait_for_dashboard();
592 };
599 };
593
600
594 casper.dashboard_test = function (test) {
601 casper.dashboard_test = function (test) {
595 // Open the dashboard page and run a test.
602 // Open the dashboard page and run a test.
596 this.open_dashboard();
603 this.open_dashboard();
597 this.then(test);
604 this.then(test);
598
605
599 this.then(function () {
606 this.then(function () {
600 this.page.close();
607 this.page.close();
601 this.page = null;
608 this.page = null;
602 });
609 });
603
610
604 // Run the browser automation.
611 // Run the browser automation.
605 this.run(function() {
612 this.run(function() {
606 this.test.done();
613 this.test.done();
607 });
614 });
608 };
615 };
609
616
610 // note that this will only work for UNIQUE events -- if you want to
617 // note that this will only work for UNIQUE events -- if you want to
611 // listen for the same event twice, this will not work!
618 // listen for the same event twice, this will not work!
612 casper.event_test = function (name, events, action, timeout) {
619 casper.event_test = function (name, events, action, timeout) {
613
620
614 // set up handlers to listen for each of the events
621 // set up handlers to listen for each of the events
615 this.thenEvaluate(function (events) {
622 this.thenEvaluate(function (events) {
616 var make_handler = function (event) {
623 var make_handler = function (event) {
617 return function () {
624 return function () {
618 IPython._events_triggered.push(event);
625 IPython._events_triggered.push(event);
619 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
626 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
620 delete IPython._event_handlers[event];
627 delete IPython._event_handlers[event];
621 };
628 };
622 };
629 };
623 IPython._event_handlers = {};
630 IPython._event_handlers = {};
624 IPython._events_triggered = [];
631 IPython._events_triggered = [];
625 for (var i=0; i < events.length; i++) {
632 for (var i=0; i < events.length; i++) {
626 IPython._event_handlers[events[i]] = make_handler(events[i]);
633 IPython._event_handlers[events[i]] = make_handler(events[i]);
627 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
634 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
628 }
635 }
629 }, [events]);
636 }, [events]);
630
637
631 // execute the requested action
638 // execute the requested action
632 this.then(action);
639 this.then(action);
633
640
634 // wait for all the events to be triggered
641 // wait for all the events to be triggered
635 this.waitFor(function () {
642 this.waitFor(function () {
636 return this.evaluate(function (events) {
643 return this.evaluate(function (events) {
637 return IPython._events_triggered.length >= events.length;
644 return IPython._events_triggered.length >= events.length;
638 }, [events]);
645 }, [events]);
639 }, undefined, undefined, timeout);
646 }, undefined, undefined, timeout);
640
647
641 // test that the events were triggered in the proper order
648 // test that the events were triggered in the proper order
642 this.then(function () {
649 this.then(function () {
643 var triggered = this.evaluate(function () {
650 var triggered = this.evaluate(function () {
644 return IPython._events_triggered;
651 return IPython._events_triggered;
645 });
652 });
646 var handlers = this.evaluate(function () {
653 var handlers = this.evaluate(function () {
647 return Object.keys(IPython._event_handlers);
654 return Object.keys(IPython._event_handlers);
648 });
655 });
649 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
656 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
650 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
657 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
651 for (var i=0; i < events.length; i++) {
658 for (var i=0; i < events.length; i++) {
652 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
659 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
653 }
660 }
654 });
661 });
655
662
656 // turn off any remaining event listeners
663 // turn off any remaining event listeners
657 this.thenEvaluate(function () {
664 this.thenEvaluate(function () {
658 for (var event in IPython._event_handlers) {
665 for (var event in IPython._event_handlers) {
659 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
666 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
660 delete IPython._event_handlers[event];
667 delete IPython._event_handlers[event];
661 }
668 }
662 });
669 });
663 };
670 };
664
671
665 casper.options.waitTimeout=10000;
672 casper.options.waitTimeout=10000;
666 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
673 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
667 this.echo("Timeout for " + casper.get_notebook_server());
674 this.echo("Timeout for " + casper.get_notebook_server());
668 this.echo("Is the notebook server running?");
675 this.echo("Is the notebook server running?");
669 });
676 });
670
677
671 casper.print_log = function () {
678 casper.print_log = function () {
672 // Pass `console.log` calls from page JS to casper.
679 // Pass `console.log` calls from page JS to casper.
673 this.on('remote.message', function(msg) {
680 this.on('remote.message', function(msg) {
674 this.echo('Remote message caught: ' + msg);
681 this.echo('Remote message caught: ' + msg);
675 });
682 });
676 };
683 };
677
684
678 casper.on("page.error", function onError(msg, trace) {
685 casper.on("page.error", function onError(msg, trace) {
679 // show errors in the browser
686 // show errors in the browser
680 this.echo("Page Error");
687 this.echo("Page Error");
681 this.echo(" Message: " + msg.split('\n').join('\n '));
688 this.echo(" Message: " + msg.split('\n').join('\n '));
682 this.echo(" Call stack:");
689 this.echo(" Call stack:");
683 var local_path = this.get_notebook_server();
690 var local_path = this.get_notebook_server();
684 for (var i = 0; i < trace.length; i++) {
691 for (var i = 0; i < trace.length; i++) {
685 var frame = trace[i];
692 var frame = trace[i];
686 var file = frame.file;
693 var file = frame.file;
687 // shorten common phantomjs evaluate url
694 // shorten common phantomjs evaluate url
688 // this will have a different value on slimerjs
695 // this will have a different value on slimerjs
689 if (file === "phantomjs://webpage.evaluate()") {
696 if (file === "phantomjs://webpage.evaluate()") {
690 file = "evaluate";
697 file = "evaluate";
691 }
698 }
692 // remove the version tag from the path
699 // remove the version tag from the path
693 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
700 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
694 // remove the local address from the beginning of the path
701 // remove the local address from the beginning of the path
695 if (file.indexOf(local_path) === 0) {
702 if (file.indexOf(local_path) === 0) {
696 file = file.substr(local_path.length);
703 file = file.substr(local_path.length);
697 }
704 }
698 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
705 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
699 this.echo(" line " + frame.line + " of " + file + frame_text);
706 this.echo(" line " + frame.line + " of " + file + frame_text);
700 }
707 }
701 });
708 });
702
709
703
710
704 casper.capture_log = function () {
711 casper.capture_log = function () {
705 // show captured errors
712 // show captured errors
706 var captured_log = [];
713 var captured_log = [];
707 var seen_errors = 0;
714 var seen_errors = 0;
708 this.on('remote.message', function(msg) {
715 this.on('remote.message', function(msg) {
709 captured_log.push(msg);
716 captured_log.push(msg);
710 });
717 });
711
718
712 var that = this;
719 var that = this;
713 this.test.on("test.done", function (result) {
720 this.test.on("test.done", function (result) {
714 // test.done runs per-file,
721 // test.done runs per-file,
715 // but suiteResults is per-suite (directory)
722 // but suiteResults is per-suite (directory)
716 var current_errors;
723 var current_errors;
717 if (this.suiteResults) {
724 if (this.suiteResults) {
718 // casper 1.1 has suiteResults
725 // casper 1.1 has suiteResults
719 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
726 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
720 } else {
727 } else {
721 // casper 1.0 has testResults instead
728 // casper 1.0 has testResults instead
722 current_errors = this.testResults.failed;
729 current_errors = this.testResults.failed;
723 }
730 }
724
731
725 if (current_errors > seen_errors && captured_log.length > 0) {
732 if (current_errors > seen_errors && captured_log.length > 0) {
726 casper.echo("\nCaptured console.log:");
733 casper.echo("\nCaptured console.log:");
727 for (var i = 0; i < captured_log.length; i++) {
734 for (var i = 0; i < captured_log.length; i++) {
728 var output = String(captured_log[i]).split('\n');
735 var output = String(captured_log[i]).split('\n');
729 for (var j = 0; j < output.length; j++) {
736 for (var j = 0; j < output.length; j++) {
730 casper.echo(" " + output[j]);
737 casper.echo(" " + output[j]);
731 }
738 }
732 }
739 }
733 }
740 }
734
741
735 seen_errors = current_errors;
742 seen_errors = current_errors;
736 captured_log = [];
743 captured_log = [];
737 });
744 });
738 };
745 };
739
746
740 casper.capture_log();
747 casper.capture_log();
@@ -1,754 +1,755
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
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 import errno
17 import errno
18 import os
18 import os
19 import sys
19 import sys
20
20
21 from distutils import log
21 from distutils import log
22 from distutils.command.build_py import build_py
22 from distutils.command.build_py import build_py
23 from distutils.command.build_scripts import build_scripts
23 from distutils.command.build_scripts import build_scripts
24 from distutils.command.install import install
24 from distutils.command.install import install
25 from distutils.command.install_scripts import install_scripts
25 from distutils.command.install_scripts import install_scripts
26 from distutils.cmd import Command
26 from distutils.cmd import Command
27 from fnmatch import fnmatch
27 from fnmatch import fnmatch
28 from glob import glob
28 from glob import glob
29 from subprocess import check_call
29 from subprocess import check_call
30
30
31 from setupext import install_data_ext
31 from setupext import install_data_ext
32
32
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34 # Useful globals and utility functions
34 # Useful globals and utility functions
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 # A few handy globals
37 # A few handy globals
38 isfile = os.path.isfile
38 isfile = os.path.isfile
39 pjoin = os.path.join
39 pjoin = os.path.join
40 repo_root = os.path.dirname(os.path.abspath(__file__))
40 repo_root = os.path.dirname(os.path.abspath(__file__))
41
41
42 def oscmd(s):
42 def oscmd(s):
43 print(">", s)
43 print(">", s)
44 os.system(s)
44 os.system(s)
45
45
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 # the full py3compat machinery.
47 # the full py3compat machinery.
48
48
49 try:
49 try:
50 execfile
50 execfile
51 except NameError:
51 except NameError:
52 def execfile(fname, globs, locs=None):
52 def execfile(fname, globs, locs=None):
53 locs = locs or globs
53 locs = locs or globs
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55
55
56 # A little utility we'll need below, since glob() does NOT allow you to do
56 # A little utility we'll need below, since glob() does NOT allow you to do
57 # exclusion on multiple endings!
57 # exclusion on multiple endings!
58 def file_doesnt_endwith(test,endings):
58 def file_doesnt_endwith(test,endings):
59 """Return true if test is a file and its name does NOT end with any
59 """Return true if test is a file and its name does NOT end with any
60 of the strings listed in endings."""
60 of the strings listed in endings."""
61 if not isfile(test):
61 if not isfile(test):
62 return False
62 return False
63 for e in endings:
63 for e in endings:
64 if test.endswith(e):
64 if test.endswith(e):
65 return False
65 return False
66 return True
66 return True
67
67
68 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
69 # Basic project information
69 # Basic project information
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71
71
72 # release.py contains version, authors, license, url, keywords, etc.
72 # release.py contains version, authors, license, url, keywords, etc.
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74
74
75 # Create a dict with the basic information
75 # Create a dict with the basic information
76 # This dict is eventually passed to setup after additional keys are added.
76 # This dict is eventually passed to setup after additional keys are added.
77 setup_args = dict(
77 setup_args = dict(
78 name = name,
78 name = name,
79 version = version,
79 version = version,
80 description = description,
80 description = description,
81 long_description = long_description,
81 long_description = long_description,
82 author = author,
82 author = author,
83 author_email = author_email,
83 author_email = author_email,
84 url = url,
84 url = url,
85 download_url = download_url,
85 download_url = download_url,
86 license = license,
86 license = license,
87 platforms = platforms,
87 platforms = platforms,
88 keywords = keywords,
88 keywords = keywords,
89 classifiers = classifiers,
89 classifiers = classifiers,
90 cmdclass = {'install_data': install_data_ext},
90 cmdclass = {'install_data': install_data_ext},
91 )
91 )
92
92
93
93
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95 # Find packages
95 # Find packages
96 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
97
97
98 def find_packages():
98 def find_packages():
99 """
99 """
100 Find all of IPython's packages.
100 Find all of IPython's packages.
101 """
101 """
102 excludes = ['deathrow', 'quarantine']
102 excludes = ['deathrow', 'quarantine']
103 packages = []
103 packages = []
104 for dir,subdirs,files in os.walk('IPython'):
104 for dir,subdirs,files in os.walk('IPython'):
105 package = dir.replace(os.path.sep, '.')
105 package = dir.replace(os.path.sep, '.')
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 # package is to be excluded (e.g. deathrow)
107 # package is to be excluded (e.g. deathrow)
108 continue
108 continue
109 if '__init__.py' not in files:
109 if '__init__.py' not in files:
110 # not a package
110 # not a package
111 continue
111 continue
112 packages.append(package)
112 packages.append(package)
113 return packages
113 return packages
114
114
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116 # Find package data
116 # Find package data
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118
118
119 def find_package_data():
119 def find_package_data():
120 """
120 """
121 Find IPython's package_data.
121 Find IPython's package_data.
122 """
122 """
123 # This is not enough for these things to appear in an sdist.
123 # This is not enough for these things to appear in an sdist.
124 # We need to muck with the MANIFEST to get this to work
124 # We need to muck with the MANIFEST to get this to work
125
125
126 # exclude components and less from the walk;
126 # exclude components and less from the walk;
127 # we will build the components separately
127 # we will build the components separately
128 excludes = [
128 excludes = [
129 pjoin('static', 'components'),
129 pjoin('static', 'components'),
130 pjoin('static', '*', 'less'),
130 pjoin('static', '*', 'less'),
131 ]
131 ]
132
132
133 # walk notebook resources:
133 # walk notebook resources:
134 cwd = os.getcwd()
134 cwd = os.getcwd()
135 os.chdir(os.path.join('IPython', 'html'))
135 os.chdir(os.path.join('IPython', 'html'))
136 static_data = []
136 static_data = []
137 for parent, dirs, files in os.walk('static'):
137 for parent, dirs, files in os.walk('static'):
138 if any(fnmatch(parent, pat) for pat in excludes):
138 if any(fnmatch(parent, pat) for pat in excludes):
139 # prevent descending into subdirs
139 # prevent descending into subdirs
140 dirs[:] = []
140 dirs[:] = []
141 continue
141 continue
142 for f in files:
142 for f in files:
143 static_data.append(pjoin(parent, f))
143 static_data.append(pjoin(parent, f))
144
144
145 components = pjoin("static", "components")
145 components = pjoin("static", "components")
146 # select the components we actually need to install
146 # select the components we actually need to install
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
148 static_data.extend([
148 static_data.extend([
149 pjoin(components, "backbone", "backbone-min.js"),
149 pjoin(components, "backbone", "backbone-min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 pjoin(components, "es6-promise", "*.js"),
153 pjoin(components, "es6-promise", "*.js"),
154 pjoin(components, "font-awesome", "fonts", "*.*"),
154 pjoin(components, "font-awesome", "fonts", "*.*"),
155 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
155 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
156 pjoin(components, "jquery", "jquery.min.js"),
156 pjoin(components, "jquery", "jquery.min.js"),
157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 pjoin(components, "marked", "lib", "marked.js"),
160 pjoin(components, "marked", "lib", "marked.js"),
161 pjoin(components, "requirejs", "require.js"),
161 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "rsvp", "rsvp.js"),
162 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "min", "moment.min.js"),
165 pjoin(components, "moment", "min", "moment.min.js"),
165 pjoin(components, "term.js", "src", "term.js"),
166 pjoin(components, "term.js", "src", "term.js"),
166 pjoin(components, "text-encoding", "lib", "encoding.js"),
167 pjoin(components, "text-encoding", "lib", "encoding.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 [
194 [
194 'tests/files/*.*',
195 'tests/files/*.*',
195 'exporters/tests/files/*.*',
196 'exporters/tests/files/*.*',
196 'preprocessors/tests/files/*.*',
197 'preprocessors/tests/files/*.*',
197 ],
198 ],
198 'IPython.nbconvert.filters' : ['marked.js'],
199 'IPython.nbconvert.filters' : ['marked.js'],
199 'IPython.nbformat' : [
200 'IPython.nbformat' : [
200 'tests/*.ipynb',
201 'tests/*.ipynb',
201 'v3/nbformat.v3.schema.json',
202 'v3/nbformat.v3.schema.json',
202 'v4/nbformat.v4.schema.json',
203 'v4/nbformat.v4.schema.json',
203 ]
204 ]
204 }
205 }
205
206
206 return package_data
207 return package_data
207
208
208
209
209 def check_package_data(package_data):
210 def check_package_data(package_data):
210 """verify that package_data globs make sense"""
211 """verify that package_data globs make sense"""
211 print("checking package data")
212 print("checking package data")
212 for pkg, data in package_data.items():
213 for pkg, data in package_data.items():
213 pkg_root = pjoin(*pkg.split('.'))
214 pkg_root = pjoin(*pkg.split('.'))
214 for d in data:
215 for d in data:
215 path = pjoin(pkg_root, d)
216 path = pjoin(pkg_root, d)
216 if '*' in path:
217 if '*' in path:
217 assert len(glob(path)) > 0, "No files match pattern %s" % path
218 assert len(glob(path)) > 0, "No files match pattern %s" % path
218 else:
219 else:
219 assert os.path.exists(path), "Missing package data: %s" % path
220 assert os.path.exists(path), "Missing package data: %s" % path
220
221
221
222
222 def check_package_data_first(command):
223 def check_package_data_first(command):
223 """decorator for checking package_data before running a given command
224 """decorator for checking package_data before running a given command
224
225
225 Probably only needs to wrap build_py
226 Probably only needs to wrap build_py
226 """
227 """
227 class DecoratedCommand(command):
228 class DecoratedCommand(command):
228 def run(self):
229 def run(self):
229 check_package_data(self.package_data)
230 check_package_data(self.package_data)
230 command.run(self)
231 command.run(self)
231 return DecoratedCommand
232 return DecoratedCommand
232
233
233
234
234 #---------------------------------------------------------------------------
235 #---------------------------------------------------------------------------
235 # Find data files
236 # Find data files
236 #---------------------------------------------------------------------------
237 #---------------------------------------------------------------------------
237
238
238 def make_dir_struct(tag,base,out_base):
239 def make_dir_struct(tag,base,out_base):
239 """Make the directory structure of all files below a starting dir.
240 """Make the directory structure of all files below a starting dir.
240
241
241 This is just a convenience routine to help build a nested directory
242 This is just a convenience routine to help build a nested directory
242 hierarchy because distutils is too stupid to do this by itself.
243 hierarchy because distutils is too stupid to do this by itself.
243
244
244 XXX - this needs a proper docstring!
245 XXX - this needs a proper docstring!
245 """
246 """
246
247
247 # we'll use these a lot below
248 # we'll use these a lot below
248 lbase = len(base)
249 lbase = len(base)
249 pathsep = os.path.sep
250 pathsep = os.path.sep
250 lpathsep = len(pathsep)
251 lpathsep = len(pathsep)
251
252
252 out = []
253 out = []
253 for (dirpath,dirnames,filenames) in os.walk(base):
254 for (dirpath,dirnames,filenames) in os.walk(base):
254 # we need to strip out the dirpath from the base to map it to the
255 # we need to strip out the dirpath from the base to map it to the
255 # output (installation) path. This requires possibly stripping the
256 # output (installation) path. This requires possibly stripping the
256 # path separator, because otherwise pjoin will not work correctly
257 # path separator, because otherwise pjoin will not work correctly
257 # (pjoin('foo/','/bar') returns '/bar').
258 # (pjoin('foo/','/bar') returns '/bar').
258
259
259 dp_eff = dirpath[lbase:]
260 dp_eff = dirpath[lbase:]
260 if dp_eff.startswith(pathsep):
261 if dp_eff.startswith(pathsep):
261 dp_eff = dp_eff[lpathsep:]
262 dp_eff = dp_eff[lpathsep:]
262 # The output path must be anchored at the out_base marker
263 # The output path must be anchored at the out_base marker
263 out_path = pjoin(out_base,dp_eff)
264 out_path = pjoin(out_base,dp_eff)
264 # Now we can generate the final filenames. Since os.walk only produces
265 # Now we can generate the final filenames. Since os.walk only produces
265 # filenames, we must join back with the dirpath to get full valid file
266 # filenames, we must join back with the dirpath to get full valid file
266 # paths:
267 # paths:
267 pfiles = [pjoin(dirpath,f) for f in filenames]
268 pfiles = [pjoin(dirpath,f) for f in filenames]
268 # Finally, generate the entry we need, which is a pari of (output
269 # Finally, generate the entry we need, which is a pari of (output
269 # path, files) for use as a data_files parameter in install_data.
270 # path, files) for use as a data_files parameter in install_data.
270 out.append((out_path, pfiles))
271 out.append((out_path, pfiles))
271
272
272 return out
273 return out
273
274
274
275
275 def find_data_files():
276 def find_data_files():
276 """
277 """
277 Find IPython's data_files.
278 Find IPython's data_files.
278
279
279 Just man pages at this point.
280 Just man pages at this point.
280 """
281 """
281
282
282 manpagebase = pjoin('share', 'man', 'man1')
283 manpagebase = pjoin('share', 'man', 'man1')
283
284
284 # Simple file lists can be made by hand
285 # Simple file lists can be made by hand
285 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
286 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
286 if not manpages:
287 if not manpages:
287 # When running from a source tree, the manpages aren't gzipped
288 # When running from a source tree, the manpages aren't gzipped
288 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
289 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
289
290
290 # And assemble the entire output list
291 # And assemble the entire output list
291 data_files = [ (manpagebase, manpages) ]
292 data_files = [ (manpagebase, manpages) ]
292
293
293 return data_files
294 return data_files
294
295
295
296
296 def make_man_update_target(manpage):
297 def make_man_update_target(manpage):
297 """Return a target_update-compliant tuple for the given manpage.
298 """Return a target_update-compliant tuple for the given manpage.
298
299
299 Parameters
300 Parameters
300 ----------
301 ----------
301 manpage : string
302 manpage : string
302 Name of the manpage, must include the section number (trailing number).
303 Name of the manpage, must include the section number (trailing number).
303
304
304 Example
305 Example
305 -------
306 -------
306
307
307 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
308 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
308 ('docs/man/ipython.1.gz',
309 ('docs/man/ipython.1.gz',
309 ['docs/man/ipython.1'],
310 ['docs/man/ipython.1'],
310 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
311 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
311 """
312 """
312 man_dir = pjoin('docs', 'man')
313 man_dir = pjoin('docs', 'man')
313 manpage_gz = manpage + '.gz'
314 manpage_gz = manpage + '.gz'
314 manpath = pjoin(man_dir, manpage)
315 manpath = pjoin(man_dir, manpage)
315 manpath_gz = pjoin(man_dir, manpage_gz)
316 manpath_gz = pjoin(man_dir, manpage_gz)
316 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
317 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
317 locals() )
318 locals() )
318 return (manpath_gz, [manpath], gz_cmd)
319 return (manpath_gz, [manpath], gz_cmd)
319
320
320 # The two functions below are copied from IPython.utils.path, so we don't need
321 # The two functions below are copied from IPython.utils.path, so we don't need
321 # to import IPython during setup, which fails on Python 3.
322 # to import IPython during setup, which fails on Python 3.
322
323
323 def target_outdated(target,deps):
324 def target_outdated(target,deps):
324 """Determine whether a target is out of date.
325 """Determine whether a target is out of date.
325
326
326 target_outdated(target,deps) -> 1/0
327 target_outdated(target,deps) -> 1/0
327
328
328 deps: list of filenames which MUST exist.
329 deps: list of filenames which MUST exist.
329 target: single filename which may or may not exist.
330 target: single filename which may or may not exist.
330
331
331 If target doesn't exist or is older than any file listed in deps, return
332 If target doesn't exist or is older than any file listed in deps, return
332 true, otherwise return false.
333 true, otherwise return false.
333 """
334 """
334 try:
335 try:
335 target_time = os.path.getmtime(target)
336 target_time = os.path.getmtime(target)
336 except os.error:
337 except os.error:
337 return 1
338 return 1
338 for dep in deps:
339 for dep in deps:
339 dep_time = os.path.getmtime(dep)
340 dep_time = os.path.getmtime(dep)
340 if dep_time > target_time:
341 if dep_time > target_time:
341 #print "For target",target,"Dep failed:",dep # dbg
342 #print "For target",target,"Dep failed:",dep # dbg
342 #print "times (dep,tar):",dep_time,target_time # dbg
343 #print "times (dep,tar):",dep_time,target_time # dbg
343 return 1
344 return 1
344 return 0
345 return 0
345
346
346
347
347 def target_update(target,deps,cmd):
348 def target_update(target,deps,cmd):
348 """Update a target with a given command given a list of dependencies.
349 """Update a target with a given command given a list of dependencies.
349
350
350 target_update(target,deps,cmd) -> runs cmd if target is outdated.
351 target_update(target,deps,cmd) -> runs cmd if target is outdated.
351
352
352 This is just a wrapper around target_outdated() which calls the given
353 This is just a wrapper around target_outdated() which calls the given
353 command if target is outdated."""
354 command if target is outdated."""
354
355
355 if target_outdated(target,deps):
356 if target_outdated(target,deps):
356 os.system(cmd)
357 os.system(cmd)
357
358
358 #---------------------------------------------------------------------------
359 #---------------------------------------------------------------------------
359 # Find scripts
360 # Find scripts
360 #---------------------------------------------------------------------------
361 #---------------------------------------------------------------------------
361
362
362 def find_entry_points():
363 def find_entry_points():
363 """Defines the command line entry points for IPython
364 """Defines the command line entry points for IPython
364
365
365 This always uses setuptools-style entry points. When setuptools is not in
366 This always uses setuptools-style entry points. When setuptools is not in
366 use, our own build_scripts_entrypt class below parses these and builds
367 use, our own build_scripts_entrypt class below parses these and builds
367 command line scripts.
368 command line scripts.
368
369
369 Each of our entry points gets both a plain name, e.g. ipython, and one
370 Each of our entry points gets both a plain name, e.g. ipython, and one
370 suffixed with the Python major version number, e.g. ipython3.
371 suffixed with the Python major version number, e.g. ipython3.
371 """
372 """
372 ep = [
373 ep = [
373 'ipython%s = IPython:start_ipython',
374 'ipython%s = IPython:start_ipython',
374 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
375 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
375 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
376 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
376 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
377 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
377 'iptest%s = IPython.testing.iptestcontroller:main',
378 'iptest%s = IPython.testing.iptestcontroller:main',
378 ]
379 ]
379 suffix = str(sys.version_info[0])
380 suffix = str(sys.version_info[0])
380 return [e % '' for e in ep] + [e % suffix for e in ep]
381 return [e % '' for e in ep] + [e % suffix for e in ep]
381
382
382 script_src = """#!{executable}
383 script_src = """#!{executable}
383 # This script was automatically generated by setup.py
384 # This script was automatically generated by setup.py
384 if __name__ == '__main__':
385 if __name__ == '__main__':
385 from {mod} import {func}
386 from {mod} import {func}
386 {func}()
387 {func}()
387 """
388 """
388
389
389 class build_scripts_entrypt(build_scripts):
390 class build_scripts_entrypt(build_scripts):
390 """Build the command line scripts
391 """Build the command line scripts
391
392
392 Parse setuptools style entry points and write simple scripts to run the
393 Parse setuptools style entry points and write simple scripts to run the
393 target functions.
394 target functions.
394
395
395 On Windows, this also creates .cmd wrappers for the scripts so that you can
396 On Windows, this also creates .cmd wrappers for the scripts so that you can
396 easily launch them from a command line.
397 easily launch them from a command line.
397 """
398 """
398 def run(self):
399 def run(self):
399 self.mkpath(self.build_dir)
400 self.mkpath(self.build_dir)
400 outfiles = []
401 outfiles = []
401 for script in find_entry_points():
402 for script in find_entry_points():
402 name, entrypt = script.split('=')
403 name, entrypt = script.split('=')
403 name = name.strip()
404 name = name.strip()
404 entrypt = entrypt.strip()
405 entrypt = entrypt.strip()
405 outfile = os.path.join(self.build_dir, name)
406 outfile = os.path.join(self.build_dir, name)
406 outfiles.append(outfile)
407 outfiles.append(outfile)
407 print('Writing script to', outfile)
408 print('Writing script to', outfile)
408
409
409 mod, func = entrypt.split(':')
410 mod, func = entrypt.split(':')
410 with open(outfile, 'w') as f:
411 with open(outfile, 'w') as f:
411 f.write(script_src.format(executable=sys.executable,
412 f.write(script_src.format(executable=sys.executable,
412 mod=mod, func=func))
413 mod=mod, func=func))
413
414
414 if sys.platform == 'win32':
415 if sys.platform == 'win32':
415 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
416 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
416 # command line
417 # command line
417 cmd_file = os.path.join(self.build_dir, name + '.cmd')
418 cmd_file = os.path.join(self.build_dir, name + '.cmd')
418 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
419 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
419 python=sys.executable, script=name)
420 python=sys.executable, script=name)
420 log.info("Writing %s wrapper script" % cmd_file)
421 log.info("Writing %s wrapper script" % cmd_file)
421 with open(cmd_file, 'w') as f:
422 with open(cmd_file, 'w') as f:
422 f.write(cmd)
423 f.write(cmd)
423
424
424 return outfiles, outfiles
425 return outfiles, outfiles
425
426
426 class install_lib_symlink(Command):
427 class install_lib_symlink(Command):
427 user_options = [
428 user_options = [
428 ('install-dir=', 'd', "directory to install to"),
429 ('install-dir=', 'd', "directory to install to"),
429 ]
430 ]
430
431
431 def initialize_options(self):
432 def initialize_options(self):
432 self.install_dir = None
433 self.install_dir = None
433
434
434 def finalize_options(self):
435 def finalize_options(self):
435 self.set_undefined_options('symlink',
436 self.set_undefined_options('symlink',
436 ('install_lib', 'install_dir'),
437 ('install_lib', 'install_dir'),
437 )
438 )
438
439
439 def run(self):
440 def run(self):
440 if sys.platform == 'win32':
441 if sys.platform == 'win32':
441 raise Exception("This doesn't work on Windows.")
442 raise Exception("This doesn't work on Windows.")
442 pkg = os.path.join(os.getcwd(), 'IPython')
443 pkg = os.path.join(os.getcwd(), 'IPython')
443 dest = os.path.join(self.install_dir, 'IPython')
444 dest = os.path.join(self.install_dir, 'IPython')
444 if os.path.islink(dest):
445 if os.path.islink(dest):
445 print('removing existing symlink at %s' % dest)
446 print('removing existing symlink at %s' % dest)
446 os.unlink(dest)
447 os.unlink(dest)
447 print('symlinking %s -> %s' % (pkg, dest))
448 print('symlinking %s -> %s' % (pkg, dest))
448 os.symlink(pkg, dest)
449 os.symlink(pkg, dest)
449
450
450 class unsymlink(install):
451 class unsymlink(install):
451 def run(self):
452 def run(self):
452 dest = os.path.join(self.install_lib, 'IPython')
453 dest = os.path.join(self.install_lib, 'IPython')
453 if os.path.islink(dest):
454 if os.path.islink(dest):
454 print('removing symlink at %s' % dest)
455 print('removing symlink at %s' % dest)
455 os.unlink(dest)
456 os.unlink(dest)
456 else:
457 else:
457 print('No symlink exists at %s' % dest)
458 print('No symlink exists at %s' % dest)
458
459
459 class install_symlinked(install):
460 class install_symlinked(install):
460 def run(self):
461 def run(self):
461 if sys.platform == 'win32':
462 if sys.platform == 'win32':
462 raise Exception("This doesn't work on Windows.")
463 raise Exception("This doesn't work on Windows.")
463
464
464 # Run all sub-commands (at least those that need to be run)
465 # Run all sub-commands (at least those that need to be run)
465 for cmd_name in self.get_sub_commands():
466 for cmd_name in self.get_sub_commands():
466 self.run_command(cmd_name)
467 self.run_command(cmd_name)
467
468
468 # 'sub_commands': a list of commands this command might have to run to
469 # 'sub_commands': a list of commands this command might have to run to
469 # get its work done. See cmd.py for more info.
470 # get its work done. See cmd.py for more info.
470 sub_commands = [('install_lib_symlink', lambda self:True),
471 sub_commands = [('install_lib_symlink', lambda self:True),
471 ('install_scripts_sym', lambda self:True),
472 ('install_scripts_sym', lambda self:True),
472 ]
473 ]
473
474
474 class install_scripts_for_symlink(install_scripts):
475 class install_scripts_for_symlink(install_scripts):
475 """Redefined to get options from 'symlink' instead of 'install'.
476 """Redefined to get options from 'symlink' instead of 'install'.
476
477
477 I love distutils almost as much as I love setuptools.
478 I love distutils almost as much as I love setuptools.
478 """
479 """
479 def finalize_options(self):
480 def finalize_options(self):
480 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
481 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
481 self.set_undefined_options('symlink',
482 self.set_undefined_options('symlink',
482 ('install_scripts', 'install_dir'),
483 ('install_scripts', 'install_dir'),
483 ('force', 'force'),
484 ('force', 'force'),
484 ('skip_build', 'skip_build'),
485 ('skip_build', 'skip_build'),
485 )
486 )
486
487
487 #---------------------------------------------------------------------------
488 #---------------------------------------------------------------------------
488 # Verify all dependencies
489 # Verify all dependencies
489 #---------------------------------------------------------------------------
490 #---------------------------------------------------------------------------
490
491
491 def check_for_dependencies():
492 def check_for_dependencies():
492 """Check for IPython's dependencies.
493 """Check for IPython's dependencies.
493
494
494 This function should NOT be called if running under setuptools!
495 This function should NOT be called if running under setuptools!
495 """
496 """
496 from setupext.setupext import (
497 from setupext.setupext import (
497 print_line, print_raw, print_status,
498 print_line, print_raw, print_status,
498 check_for_sphinx, check_for_pygments,
499 check_for_sphinx, check_for_pygments,
499 check_for_nose, check_for_pexpect,
500 check_for_nose, check_for_pexpect,
500 check_for_pyzmq, check_for_readline,
501 check_for_pyzmq, check_for_readline,
501 check_for_jinja2, check_for_tornado
502 check_for_jinja2, check_for_tornado
502 )
503 )
503 print_line()
504 print_line()
504 print_raw("BUILDING IPYTHON")
505 print_raw("BUILDING IPYTHON")
505 print_status('python', sys.version)
506 print_status('python', sys.version)
506 print_status('platform', sys.platform)
507 print_status('platform', sys.platform)
507 if sys.platform == 'win32':
508 if sys.platform == 'win32':
508 print_status('Windows version', sys.getwindowsversion())
509 print_status('Windows version', sys.getwindowsversion())
509
510
510 print_raw("")
511 print_raw("")
511 print_raw("OPTIONAL DEPENDENCIES")
512 print_raw("OPTIONAL DEPENDENCIES")
512
513
513 check_for_sphinx()
514 check_for_sphinx()
514 check_for_pygments()
515 check_for_pygments()
515 check_for_nose()
516 check_for_nose()
516 if os.name == 'posix':
517 if os.name == 'posix':
517 check_for_pexpect()
518 check_for_pexpect()
518 check_for_pyzmq()
519 check_for_pyzmq()
519 check_for_tornado()
520 check_for_tornado()
520 check_for_readline()
521 check_for_readline()
521 check_for_jinja2()
522 check_for_jinja2()
522
523
523 #---------------------------------------------------------------------------
524 #---------------------------------------------------------------------------
524 # VCS related
525 # VCS related
525 #---------------------------------------------------------------------------
526 #---------------------------------------------------------------------------
526
527
527 # utils.submodule has checks for submodule status
528 # utils.submodule has checks for submodule status
528 execfile(pjoin('IPython','utils','submodule.py'), globals())
529 execfile(pjoin('IPython','utils','submodule.py'), globals())
529
530
530 class UpdateSubmodules(Command):
531 class UpdateSubmodules(Command):
531 """Update git submodules
532 """Update git submodules
532
533
533 IPython's external javascript dependencies live in a separate repo.
534 IPython's external javascript dependencies live in a separate repo.
534 """
535 """
535 description = "Update git submodules"
536 description = "Update git submodules"
536 user_options = []
537 user_options = []
537
538
538 def initialize_options(self):
539 def initialize_options(self):
539 pass
540 pass
540
541
541 def finalize_options(self):
542 def finalize_options(self):
542 pass
543 pass
543
544
544 def run(self):
545 def run(self):
545 failure = False
546 failure = False
546 try:
547 try:
547 self.spawn('git submodule init'.split())
548 self.spawn('git submodule init'.split())
548 self.spawn('git submodule update --recursive'.split())
549 self.spawn('git submodule update --recursive'.split())
549 except Exception as e:
550 except Exception as e:
550 failure = e
551 failure = e
551 print(e)
552 print(e)
552
553
553 if not check_submodule_status(repo_root) == 'clean':
554 if not check_submodule_status(repo_root) == 'clean':
554 print("submodules could not be checked out")
555 print("submodules could not be checked out")
555 sys.exit(1)
556 sys.exit(1)
556
557
557
558
558 def git_prebuild(pkg_dir, build_cmd=build_py):
559 def git_prebuild(pkg_dir, build_cmd=build_py):
559 """Return extended build or sdist command class for recording commit
560 """Return extended build or sdist command class for recording commit
560
561
561 records git commit in IPython.utils._sysinfo.commit
562 records git commit in IPython.utils._sysinfo.commit
562
563
563 for use in IPython.utils.sysinfo.sys_info() calls after installation.
564 for use in IPython.utils.sysinfo.sys_info() calls after installation.
564
565
565 Also ensures that submodules exist prior to running
566 Also ensures that submodules exist prior to running
566 """
567 """
567
568
568 class MyBuildPy(build_cmd):
569 class MyBuildPy(build_cmd):
569 ''' Subclass to write commit data into installation tree '''
570 ''' Subclass to write commit data into installation tree '''
570 def run(self):
571 def run(self):
571 build_cmd.run(self)
572 build_cmd.run(self)
572 # this one will only fire for build commands
573 # this one will only fire for build commands
573 if hasattr(self, 'build_lib'):
574 if hasattr(self, 'build_lib'):
574 self._record_commit(self.build_lib)
575 self._record_commit(self.build_lib)
575
576
576 def make_release_tree(self, base_dir, files):
577 def make_release_tree(self, base_dir, files):
577 # this one will fire for sdist
578 # this one will fire for sdist
578 build_cmd.make_release_tree(self, base_dir, files)
579 build_cmd.make_release_tree(self, base_dir, files)
579 self._record_commit(base_dir)
580 self._record_commit(base_dir)
580
581
581 def _record_commit(self, base_dir):
582 def _record_commit(self, base_dir):
582 import subprocess
583 import subprocess
583 proc = subprocess.Popen('git rev-parse --short HEAD',
584 proc = subprocess.Popen('git rev-parse --short HEAD',
584 stdout=subprocess.PIPE,
585 stdout=subprocess.PIPE,
585 stderr=subprocess.PIPE,
586 stderr=subprocess.PIPE,
586 shell=True)
587 shell=True)
587 repo_commit, _ = proc.communicate()
588 repo_commit, _ = proc.communicate()
588 repo_commit = repo_commit.strip().decode("ascii")
589 repo_commit = repo_commit.strip().decode("ascii")
589
590
590 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
591 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
591 if os.path.isfile(out_pth) and not repo_commit:
592 if os.path.isfile(out_pth) and not repo_commit:
592 # nothing to write, don't clobber
593 # nothing to write, don't clobber
593 return
594 return
594
595
595 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
596 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
596
597
597 # remove to avoid overwriting original via hard link
598 # remove to avoid overwriting original via hard link
598 try:
599 try:
599 os.remove(out_pth)
600 os.remove(out_pth)
600 except (IOError, OSError):
601 except (IOError, OSError):
601 pass
602 pass
602 with open(out_pth, 'w') as out_file:
603 with open(out_pth, 'w') as out_file:
603 out_file.writelines([
604 out_file.writelines([
604 '# GENERATED BY setup.py\n',
605 '# GENERATED BY setup.py\n',
605 'commit = u"%s"\n' % repo_commit,
606 'commit = u"%s"\n' % repo_commit,
606 ])
607 ])
607 return require_submodules(MyBuildPy)
608 return require_submodules(MyBuildPy)
608
609
609
610
610 def require_submodules(command):
611 def require_submodules(command):
611 """decorator for instructing a command to check for submodules before running"""
612 """decorator for instructing a command to check for submodules before running"""
612 class DecoratedCommand(command):
613 class DecoratedCommand(command):
613 def run(self):
614 def run(self):
614 if not check_submodule_status(repo_root) == 'clean':
615 if not check_submodule_status(repo_root) == 'clean':
615 print("submodules missing! Run `setup.py submodule` and try again")
616 print("submodules missing! Run `setup.py submodule` and try again")
616 sys.exit(1)
617 sys.exit(1)
617 command.run(self)
618 command.run(self)
618 return DecoratedCommand
619 return DecoratedCommand
619
620
620 #---------------------------------------------------------------------------
621 #---------------------------------------------------------------------------
621 # bdist related
622 # bdist related
622 #---------------------------------------------------------------------------
623 #---------------------------------------------------------------------------
623
624
624 def get_bdist_wheel():
625 def get_bdist_wheel():
625 """Construct bdist_wheel command for building wheels
626 """Construct bdist_wheel command for building wheels
626
627
627 Constructs py2-none-any tag, instead of py2.7-none-any
628 Constructs py2-none-any tag, instead of py2.7-none-any
628 """
629 """
629 class RequiresWheel(Command):
630 class RequiresWheel(Command):
630 description = "Dummy command for missing bdist_wheel"
631 description = "Dummy command for missing bdist_wheel"
631 user_options = []
632 user_options = []
632
633
633 def initialize_options(self):
634 def initialize_options(self):
634 pass
635 pass
635
636
636 def finalize_options(self):
637 def finalize_options(self):
637 pass
638 pass
638
639
639 def run(self):
640 def run(self):
640 print("bdist_wheel requires the wheel package")
641 print("bdist_wheel requires the wheel package")
641 sys.exit(1)
642 sys.exit(1)
642
643
643 if 'setuptools' not in sys.modules:
644 if 'setuptools' not in sys.modules:
644 return RequiresWheel
645 return RequiresWheel
645 else:
646 else:
646 try:
647 try:
647 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
648 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
648 except ImportError:
649 except ImportError:
649 return RequiresWheel
650 return RequiresWheel
650
651
651 class bdist_wheel_tag(bdist_wheel):
652 class bdist_wheel_tag(bdist_wheel):
652
653
653 def add_requirements(self, metadata_path):
654 def add_requirements(self, metadata_path):
654 """transform platform-dependent requirements"""
655 """transform platform-dependent requirements"""
655 pkg_info = read_pkg_info(metadata_path)
656 pkg_info = read_pkg_info(metadata_path)
656 # pkg_info is an email.Message object (?!)
657 # pkg_info is an email.Message object (?!)
657 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
658 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
658 # and transform them to conditionals
659 # and transform them to conditionals
659 requires = pkg_info.get_all('Requires-Dist')
660 requires = pkg_info.get_all('Requires-Dist')
660 del pkg_info['Requires-Dist']
661 del pkg_info['Requires-Dist']
661 def _remove_startswith(lis, prefix):
662 def _remove_startswith(lis, prefix):
662 """like list.remove, but with startswith instead of =="""
663 """like list.remove, but with startswith instead of =="""
663 found = False
664 found = False
664 for idx, item in enumerate(lis):
665 for idx, item in enumerate(lis):
665 if item.startswith(prefix):
666 if item.startswith(prefix):
666 found = True
667 found = True
667 break
668 break
668 if found:
669 if found:
669 lis.pop(idx)
670 lis.pop(idx)
670
671
671 for pkg in ("gnureadline", "pyreadline", "mock"):
672 for pkg in ("gnureadline", "pyreadline", "mock"):
672 _remove_startswith(requires, pkg)
673 _remove_startswith(requires, pkg)
673 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
674 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
674 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
675 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
675 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 requires.append("mock; extra == 'test' and python_version < '3.3'")
677 requires.append("mock; extra == 'test' and python_version < '3.3'")
677 for r in requires:
678 for r in requires:
678 pkg_info['Requires-Dist'] = r
679 pkg_info['Requires-Dist'] = r
679 write_pkg_info(metadata_path, pkg_info)
680 write_pkg_info(metadata_path, pkg_info)
680
681
681 return bdist_wheel_tag
682 return bdist_wheel_tag
682
683
683 #---------------------------------------------------------------------------
684 #---------------------------------------------------------------------------
684 # Notebook related
685 # Notebook related
685 #---------------------------------------------------------------------------
686 #---------------------------------------------------------------------------
686
687
687 class CompileCSS(Command):
688 class CompileCSS(Command):
688 """Recompile Notebook CSS
689 """Recompile Notebook CSS
689
690
690 Regenerate the compiled CSS from LESS sources.
691 Regenerate the compiled CSS from LESS sources.
691
692
692 Requires various dev dependencies, such as invoke and lessc.
693 Requires various dev dependencies, such as invoke and lessc.
693 """
694 """
694 description = "Recompile Notebook CSS"
695 description = "Recompile Notebook CSS"
695 user_options = [
696 user_options = [
696 ('minify', 'x', "minify CSS"),
697 ('minify', 'x', "minify CSS"),
697 ('force', 'f', "force recompilation of CSS"),
698 ('force', 'f', "force recompilation of CSS"),
698 ]
699 ]
699
700
700 def initialize_options(self):
701 def initialize_options(self):
701 self.minify = False
702 self.minify = False
702 self.force = False
703 self.force = False
703
704
704 def finalize_options(self):
705 def finalize_options(self):
705 self.minify = bool(self.minify)
706 self.minify = bool(self.minify)
706 self.force = bool(self.force)
707 self.force = bool(self.force)
707
708
708 def run(self):
709 def run(self):
709 cmd = ['invoke', 'css']
710 cmd = ['invoke', 'css']
710 if self.minify:
711 if self.minify:
711 cmd.append('--minify')
712 cmd.append('--minify')
712 if self.force:
713 if self.force:
713 cmd.append('--force')
714 cmd.append('--force')
714 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
715 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
715
716
716
717
717 class JavascriptVersion(Command):
718 class JavascriptVersion(Command):
718 """write the javascript version to notebook javascript"""
719 """write the javascript version to notebook javascript"""
719 description = "Write IPython version to javascript"
720 description = "Write IPython version to javascript"
720 user_options = []
721 user_options = []
721
722
722 def initialize_options(self):
723 def initialize_options(self):
723 pass
724 pass
724
725
725 def finalize_options(self):
726 def finalize_options(self):
726 pass
727 pass
727
728
728 def run(self):
729 def run(self):
729 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
730 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
730 with open(nsfile) as f:
731 with open(nsfile) as f:
731 lines = f.readlines()
732 lines = f.readlines()
732 with open(nsfile, 'w') as f:
733 with open(nsfile, 'w') as f:
733 for line in lines:
734 for line in lines:
734 if line.startswith("IPython.version"):
735 if line.startswith("IPython.version"):
735 line = 'IPython.version = "{0}";\n'.format(version)
736 line = 'IPython.version = "{0}";\n'.format(version)
736 f.write(line)
737 f.write(line)
737
738
738
739
739 def css_js_prerelease(command, strict=True):
740 def css_js_prerelease(command, strict=True):
740 """decorator for building js/minified css prior to a release"""
741 """decorator for building js/minified css prior to a release"""
741 class DecoratedCommand(command):
742 class DecoratedCommand(command):
742 def run(self):
743 def run(self):
743 self.distribution.run_command('jsversion')
744 self.distribution.run_command('jsversion')
744 css = self.distribution.get_command_obj('css')
745 css = self.distribution.get_command_obj('css')
745 css.minify = True
746 css.minify = True
746 try:
747 try:
747 self.distribution.run_command('css')
748 self.distribution.run_command('css')
748 except Exception as e:
749 except Exception as e:
749 if strict:
750 if strict:
750 raise
751 raise
751 else:
752 else:
752 log.warn("Failed to build css sourcemaps: %s" % e)
753 log.warn("Failed to build css sourcemaps: %s" % e)
753 command.run(self)
754 command.run(self)
754 return DecoratedCommand
755 return DecoratedCommand
General Comments 0
You need to be logged in to leave comments. Login now