1 | /* ***** BEGIN LICENSE BLOCK ***** |
---|
2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
---|
3 | * |
---|
4 | * The contents of this file are subject to the Mozilla Public License Version |
---|
5 | * 1.1 (the "License"); you may not use this file except in compliance with |
---|
6 | * the License. You may obtain a copy of the License at |
---|
7 | * http://www.mozilla.org/MPL/ |
---|
8 | * |
---|
9 | * Software distributed under the License is distributed on an "AS IS" basis, |
---|
10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
---|
11 | * for the specific language governing rights and limitations under the |
---|
12 | * License. |
---|
13 | * |
---|
14 | * The Original Code is Mozilla Skywriter. |
---|
15 | * |
---|
16 | * The Initial Developer of the Original Code is |
---|
17 | * Mozilla. |
---|
18 | * Portions created by the Initial Developer are Copyright (C) 2009 |
---|
19 | * the Initial Developer. All Rights Reserved. |
---|
20 | * |
---|
21 | * Contributor(s): |
---|
22 | * Kevin Dangoor (kdangoor@mozilla.com) |
---|
23 | * |
---|
24 | * Alternatively, the contents of this file may be used under the terms of |
---|
25 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
---|
26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
---|
27 | * in which case the provisions of the GPL or the LGPL are applicable instead |
---|
28 | * of those above. If you wish to allow use of your version of this file only |
---|
29 | * under the terms of either the GPL or the LGPL, and not to allow others to |
---|
30 | * use your version of this file under the terms of the MPL, indicate your |
---|
31 | * decision by deleting the provisions above and replace them with the notice |
---|
32 | * and other provisions required by the GPL or the LGPL. If you do not delete |
---|
33 | * the provisions above, a recipient may use your version of this file under |
---|
34 | * the terms of any one of the MPL, the GPL or the LGPL. |
---|
35 | * |
---|
36 | * ***** END LICENSE BLOCK ***** */ |
---|
37 | |
---|
38 | define('cockpit/index', ['require', 'exports', 'module' , 'pilot/index', 'cockpit/cli', 'cockpit/ui/settings', 'cockpit/ui/cli_view', 'cockpit/commands/basic'], function(require, exports, module) { |
---|
39 | |
---|
40 | |
---|
41 | exports.startup = function(data, reason) { |
---|
42 | require('pilot/index'); |
---|
43 | require('cockpit/cli').startup(data, reason); |
---|
44 | // window.testCli = require('cockpit/test/testCli'); |
---|
45 | |
---|
46 | require('cockpit/ui/settings').startup(data, reason); |
---|
47 | require('cockpit/ui/cli_view').startup(data, reason); |
---|
48 | require('cockpit/commands/basic').startup(data, reason); |
---|
49 | }; |
---|
50 | |
---|
51 | /* |
---|
52 | exports.shutdown(data, reason) { |
---|
53 | }; |
---|
54 | */ |
---|
55 | |
---|
56 | |
---|
57 | }); |
---|
58 | /* ***** BEGIN LICENSE BLOCK ***** |
---|
59 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
---|
60 | * |
---|
61 | * The contents of this file are subject to the Mozilla Public License Version |
---|
62 | * 1.1 (the "License"); you may not use this file except in compliance with |
---|
63 | * the License. You may obtain a copy of the License at |
---|
64 | * http://www.mozilla.org/MPL/ |
---|
65 | * |
---|
66 | * Software distributed under the License is distributed on an "AS IS" basis, |
---|
67 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
---|
68 | * for the specific language governing rights and limitations under the |
---|
69 | * License. |
---|
70 | * |
---|
71 | * The Original Code is Skywriter. |
---|
72 | * |
---|
73 | * The Initial Developer of the Original Code is |
---|
74 | * Mozilla. |
---|
75 | * Portions created by the Initial Developer are Copyright (C) 2009 |
---|
76 | * the Initial Developer. All Rights Reserved. |
---|
77 | * |
---|
78 | * Contributor(s): |
---|
79 | * Joe Walker (jwalker@mozilla.com) |
---|
80 | * |
---|
81 | * Alternatively, the contents of this file may be used under the terms of |
---|
82 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
---|
83 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
---|
84 | * in which case the provisions of the GPL or the LGPL are applicable instead |
---|
85 | * of those above. If you wish to allow use of your version of this file only |
---|
86 | * under the terms of either the GPL or the LGPL, and not to allow others to |
---|
87 | * use your version of this file under the terms of the MPL, indicate your |
---|
88 | * decision by deleting the provisions above and replace them with the notice |
---|
89 | * and other provisions required by the GPL or the LGPL. If you do not delete |
---|
90 | * the provisions above, a recipient may use your version of this file under |
---|
91 | * the terms of any one of the MPL, the GPL or the LGPL. |
---|
92 | * |
---|
93 | * ***** END LICENSE BLOCK ***** */ |
---|
94 | |
---|
95 | define('cockpit/cli', ['require', 'exports', 'module' , 'pilot/console', 'pilot/lang', 'pilot/oop', 'pilot/event_emitter', 'pilot/types', 'pilot/canon'], function(require, exports, module) { |
---|
96 | |
---|
97 | |
---|
98 | var console = require('pilot/console'); |
---|
99 | var lang = require('pilot/lang'); |
---|
100 | var oop = require('pilot/oop'); |
---|
101 | var EventEmitter = require('pilot/event_emitter').EventEmitter; |
---|
102 | |
---|
103 | //var keyboard = require('keyboard/keyboard'); |
---|
104 | var types = require('pilot/types'); |
---|
105 | var Status = require('pilot/types').Status; |
---|
106 | var Conversion = require('pilot/types').Conversion; |
---|
107 | var canon = require('pilot/canon'); |
---|
108 | |
---|
109 | /** |
---|
110 | * Normally type upgrade is done when the owning command is registered, but |
---|
111 | * out commandParam isn't part of a command, so it misses out. |
---|
112 | */ |
---|
113 | exports.startup = function(data, reason) { |
---|
114 | canon.upgradeType('command', commandParam); |
---|
115 | }; |
---|
116 | |
---|
117 | /** |
---|
118 | * The information required to tell the user there is a problem with their |
---|
119 | * input. |
---|
120 | * TODO: There a several places where {start,end} crop up. Perhaps we should |
---|
121 | * have a Cursor object. |
---|
122 | */ |
---|
123 | function Hint(status, message, start, end, predictions) { |
---|
124 | this.status = status; |
---|
125 | this.message = message; |
---|
126 | |
---|
127 | if (typeof start === 'number') { |
---|
128 | this.start = start; |
---|
129 | this.end = end; |
---|
130 | this.predictions = predictions; |
---|
131 | } |
---|
132 | else { |
---|
133 | var arg = start; |
---|
134 | this.start = arg.start; |
---|
135 | this.end = arg.end; |
---|
136 | this.predictions = arg.predictions; |
---|
137 | } |
---|
138 | } |
---|
139 | Hint.prototype = { |
---|
140 | }; |
---|
141 | /** |
---|
142 | * Loop over the array of hints finding the one we should display. |
---|
143 | * @param hints array of hints |
---|
144 | */ |
---|
145 | Hint.sort = function(hints, cursor) { |
---|
146 | // Calculate 'distance from cursor' |
---|
147 | if (cursor !== undefined) { |
---|
148 | hints.forEach(function(hint) { |
---|
149 | if (hint.start === Argument.AT_CURSOR) { |
---|
150 | hint.distance = 0; |
---|
151 | } |
---|
152 | else if (cursor < hint.start) { |
---|
153 | hint.distance = hint.start - cursor; |
---|
154 | } |
---|
155 | else if (cursor > hint.end) { |
---|
156 | hint.distance = cursor - hint.end; |
---|
157 | } |
---|
158 | else { |
---|
159 | hint.distance = 0; |
---|
160 | } |
---|
161 | }, this); |
---|
162 | } |
---|
163 | // Sort |
---|
164 | hints.sort(function(hint1, hint2) { |
---|
165 | // Compare first based on distance from cursor |
---|
166 | if (cursor !== undefined) { |
---|
167 | var diff = hint1.distance - hint2.distance; |
---|
168 | if (diff != 0) { |
---|
169 | return diff; |
---|
170 | } |
---|
171 | } |
---|
172 | // otherwise go with hint severity |
---|
173 | return hint2.status - hint1.status; |
---|
174 | }); |
---|
175 | // tidy-up |
---|
176 | if (cursor !== undefined) { |
---|
177 | hints.forEach(function(hint) { |
---|
178 | delete hint.distance; |
---|
179 | }, this); |
---|
180 | } |
---|
181 | return hints; |
---|
182 | }; |
---|
183 | exports.Hint = Hint; |
---|
184 | |
---|
185 | /** |
---|
186 | * A Hint that arose as a result of a Conversion |
---|
187 | */ |
---|
188 | function ConversionHint(conversion, arg) { |
---|
189 | this.status = conversion.status; |
---|
190 | this.message = conversion.message; |
---|
191 | if (arg) { |
---|
192 | this.start = arg.start; |
---|
193 | this.end = arg.end; |
---|
194 | } |
---|
195 | else { |
---|
196 | this.start = 0; |
---|
197 | this.end = 0; |
---|
198 | } |
---|
199 | this.predictions = conversion.predictions; |
---|
200 | }; |
---|
201 | oop.inherits(ConversionHint, Hint); |
---|
202 | |
---|
203 | |
---|
204 | /** |
---|
205 | * We record where in the input string an argument comes so we can report errors |
---|
206 | * against those string positions. |
---|
207 | * We publish a 'change' event when-ever the text changes |
---|
208 | * @param emitter Arguments use something else to pass on change events. |
---|
209 | * Currently this will be the creating Requisition. This prevents dependency |
---|
210 | * loops and prevents us from needing to merge listener lists. |
---|
211 | * @param text The string (trimmed) that contains the argument |
---|
212 | * @param start The position of the text in the original input string |
---|
213 | * @param end See start |
---|
214 | * @param prefix Knowledge of quotation marks and whitespace used prior to the |
---|
215 | * text in the input string allows us to re-generate the original input from |
---|
216 | * the arguments. |
---|
217 | * @param suffix Any quotation marks and whitespace used after the text. |
---|
218 | * Whitespace is normally placed in the prefix to the succeeding argument, but |
---|
219 | * can be used here when this is the last argument. |
---|
220 | * @constructor |
---|
221 | */ |
---|
222 | function Argument(emitter, text, start, end, prefix, suffix) { |
---|
223 | this.emitter = emitter; |
---|
224 | this.setText(text); |
---|
225 | this.start = start; |
---|
226 | this.end = end; |
---|
227 | this.prefix = prefix; |
---|
228 | this.suffix = suffix; |
---|
229 | } |
---|
230 | Argument.prototype = { |
---|
231 | /** |
---|
232 | * Return the result of merging these arguments. |
---|
233 | * TODO: What happens when we're merging arguments for the single string |
---|
234 | * case and some of the arguments are in quotation marks? |
---|
235 | */ |
---|
236 | merge: function(following) { |
---|
237 | if (following.emitter != this.emitter) { |
---|
238 | throw new Error('Can\'t merge Arguments from different EventEmitters'); |
---|
239 | } |
---|
240 | return new Argument( |
---|
241 | this.emitter, |
---|
242 | this.text + this.suffix + following.prefix + following.text, |
---|
243 | this.start, following.end, |
---|
244 | this.prefix, |
---|
245 | following.suffix); |
---|
246 | }, |
---|
247 | |
---|
248 | /** |
---|
249 | * See notes on events in Assignment. We might need to hook changes here |
---|
250 | * into a CliRequisition so they appear of the command line. |
---|
251 | */ |
---|
252 | setText: function(text) { |
---|
253 | if (text == null) { |
---|
254 | throw new Error('Illegal text for Argument: ' + text); |
---|
255 | } |
---|
256 | var ev = { argument: this, oldText: this.text, text: text }; |
---|
257 | this.text = text; |
---|
258 | this.emitter._dispatchEvent('argumentChange', ev); |
---|
259 | }, |
---|
260 | |
---|
261 | /** |
---|
262 | * Helper when we're putting arguments back together |
---|
263 | */ |
---|
264 | toString: function() { |
---|
265 | // TODO: There is a bug here - we should re-escape escaped characters |
---|
266 | // But can we do that reliably? |
---|
267 | return this.prefix + this.text + this.suffix; |
---|
268 | } |
---|
269 | }; |
---|
270 | |
---|
271 | /** |
---|
272 | * Merge an array of arguments into a single argument. |
---|
273 | * All Arguments in the array are expected to have the same emitter |
---|
274 | */ |
---|
275 | Argument.merge = function(argArray, start, end) { |
---|
276 | start = (start === undefined) ? 0 : start; |
---|
277 | end = (end === undefined) ? argArray.length : end; |
---|
278 | |
---|
279 | var joined; |
---|
280 | for (var i = start; i < end; i++) { |
---|
281 | var arg = argArray[i]; |
---|
282 | if (!joined) { |
---|
283 | joined = arg; |
---|
284 | } |
---|
285 | else { |
---|
286 | joined = joined.merge(arg); |
---|
287 | } |
---|
288 | } |
---|
289 | return joined; |
---|
290 | }; |
---|
291 | |
---|
292 | /** |
---|
293 | * We sometimes need a way to say 'this error occurs where ever the cursor is' |
---|
294 | */ |
---|
295 | Argument.AT_CURSOR = -1; |
---|
296 | |
---|
297 | |
---|
298 | /** |
---|
299 | * A link between a parameter and the data for that parameter. |
---|
300 | * The data for the parameter is available as in the preferred type and as |
---|
301 | * an Argument for the CLI. |
---|
302 | * <p>We also record validity information where applicable. |
---|
303 | * <p>For values, null and undefined have distinct definitions. null means |
---|
304 | * that a value has been provided, undefined means that it has not. |
---|
305 | * Thus, null is a valid default value, and common because it identifies an |
---|
306 | * parameter that is optional. undefined means there is no value from |
---|
307 | * the command line. |
---|
308 | * @constructor |
---|
309 | */ |
---|
310 | function Assignment(param, requisition) { |
---|
311 | this.param = param; |
---|
312 | this.requisition = requisition; |
---|
313 | this.setValue(param.defaultValue); |
---|
314 | }; |
---|
315 | Assignment.prototype = { |
---|
316 | /** |
---|
317 | * The parameter that we are assigning to |
---|
318 | * @readonly |
---|
319 | */ |
---|
320 | param: undefined, |
---|
321 | |
---|
322 | /** |
---|
323 | * Report on the status of the last parse() conversion. |
---|
324 | * @see types.Conversion |
---|
325 | */ |
---|
326 | conversion: undefined, |
---|
327 | |
---|
328 | /** |
---|
329 | * The current value in a type as specified by param.type |
---|
330 | */ |
---|
331 | value: undefined, |
---|
332 | |
---|
333 | /** |
---|
334 | * The string version of the current value |
---|
335 | */ |
---|
336 | arg: undefined, |
---|
337 | |
---|
338 | /** |
---|
339 | * The current value (i.e. not the string representation) |
---|
340 | * Use setValue() to mutate |
---|
341 | */ |
---|
342 | value: undefined, |
---|
343 | setValue: function(value) { |
---|
344 | if (this.value === value) { |
---|
345 | return; |
---|
346 | } |
---|
347 | |
---|
348 | if (value === undefined) { |
---|
349 | this.value = this.param.defaultValue; |
---|
350 | this.conversion = this.param.getDefault ? |
---|
351 | this.param.getDefault() : |
---|
352 | this.param.type.getDefault(); |
---|
353 | this.arg = undefined; |
---|
354 | } else { |
---|
355 | this.value = value; |
---|
356 | this.conversion = undefined; |
---|
357 | var text = (value == null) ? '' : this.param.type.stringify(value); |
---|
358 | if (this.arg) { |
---|
359 | this.arg.setText(text); |
---|
360 | } |
---|
361 | } |
---|
362 | |
---|
363 | this.requisition._assignmentChanged(this); |
---|
364 | }, |
---|
365 | |
---|
366 | /** |
---|
367 | * The textual representation of the current value |
---|
368 | * Use setValue() to mutate |
---|
369 | */ |
---|
370 | arg: undefined, |
---|
371 | setArgument: function(arg) { |
---|
372 | if (this.arg === arg) { |
---|
373 | return; |
---|
374 | } |
---|
375 | this.arg = arg; |
---|
376 | this.conversion = this.param.type.parse(arg.text); |
---|
377 | this.conversion.arg = arg; // TODO: make this automatic? |
---|
378 | this.value = this.conversion.value; |
---|
379 | this.requisition._assignmentChanged(this); |
---|
380 | }, |
---|
381 | |
---|
382 | /** |
---|
383 | * Create a list of the hints associated with this parameter assignment. |
---|
384 | * Generally there will be only one hint generated because we're currently |
---|
385 | * only displaying one hint at a time, ordering by distance from cursor |
---|
386 | * and severity. Since distance from cursor will be the same for all hints |
---|
387 | * from this assignment all but the most severe will ever be used. It might |
---|
388 | * make sense with more experience to alter this to function to be getHint() |
---|
389 | */ |
---|
390 | getHint: function() { |
---|
391 | // Allow the parameter to provide documentation |
---|
392 | if (this.param.getCustomHint && this.value && this.arg) { |
---|
393 | var hint = this.param.getCustomHint(this.value, this.arg); |
---|
394 | if (hint) { |
---|
395 | return hint; |
---|
396 | } |
---|
397 | } |
---|
398 | |
---|
399 | // If there is no argument, use the cursor position |
---|
400 | var message = '<strong>' + this.param.name + '</strong>: '; |
---|
401 | if (this.param.description) { |
---|
402 | // TODO: This should be a short description - do we need to trim? |
---|
403 | message += this.param.description.trim(); |
---|
404 | |
---|
405 | // Ensure the help text ends with '. ' |
---|
406 | if (message.charAt(message.length - 1) !== '.') { |
---|
407 | message += '.'; |
---|
408 | } |
---|
409 | if (message.charAt(message.length - 1) !== ' ') { |
---|
410 | message += ' '; |
---|
411 | } |
---|
412 | } |
---|
413 | var status = Status.VALID; |
---|
414 | var start = this.arg ? this.arg.start : Argument.AT_CURSOR; |
---|
415 | var end = this.arg ? this.arg.end : Argument.AT_CURSOR; |
---|
416 | var predictions; |
---|
417 | |
---|
418 | // Non-valid conversions will have useful information to pass on |
---|
419 | if (this.conversion) { |
---|
420 | status = this.conversion.status; |
---|
421 | if (this.conversion.message) { |
---|
422 | message += this.conversion.message; |
---|
423 | } |
---|
424 | predictions = this.conversion.predictions; |
---|
425 | } |
---|
426 | |
---|
427 | // Hint if the param is required, but not provided |
---|
428 | var argProvided = this.arg && this.arg.text !== ''; |
---|
429 | var dataProvided = this.value !== undefined || argProvided; |
---|
430 | if (this.param.defaultValue === undefined && !dataProvided) { |
---|
431 | status = Status.INVALID; |
---|
432 | message += '<strong>Required<\strong>'; |
---|
433 | } |
---|
434 | |
---|
435 | return new Hint(status, message, start, end, predictions); |
---|
436 | }, |
---|
437 | |
---|
438 | /** |
---|
439 | * Basically <tt>setValue(conversion.predictions[0])</tt> done in a safe |
---|
440 | * way. |
---|
441 | */ |
---|
442 | complete: function() { |
---|
443 | if (this.conversion && this.conversion.predictions && |
---|
444 | this.conversion.predictions.length > 0) { |
---|
445 | this.setValue(this.conversion.predictions[0]); |
---|
446 | } |
---|
447 | }, |
---|
448 | |
---|
449 | /** |
---|
450 | * If the cursor is at 'position', do we have sufficient data to start |
---|
451 | * displaying the next hint. This is both complex and important. |
---|
452 | * For example, if the user has just typed:<ul> |
---|
453 | * <li>'set tabstop ' then they clearly want to know about the valid |
---|
454 | * values for the tabstop setting, so the hint is based on the next |
---|
455 | * parameter. |
---|
456 | * <li>'set tabstop' (without trailing space) - they will probably still |
---|
457 | * want to know about the valid values for the tabstop setting because |
---|
458 | * there is no confusion about the setting in question. |
---|
459 | * <li>'set tabsto' they've not finished typing a setting name so the hint |
---|
460 | * should be based on the current parameter. |
---|
461 | * <li>'set tabstop' (when there is an additional tabstopstyle setting) we |
---|
462 | * can't make assumptions about the setting - we're not finished. |
---|
463 | * </ul> |
---|
464 | * <p>Note that the input for 2 and 4 is identical, only the configuration |
---|
465 | * has changed, so hint display is environmental. |
---|
466 | * |
---|
467 | * <p>This function works out if the cursor is before the end of this |
---|
468 | * assignment (assuming that we've asked the same thing of the previous |
---|
469 | * assignment) and then attempts to work out if we should use the hint from |
---|
470 | * the next assignment even though technically the cursor is still inside |
---|
471 | * this one due to the rules above. |
---|
472 | */ |
---|
473 | isPositionCaptured: function(position) { |
---|
474 | if (!this.arg) { |
---|
475 | return false; |
---|
476 | } |
---|
477 | |
---|
478 | // Note we don't check if position >= this.arg.start because that's |
---|
479 | // implied by the fact that we're asking the assignments in turn, and |
---|
480 | // we want to avoid thing falling between the cracks, but we do need |
---|
481 | // to check that the argument does have a position |
---|
482 | if (this.arg.start === -1) { |
---|
483 | return false; |
---|
484 | } |
---|
485 | |
---|
486 | // We're clearly done if the position is past the end of the text |
---|
487 | if (position > this.arg.end) { |
---|
488 | return false; |
---|
489 | } |
---|
490 | |
---|
491 | // If we're AT the end, the position is captured if either the status |
---|
492 | // is not valid or if there are other valid options including current |
---|
493 | if (position === this.arg.end) { |
---|
494 | return this.conversion.status !== Status.VALID || |
---|
495 | this.conversion.predictions.length !== 0; |
---|
496 | } |
---|
497 | |
---|
498 | // Otherwise we're clearly inside |
---|
499 | return true; |
---|
500 | }, |
---|
501 | |
---|
502 | /** |
---|
503 | * Replace the current value with the lower value if such a concept |
---|
504 | * exists. |
---|
505 | */ |
---|
506 | decrement: function() { |
---|
507 | var replacement = this.param.type.decrement(this.value); |
---|
508 | if (replacement != null) { |
---|
509 | this.setValue(replacement); |
---|
510 | } |
---|
511 | }, |
---|
512 | |
---|
513 | /** |
---|
514 | * Replace the current value with the higher value if such a concept |
---|
515 | * exists. |
---|
516 | */ |
---|
517 | increment: function() { |
---|
518 | var replacement = this.param.type.increment(this.value); |
---|
519 | if (replacement != null) { |
---|
520 | this.setValue(replacement); |
---|
521 | } |
---|
522 | }, |
---|
523 | |
---|
524 | /** |
---|
525 | * Helper when we're rebuilding command lines. |
---|
526 | */ |
---|
527 | toString: function() { |
---|
528 | return this.arg ? this.arg.toString() : ''; |
---|
529 | } |
---|
530 | }; |
---|
531 | exports.Assignment = Assignment; |
---|
532 | |
---|
533 | |
---|
534 | /** |
---|
535 | * This is a special parameter to reflect the command itself. |
---|
536 | */ |
---|
537 | var commandParam = { |
---|
538 | name: '__command', |
---|
539 | type: 'command', |
---|
540 | description: 'The command to execute', |
---|
541 | |
---|
542 | /** |
---|
543 | * Provide some documentation for a command. |
---|
544 | */ |
---|
545 | getCustomHint: function(command, arg) { |
---|
546 | var docs = []; |
---|
547 | docs.push('<strong><tt> > '); |
---|
548 | docs.push(command.name); |
---|
549 | if (command.params && command.params.length > 0) { |
---|
550 | command.params.forEach(function(param) { |
---|
551 | if (param.defaultValue === undefined) { |
---|
552 | docs.push(' [' + param.name + ']'); |
---|
553 | } |
---|
554 | else { |
---|
555 | docs.push(' <em>[' + param.name + ']</em>'); |
---|
556 | } |
---|
557 | }, this); |
---|
558 | } |
---|
559 | docs.push('</tt></strong><br/>'); |
---|
560 | |
---|
561 | docs.push(command.description ? command.description : '(No description)'); |
---|
562 | docs.push('<br/>'); |
---|
563 | |
---|
564 | if (command.params && command.params.length > 0) { |
---|
565 | docs.push('<ul>'); |
---|
566 | command.params.forEach(function(param) { |
---|
567 | docs.push('<li>'); |
---|
568 | docs.push('<strong><tt>' + param.name + '</tt></strong>: '); |
---|
569 | docs.push(param.description ? param.description : '(No description)'); |
---|
570 | if (param.defaultValue === undefined) { |
---|
571 | docs.push(' <em>[Required]</em>'); |
---|
572 | } |
---|
573 | else if (param.defaultValue === null) { |
---|
574 | docs.push(' <em>[Optional]</em>'); |
---|
575 | } |
---|
576 | else { |
---|
577 | docs.push(' <em>[Default: ' + param.defaultValue + ']</em>'); |
---|
578 | } |
---|
579 | docs.push('</li>'); |
---|
580 | }, this); |
---|
581 | docs.push('</ul>'); |
---|
582 | } |
---|
583 | |
---|
584 | return new Hint(Status.VALID, docs.join(''), arg); |
---|
585 | } |
---|
586 | }; |
---|
587 | |
---|
588 | /** |
---|
589 | * A Requisition collects the information needed to execute a command. |
---|
590 | * There is no point in a requisition for parameter-less commands because there |
---|
591 | * is no information to collect. A Requisition is a collection of assignments |
---|
592 | * of values to parameters, each handled by an instance of Assignment. |
---|
593 | * CliRequisition adds functions for parsing input from a command line to this |
---|
594 | * class. |
---|
595 | * <h2>Events<h2> |
---|
596 | * We publish the following events:<ul> |
---|
597 | * <li>argumentChange: The text of some argument has changed. It is likely that |
---|
598 | * any UI component displaying this argument will need to be updated. (Note that |
---|
599 | * this event is actually published by the Argument itself - see the docs for |
---|
600 | * Argument for more details) |
---|
601 | * The event object looks like: { argument: A, oldText: B, text: B } |
---|
602 | * <li>commandChange: The command has changed. It is likely that a UI |
---|
603 | * structure will need updating to match the parameters of the new command. |
---|
604 | * The event object looks like { command: A } |
---|
605 | * @constructor |
---|
606 | */ |
---|
607 | function Requisition(env) { |
---|
608 | this.env = env; |
---|
609 | this.commandAssignment = new Assignment(commandParam, this); |
---|
610 | } |
---|
611 | |
---|
612 | Requisition.prototype = { |
---|
613 | /** |
---|
614 | * The command that we are about to execute. |
---|
615 | * @see setCommandConversion() |
---|
616 | * @readonly |
---|
617 | */ |
---|
618 | commandAssignment: undefined, |
---|
619 | |
---|
620 | /** |
---|
621 | * The count of assignments. Excludes the commandAssignment |
---|
622 | * @readonly |
---|
623 | */ |
---|
624 | assignmentCount: undefined, |
---|
625 | |
---|
626 | /** |
---|
627 | * The object that stores of Assignment objects that we are filling out. |
---|
628 | * The Assignment objects are stored under their param.name for named |
---|
629 | * lookup. Note: We make use of the property of Javascript objects that |
---|
630 | * they are not just hashmaps, but linked-list hashmaps which iterate in |
---|
631 | * insertion order. |
---|
632 | * Excludes the commandAssignment. |
---|
633 | */ |
---|
634 | _assignments: undefined, |
---|
635 | |
---|
636 | /** |
---|
637 | * The store of hints generated by the assignments. We are trying to prevent |
---|
638 | * the UI from needing to access this in broad form, but instead use |
---|
639 | * methods that query part of this structure. |
---|
640 | */ |
---|
641 | _hints: undefined, |
---|
642 | |
---|
643 | /** |
---|
644 | * When the command changes, we need to keep a bunch of stuff in sync |
---|
645 | */ |
---|
646 | _assignmentChanged: function(assignment) { |
---|
647 | // This is all about re-creating Assignments |
---|
648 | if (assignment.param.name !== '__command') { |
---|
649 | return; |
---|
650 | } |
---|
651 | |
---|
652 | this._assignments = {}; |
---|
653 | |
---|
654 | if (assignment.value) { |
---|
655 | assignment.value.params.forEach(function(param) { |
---|
656 | this._assignments[param.name] = new Assignment(param, this); |
---|
657 | }, this); |
---|
658 | } |
---|
659 | |
---|
660 | this.assignmentCount = Object.keys(this._assignments).length; |
---|
661 | this._dispatchEvent('commandChange', { command: assignment.value }); |
---|
662 | }, |
---|
663 | |
---|
664 | /** |
---|
665 | * Assignments have an order, so we need to store them in an array. |
---|
666 | * But we also need named access ... |
---|
667 | */ |
---|
668 | getAssignment: function(nameOrNumber) { |
---|
669 | var name = (typeof nameOrNumber === 'string') ? |
---|
670 | nameOrNumber : |
---|
671 | Object.keys(this._assignments)[nameOrNumber]; |
---|
672 | return this._assignments[name]; |
---|
673 | }, |
---|
674 | |
---|
675 | /** |
---|
676 | * Where parameter name == assignment names - they are the same. |
---|
677 | */ |
---|
678 | getParameterNames: function() { |
---|
679 | return Object.keys(this._assignments); |
---|
680 | }, |
---|
681 | |
---|
682 | /** |
---|
683 | * A *shallow* clone of the assignments. |
---|
684 | * This is useful for systems that wish to go over all the assignments |
---|
685 | * finding values one way or another and wish to trim an array as they go. |
---|
686 | */ |
---|
687 | cloneAssignments: function() { |
---|
688 | return Object.keys(this._assignments).map(function(name) { |
---|
689 | return this._assignments[name]; |
---|
690 | }, this); |
---|
691 | }, |
---|
692 | |
---|
693 | /** |
---|
694 | * Collect the statuses from the Assignments. |
---|
695 | * The hints returned are sorted by severity |
---|
696 | */ |
---|
697 | _updateHints: function() { |
---|
698 | // TODO: work out when to clear this out for the plain Requisition case |
---|
699 | // this._hints = []; |
---|
700 | this.getAssignments(true).forEach(function(assignment) { |
---|
701 | this._hints.push(assignment.getHint()); |
---|
702 | }, this); |
---|
703 | Hint.sort(this._hints); |
---|
704 | |
---|
705 | // We would like to put some initial help here, but for anyone but |
---|
706 | // a complete novice a 'type help' message is very annoying, so we |
---|
707 | // need to find a way to only display this message once, or for |
---|
708 | // until the user click a 'close' button or similar |
---|
709 | // TODO: Add special case for '' input |
---|
710 | }, |
---|
711 | |
---|
712 | /** |
---|
713 | * Returns the most severe status |
---|
714 | */ |
---|
715 | getWorstHint: function() { |
---|
716 | return this._hints[0]; |
---|
717 | }, |
---|
718 | |
---|
719 | /** |
---|
720 | * Extract the names and values of all the assignments, and return as |
---|
721 | * an object. |
---|
722 | */ |
---|
723 | getArgsObject: function() { |
---|
724 | var args = {}; |
---|
725 | this.getAssignments().forEach(function(assignment) { |
---|
726 | args[assignment.param.name] = assignment.value; |
---|
727 | }, this); |
---|
728 | return args; |
---|
729 | }, |
---|
730 | |
---|
731 | /** |
---|
732 | * Access the arguments as an array. |
---|
733 | * @param includeCommand By default only the parameter arguments are |
---|
734 | * returned unless (includeCommand === true), in which case the list is |
---|
735 | * prepended with commandAssignment.arg |
---|
736 | */ |
---|
737 | getAssignments: function(includeCommand) { |
---|
738 | var args = []; |
---|
739 | if (includeCommand === true) { |
---|
740 | args.push(this.commandAssignment); |
---|
741 | } |
---|
742 | Object.keys(this._assignments).forEach(function(name) { |
---|
743 | args.push(this.getAssignment(name)); |
---|
744 | }, this); |
---|
745 | return args; |
---|
746 | }, |
---|
747 | |
---|
748 | /** |
---|
749 | * Reset all the assignments to their default values |
---|
750 | */ |
---|
751 | setDefaultValues: function() { |
---|
752 | this.getAssignments().forEach(function(assignment) { |
---|
753 | assignment.setValue(undefined); |
---|
754 | }, this); |
---|
755 | }, |
---|
756 | |
---|
757 | /** |
---|
758 | * Helper to call canon.exec |
---|
759 | */ |
---|
760 | exec: function() { |
---|
761 | canon.exec(this.commandAssignment.value, |
---|
762 | this.env, |
---|
763 | "cli", |
---|
764 | this.getArgsObject(), |
---|
765 | this.toCanonicalString()); |
---|
766 | }, |
---|
767 | |
---|
768 | /** |
---|
769 | * Extract a canonical version of the input |
---|
770 | */ |
---|
771 | toCanonicalString: function() { |
---|
772 | var line = []; |
---|
773 | line.push(this.commandAssignment.value.name); |
---|
774 | Object.keys(this._assignments).forEach(function(name) { |
---|
775 | var assignment = this._assignments[name]; |
---|
776 | var type = assignment.param.type; |
---|
777 | // TODO: This will cause problems if there is a non-default value |
---|
778 | // after a default value. Also we need to decide when to use |
---|
779 | // named parameters in place of positional params. Both can wait. |
---|
780 | if (assignment.value !== assignment.param.defaultValue) { |
---|
781 | line.push(' '); |
---|
782 | line.push(type.stringify(assignment.value)); |
---|
783 | } |
---|
784 | }, this); |
---|
785 | return line.join(''); |
---|
786 | } |
---|
787 | }; |
---|
788 | oop.implement(Requisition.prototype, EventEmitter); |
---|
789 | exports.Requisition = Requisition; |
---|
790 | |
---|
791 | |
---|
792 | /** |
---|
793 | * An object used during command line parsing to hold the various intermediate |
---|
794 | * data steps. |
---|
795 | * <p>The 'output' of the update is held in 2 objects: input.hints which is an |
---|
796 | * array of hints to display to the user. In the future this will become a |
---|
797 | * single value. |
---|
798 | * <p>The other output value is input.requisition which gives access to an |
---|
799 | * args object for use in executing the final command. |
---|
800 | * |
---|
801 | * <p>The majority of the functions in this class are called in sequence by the |
---|
802 | * constructor. Their task is to add to <tt>hints</tt> fill out the requisition. |
---|
803 | * <p>The general sequence is:<ul> |
---|
804 | * <li>_tokenize(): convert _typed into _parts |
---|
805 | * <li>_split(): convert _parts into _command and _unparsedArgs |
---|
806 | * <li>_assign(): convert _unparsedArgs into requisition |
---|
807 | * </ul> |
---|
808 | * |
---|
809 | * @param typed {string} The instruction as typed by the user so far |
---|
810 | * @param options {object} A list of optional named parameters. Can be any of: |
---|
811 | * <b>flags</b>: Flags for us to check against the predicates specified with the |
---|
812 | * commands. Defaulted to <tt>keyboard.buildFlags({ });</tt> |
---|
813 | * if not specified. |
---|
814 | * @constructor |
---|
815 | */ |
---|
816 | function CliRequisition(env, options) { |
---|
817 | Requisition.call(this, env); |
---|
818 | |
---|
819 | if (options && options.flags) { |
---|
820 | /** |
---|
821 | * TODO: We were using a default of keyboard.buildFlags({ }); |
---|
822 | * This allowed us to have commands that only existed in certain contexts |
---|
823 | * - i.e. Javascript specific commands. |
---|
824 | */ |
---|
825 | this.flags = options.flags; |
---|
826 | } |
---|
827 | } |
---|
828 | oop.inherits(CliRequisition, Requisition); |
---|
829 | (function() { |
---|
830 | /** |
---|
831 | * Called by the UI when ever the user interacts with a command line input |
---|
832 | * @param input A structure that details the state of the input field. |
---|
833 | * It should look something like: { typed:a, cursor: { start:b, end:c } } |
---|
834 | * Where a is the contents of the input field, and b and c are the start |
---|
835 | * and end of the cursor/selection respectively. |
---|
836 | */ |
---|
837 | CliRequisition.prototype.update = function(input) { |
---|
838 | this.input = input; |
---|
839 | this._hints = []; |
---|
840 | |
---|
841 | var args = this._tokenize(input.typed); |
---|
842 | this._split(args); |
---|
843 | |
---|
844 | if (this.commandAssignment.value) { |
---|
845 | this._assign(args); |
---|
846 | } |
---|
847 | |
---|
848 | this._updateHints(); |
---|
849 | }; |
---|
850 | |
---|
851 | /** |
---|
852 | * Return an array of Status scores so we can create a marked up |
---|
853 | * version of the command line input. |
---|
854 | */ |
---|
855 | CliRequisition.prototype.getInputStatusMarkup = function() { |
---|
856 | // 'scores' is an array which tells us what chars are errors |
---|
857 | // Initialize with everything VALID |
---|
858 | var scores = this.toString().split('').map(function(ch) { |
---|
859 | return Status.VALID; |
---|
860 | }); |
---|
861 | // For all chars in all hints, check and upgrade the score |
---|
862 | this._hints.forEach(function(hint) { |
---|
863 | for (var i = hint.start; i <= hint.end; i++) { |
---|
864 | if (hint.status > scores[i]) { |
---|
865 | scores[i] = hint.status; |
---|
866 | } |
---|
867 | } |
---|
868 | }, this); |
---|
869 | return scores; |
---|
870 | }; |
---|
871 | |
---|
872 | /** |
---|
873 | * Reconstitute the input from the args |
---|
874 | */ |
---|
875 | CliRequisition.prototype.toString = function() { |
---|
876 | return this.getAssignments(true).map(function(assignment) { |
---|
877 | return assignment.toString(); |
---|
878 | }, this).join(''); |
---|
879 | }; |
---|
880 | |
---|
881 | var superUpdateHints = CliRequisition.prototype._updateHints; |
---|
882 | /** |
---|
883 | * Marks up hints in a number of ways: |
---|
884 | * - Makes INCOMPLETE hints that are not near the cursor INVALID since |
---|
885 | * they can't be completed by typing |
---|
886 | * - Finds the most severe hint, and annotates the array with it |
---|
887 | * - Finds the hint to display, and also annotates the array with it |
---|
888 | * TODO: I'm wondering if array annotation is evil and we should replace |
---|
889 | * this with an object. Need to find out more. |
---|
890 | */ |
---|
891 | CliRequisition.prototype._updateHints = function() { |
---|
892 | superUpdateHints.call(this); |
---|
893 | |
---|
894 | // Not knowing about cursor positioning, the requisition and assignments |
---|
895 | // can't know this, but anything they mark as INCOMPLETE is actually |
---|
896 | // INVALID unless the cursor is actually inside that argument. |
---|
897 | var c = this.input.cursor; |
---|
898 | this._hints.forEach(function(hint) { |
---|
899 | var startInHint = c.start >= hint.start && c.start <= hint.end; |
---|
900 | var endInHint = c.end >= hint.start && c.end <= hint.end; |
---|
901 | var inHint = startInHint || endInHint; |
---|
902 | if (!inHint && hint.status === Status.INCOMPLETE) { |
---|
903 | hint.status = Status.INVALID; |
---|
904 | } |
---|
905 | }, this); |
---|
906 | |
---|
907 | Hint.sort(this._hints); |
---|
908 | }; |
---|
909 | |
---|
910 | /** |
---|
911 | * Accessor for the hints array. |
---|
912 | * While we could just use the hints property, using getHints() is |
---|
913 | * preferred for symmetry with Requisition where it needs a function due to |
---|
914 | * lack of an atomic update system. |
---|
915 | */ |
---|
916 | CliRequisition.prototype.getHints = function() { |
---|
917 | return this._hints; |
---|
918 | }; |
---|
919 | |
---|
920 | /** |
---|
921 | * Look through the arguments attached to our assignments for the assignment |
---|
922 | * at the given position. |
---|
923 | */ |
---|
924 | CliRequisition.prototype.getAssignmentAt = function(position) { |
---|
925 | var assignments = this.getAssignments(true); |
---|
926 | for (var i = 0; i < assignments.length; i++) { |
---|
927 | var assignment = assignments[i]; |
---|
928 | if (!assignment.arg) { |
---|
929 | // There is no argument in this assignment, we've fallen off |
---|
930 | // the end of the obvious answers - it must be this one. |
---|
931 | return assignment; |
---|
932 | } |
---|
933 | if (assignment.isPositionCaptured(position)) { |
---|
934 | return assignment; |
---|
935 | } |
---|
936 | } |
---|
937 | |
---|
938 | return assignment; |
---|
939 | }; |
---|
940 | |
---|
941 | /** |
---|
942 | * Split up the input taking into account ' and " |
---|
943 | */ |
---|
944 | CliRequisition.prototype._tokenize = function(typed) { |
---|
945 | // For blank input, place a dummy empty argument into the list |
---|
946 | if (typed == null || typed.length === 0) { |
---|
947 | return [ new Argument(this, '', 0, 0, '', '') ]; |
---|
948 | } |
---|
949 | |
---|
950 | var OUTSIDE = 1; // The last character was whitespace |
---|
951 | var IN_SIMPLE = 2; // The last character was part of a parameter |
---|
952 | var IN_SINGLE_Q = 3; // We're inside a single quote: ' |
---|
953 | var IN_DOUBLE_Q = 4; // We're inside double quotes: " |
---|
954 | |
---|
955 | var mode = OUTSIDE; |
---|
956 | |
---|
957 | // First we un-escape. This list was taken from: |
---|
958 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Unicode |
---|
959 | // We are generally converting to their real values except for \', \" |
---|
960 | // and '\ ' which we are converting to unicode private characters so we |
---|
961 | // can distinguish them from ', " and ' ', which have special meaning. |
---|
962 | // They need swapping back post-split - see unescape2() |
---|
963 | typed = typed |
---|
964 | .replace(/\\\\/g, '\\') |
---|
965 | .replace(/\\b/g, '\b') |
---|
966 | .replace(/\\f/g, '\f') |
---|
967 | .replace(/\\n/g, '\n') |
---|
968 | .replace(/\\r/g, '\r') |
---|
969 | .replace(/\\t/g, '\t') |
---|
970 | .replace(/\\v/g, '\v') |
---|
971 | .replace(/\\n/g, '\n') |
---|
972 | .replace(/\\r/g, '\r') |
---|
973 | .replace(/\\ /g, '\uF000') |
---|
974 | .replace(/\\'/g, '\uF001') |
---|
975 | .replace(/\\"/g, '\uF002'); |
---|
976 | |
---|
977 | function unescape2(str) { |
---|
978 | return str |
---|
979 | .replace(/\uF000/g, ' ') |
---|
980 | .replace(/\uF001/g, '\'') |
---|
981 | .replace(/\uF002/g, '"'); |
---|
982 | } |
---|
983 | |
---|
984 | var i = 0; |
---|
985 | var start = 0; // Where did this section start? |
---|
986 | var prefix = ''; |
---|
987 | var args = []; |
---|
988 | |
---|
989 | while (true) { |
---|
990 | if (i >= typed.length) { |
---|
991 | // There is nothing else to read - tidy up |
---|
992 | if (mode !== OUTSIDE) { |
---|
993 | var str = unescape2(typed.substring(start, i)); |
---|
994 | args.push(new Argument(this, str, start, i, prefix, '')); |
---|
995 | } |
---|
996 | else { |
---|
997 | if (i !== start) { |
---|
998 | // There's a bunch of whitespace at the end of the |
---|
999 | // command add it to the last argument's suffix, |
---|
1000 | // creating an empty argument if needed. |
---|
1001 | var extra = typed.substring(start, i); |
---|
1002 | var lastArg = args[args.length - 1]; |
---|
1003 | if (!lastArg) { |
---|
1004 | lastArg = new Argument(this, '', i, i, extra, ''); |
---|
1005 | args.push(lastArg); |
---|
1006 | } |
---|
1007 | else { |
---|
1008 | lastArg.suffix += extra; |
---|
1009 | } |
---|
1010 | } |
---|
1011 | } |
---|
1012 | break; |
---|
1013 | } |
---|
1014 | |
---|
1015 | var c = typed[i]; |
---|
1016 | switch (mode) { |
---|
1017 | case OUTSIDE: |
---|
1018 | if (c === '\'') { |
---|
1019 | prefix = typed.substring(start, i + 1); |
---|
1020 | mode = IN_SINGLE_Q; |
---|
1021 | start = i + 1; |
---|
1022 | } |
---|
1023 | else if (c === '"') { |
---|
1024 | prefix = typed.substring(start, i + 1); |
---|
1025 | mode = IN_DOUBLE_Q; |
---|
1026 | start = i + 1; |
---|
1027 | } |
---|
1028 | else if (/ /.test(c)) { |
---|
1029 | // Still whitespace, do nothing |
---|
1030 | } |
---|
1031 | else { |
---|
1032 | prefix = typed.substring(start, i); |
---|
1033 | mode = IN_SIMPLE; |
---|
1034 | start = i; |
---|
1035 | } |
---|
1036 | break; |
---|
1037 | |
---|
1038 | case IN_SIMPLE: |
---|
1039 | // There is an edge case of xx'xx which we are assuming to |
---|
1040 | // be a single parameter (and same with ") |
---|
1041 | if (c === ' ') { |
---|
1042 | var str = unescape2(typed.substring(start, i)); |
---|
1043 | args.push(new Argument(this, str, |
---|
1044 | start, i, prefix, '')); |
---|
1045 | mode = OUTSIDE; |
---|
1046 | start = i; |
---|
1047 | prefix = ''; |
---|
1048 | } |
---|
1049 | break; |
---|
1050 | |
---|
1051 | case IN_SINGLE_Q: |
---|
1052 | if (c === '\'') { |
---|
1053 | var str = unescape2(typed.substring(start, i)); |
---|
1054 | args.push(new Argument(this, str, |
---|
1055 | start - 1, i + 1, prefix, c)); |
---|
1056 | mode = OUTSIDE; |
---|
1057 | start = i + 1; |
---|
1058 | prefix = ''; |
---|
1059 | } |
---|
1060 | break; |
---|
1061 | |
---|
1062 | case IN_DOUBLE_Q: |
---|
1063 | if (c === '"') { |
---|
1064 | var str = unescape2(typed.substring(start, i)); |
---|
1065 | args.push(new Argument(this, str, |
---|
1066 | start - 1, i + 1, prefix, c)); |
---|
1067 | mode = OUTSIDE; |
---|
1068 | start = i + 1; |
---|
1069 | prefix = ''; |
---|
1070 | } |
---|
1071 | break; |
---|
1072 | } |
---|
1073 | |
---|
1074 | i++; |
---|
1075 | } |
---|
1076 | |
---|
1077 | return args; |
---|
1078 | }; |
---|
1079 | |
---|
1080 | /** |
---|
1081 | * Looks in the canon for a command extension that matches what has been |
---|
1082 | * typed at the command line. |
---|
1083 | */ |
---|
1084 | CliRequisition.prototype._split = function(args) { |
---|
1085 | var argsUsed = 1; |
---|
1086 | var arg; |
---|
1087 | |
---|
1088 | while (argsUsed <= args.length) { |
---|
1089 | var arg = Argument.merge(args, 0, argsUsed); |
---|
1090 | this.commandAssignment.setArgument(arg); |
---|
1091 | |
---|
1092 | if (!this.commandAssignment.value) { |
---|
1093 | // Not found. break with value == null |
---|
1094 | break; |
---|
1095 | } |
---|
1096 | |
---|
1097 | /* |
---|
1098 | // Previously we needed a way to hide commands depending context. |
---|
1099 | // We have not resurrected that feature yet. |
---|
1100 | if (!keyboard.flagsMatch(command.predicates, this.flags)) { |
---|
1101 | // If the predicates say 'no match' then go LA LA LA |
---|
1102 | command = null; |
---|
1103 | break; |
---|
1104 | } |
---|
1105 | */ |
---|
1106 | |
---|
1107 | if (this.commandAssignment.value.exec) { |
---|
1108 | // Valid command, break with command valid |
---|
1109 | for (var i = 0; i < argsUsed; i++) { |
---|
1110 | args.shift(); |
---|
1111 | } |
---|
1112 | break; |
---|
1113 | } |
---|
1114 | |
---|
1115 | argsUsed++; |
---|
1116 | } |
---|
1117 | }; |
---|
1118 | |
---|
1119 | /** |
---|
1120 | * Work out which arguments are applicable to which parameters. |
---|
1121 | * <p>This takes #_command.params and #_unparsedArgs and creates a map of |
---|
1122 | * param names to 'assignment' objects, which have the following properties: |
---|
1123 | * <ul> |
---|
1124 | * <li>param - The matching parameter. |
---|
1125 | * <li>index - Zero based index into where the match came from on the input |
---|
1126 | * <li>value - The matching input |
---|
1127 | * </ul> |
---|
1128 | */ |
---|
1129 | CliRequisition.prototype._assign = function(args) { |
---|
1130 | if (args.length === 0) { |
---|
1131 | this.setDefaultValues(); |
---|
1132 | return; |
---|
1133 | } |
---|
1134 | |
---|
1135 | // Create an error if the command does not take parameters, but we have |
---|
1136 | // been given them ... |
---|
1137 | if (this.assignmentCount === 0) { |
---|
1138 | // TODO: previously we were doing some extra work to avoid this if |
---|
1139 | // we determined that we had args that were all whitespace, but |
---|
1140 | // probably given our tighter tokenize() this won't be an issue? |
---|
1141 | this._hints.push(new Hint(Status.INVALID, |
---|
1142 | this.commandAssignment.value.name + |
---|
1143 | ' does not take any parameters', |
---|
1144 | Argument.merge(args))); |
---|
1145 | return; |
---|
1146 | } |
---|
1147 | |
---|
1148 | // Special case: if there is only 1 parameter, and that's of type |
---|
1149 | // text we put all the params into the first param |
---|
1150 | if (this.assignmentCount === 1) { |
---|
1151 | var assignment = this.getAssignment(0); |
---|
1152 | if (assignment.param.type.name === 'text') { |
---|
1153 | assignment.setArgument(Argument.merge(args)); |
---|
1154 | return; |
---|
1155 | } |
---|
1156 | } |
---|
1157 | |
---|
1158 | var assignments = this.cloneAssignments(); |
---|
1159 | var names = this.getParameterNames(); |
---|
1160 | |
---|
1161 | // Extract all the named parameters |
---|
1162 | var used = []; |
---|
1163 | assignments.forEach(function(assignment) { |
---|
1164 | var namedArgText = '--' + assignment.name; |
---|
1165 | |
---|
1166 | var i = 0; |
---|
1167 | while (true) { |
---|
1168 | var arg = args[i]; |
---|
1169 | if (namedArgText !== arg.text) { |
---|
1170 | i++; |
---|
1171 | if (i >= args.length) { |
---|
1172 | break; |
---|
1173 | } |
---|
1174 | continue; |
---|
1175 | } |
---|
1176 | |
---|
1177 | // boolean parameters don't have values, default to false |
---|
1178 | if (assignment.param.type.name === 'boolean') { |
---|
1179 | assignment.setValue(true); |
---|
1180 | } |
---|
1181 | else { |
---|
1182 | if (i + 1 < args.length) { |
---|
1183 | // Missing value portion of this named param |
---|
1184 | this._hints.push(new Hint(Status.INCOMPLETE, |
---|
1185 | 'Missing value for: ' + namedArgText, |
---|
1186 | args[i])); |
---|
1187 | } |
---|
1188 | else { |
---|
1189 | args.splice(i + 1, 1); |
---|
1190 | assignment.setArgument(args[i + 1]); |
---|
1191 | } |
---|
1192 | } |
---|
1193 | |
---|
1194 | lang.arrayRemove(names, assignment.name); |
---|
1195 | args.splice(i, 1); |
---|
1196 | // We don't need to i++ if we splice |
---|
1197 | } |
---|
1198 | }, this); |
---|
1199 | |
---|
1200 | // What's left are positional parameters assign in order |
---|
1201 | names.forEach(function(name) { |
---|
1202 | var assignment = this.getAssignment(name); |
---|
1203 | if (args.length === 0) { |
---|
1204 | // No more values |
---|
1205 | assignment.setValue(undefined); // i.e. default |
---|
1206 | } |
---|
1207 | else { |
---|
1208 | var arg = args[0]; |
---|
1209 | args.splice(0, 1); |
---|
1210 | assignment.setArgument(arg); |
---|
1211 | } |
---|
1212 | }, this); |
---|
1213 | |
---|
1214 | if (args.length > 0) { |
---|
1215 | var remaining = Argument.merge(args); |
---|
1216 | this._hints.push(new Hint(Status.INVALID, |
---|
1217 | 'Input \'' + remaining.text + '\' makes no sense.', |
---|
1218 | remaining)); |
---|
1219 | } |
---|
1220 | }; |
---|
1221 | |
---|
1222 | })(); |
---|
1223 | exports.CliRequisition = CliRequisition; |
---|
1224 | |
---|
1225 | |
---|
1226 | }); |
---|
1227 | /* ***** BEGIN LICENSE BLOCK ***** |
---|
1228 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
---|
1229 | * |
---|
1230 | * The contents of this file are subject to the Mozilla Public License Version |
---|
1231 | * 1.1 (the "License"); you may not use this file except in compliance with |
---|
1232 | * the License. You may obtain a copy of the License at |
---|
1233 | * http://www.mozilla.org/MPL/ |
---|
1234 | * |
---|
1235 | * Software distributed under the License is distributed on an "AS IS" basis, |
---|
1236 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
---|
1237 | * for the specific language governing rights and limitations under the |
---|
1238 | * License. |
---|
1239 | * |
---|
1240 | * The Original Code is Mozilla Skywriter. |
---|
1241 | * |
---|
1242 | * The Initial Developer of the Original Code is |
---|
1243 | * Mozilla. |
---|
1244 | * Portions created by the Initial Developer are Copyright (C) 2009 |
---|
1245 | * the Initial Developer. All Rights Reserved. |
---|
1246 | * |
---|
1247 | * Contributor(s): |
---|
1248 | * Joe Walker (jwalker@mozilla.com) |
---|
1249 | * |
---|
1250 | * Alternatively, the contents of this file may be used under the terms of |
---|
1251 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
---|
1252 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
---|
1253 | * in which case the provisions of the GPL or the LGPL are applicable instead |
---|
1254 | * of those above. If you wish to allow use of your version of this file only |
---|
1255 | * under the terms of either the GPL or the LGPL, and not to allow others to |
---|
1256 | * use your version of this file under the terms of the MPL, indicate your |
---|
1257 | * decision by deleting the provisions above and replace them with the notice |
---|
1258 | * and other provisions required by the GPL or the LGPL. If you do not delete |
---|
1259 | * the provisions above, a recipient may use your version of this file under |
---|
1260 | * the terms of any one of the MPL, the GPL or the LGPL. |
---|
1261 | * |
---|
1262 | * ***** END LICENSE BLOCK ***** */ |
---|
1263 | |
---|
1264 | define('cockpit/ui/settings', ['require', 'exports', 'module' , 'pilot/types', 'pilot/types/basic'], function(require, exports, module) { |
---|
1265 | |
---|
1266 | |
---|
1267 | var types = require("pilot/types"); |
---|
1268 | var SelectionType = require('pilot/types/basic').SelectionType; |
---|
1269 | |
---|
1270 | var direction = new SelectionType({ |
---|
1271 | name: 'direction', |
---|
1272 | data: [ 'above', 'below' ] |
---|
1273 | }); |
---|
1274 | |
---|
1275 | var hintDirectionSetting = { |
---|
1276 | name: "hintDirection", |
---|
1277 | description: "Are hints shown above or below the command line?", |
---|
1278 | type: "direction", |
---|
1279 | defaultValue: "above" |
---|
1280 | }; |
---|
1281 | |
---|
1282 | var outputDirectionSetting = { |
---|
1283 | name: "outputDirection", |
---|
1284 | description: "Is the output window shown above or below the command line?", |
---|
1285 | type: "direction", |
---|
1286 | defaultValue: "above" |
---|
1287 | }; |
---|
1288 | |
---|
1289 | var outputHeightSetting = { |
---|
1290 | name: "outputHeight", |
---|
1291 | description: "What height should the output panel be?", |
---|
1292 | type: "number", |
---|
1293 | defaultValue: 300 |
---|
1294 | }; |
---|
1295 | |
---|
1296 | exports.startup = function(data, reason) { |
---|
1297 | types.registerType(direction); |
---|
1298 | data.env.settings.addSetting(hintDirectionSetting); |
---|
1299 | data.env.settings.addSetting(outputDirectionSetting); |
---|
1300 | data.env.settings.addSetting(outputHeightSetting); |
---|
1301 | }; |
---|
1302 | |
---|
1303 | exports.shutdown = function(data, reason) { |
---|
1304 | types.unregisterType(direction); |
---|
1305 | data.env.settings.removeSetting(hintDirectionSetting); |
---|
1306 | data.env.settings.removeSetting(outputDirectionSetting); |
---|
1307 | data.env.settings.removeSetting(outputHeightSetting); |
---|
1308 | }; |
---|
1309 | |
---|
1310 | |
---|
1311 | }); |
---|
1312 | /* ***** BEGIN LICENSE BLOCK ***** |
---|
1313 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
---|
1314 | * |
---|
1315 | * The contents of this file are subject to the Mozilla Public License Version |
---|
1316 | * 1.1 (the "License"); you may not use this file except in compliance with |
---|
1317 | * the License. You may obtain a copy of the License at |
---|
1318 | * http://www.mozilla.org/MPL/ |
---|
1319 | * |
---|
1320 | * Software distributed under the License is distributed on an "AS IS" basis, |
---|
1321 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
---|
1322 | * for the specific language governing rights and limitations under the |
---|
1323 | * License. |
---|
1324 | * |
---|
1325 | * The Original Code is Skywriter. |
---|
1326 | * |
---|
1327 | * The Initial Developer of the Original Code is |
---|
1328 | * Mozilla. |
---|
1329 | * Portions created by the Initial Developer are Copyright (C) 2009 |
---|
1330 | * the Initial Developer. All Rights Reserved. |
---|
1331 | * |
---|
1332 | * Contributor(s): |
---|
1333 | * Joe Walker (jwalker@mozilla.com) |
---|
1334 | * |
---|
1335 | * Alternatively, the contents of this file may be used under the terms of |
---|
1336 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
---|
1337 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
---|
1338 | * in which case the provisions of the GPL or the LGPL are applicable instead |
---|
1339 | * of those above. If you wish to allow use of your version of this file only |
---|
1340 | * under the terms of either the GPL or the LGPL, and not to allow others to |
---|
1341 | * use your version of this file under the terms of the MPL, indicate your |
---|
1342 | * decision by deleting the provisions above and replace them with the notice |
---|
1343 | * and other provisions required by the GPL or the LGPL. If you do not delete |
---|
1344 | * the provisions above, a recipient may use your version of this file under |
---|
1345 | * the terms of any one of the MPL, the GPL or the LGPL. |
---|
1346 | * |
---|
1347 | * ***** END LICENSE BLOCK ***** */ |
---|
1348 | |
---|
1349 | define('cockpit/ui/cli_view', ['require', 'exports', 'module' , 'text!cockpit/ui/cli_view.css', 'pilot/event', 'pilot/dom', 'pilot/keys', 'pilot/canon', 'pilot/types', 'cockpit/cli', 'cockpit/ui/request_view'], function(require, exports, module) { |
---|
1350 | |
---|
1351 | |
---|
1352 | var editorCss = require("text!cockpit/ui/cli_view.css"); |
---|
1353 | var event = require("pilot/event"); |
---|
1354 | var dom = require("pilot/dom"); |
---|
1355 | |
---|
1356 | dom.importCssString(editorCss); |
---|
1357 | |
---|
1358 | var event = require("pilot/event"); |
---|
1359 | var keys = require("pilot/keys"); |
---|
1360 | var canon = require("pilot/canon"); |
---|
1361 | var Status = require('pilot/types').Status; |
---|
1362 | |
---|
1363 | var CliRequisition = require('cockpit/cli').CliRequisition; |
---|
1364 | var Hint = require('cockpit/cli').Hint; |
---|
1365 | var RequestView = require('cockpit/ui/request_view').RequestView; |
---|
1366 | |
---|
1367 | var NO_HINT = new Hint(Status.VALID, '', 0, 0); |
---|
1368 | |
---|
1369 | /** |
---|
1370 | * On startup we need to: |
---|
1371 | * 1. Add 3 sets of elements to the DOM for: |
---|
1372 | * - command line output |
---|
1373 | * - input hints |
---|
1374 | * - completion |
---|
1375 | * 2. Attach a set of events so the command line works |
---|
1376 | */ |
---|
1377 | exports.startup = function(data, reason) { |
---|
1378 | var cli = new CliRequisition(data.env); |
---|
1379 | var cliView = new CliView(cli, data.env); |
---|
1380 | data.env.cli = cli; |
---|
1381 | }; |
---|
1382 | |
---|
1383 | /** |
---|
1384 | * A class to handle the simplest UI implementation |
---|
1385 | */ |
---|
1386 | function CliView(cli, env) { |
---|
1387 | cli.cliView = this; |
---|
1388 | this.cli = cli; |
---|
1389 | this.doc = document; |
---|
1390 | this.win = dom.getParentWindow(this.doc); |
---|
1391 | this.env = env; |
---|
1392 | |
---|
1393 | // TODO: we should have a better way to specify command lines??? |
---|
1394 | this.element = this.doc.getElementById('cockpitInput'); |
---|
1395 | if (!this.element) { |
---|
1396 | // console.log('No element with an id of cockpit. Bailing on cli'); |
---|
1397 | return; |
---|
1398 | } |
---|
1399 | |
---|
1400 | this.settings = env.settings; |
---|
1401 | this.hintDirection = this.settings.getSetting('hintDirection'); |
---|
1402 | this.outputDirection = this.settings.getSetting('outputDirection'); |
---|
1403 | this.outputHeight = this.settings.getSetting('outputHeight'); |
---|
1404 | |
---|
1405 | // If the requisition tells us something has changed, we use this to know |
---|
1406 | // if we should ignore it |
---|
1407 | this.isUpdating = false; |
---|
1408 | |
---|
1409 | this.createElements(); |
---|
1410 | this.update(); |
---|
1411 | } |
---|
1412 | CliView.prototype = { |
---|
1413 | /** |
---|
1414 | * Create divs for completion, hints and output |
---|
1415 | */ |
---|
1416 | createElements: function() { |
---|
1417 | var input = this.element; |
---|
1418 | |
---|
1419 | this.element.spellcheck = false; |
---|
1420 | |
---|
1421 | this.output = this.doc.getElementById('cockpitOutput'); |
---|
1422 | this.popupOutput = (this.output == null); |
---|
1423 | if (!this.output) { |
---|
1424 | this.output = this.doc.createElement('div'); |
---|
1425 | this.output.id = 'cockpitOutput'; |
---|
1426 | this.output.className = 'cptOutput'; |
---|
1427 | input.parentNode.insertBefore(this.output, input.nextSibling); |
---|
1428 | |
---|
1429 | var setMaxOutputHeight = function() { |
---|
1430 | this.output.style.maxHeight = this.outputHeight.get() + 'px'; |
---|
1431 | }.bind(this); |
---|
1432 | this.outputHeight.addEventListener('change', setMaxOutputHeight); |
---|
1433 | setMaxOutputHeight(); |
---|
1434 | } |
---|
1435 | |
---|
1436 | this.completer = this.doc.createElement('div'); |
---|
1437 | this.completer.className = 'cptCompletion VALID'; |
---|
1438 | |
---|
1439 | this.completer.style.color = dom.computedStyle(input, "color"); |
---|
1440 | this.completer.style.fontSize = dom.computedStyle(input, "fontSize"); |
---|
1441 | this.completer.style.fontFamily = dom.computedStyle(input, "fontFamily"); |
---|
1442 | this.completer.style.fontWeight = dom.computedStyle(input, "fontWeight"); |
---|
1443 | this.completer.style.fontStyle = dom.computedStyle(input, "fontStyle"); |
---|
1444 | input.parentNode.insertBefore(this.completer, input.nextSibling); |
---|
1445 | |
---|
1446 | // Transfer background styling to the completer. |
---|
1447 | this.completer.style.backgroundColor = input.style.backgroundColor; |
---|
1448 | input.style.backgroundColor = 'transparent'; |
---|
1449 | |
---|
1450 | this.hinter = this.doc.createElement('div'); |
---|
1451 | this.hinter.className = 'cptHints'; |
---|
1452 | input.parentNode.insertBefore(this.hinter, input.nextSibling); |
---|
1453 | |
---|
1454 | var resizer = this.resizer.bind(this); |
---|
1455 | event.addListener(this.win, 'resize', resizer); |
---|
1456 | this.hintDirection.addEventListener('change', resizer); |
---|
1457 | this.outputDirection.addEventListener('change', resizer); |
---|
1458 | resizer(); |
---|
1459 | |
---|
1460 | canon.addEventListener('output', function(ev) { |
---|
1461 | new RequestView(ev.request, this); |
---|
1462 | }.bind(this)); |
---|
1463 | event.addCommandKeyListener(input, this.onCommandKey.bind(this)); |
---|
1464 | event.addListener(input, 'keyup', this.onKeyUp.bind(this)); |
---|
1465 | |
---|
1466 | // cursor position affects hint severity. TODO: shortcuts for speed |
---|
1467 | event.addListener(input, 'mouseup', function(ev) { |
---|
1468 | this.isUpdating = true; |
---|
1469 | this.update(); |
---|
1470 | this.isUpdating = false; |
---|
1471 | }.bind(this)); |
---|
1472 | |
---|
1473 | this.cli.addEventListener('argumentChange', this.onArgChange.bind(this)); |
---|
1474 | |
---|
1475 | event.addListener(input, "focus", function() { |
---|
1476 | dom.addCssClass(this.output, "cptFocusPopup"); |
---|
1477 | dom.addCssClass(this.hinter, "cptFocusPopup"); |
---|
1478 | }.bind(this)); |
---|
1479 | |
---|
1480 | function hideOutput() { |
---|
1481 | dom.removeCssClass(this.output, "cptFocusPopup"); |
---|
1482 | dom.removeCssClass(this.hinter, "cptFocusPopup"); |
---|
1483 | }; |
---|
1484 | event.addListener(input, "blur", hideOutput.bind(this)); |
---|
1485 | hideOutput.call(this); |
---|
1486 | }, |
---|
1487 | |
---|
1488 | /** |
---|
1489 | * We need to see the output of the latest command entered |
---|
1490 | */ |
---|
1491 | scrollOutputToBottom: function() { |
---|
1492 | // Certain browsers have a bug such that scrollHeight is too small |
---|
1493 | // when content does not fill the client area of the element |
---|
1494 | var scrollHeight = Math.max(this.output.scrollHeight, this.output.clientHeight); |
---|
1495 | this.output.scrollTop = scrollHeight - this.output.clientHeight; |
---|
1496 | }, |
---|
1497 | |
---|
1498 | /** |
---|
1499 | * To be called on window resize or any time we want to align the elements |
---|
1500 | * with the input box. |
---|
1501 | */ |
---|
1502 | resizer: function() { |
---|
1503 | var rect = this.element.getClientRects()[0]; |
---|
1504 | |
---|
1505 | this.completer.style.top = rect.top + 'px'; |
---|
1506 | var height = rect.bottom - rect.top; |
---|
1507 | this.completer.style.height = height + 'px'; |
---|
1508 | this.completer.style.lineHeight = height + 'px'; |
---|
1509 | this.completer.style.left = rect.left + 'px'; |
---|
1510 | var width = rect.right - rect.left; |
---|
1511 | this.completer.style.width = width + 'px'; |
---|
1512 | |
---|
1513 | if (this.hintDirection.get() === 'below') { |
---|
1514 | this.hinter.style.top = rect.bottom + 'px'; |
---|
1515 | this.hinter.style.bottom = 'auto'; |
---|
1516 | } |
---|
1517 | else { |
---|
1518 | this.hinter.style.top = 'auto'; |
---|
1519 | this.hinter.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px'; |
---|
1520 | } |
---|
1521 | this.hinter.style.left = (rect.left + 30) + 'px'; |
---|
1522 | this.hinter.style.maxWidth = (width - 110) + 'px'; |
---|
1523 | |
---|
1524 | if (this.popupOutput) { |
---|
1525 | if (this.outputDirection.get() === 'below') { |
---|
1526 | this.output.style.top = rect.bottom + 'px'; |
---|
1527 | this.output.style.bottom = 'auto'; |
---|
1528 | } |
---|
1529 | else { |
---|
1530 | this.output.style.top = 'auto'; |
---|
1531 | this.output.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px'; |
---|
1532 | } |
---|
1533 | this.output.style.left = rect.left + 'px'; |
---|
1534 | this.output.style.width = (width - 80) + 'px'; |
---|
1535 | } |
---|
1536 | }, |
---|
1537 | |
---|
1538 | /** |
---|
1539 | * Ensure that TAB isn't handled by the browser |
---|
1540 | */ |
---|
1541 | onCommandKey: function(ev, hashId, keyCode) { |
---|
1542 | var stopEvent; |
---|
1543 | if (keyCode === keys.TAB || |
---|
1544 | keyCode === keys.UP || |
---|
1545 | keyCode === keys.DOWN) { |
---|
1546 | stopEvent = true; |
---|
1547 | } else if (hashId != 0 || keyCode != 0) { |
---|
1548 | stopEvent = canon.execKeyCommand(this.env, 'cli', hashId, keyCode); |
---|
1549 | } |
---|
1550 | stopEvent && event.stopEvent(ev); |
---|
1551 | }, |
---|
1552 | |
---|
1553 | /** |
---|
1554 | * The main keyboard processing loop |
---|
1555 | */ |
---|
1556 | onKeyUp: function(ev) { |
---|
1557 | var handled; |
---|
1558 | /* |
---|
1559 | var handled = keyboardManager.processKeyEvent(ev, this, { |
---|
1560 | isCommandLine: true, isKeyUp: true |
---|
1561 | }); |
---|
1562 | */ |
---|
1563 | |
---|
1564 | // RETURN does a special exec/highlight thing |
---|
1565 | if (ev.keyCode === keys.RETURN) { |
---|
1566 | var worst = this.cli.getWorstHint(); |
---|
1567 | // Deny RETURN unless the command might work |
---|
1568 | if (worst.status === Status.VALID) { |
---|
1569 | this.cli.exec(); |
---|
1570 | this.element.value = ''; |
---|
1571 | } |
---|
1572 | else { |
---|
1573 | // If we've denied RETURN because the command was not VALID, |
---|
1574 | // select the part of the command line that is causing problems |
---|
1575 | // TODO: if there are 2 errors are we picking the right one? |
---|
1576 | dom.setSelectionStart(this.element, worst.start); |
---|
1577 | dom.setSelectionEnd(this.element, worst.end); |
---|
1578 | } |
---|
1579 | } |
---|
1580 | |
---|
1581 | this.update(); |
---|
1582 | |
---|
1583 | // Special actions which delegate to the assignment |
---|
1584 | var current = this.cli.getAssignmentAt(dom.getSelectionStart(this.element)); |
---|
1585 | if (current) { |
---|
1586 | // TAB does a special complete thing |
---|
1587 | if (ev.keyCode === keys.TAB) { |
---|
1588 | current.complete(); |
---|
1589 | this.update(); |
---|
1590 | } |
---|
1591 | |
---|
1592 | // UP/DOWN look for some history |
---|
1593 | if (ev.keyCode === keys.UP) { |
---|
1594 | current.increment(); |
---|
1595 | this.update(); |
---|
1596 | } |
---|
1597 | if (ev.keyCode === keys.DOWN) { |
---|
1598 | current.decrement(); |
---|
1599 | this.update(); |
---|
1600 | } |
---|
1601 | } |
---|
1602 | |
---|
1603 | return handled; |
---|
1604 | }, |
---|
1605 | |
---|
1606 | /** |
---|
1607 | * Actually parse the input and make sure we're all up to date |
---|
1608 | */ |
---|
1609 | update: function() { |
---|
1610 | this.isUpdating = true; |
---|
1611 | var input = { |
---|
1612 | typed: this.element.value, |
---|
1613 | cursor: { |
---|
1614 | start: dom.getSelectionStart(this.element), |
---|
1615 | end: dom.getSelectionEnd(this.element.selectionEnd) |
---|
1616 | } |
---|
1617 | }; |
---|
1618 | this.cli.update(input); |
---|
1619 | |
---|
1620 | var display = this.cli.getAssignmentAt(input.cursor.start).getHint(); |
---|
1621 | |
---|
1622 | // 1. Update the completer with prompt/error marker/TAB info |
---|
1623 | dom.removeCssClass(this.completer, Status.VALID.toString()); |
---|
1624 | dom.removeCssClass(this.completer, Status.INCOMPLETE.toString()); |
---|
1625 | dom.removeCssClass(this.completer, Status.INVALID.toString()); |
---|
1626 | |
---|
1627 | var completion = '<span class="cptPrompt">></span> '; |
---|
1628 | if (this.element.value.length > 0) { |
---|
1629 | var scores = this.cli.getInputStatusMarkup(); |
---|
1630 | completion += this.markupStatusScore(scores); |
---|
1631 | } |
---|
1632 | |
---|
1633 | // Display the "-> prediction" at the end of the completer |
---|
1634 | if (this.element.value.length > 0 && |
---|
1635 | display.predictions && display.predictions.length > 0) { |
---|
1636 | var tab = display.predictions[0]; |
---|
1637 | completion += ' ⇥ ' + (tab.name ? tab.name : tab); |
---|
1638 | } |
---|
1639 | this.completer.innerHTML = completion; |
---|
1640 | dom.addCssClass(this.completer, this.cli.getWorstHint().status.toString()); |
---|
1641 | |
---|
1642 | // 2. Update the hint element |
---|
1643 | var hint = ''; |
---|
1644 | if (this.element.value.length !== 0) { |
---|
1645 | hint += display.message; |
---|
1646 | if (display.predictions && display.predictions.length > 0) { |
---|
1647 | hint += ': [ '; |
---|
1648 | display.predictions.forEach(function(prediction) { |
---|
1649 | hint += (prediction.name ? prediction.name : prediction); |
---|
1650 | hint += ' | '; |
---|
1651 | }, this); |
---|
1652 | hint = hint.replace(/\| $/, ']'); |
---|
1653 | } |
---|
1654 | } |
---|
1655 | |
---|
1656 | this.hinter.innerHTML = hint; |
---|
1657 | if (hint.length === 0) { |
---|
1658 | dom.addCssClass(this.hinter, 'cptNoPopup'); |
---|
1659 | } |
---|
1660 | else { |
---|
1661 | dom.removeCssClass(this.hinter, 'cptNoPopup'); |
---|
1662 | } |
---|
1663 | |
---|
1664 | this.isUpdating = false; |
---|
1665 | }, |
---|
1666 | |
---|
1667 | /** |
---|
1668 | * Markup an array of Status values with spans |
---|
1669 | */ |
---|
1670 | markupStatusScore: function(scores) { |
---|
1671 | var completion = ''; |
---|
1672 | // Create mark-up |
---|
1673 | var i = 0; |
---|
1674 | var lastStatus = -1; |
---|
1675 | while (true) { |
---|
1676 | if (lastStatus !== scores[i]) { |
---|
1677 | completion += '<span class=' + scores[i].toString() + '>'; |
---|
1678 | lastStatus = scores[i]; |
---|
1679 | } |
---|
1680 | completion += this.element.value[i]; |
---|
1681 | i++; |
---|
1682 | if (i === this.element.value.length) { |
---|
1683 | completion += '</span>'; |
---|
1684 | break; |
---|
1685 | } |
---|
1686 | if (lastStatus !== scores[i]) { |
---|
1687 | completion += '</span>'; |
---|
1688 | } |
---|
1689 | } |
---|
1690 | |
---|
1691 | return completion; |
---|
1692 | }, |
---|
1693 | |
---|
1694 | /** |
---|
1695 | * Update the input element to reflect the changed argument |
---|
1696 | */ |
---|
1697 | onArgChange: function(ev) { |
---|
1698 | if (this.isUpdating) { |
---|
1699 | return; |
---|
1700 | } |
---|
1701 | |
---|
1702 | var prefix = this.element.value.substring(0, ev.argument.start); |
---|
1703 | var suffix = this.element.value.substring(ev.argument.end); |
---|
1704 | var insert = typeof ev.text === 'string' ? ev.text : ev.text.name; |
---|
1705 | this.element.value = prefix + insert + suffix; |
---|
1706 | // Fix the cursor. |
---|
1707 | var insertEnd = (prefix + insert).length; |
---|
1708 | this.element.selectionStart = insertEnd; |
---|
1709 | this.element.selectionEnd = insertEnd; |
---|
1710 | } |
---|
1711 | }; |
---|
1712 | exports.CliView = CliView; |
---|
1713 | |
---|
1714 | |
---|
1715 | }); |
---|
1716 | /* ***** BEGIN LICENSE BLOCK ***** |
---|
1717 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
---|
1718 | * |
---|
1719 | * The contents of this file are subject to the Mozilla Public License Version |
---|
1720 | * 1.1 (the "License"); you may not use this file except in compliance with |
---|
1721 | * the License. You may obtain a copy of the License at |
---|
1722 | * http://www.mozilla.org/MPL/ |
---|
1723 | * |
---|
1724 | * Software distributed under the License is distributed on an "AS IS" basis, |
---|
1725 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
---|
1726 | * for the specific language governing rights and limitations under the |
---|
1727 | * License. |
---|
1728 | * |
---|
1729 | * The Original Code is Skywriter. |
---|
1730 | * |
---|
1731 | * The Initial Developer of the Original Code is |
---|
1732 | * Mozilla. |
---|
1733 | * Portions created by the Initial Developer are Copyright (C) 2009 |
---|
1734 | * the Initial Developer. All Rights Reserved. |
---|
1735 | * |
---|
1736 | * Contributor(s): |
---|
1737 | * Joe Walker (jwalker@mozilla.com) |
---|
1738 | * |
---|
1739 | * Alternatively, the contents of this file may be used under the terms of |
---|
1740 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
---|
1741 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
---|
1742 | * in which case the provisions of the GPL or the LGPL are applicable instead |
---|
1743 | * of those above. If you wish to allow use of your version of this file only |
---|
1744 | * under the terms of either the GPL or the LGPL, and not to allow others to |
---|
1745 | * use your version of this file under the terms of the MPL, indicate your |
---|
1746 | * decision by deleting the provisions above and replace them with the notice |
---|
1747 | * and other provisions required by the GPL or the LGPL. If you do not delete |
---|
1748 | * the provisions above, a recipient may use your version of this file under |
---|
1749 | * the terms of any one of the MPL, the GPL or the LGPL. |
---|
1750 | * |
---|
1751 | * ***** END LICENSE BLOCK ***** */ |
---|
1752 | |
---|
1753 | define('cockpit/ui/request_view', ['require', 'exports', 'module' , 'pilot/dom', 'pilot/event', 'text!cockpit/ui/request_view.html', 'pilot/domtemplate', 'text!cockpit/ui/request_view.css'], function(require, exports, module) { |
---|
1754 | |
---|
1755 | var dom = require("pilot/dom"); |
---|
1756 | var event = require("pilot/event"); |
---|
1757 | var requestViewHtml = require("text!cockpit/ui/request_view.html"); |
---|
1758 | var Templater = require("pilot/domtemplate").Templater; |
---|
1759 | |
---|
1760 | var requestViewCss = require("text!cockpit/ui/request_view.css"); |
---|
1761 | dom.importCssString(requestViewCss); |
---|
1762 | |
---|
1763 | /** |
---|
1764 | * Pull the HTML into the DOM, but don't add it to the document |
---|
1765 | */ |
---|
1766 | var templates = document.createElement('div'); |
---|
1767 | templates.innerHTML = requestViewHtml; |
---|
1768 | var row = templates.querySelector('.cptRow'); |
---|
1769 | |
---|
1770 | /** |
---|
1771 | * Work out the path for images. |
---|
1772 | * TODO: This should probably live in some utility area somewhere |
---|
1773 | */ |
---|
1774 | function imageUrl(path) { |
---|
1775 | var dataUrl; |
---|
1776 | try { |
---|
1777 | dataUrl = require('text!cockpit/ui/' + path); |
---|
1778 | } catch (e) { } |
---|
1779 | if (dataUrl) { |
---|
1780 | return dataUrl; |
---|
1781 | } |
---|
1782 | |
---|
1783 | var filename = module.id.split('/').pop() + '.js'; |
---|
1784 | var imagePath; |
---|
1785 | |
---|
1786 | if (module.uri.substr(-filename.length) !== filename) { |
---|
1787 | console.error('Can\'t work out path from module.uri/module.id'); |
---|
1788 | return path; |
---|
1789 | } |
---|
1790 | |
---|
1791 | if (module.uri) { |
---|
1792 | var end = module.uri.length - filename.length - 1; |
---|
1793 | return module.uri.substr(0, end) + "/" + path; |
---|
1794 | } |
---|
1795 | |
---|
1796 | return filename + path; |
---|
1797 | } |
---|
1798 | |
---|
1799 | |
---|
1800 | /** |
---|
1801 | * Adds a row to the CLI output display |
---|
1802 | */ |
---|
1803 | function RequestView(request, cliView) { |
---|
1804 | this.request = request; |
---|
1805 | this.cliView = cliView; |
---|
1806 | this.imageUrl = imageUrl; |
---|
1807 | |
---|
1808 | // Elements attached to this by the templater. For info only |
---|
1809 | this.rowin = null; |
---|
1810 | this.rowout = null; |
---|
1811 | this.output = null; |
---|
1812 | this.hide = null; |
---|
1813 | this.show = null; |
---|
1814 | this.duration = null; |
---|
1815 | this.throb = null; |
---|
1816 | |
---|
1817 | new Templater().processNode(row.cloneNode(true), this); |
---|
1818 | |
---|
1819 | this.cliView.output.appendChild(this.rowin); |
---|
1820 | this.cliView.output.appendChild(this.rowout); |
---|
1821 | |
---|
1822 | this.request.addEventListener('output', this.onRequestChange.bind(this)); |
---|
1823 | }; |
---|
1824 | |
---|
1825 | RequestView.prototype = { |
---|
1826 | /** |
---|
1827 | * A single click on an invocation line in the console copies the command to |
---|
1828 | * the command line |
---|
1829 | */ |
---|
1830 | copyToInput: function() { |
---|
1831 | this.cliView.element.value = this.request.typed; |
---|
1832 | }, |
---|
1833 | |
---|
1834 | /** |
---|
1835 | * A double click on an invocation line in the console executes the command |
---|
1836 | */ |
---|
1837 | executeRequest: function(ev) { |
---|
1838 | this.cliView.cli.update({ |
---|
1839 | typed: this.request.typed, |
---|
1840 | cursor: { start:0, end:0 } |
---|
1841 | }); |
---|
1842 | this.cliView.cli.exec(); |
---|
1843 | }, |
---|
1844 | |
---|
1845 | hideOutput: function(ev) { |
---|
1846 | this.output.style.display = 'none'; |
---|
1847 | dom.addCssClass(this.hide, 'cmd_hidden'); |
---|
1848 | dom.removeCssClass(this.show, 'cmd_hidden'); |
---|
1849 | |
---|
1850 | event.stopPropagation(ev); |
---|
1851 | }, |
---|
1852 | |
---|
1853 | showOutput: function(ev) { |
---|
1854 | this.output.style.display = 'block'; |
---|
1855 | dom.removeCssClass(this.hide, 'cmd_hidden'); |
---|
1856 | dom.addCssClass(this.show, 'cmd_hidden'); |
---|
1857 | |
---|
1858 | event.stopPropagation(ev); |
---|
1859 | }, |
---|
1860 | |
---|
1861 | remove: function(ev) { |
---|
1862 | this.cliView.output.removeChild(this.rowin); |
---|
1863 | this.cliView.output.removeChild(this.rowout); |
---|
1864 | event.stopPropagation(ev); |
---|
1865 | }, |
---|
1866 | |
---|
1867 | onRequestChange: function(ev) { |
---|
1868 | this.duration.innerHTML = this.request.duration ? |
---|
1869 | 'completed in ' + (this.request.duration / 1000) + ' sec ' : |
---|
1870 | ''; |
---|
1871 | |
---|
1872 | this.output.innerHTML = ''; |
---|
1873 | this.request.outputs.forEach(function(output) { |
---|
1874 | var node; |
---|
1875 | if (typeof output == 'string') { |
---|
1876 | node = document.createElement('p'); |
---|
1877 | node.innerHTML = output; |
---|
1878 | } else { |
---|
1879 | node = output; |
---|
1880 | } |
---|
1881 | this.output.appendChild(node); |
---|
1882 | }, this); |
---|
1883 | this.cliView.scrollOutputToBottom(); |
---|
1884 | |
---|
1885 | dom.setCssClass(this.output, 'cmd_error', this.request.error); |
---|
1886 | |
---|
1887 | this.throb.style.display = this.request.completed ? 'none' : 'block'; |
---|
1888 | } |
---|
1889 | }; |
---|
1890 | exports.RequestView = RequestView; |
---|
1891 | |
---|
1892 | |
---|
1893 | }); |
---|
1894 | /* ***** BEGIN LICENSE BLOCK ***** |
---|
1895 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
---|
1896 | * |
---|
1897 | * The contents of this file are subject to the Mozilla Public License Version |
---|
1898 | * 1.1 (the "License"); you may not use this file except in compliance with |
---|
1899 | * the License. You may obtain a copy of the License at |
---|
1900 | * http://www.mozilla.org/MPL/ |
---|
1901 | * |
---|
1902 | * Software distributed under the License is distributed on an "AS IS" basis, |
---|
1903 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
---|
1904 | * for the specific language governing rights and limitations under the |
---|
1905 | * License. |
---|
1906 | * |
---|
1907 | * The Original Code is DomTemplate. |
---|
1908 | * |
---|
1909 | * The Initial Developer of the Original Code is Mozilla. |
---|
1910 | * Portions created by the Initial Developer are Copyright (C) 2009 |
---|
1911 | * the Initial Developer. All Rights Reserved. |
---|
1912 | * |
---|
1913 | * Contributor(s): |
---|
1914 | * Joe Walker (jwalker@mozilla.com) (original author) |
---|
1915 | * |
---|
1916 | * Alternatively, the contents of this file may be used under the terms of |
---|
1917 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
---|
1918 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
---|
1919 | * in which case the provisions of the GPL or the LGPL are applicable instead |
---|
1920 | * of those above. If you wish to allow use of your version of this file only |
---|
1921 | * under the terms of either the GPL or the LGPL, and not to allow others to |
---|
1922 | * use your version of this file under the terms of the MPL, indicate your |
---|
1923 | * decision by deleting the provisions above and replace them with the notice |
---|
1924 | * and other provisions required by the GPL or the LGPL. If you do not delete |
---|
1925 | * the provisions above, a recipient may use your version of this file under |
---|
1926 | * the terms of any one of the MPL, the GPL or the LGPL. |
---|
1927 | * |
---|
1928 | * ***** END LICENSE BLOCK ***** */ |
---|
1929 | |
---|
1930 | define('pilot/domtemplate', ['require', 'exports', 'module' ], function(require, exports, module) { |
---|
1931 | |
---|
1932 | |
---|
1933 | // WARNING: do not 'use_strict' without reading the notes in envEval; |
---|
1934 | |
---|
1935 | /** |
---|
1936 | * A templater that allows one to quickly template DOM nodes. |
---|
1937 | */ |
---|
1938 | function Templater() { |
---|
1939 | this.scope = []; |
---|
1940 | }; |
---|
1941 | |
---|
1942 | /** |
---|
1943 | * Recursive function to walk the tree processing the attributes as it goes. |
---|
1944 | * @param node the node to process. If you pass a string in instead of a DOM |
---|
1945 | * element, it is assumed to be an id for use with document.getElementById() |
---|
1946 | * @param data the data to use for node processing. |
---|
1947 | */ |
---|
1948 | Templater.prototype.processNode = function(node, data) { |
---|
1949 | if (typeof node === 'string') { |
---|
1950 | node = document.getElementById(node); |
---|
1951 | } |
---|
1952 | if (data === null || data === undefined) { |
---|
1953 | data = {}; |
---|
1954 | } |
---|
1955 | this.scope.push(node.nodeName + (node.id ? '#' + node.id : '')); |
---|
1956 | try { |
---|
1957 | // Process attributes |
---|
1958 | if (node.attributes && node.attributes.length) { |
---|
1959 | // We need to handle 'foreach' and 'if' first because they might stop |
---|
1960 | // some types of processing from happening, and foreach must come first |
---|
1961 | // because it defines new data on which 'if' might depend. |
---|
1962 | if (node.hasAttribute('foreach')) { |
---|
1963 | this.processForEach(node, data); |
---|
1964 | return; |
---|
1965 | } |
---|
1966 | if (node.hasAttribute('if')) { |
---|
1967 | if (!this.processIf(node, data)) { |
---|
1968 | return; |
---|
1969 | } |
---|
1970 | } |
---|
1971 | // Only make the node available once we know it's not going away |
---|
1972 | data.__element = node; |
---|
1973 | // It's good to clean up the attributes when we've processed them, |
---|
1974 | // but if we do it straight away, we mess up the array index |
---|
1975 | var attrs = Array.prototype.slice.call(node.attributes); |
---|
1976 | for (var i = 0; i < attrs.length; i++) { |
---|
1977 | var value = attrs[i].value; |
---|
1978 | var name = attrs[i].name; |
---|
1979 | this.scope.push(name); |
---|
1980 | try { |
---|
1981 | if (name === 'save') { |
---|
1982 | // Save attributes are a setter using the node |
---|
1983 | value = this.stripBraces(value); |
---|
1984 | this.property(value, data, node); |
---|
1985 | node.removeAttribute('save'); |
---|
1986 | } else if (name.substring(0, 2) === 'on') { |
---|
1987 | // Event registration relies on property doing a bind |
---|
1988 | value = this.stripBraces(value); |
---|
1989 | var func = this.property(value, data); |
---|
1990 | if (typeof func !== 'function') { |
---|
1991 | this.handleError('Expected ' + value + |
---|
1992 | ' to resolve to a function, but got ' + typeof func); |
---|
1993 | } |
---|
1994 | node.removeAttribute(name); |
---|
1995 | var capture = node.hasAttribute('capture' + name.substring(2)); |
---|
1996 | node.addEventListener(name.substring(2), func, capture); |
---|
1997 | if (capture) { |
---|
1998 | node.removeAttribute('capture' + name.substring(2)); |
---|
1999 | } |
---|
2000 | } else { |
---|
2001 | // Replace references in all other attributes |
---|
2002 | var self = this; |
---|
2003 | var newValue = value.replace(/\$\{[^}]*\}/g, function(path) { |
---|
2004 | return self.envEval(path.slice(2, -1), data, value); |
---|
2005 | }); |
---|
2006 | // Remove '_' prefix of attribute names so the DOM won't try |
---|
2007 | // to use them before we've processed the template |
---|
2008 | if (name.charAt(0) === '_') { |
---|
2009 | node.removeAttribute(name); |
---|
2010 | node.setAttribute(name.substring(1), newValue); |
---|
2011 | } else if (value !== newValue) { |
---|
2012 | attrs[i].value = newValue; |
---|
2013 | } |
---|
2014 | } |
---|
2015 | } finally { |
---|
2016 | this.scope.pop(); |
---|
2017 | } |
---|
2018 | } |
---|
2019 | } |
---|
2020 | |
---|
2021 | // Loop through our children calling processNode. First clone them, so the |
---|
2022 | // set of nodes that we visit will be unaffected by additions or removals. |
---|
2023 | var childNodes = Array.prototype.slice.call(node.childNodes); |
---|
2024 | for (var j = 0; j < childNodes.length; j++) { |
---|
2025 | this.processNode(childNodes[j], data); |
---|
2026 | } |
---|
2027 | |
---|
2028 | if (node.nodeType === Node.TEXT_NODE) { |
---|
2029 | this.processTextNode(node, data); |
---|
2030 | } |
---|
2031 | } finally { |
---|
2032 | this.scope.pop(); |
---|
2033 | } |
---|
2034 | }; |
---|
2035 | |
---|
2036 | /** |
---|
2037 | * Handle <x if="${...}"> |
---|
2038 | * @param node An element with an 'if' attribute |
---|
2039 | * @param data The data to use with envEval |
---|
2040 | * @returns true if processing should continue, false otherwise |
---|
2041 | */ |
---|
2042 | Templater.prototype.processIf = function(node, data) { |
---|
2043 | this.scope.push('if'); |
---|
2044 | try { |
---|
2045 | var originalValue = node.getAttribute('if'); |
---|
2046 | var value = this.stripBraces(originalValue); |
---|
2047 | var recurse = true; |
---|
2048 | try { |
---|
2049 | var reply = this.envEval(value, data, originalValue); |
---|
2050 | recurse = !!reply; |
---|
2051 | } catch (ex) { |
---|
2052 | this.handleError('Error with \'' + value + '\'', ex); |
---|
2053 | recurse = false; |
---|
2054 | } |
---|
2055 | if (!recurse) { |
---|
2056 | node.parentNode.removeChild(node); |
---|
2057 | } |
---|
2058 | node.removeAttribute('if'); |
---|
2059 | return recurse; |
---|
2060 | } finally { |
---|
2061 | this.scope.pop(); |
---|
2062 | } |
---|
2063 | }; |
---|
2064 | |
---|
2065 | /** |
---|
2066 | * Handle <x foreach="param in ${array}"> and the special case of |
---|
2067 | * <loop foreach="param in ${array}"> |
---|
2068 | * @param node An element with a 'foreach' attribute |
---|
2069 | * @param data The data to use with envEval |
---|
2070 | */ |
---|
2071 | Templater.prototype.processForEach = function(node, data) { |
---|
2072 | this.scope.push('foreach'); |
---|
2073 | try { |
---|
2074 | var originalValue = node.getAttribute('foreach'); |
---|
2075 | var value = originalValue; |
---|
2076 | |
---|
2077 | var paramName = 'param'; |
---|
2078 | if (value.charAt(0) === '$') { |
---|
2079 | // No custom loop variable name. Use the default: 'param' |
---|
2080 | value = this.stripBraces(value); |
---|
2081 | } else { |
---|
2082 | // Extract the loop variable name from 'NAME in ${ARRAY}' |
---|
2083 | var nameArr = value.split(' in '); |
---|
2084 | paramName = nameArr[0].trim(); |
---|
2085 | value = this.stripBraces(nameArr[1].trim()); |
---|
2086 | } |
---|
2087 | node.removeAttribute('foreach'); |
---|
2088 | try { |
---|
2089 | var self = this; |
---|
2090 | // Process a single iteration of a loop |
---|
2091 | var processSingle = function(member, clone, ref) { |
---|
2092 | ref.parentNode.insertBefore(clone, ref); |
---|
2093 | data[paramName] = member; |
---|
2094 | self.processNode(clone, data); |
---|
2095 | delete data[paramName]; |
---|
2096 | }; |
---|
2097 | |
---|
2098 | // processSingle is no good for <loop> nodes where we want to work on |
---|
2099 | // the childNodes rather than the node itself |
---|
2100 | var processAll = function(scope, member) { |
---|
2101 | self.scope.push(scope); |
---|
2102 | try { |
---|
2103 | if (node.nodeName === 'LOOP') { |
---|
2104 | for (var i = 0; i < node.childNodes.length; i++) { |
---|
2105 | var clone = node.childNodes[i].cloneNode(true); |
---|
2106 | processSingle(member, clone, node); |
---|
2107 | } |
---|
2108 | } else { |
---|
2109 | var clone = node.cloneNode(true); |
---|
2110 | clone.removeAttribute('foreach'); |
---|
2111 | processSingle(member, clone, node); |
---|
2112 | } |
---|
2113 | } finally { |
---|
2114 | self.scope.pop(); |
---|
2115 | } |
---|
2116 | }; |
---|
2117 | |
---|
2118 | var reply = this.envEval(value, data, originalValue); |
---|
2119 | if (Array.isArray(reply)) { |
---|
2120 | reply.forEach(function(data, i) { |
---|
2121 | processAll('' + i, data); |
---|
2122 | }, this); |
---|
2123 | } else { |
---|
2124 | for (var param in reply) { |
---|
2125 | if (reply.hasOwnProperty(param)) { |
---|
2126 | processAll(param, param); |
---|
2127 | } |
---|
2128 | } |
---|
2129 | } |
---|
2130 | node.parentNode.removeChild(node); |
---|
2131 | } catch (ex) { |
---|
2132 | this.handleError('Error with \'' + value + '\'', ex); |
---|
2133 | } |
---|
2134 | } finally { |
---|
2135 | this.scope.pop(); |
---|
2136 | } |
---|
2137 | }; |
---|
2138 | |
---|
2139 | /** |
---|
2140 | * Take a text node and replace it with another text node with the ${...} |
---|
2141 | * sections parsed out. We replace the node by altering node.parentNode but |
---|
2142 | * we could probably use a DOM Text API to achieve the same thing. |
---|
2143 | * @param node The Text node to work on |
---|
2144 | * @param data The data to use in calls to envEval |
---|
2145 | */ |
---|
2146 | Templater.prototype.processTextNode = function(node, data) { |
---|
2147 | // Replace references in other attributes |
---|
2148 | var value = node.data; |
---|
2149 | // We can't use the string.replace() with function trick (see generic |
---|
2150 | // attribute processing in processNode()) because we need to support |
---|
2151 | // functions that return DOM nodes, so we can't have the conversion to a |
---|
2152 | // string. |
---|
2153 | // Instead we process the string as an array of parts. In order to split |
---|
2154 | // the string up, we first replace '${' with '\uF001$' and '}' with '\uF002' |
---|
2155 | // We can then split using \uF001 or \uF002 to get an array of strings |
---|
2156 | // where scripts are prefixed with $. |
---|
2157 | // \uF001 and \uF002 are just unicode chars reserved for private use. |
---|
2158 | value = value.replace(/\$\{([^}]*)\}/g, '\uF001$$$1\uF002'); |
---|
2159 | var parts = value.split(/\uF001|\uF002/); |
---|
2160 | if (parts.length > 1) { |
---|
2161 | parts.forEach(function(part) { |
---|
2162 | if (part === null || part === undefined || part === '') { |
---|
2163 | return; |
---|
2164 | } |
---|
2165 | if (part.charAt(0) === '$') { |
---|
2166 | part = this.envEval(part.slice(1), data, node.data); |
---|
2167 | } |
---|
2168 | // It looks like this was done a few lines above but see envEval |
---|
2169 | if (part === null) { |
---|
2170 | part = "null"; |
---|
2171 | } |
---|
2172 | if (part === undefined) { |
---|
2173 | part = "undefined"; |
---|
2174 | } |
---|
2175 | // if (isDOMElement(part)) { ... } |
---|
2176 | if (typeof part.cloneNode !== 'function') { |
---|
2177 | part = node.ownerDocument.createTextNode(part.toString()); |
---|
2178 | } |
---|
2179 | node.parentNode.insertBefore(part, node); |
---|
2180 | }, this); |
---|
2181 | node.parentNode.removeChild(node); |
---|
2182 | } |
---|
2183 | }; |
---|
2184 | |
---|
2185 | /** |
---|
2186 | * Warn of string does not begin '${' and end '}' |
---|
2187 | * @param str the string to check. |
---|
2188 | * @return The string stripped of ${ and }, or untouched if it does not match |
---|
2189 | */ |
---|
2190 | Templater.prototype.stripBraces = function(str) { |
---|
2191 | if (!str.match(/\$\{.*\}/g)) { |
---|
2192 | this.handleError('Expected ' + str + ' to match ${...}'); |
---|
2193 | return str; |
---|
2194 | } |
---|
2195 | return str.slice(2, -1); |
---|
2196 | }; |
---|
2197 | |
---|
2198 | /** |
---|
2199 | * Combined getter and setter that works with a path through some data set. |
---|
2200 | * For example: |
---|
2201 | * <ul> |
---|
2202 | * <li>property('a.b', { a: { b: 99 }}); // returns 99 |
---|
2203 | * <li>property('a', { a: { b: 99 }}); // returns { b: 99 } |
---|
2204 | * <li>property('a', { a: { b: 99 }}, 42); // returns 99 and alters the |
---|
2205 | * input data to be { a: { b: 42 }} |
---|
2206 | * </ul> |
---|
2207 | * @param path An array of strings indicating the path through the data, or |
---|
2208 | * a string to be cut into an array using <tt>split('.')</tt> |
---|
2209 | * @param data An object to look in for the <tt>path</tt> argument |
---|
2210 | * @param newValue (optional) If defined, this value will replace the |
---|
2211 | * original value for the data at the path specified. |
---|
2212 | * @return The value pointed to by <tt>path</tt> before any |
---|
2213 | * <tt>newValue</tt> is applied. |
---|
2214 | */ |
---|
2215 | Templater.prototype.property = function(path, data, newValue) { |
---|
2216 | this.scope.push(path); |
---|
2217 | try { |
---|
2218 | if (typeof path === 'string') { |
---|
2219 | path = path.split('.'); |
---|
2220 | } |
---|
2221 | var value = data[path[0]]; |
---|
2222 | if (path.length === 1) { |
---|
2223 | if (newValue !== undefined) { |
---|
2224 | data[path[0]] = newValue; |
---|
2225 | } |
---|
2226 | if (typeof value === 'function') { |
---|
2227 | return function() { |
---|
2228 | return value.apply(data, arguments); |
---|
2229 | }; |
---|
2230 | } |
---|
2231 | return value; |
---|
2232 | } |
---|
2233 | if (!value) { |
---|
2234 | this.handleError('Can\'t find path=' + path); |
---|
2235 | return null; |
---|
2236 | } |
---|
2237 | return this.property(path.slice(1), value, newValue); |
---|
2238 | } finally { |
---|
2239 | this.scope.pop(); |
---|
2240 | } |
---|
2241 | }; |
---|
2242 | |
---|
2243 | /** |
---|
2244 | * Like eval, but that creates a context of the variables in <tt>env</tt> in |
---|
2245 | * which the script is evaluated. |
---|
2246 | * WARNING: This script uses 'with' which is generally regarded to be evil. |
---|
2247 | * The alternative is to create a Function at runtime that takes X parameters |
---|
2248 | * according to the X keys in the env object, and then call that function using |
---|
2249 | * the values in the env object. This is likely to be slow, but workable. |
---|
2250 | * @param script The string to be evaluated. |
---|
2251 | * @param env The environment in which to eval the script. |
---|
2252 | * @param context Optional debugging string in case of failure |
---|
2253 | * @return The return value of the script, or the error message if the script |
---|
2254 | * execution failed. |
---|
2255 | */ |
---|
2256 | Templater.prototype.envEval = function(script, env, context) { |
---|
2257 | with (env) { |
---|
2258 | try { |
---|
2259 | this.scope.push(context); |
---|
2260 | return eval(script); |
---|
2261 | } catch (ex) { |
---|
2262 | this.handleError('Template error evaluating \'' + script + '\'', ex); |
---|
2263 | return script; |
---|
2264 | } finally { |
---|
2265 | this.scope.pop(); |
---|
2266 | } |
---|
2267 | } |
---|
2268 | }; |
---|
2269 | |
---|
2270 | /** |
---|
2271 | * A generic way of reporting errors, for easy overloading in different |
---|
2272 | * environments. |
---|
2273 | * @param message the error message to report. |
---|
2274 | * @param ex optional associated exception. |
---|
2275 | */ |
---|
2276 | Templater.prototype.handleError = function(message, ex) { |
---|
2277 | this.logError(message); |
---|
2278 | this.logError('In: ' + this.scope.join(' > ')); |
---|
2279 | if (ex) { |
---|
2280 | this.logError(ex); |
---|
2281 | } |
---|
2282 | }; |
---|
2283 | |
---|
2284 | |
---|
2285 | /** |
---|
2286 | * A generic way of reporting errors, for easy overloading in different |
---|
2287 | * environments. |
---|
2288 | * @param message the error message to report. |
---|
2289 | */ |
---|
2290 | Templater.prototype.logError = function(message) { |
---|
2291 | window.console && window.console.log && console.log(message); |
---|
2292 | }; |
---|
2293 | |
---|
2294 | exports.Templater = Templater; |
---|
2295 | |
---|
2296 | |
---|
2297 | }); |
---|
2298 | /* ***** BEGIN LICENSE BLOCK ***** |
---|
2299 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
---|
2300 | * |
---|
2301 | * The contents of this file are subject to the Mozilla Public License Version |
---|
2302 | * 1.1 (the "License"); you may not use this file except in compliance with |
---|
2303 | * the License. You may obtain a copy of the License at |
---|
2304 | * http://www.mozilla.org/MPL/ |
---|
2305 | * |
---|
2306 | * Software distributed under the License is distributed on an "AS IS" basis, |
---|
2307 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
---|
2308 | * for the specific language governing rights and limitations under the |
---|
2309 | * License. |
---|
2310 | * |
---|
2311 | * The Original Code is Skywriter. |
---|
2312 | * |
---|
2313 | * The Initial Developer of the Original Code is |
---|
2314 | * Mozilla. |
---|
2315 | * Portions created by the Initial Developer are Copyright (C) 2009 |
---|
2316 | * the Initial Developer. All Rights Reserved. |
---|
2317 | * |
---|
2318 | * Contributor(s): |
---|
2319 | * Skywriter Team (skywriter@mozilla.com) |
---|
2320 | * |
---|
2321 | * Alternatively, the contents of this file may be used under the terms of |
---|
2322 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
---|
2323 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
---|
2324 | * in which case the provisions of the GPL or the LGPL are applicable instead |
---|
2325 | * of those above. If you wish to allow use of your version of this file only |
---|
2326 | * under the terms of either the GPL or the LGPL, and not to allow others to |
---|
2327 | * use your version of this file under the terms of the MPL, indicate your |
---|
2328 | * decision by deleting the provisions above and replace them with the notice |
---|
2329 | * and other provisions required by the GPL or the LGPL. If you do not delete |
---|
2330 | * the provisions above, a recipient may use your version of this file under |
---|
2331 | * the terms of any one of the MPL, the GPL or the LGPL. |
---|
2332 | * |
---|
2333 | * ***** END LICENSE BLOCK ***** */ |
---|
2334 | |
---|
2335 | define('cockpit/commands/basic', ['require', 'exports', 'module' , 'pilot/canon'], function(require, exports, module) { |
---|
2336 | |
---|
2337 | |
---|
2338 | var canon = require('pilot/canon'); |
---|
2339 | |
---|
2340 | /** |
---|
2341 | * '!' command |
---|
2342 | */ |
---|
2343 | var bangCommandSpec = { |
---|
2344 | name: 'sh', |
---|
2345 | description: 'Execute a system command (requires server support)', |
---|
2346 | params: [ |
---|
2347 | { |
---|
2348 | name: 'command', |
---|
2349 | type: 'text', |
---|
2350 | description: 'The string to send to the os shell.' |
---|
2351 | } |
---|
2352 | ], |
---|
2353 | exec: function(env, args, request) { |
---|
2354 | var req = new XMLHttpRequest(); |
---|
2355 | req.open('GET', '/exec?args=' + args.command, true); |
---|
2356 | req.onreadystatechange = function(ev) { |
---|
2357 | if (req.readyState == 4) { |
---|
2358 | if (req.status == 200) { |
---|
2359 | request.done('<pre>' + req.responseText + '</pre>'); |
---|
2360 | } |
---|
2361 | } |
---|
2362 | }; |
---|
2363 | req.send(null); |
---|
2364 | } |
---|
2365 | }; |
---|
2366 | |
---|
2367 | var canon = require('pilot/canon'); |
---|
2368 | |
---|
2369 | exports.startup = function(data, reason) { |
---|
2370 | canon.addCommand(bangCommandSpec); |
---|
2371 | }; |
---|
2372 | |
---|
2373 | exports.shutdown = function(data, reason) { |
---|
2374 | canon.removeCommand(bangCommandSpec); |
---|
2375 | }; |
---|
2376 | |
---|
2377 | |
---|
2378 | }); |
---|
2379 | define("text!cockpit/ui/cli_view.css", [], "" + |
---|
2380 | "#cockpitInput { padding-left: 16px; }" + |
---|
2381 | "" + |
---|
2382 | ".cptOutput { overflow: auto; position: absolute; z-index: 999; display: none; }" + |
---|
2383 | "" + |
---|
2384 | ".cptCompletion { padding: 0; position: absolute; z-index: -1000; }" + |
---|
2385 | ".cptCompletion.VALID { background: #FFF; }" + |
---|
2386 | ".cptCompletion.INCOMPLETE { background: #DDD; }" + |
---|
2387 | ".cptCompletion.INVALID { background: #DDD; }" + |
---|
2388 | ".cptCompletion span { color: #FFF; }" + |
---|
2389 | ".cptCompletion span.INCOMPLETE { color: #DDD; border-bottom: 2px dotted #F80; }" + |
---|
2390 | ".cptCompletion span.INVALID { color: #DDD; border-bottom: 2px dotted #F00; }" + |
---|
2391 | "span.cptPrompt { color: #66F; font-weight: bold; }" + |
---|
2392 | "" + |
---|
2393 | "" + |
---|
2394 | ".cptHints {" + |
---|
2395 | " color: #000;" + |
---|
2396 | " position: absolute;" + |
---|
2397 | " border: 1px solid rgba(230, 230, 230, 0.8);" + |
---|
2398 | " background: rgba(250, 250, 250, 0.8);" + |
---|
2399 | " -moz-border-radius-topleft: 10px;" + |
---|
2400 | " -moz-border-radius-topright: 10px;" + |
---|
2401 | " border-top-left-radius: 10px; border-top-right-radius: 10px;" + |
---|
2402 | " z-index: 1000;" + |
---|
2403 | " padding: 8px;" + |
---|
2404 | " display: none;" + |
---|
2405 | "}" + |
---|
2406 | "" + |
---|
2407 | ".cptFocusPopup { display: block; }" + |
---|
2408 | ".cptFocusPopup.cptNoPopup { display: none; }" + |
---|
2409 | "" + |
---|
2410 | ".cptHints ul { margin: 0; padding: 0 15px; }" + |
---|
2411 | "" + |
---|
2412 | ".cptGt { font-weight: bold; font-size: 120%; }" + |
---|
2413 | ""); |
---|
2414 | |
---|
2415 | define("text!cockpit/ui/request_view.css", [], "" + |
---|
2416 | ".cptRowIn {" + |
---|
2417 | " display: box; display: -moz-box; display: -webkit-box;" + |
---|
2418 | " box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal;" + |
---|
2419 | " box-align: center; -moz-box-align: center; -webkit-box-align: center;" + |
---|
2420 | " color: #333;" + |
---|
2421 | " background-color: #EEE;" + |
---|
2422 | " width: 100%;" + |
---|
2423 | " font-family: consolas, courier, monospace;" + |
---|
2424 | "}" + |
---|
2425 | ".cptRowIn > * { padding-left: 2px; padding-right: 2px; }" + |
---|
2426 | ".cptRowIn > img { cursor: pointer; }" + |
---|
2427 | ".cptHover { display: none; }" + |
---|
2428 | ".cptRowIn:hover > .cptHover { display: block; }" + |
---|
2429 | ".cptRowIn:hover > .cptHover.cptHidden { display: none; }" + |
---|
2430 | ".cptOutTyped {" + |
---|
2431 | " box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1;" + |
---|
2432 | " font-weight: bold; color: #000; font-size: 120%;" + |
---|
2433 | "}" + |
---|
2434 | ".cptRowOutput { padding-left: 10px; line-height: 1.2em; }" + |
---|
2435 | ".cptRowOutput strong," + |
---|
2436 | ".cptRowOutput b," + |
---|
2437 | ".cptRowOutput th," + |
---|
2438 | ".cptRowOutput h1," + |
---|
2439 | ".cptRowOutput h2," + |
---|
2440 | ".cptRowOutput h3 { color: #000; }" + |
---|
2441 | ".cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; }" + |
---|
2442 | ".cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }" + |
---|
2443 | ".cptRowOutput input[type=password]," + |
---|
2444 | ".cptRowOutput input[type=text]," + |
---|
2445 | ".cptRowOutput textarea {" + |
---|
2446 | " color: #000; font-size: 120%;" + |
---|
2447 | " background: transparent; padding: 3px;" + |
---|
2448 | " border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;" + |
---|
2449 | "}" + |
---|
2450 | ".cptRowOutput table," + |
---|
2451 | ".cptRowOutput td," + |
---|
2452 | ".cptRowOutput th { border: 0; padding: 0 2px; }" + |
---|
2453 | ".cptRowOutput .right { text-align: right; }" + |
---|
2454 | ""); |
---|
2455 | |
---|
2456 | define("text!cockpit/ui/request_view.html", [], "" + |
---|
2457 | "<div class=cptRow>" + |
---|
2458 | " <!-- The div for the input (i.e. what was typed) -->" + |
---|
2459 | " <div class=\"cptRowIn\" save=\"${rowin}\"" + |
---|
2460 | " onclick=\"${copyToInput}\"" + |
---|
2461 | " ondblclick=\"${executeRequest}\">" + |
---|
2462 | "" + |
---|
2463 | " <!-- What the user actually typed -->" + |
---|
2464 | " <div class=\"cptGt\">> </div>" + |
---|
2465 | " <div class=\"cptOutTyped\">${request.typed}</div>" + |
---|
2466 | "" + |
---|
2467 | " <!-- The extra details that appear on hover -->" + |
---|
2468 | " <div class=cptHover save=\"${duration}\"></div>" + |
---|
2469 | " <img class=cptHover onclick=\"${hideOutput}\" save=\"${hide}\"" + |
---|
2470 | " alt=\"Hide command output\" _src=\"${imageUrl('images/minus.png')}\"/>" + |
---|
2471 | " <img class=\"cptHover cptHidden\" onclick=\"${showOutput}\" save=\"${show}\"" + |
---|
2472 | " alt=\"Show command output\" _src=\"${imageUrl('images/plus.png')}\"/>" + |
---|
2473 | " <img class=cptHover onclick=\"${remove}\"" + |
---|
2474 | " alt=\"Remove this command from the history\"" + |
---|
2475 | " _src=\"${imageUrl('images/closer.png')}\"/>" + |
---|
2476 | "" + |
---|
2477 | " </div>" + |
---|
2478 | "" + |
---|
2479 | " <!-- The div for the command output -->" + |
---|
2480 | " <div class=\"cptRowOut\" save=\"${rowout}\">" + |
---|
2481 | " <div class=\"cptRowOutput\" save=\"${output}\"></div>" + |
---|
2482 | " <img _src=\"${imageUrl('images/throbber.gif')}\" save=\"${throb}\"/>" + |
---|
2483 | " </div>" + |
---|
2484 | "</div>" + |
---|
2485 | ""); |
---|
2486 | |
---|
2487 | define("text!cockpit/ui/images/closer.png", [], "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAj9JREFUeNp0ks+LUlEUx7/vV1o8Z8wUx3IEHcQmiBiQlomjRNCiZpEuEqF/oEUwq/6EhvoHggmRcJUQBM1CRJAW0aLIaGQimZJxJsWxyV/P9/R1zzWlFl04vPvOPZ9z7rnnK5imidmKRCIq+zxgdoPZ1T/ut8xeM3tcKpW6s1hhBkaj0Qj7bDebTX+324WmadxvsVigqipcLleN/d4rFoulORiLxTZY8ItOp8MBCpYkiYPj8Xjus9vtlORWoVB4KcTjcQc732dLpSRXvCZaAws6Q4WDdqsO52kNH+oCRFGEz+f7ydwBKRgMPmTXi49GI1x2D/DsznesB06ws2eDbI7w9HYN6bVjvGss4KAjwDAMq81mM2SW5Wa/3weBbz42UL9uYnVpiO2Nr9ANHSGXib2Wgm9tCYIggGKJEVkvlwgi5/FQRmTLxO6hgJVzI1x0T/fJrBtHJxPeL6tI/fsZLA6ot8lkQi8HRVbw94gkWYI5MaHrOjcCGSNRxZosy9y5cErDzn0Dqx7gcwO8WtBp4PndI35GMYqiUMUvBL5yOBz8yRfFNpbPmqgcCFh/IuHa1nR/YXGM8+oUpFhihEQiwcdRLpfVRqOBtWXWq34Gra6AXq8Hp2piZcmKT4cKnE4nwuHwdByVSmWQz+d32WCTlHG/qaHHREN9kgi0sYQfv0R4PB4EAgESQDKXy72fSy6VSnHJVatVf71eR7vd5n66mtfrRSgU4pLLZrOlf7RKK51Ok8g3/yPyR5lMZi7y3wIMAME4EigHWgKnAAAAAElFTkSuQmCC"); |
---|
2488 | |
---|
2489 | define("text!cockpit/ui/images/dot_clear.gif", [], "data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAEBMgA7"); |
---|
2490 | |
---|
2491 | define("text!cockpit/ui/images/minus.png", [], "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAAZiS0dEANIA0gDS7KbF4AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFGw4xMrIJw5EAAAHcSURBVCjPhZIxSxtxGMZ/976XhJA/RA5EAyJcFksnp64hjUPBoXRyCYLQTyD0UxScu0nFwalCQSgFCVk7dXAwUAiBDA2RO4W7yN1x9+9gcyhU+pteHt4H3pfncay1LOl0OgY4BN4Ar/7KP4BvwNFwOIyWu87S2O12O8DxfD73oygiSRIAarUaxhhWV1fHwMFgMBiWxl6v9y6Koi+3t7ckSUKtVkNVAcjzvNRWVlYwxry9vLz86uzs7HjAZDKZGGstjUaDfxHHMSLC5ubmHdB2VfVwNpuZ5clxHPMcRVFwc3PTXFtbO3RFZHexWJCmabnweAaoVqvlv4vFAhHZdVX1ZZqmOI5DURR8fz/lxbp9Yrz+7bD72SfPcwBU1XdF5N5aWy2KgqIoeBzPEnWVLMseYnAcRERdVR27rrsdxzGqyutP6898+GBsNBqo6i9XVS88z9sOggAR4X94noeqXoiIHPm+H9XrdYIgIAxDwjAkTVPCMESzBy3LMprNJr7v34nIkV5dXd2fn59fG2P2siwjSRIqlQrWWlSVJFcqlQqtVot2u40xZu/s7OxnWbl+v98BjkejkT+dTgmCoDxtY2ODra2tMXBweno6fNJVgP39fQN8eKbkH09OTsqS/wHFRdHPfTSfjwAAAABJRU5ErkJggg=="); |
---|
2492 | |
---|
2493 | define("text!cockpit/ui/images/pinaction.png", [], "data:image/png;base64,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"); |
---|
2494 | |
---|
2495 | define("text!cockpit/ui/images/pinin.png", [], "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZ0lEQVQ4Ea2TPUsDQRCGZ89Eo4FACkULEQs1CH4Uamfjn7GxEYJFIFXgChFsbPwzNnZioREkaiHBQtEiEEiMRm/dZ8OEGAxR4sBxx877Pju7M2estTJIxLrNuVwuMxQEx0ZkzcFHyRtjXt02559RtB2GYanTYzoryOfz+6l4Nbszf2niwffKmpGRo9sVW22mDgqFwp5C2gDMm+P32a3JB1N+n5JifUGeP9JeNxGryPLYjcwMP8rJ07Q9fZltQzyAstOJ2vVu5sKc1ZZkRBrOcKeb+HexPidvkpCN5JUcllZtpZFc5DgBWc5M2eysZuMuofMBSA4NWjx4PUCsXefMlI0QY3ewRg4NWi4ZTQsgrjYXema+e4VqtEMK6KXvu+4B9Bklt90vVKMeD2BI6DOt4rZ/Gk7WyKFBi4fNPIAJY0joM61SCCZ9tI1o0OIB8D+DBIkYaJRbCBH9mZgNt+bb++ufSSF/eX8BYcDeAzuQJVUAAAAASUVORK5CYII="); |
---|
2496 | |
---|
2497 | define("text!cockpit/ui/images/pinout.png", [], "data:image/png;base64,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"); |
---|
2498 | |
---|
2499 | define("text!cockpit/ui/images/pins.png", [], "data:image/png;base64,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"); |
---|
2500 | |
---|
2501 | define("text!cockpit/ui/images/plus.png", [], "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAAZiS0dEANIA0gDS7KbF4AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFGw4yFTwuJTkAAAH7SURBVCjPdZKxa1NRFMZ/956XZMgFyyMlCZRA4hBx6lBcQ00GoYi4tEstFPwLAs7iLDi7FWuHThaUggihBDI5OWRoQAmBQFISQgvvpbwX3rsOaR4K+o2H8zvfOZxPWWtZqVarGaAJPAEe3ZW/A1+Bd+1221v1qhW4vb1dA44mk0nZ8zyCIAAgk8lgjGF9fb0PHF5cXLQTsF6vP/c879P19TVBEJDJZBARAKIoSmpra2sYY561Wq3PqtFouMBgMBgYay3ZbJZ/yfd9tNaUSqUboOKISPPq6sqsVvZ9H4AvL34B8PTj/QSO45jpdHovn883Ha31znw+JwzDpCEMQx4UloM8zyOdTif3zudztNY7jog8DMMQpRRxHPPt5TCBAEZvxlyOFTsfykRRBICIlB2t9a21Nh3HMXEc8+d7VhJHWCwWyzcohdZaHBHpO46z6fs+IsLj94XECaD4unCHL8FsNouI/HRE5Nx13c3ZbIbWOnG5HKtl+53TSq7rIiLnand31wUGnU7HjEYjlFLJZN/3yRnL1FMYY8jlcmxtbd0AFel2u7dnZ2eXxpi9xWJBEASkUimstYgIQSSkUimKxSKVSgVjzN7p6emPJHL7+/s14KjX65WHwyGz2SxZbWNjg2q12gcOT05O2n9lFeDg4MAAr/4T8rfHx8dJyH8DvvbYGzKvWukAAAAASUVORK5CYII="); |
---|
2502 | |
---|
2503 | define("text!cockpit/ui/images/throbber.gif", [], "data:image/gif;base64,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"); |
---|
2504 | |
---|