toolkit/devtools/gcli/source/docs/writing-commands.md

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:6ee928c5af7c
1
2 # Writing Commands
3
4 ## Basics
5
6 GCLI has opinions about how commands should be written, and it encourages you
7 to do The Right Thing. The opinions are based on helping users convert their
8 intentions to commands and commands to what's actually going to happen.
9
10 - Related commands should be sub-commands of a parent command. One of the goals
11 of GCLI is to support a large number of commands without things becoming
12 confusing, this will require some sort of namespacing or there will be
13 many people wanting to implement the ``add`` command. This style of
14 writing commands has become common place in Unix as the number of commands
15 has gone up.
16 The ```context``` command allows users to focus on a parent command, promoting
17 its sub-commands above others.
18
19 - Each command should do exactly and only one thing. An example of a Unix
20 command that breaks this principle is the ``tar`` command
21
22 $ tar -zcf foo.tar.gz .
23 $ tar -zxf foo.tar.gz .
24
25 These 2 commands do exactly opposite things. Many a file has died as a result
26 of a x/c typo. In GCLI this would be better expressed:
27
28 $ tar create foo.tar.gz -z .
29 $ tar extract foo.tar.gz -z .
30
31 There may be commands (like tar) which have enough history behind them
32 that we shouldn't force everyone to re-learn a new syntax. The can be achieved
33 by having a single string parameter and parsing the input in the command)
34
35 - Avoid errors. We try to avoid the user having to start again with a command
36 due to some problem. The majority of problems are simple typos which we can
37 catch using command metadata, but there are 2 things command authors can do
38 to prevent breakage.
39
40 - Where possible avoid the need to validate command line parameters in the
41 exec function. This can be done by good parameter design (see 'do exactly
42 and only one thing' above)
43
44 - If there is an obvious fix for an unpredictable problem, offer the
45 solution in the command output. So rather than use request.error (see
46 Request Object below) output some HTML which contains a link to a fixed
47 command line.
48
49 Currently these concepts are not enforced at a code level, but they could be in
50 the future.
51
52
53 ## How commands work
54
55 This is how to create a basic ``greet`` command:
56
57 gcli.addCommand({
58 name: 'greet',
59 description: 'Show a greeting',
60 params: [
61 {
62 name: 'name',
63 type: 'string',
64 description: 'The name to greet'
65 }
66 ],
67 returnType: 'string',
68 exec: function(args, context) {
69 return 'Hello, ' + args.name;
70 }
71 });
72
73 This command is used as follows:
74
75 : greet Joe
76 Hello, Joe
77
78 Some terminology that isn't always obvious: a function has 'parameters', and
79 when you call a function, you pass 'arguments' to it.
80
81
82 ## Internationalization (i18n)
83
84 There are several ways that GCLI commands can be localized. The best method
85 depends on what context you are writing your command for.
86
87 ### Firefox Embedding
88
89 GCLI supports Mozilla style localization. To add a command that will only ever
90 be used embedded in Firefox, this is the way to go. Your strings should be
91 stored in ``browser/locales/en-US/chrome/browser/devtools/gclicommands.properties``,
92 And you should access them using ``gcli.lookup(...)`` or ``gcli.lookupFormat()``
93
94 For examples of existing commands, take a look in
95 ``browser/devtools/webconsole/GcliCommands.jsm``, which contains most of the
96 current GCLI commands. If you will be adding a number of new commands, then
97 consider starting a new JSM.
98
99 Your command will then look something like this:
100
101 gcli.addCommand({
102 name: 'greet',
103 description: gcli.lookup("greetDesc")
104 ...
105 });
106
107 ### Web Commands
108
109 There are 2 ways to provide translated strings for web use. The first is to
110 supply the translated strings in the description:
111
112 gcli.addCommand({
113 name: 'greet',
114 description: {
115 'root': 'Show a greeting',
116 'fr-fr': 'Afficher un message d'accueil',
117 'de-de': 'Zeige einen Gruß',
118 'gk-gk': 'Εμφάνιση ένα χαιρετισμό',
119 ...
120 }
121 ...
122 });
123
124 Each description should contain at least a 'root' entry which is the
125 default if no better match is found. This method has the benefit of being
126 compact and simple, however it has the significant drawback of being wasteful
127 of memory and bandwidth to transmit and store a significant number of strings,
128 the majority of which will never be used.
129
130 More efficient is to supply a lookup key and ask GCLI to lookup the key from an
131 appropriate localized strings file:
132
133 gcli.addCommand({
134 name: 'greet',
135 description: { 'key': 'demoGreetingDesc' }
136 ...
137 });
138
139 For web usage, the central store of localized strings is
140 ``lib/gcli/nls/strings.js``. Other string files can be added using the
141 ``l10n.registerStringsSource(...)`` function.
142
143 This method can be used both in Firefox and on the Web (see the help command
144 for an example). However this method has the drawback that it will not work
145 with DryIce built files until we fix bug 683844.
146
147
148 ## Default argument values
149
150 The ``greet`` command requires the entry of the ``name`` parameter. This
151 parameter can be made optional with the addition of a ``defaultValue`` to the
152 parameter:
153
154 gcli.addCommand({
155 name: 'greet',
156 description: 'Show a message to someone',
157 params: [
158 {
159 name: 'name',
160 type: 'string',
161 description: 'The name to greet',
162 defaultValue: 'World!'
163 }
164 ],
165 returnType: 'string',
166 exec: function(args, context) {
167 return "Hello, " + args.name;
168 }
169 });
170
171 Now we can also use the ``greet`` command as follows:
172
173 : greet
174 Hello, World!
175
176
177 ## Positional vs. named arguments
178
179 Arguments can be entered either positionally or as named arguments. Generally
180 users will prefer to type the positional version, however the named alternative
181 can be more self documenting.
182
183 For example, we can also invoke the greet command as follows:
184
185 : greet --name Joe
186 Hello, Joe
187
188
189 ## Short argument names
190
191 GCLI allows you to specify a 'short' character for any parameter:
192
193 gcli.addCommand({
194 name: 'greet',
195 params: [
196 {
197 name: 'name',
198 short: 'n',
199 type: 'string',
200 ...
201 }
202 ],
203 ...
204 });
205
206 This is used as follows:
207
208 : greet -n Fred
209 Hello, Fred
210
211 Currently GCLI does not allow short parameter merging (i.e. ```ls -la```)
212 however this is planned.
213
214
215 ## Parameter types
216
217 Initially the available types are:
218
219 - string
220 - boolean
221 - number
222 - selection
223 - delegate
224 - date
225 - array
226 - file
227 - node
228 - nodelist
229 - resource
230 - command
231 - setting
232
233 This list can be extended. See [Writing Types](writing-types.md) on types for
234 more information.
235
236 The following examples assume the following definition of the ```greet```
237 command:
238
239 gcli.addCommand({
240 name: 'greet',
241 params: [
242 { name: 'name', type: 'string' },
243 { name: 'repeat', type: 'number' }
244 ],
245 ...
246 });
247
248 Parameters can be specified either with named arguments:
249
250 : greet --name Joe --repeat 2
251
252 And sometimes positionally:
253
254 : greet Joe 2
255
256 Parameters can be specified positionally if they are considered 'important'.
257 Unimportant parameters must be specified with a named argument.
258
259 Named arguments can be specified anywhere on the command line (after the
260 command itself) however positional arguments must be in order. So
261 these examples are the same:
262
263 : greet --name Joe --repeat 2
264 : greet --repeat 2 --name Joe
265
266 However (obviously) these are not the same:
267
268 : greet Joe 2
269 : greet 2 Joe
270
271 (The second would be an error because 'Joe' is not a number).
272
273 Named arguments are assigned first, then the remaining arguments are assigned
274 to the remaining parameters. So the following is valid and unambiguous:
275
276 : greet 2 --name Joe
277
278 Positional parameters quickly become unwieldy with long parameter lists so we
279 recommend only having 2 or 3 important parameters. GCLI provides hints for
280 important parameters more obviously than unimportant ones.
281
282 Parameters are 'important' if they are not in a parameter group. The easiest way
283 to achieve this is to use the ```option: true``` property.
284
285 For example, using:
286
287 gcli.addCommand({
288 name: 'greet',
289 params: [
290 { name: 'name', type: 'string' },
291 { name: 'repeat', type: 'number', option: true, defaultValue: 1 }
292 ],
293 ...
294 });
295
296 Would mean that this is an error
297
298 : greet Joe 2
299
300 You would instead need to do the following:
301
302 : greet Joe --repeat 2
303
304 For more on parameter groups, see below.
305
306 In addition to being 'important' and 'unimportant' parameters can also be
307 optional. If is possible to be important and optional, but it is not possible
308 to be unimportant and non-optional.
309
310 Parameters are optional if they either:
311 - Have a ```defaultValue``` property
312 - Are of ```type=boolean``` (boolean arguments automatically default to being false)
313
314 There is currently no way to make parameters mutually exclusive.
315
316
317 ## Selection types
318
319 Parameters can have a type of ``selection``. For example:
320
321 gcli.addCommand({
322 name: 'greet',
323 params: [
324 { name: 'name', ... },
325 {
326 name: 'lang',
327 description: 'In which language should we greet',
328 type: { name: 'selection', data: [ 'en', 'fr', 'de', 'es', 'gk' ] },
329 defaultValue: 'en'
330 }
331 ],
332 ...
333 });
334
335 GCLI will enforce that the value of ``arg.lang`` was one of the values
336 specified. Alternatively ``data`` can be a function which returns an array of
337 strings.
338
339 The ``data`` property is useful when the underlying type is a string but it
340 doesn't work when the underlying type is something else. For this use the
341 ``lookup`` property as follows:
342
343 type: {
344 name: 'selection',
345 lookup: {
346 'en': Locale.EN,
347 'fr': Locale.FR,
348 ...
349 }
350 },
351
352 Similarly, ``lookup`` can be a function returning the data of this type.
353
354
355 ## Number types
356
357 Number types are mostly self explanatory, they have one special property which
358 is the ability to specify upper and lower bounds for the number:
359
360 gcli.addCommand({
361 name: 'volume',
362 params: [
363 {
364 name: 'vol',
365 description: 'How loud should we go',
366 type: { name: 'number', min: 0, max: 11 }
367 }
368 ],
369 ...
370 });
371
372 You can also specify a ``step`` property which specifies by what amount we
373 should increment and decrement the values. The ``min``, ``max``, and ``step``
374 properties are used by the command line when up and down are pressed and in
375 the input type of a dialog generated from this command.
376
377
378 ## Delegate types
379
380 Delegate types are needed when the type of some parameter depends on the type
381 of another parameter. For example:
382
383 : set height 100
384 : set name "Joe Walker"
385
386 We can achieve this as follows:
387
388 gcli.addCommand({
389 name: 'set',
390 params: [
391 {
392 name: 'setting',
393 type: { name: 'selection', values: [ 'height', 'name' ] }
394 },
395 {
396 name: 'value',
397 type: {
398 name: 'delegate',
399 delegateType: function() { ... }
400 }
401 }
402 ],
403 ...
404 });
405
406 Several details are left out of this example, like how the delegateType()
407 function knows what the current setting is. See the ``pref`` command for an
408 example.
409
410
411 ## Array types
412
413 Parameters can have a type of ``array``. For example:
414
415 gcli.addCommand({
416 name: 'greet',
417 params: [
418 {
419 name: 'names',
420 type: { name: 'array', subtype: 'string' },
421 description: 'The names to greet',
422 defaultValue: [ 'World!' ]
423 }
424 ],
425 ...
426 exec: function(args, context) {
427 return "Hello, " + args.names.join(', ') + '.';
428 }
429 });
430
431 This would be used as follows:
432
433 : greet Fred Jim Shiela
434 Hello, Fred, Jim, Shiela.
435
436 Or using named arguments:
437
438 : greet --names Fred --names Jim --names Shiela
439 Hello, Fred, Jim, Shiela.
440
441 There can only be one ungrouped parameter with an array type, and it must be
442 at the end of the list of parameters (i.e. just before any parameter groups).
443 This avoids confusion as to which parameter an argument should be assigned.
444
445
446 ## Sub-commands
447
448 It is common for commands to be groups into those with similar functionality.
449 Examples include virtually all VCS commands, ``apt-get``, etc. There are many
450 examples of commands that should be structured as in a sub-command style -
451 ``tar`` being the obvious example, but others include ``crontab``.
452
453 Groups of commands are specified with the top level command not having an
454 exec function:
455
456 gcli.addCommand({
457 name: 'tar',
458 description: 'Commands to manipulate archives',
459 });
460 gcli.addCommand({
461 name: 'tar create',
462 description: 'Create a new archive',
463 exec: function(args, context) { ... },
464 ...
465 });
466 gcli.addCommand({
467 name: 'tar extract',
468 description: 'Extract from an archive',
469 exec: function(args, context) { ... },
470 ...
471 });
472
473
474 ## Parameter groups
475
476 Parameters can be grouped into sections.
477
478 There are 3 ways to assign a parameter to a group.
479
480 The simplest uses ```option: true``` to put a parameter into the default
481 'Options' group:
482
483 gcli.addCommand({
484 name: 'greet',
485 params: [
486 { name: 'repeat', type: 'number', option: true }
487 ],
488 ...
489 });
490
491 The ```option``` property can also take a string to use an alternative parameter
492 group:
493
494 gcli.addCommand({
495 name: 'greet',
496 params: [
497 { name: 'repeat', type: 'number', option: 'Advanced' }
498 ],
499 ...
500 });
501
502 An example of how this can be useful is 'git' which categorizes parameters into
503 'porcelain' and 'plumbing'.
504
505 Finally, parameters can be grouped together as follows:
506
507 gcli.addCommand({
508 name: 'greet',
509 params: [
510 { name: 'name', type: 'string', description: 'The name to greet' },
511 {
512 group: 'Advanced Options',
513 params: [
514 { name: 'repeat', type: 'number', defaultValue: 1 },
515 { name: 'debug', type: 'boolean' }
516 ]
517 }
518 ],
519 ...
520 });
521
522 This could be used as follows:
523
524 : greet Joe --repeat 2 --debug
525 About to send greeting
526 Hello, Joe
527 Hello, Joe
528 Done!
529
530 Parameter groups must come after non-grouped parameters because non-grouped
531 parameters can be assigned positionally, so their index is important. We don't
532 want 'holes' in the order caused by parameter groups.
533
534
535 ## Command metadata
536
537 Each command should have the following properties:
538
539 - A string ``name``.
540 - A short ``description`` string. Generally no more than 20 characters without
541 a terminating period/fullstop.
542 - A function to ``exec``ute. (Optional for the parent containing sub-commands)
543 See below for more details.
544
545 And optionally the following extra properties:
546
547 - A declaration of the accepted ``params``.
548 - A ``hidden`` property to stop the command showing up in requests for help.
549 - A ``context`` property which defines the scope of the function that we're
550 calling. Rather than simply call ``exec()``, we do ``exec.call(context)``.
551 - A ``manual`` property which allows a fuller description of the purpose of the
552 command.
553 - A ``returnType`` specifying how we should handle the value returned from the
554 exec function.
555
556 The ``params`` property is an array of objects, one for each parameter. Each
557 parameter object should have the following 3 properties:
558
559 - A string ``name``.
560 - A short string ``description`` as for the command.
561 - A ``type`` which refers to an existing Type (see Writing Types).
562
563 Optionally each parameter can have these properties:
564
565 - A ``defaultValue`` (which should be in the type specified in ``type``).
566 The defaultValue will be used when there is no argument supplied for this
567 parameter on the command line.
568 If the parameter has a ``defaultValue``, other than ``undefined`` then the
569 parameter is optional, and if unspecified on the command line, the matching
570 argument will have this value when the function is called.
571 If ``defaultValue`` is missing, or if it is set to ``undefined``, then the
572 system will ensure that a value is provided before anything is executed.
573 There are 2 special cases:
574 - If the type is ``selection``, then defaultValue must not be undefined.
575 The defaultValue must either be ``null`` (meaning that a value must be
576 supplied by the user) or one of the selection values.
577 - If the type is ``boolean``, then ``defaultValue:false`` is implied and
578 can't be changed. Boolean toggles are assumed to be off by default, and
579 should be named to match.
580 - A ``manual`` property for parameters is exactly analogous to the ``manual``
581 property for commands - descriptive text that is longer than than 20
582 characters.
583
584
585 ## The Command Function (exec)
586
587 The parameters to the exec function are designed to be useful when you have a
588 large number of parameters, and to give direct access to the environment (if
589 used).
590
591 gcli.addCommand({
592 name: 'echo',
593 description: 'The message to display.',
594 params: [
595 {
596 name: 'message',
597 type: 'string',
598 description: 'The message to display.'
599 }
600 ],
601 returnType: 'string',
602 exec: function(args, context) {
603 return args.message;
604 }
605 });
606
607 The ``args`` object contains the values specified on the params section and
608 provided on the command line. In this example it would contain the message for
609 display as ``args.message``.
610
611 The ``context`` object has the following signature:
612
613 {
614 environment: ..., // environment object passed to createTerminal()
615 exec: ..., // function to execute a command
616 update: ..., // function to alter the text of the input area
617 createView: ..., // function to help creating rich output
618 defer: ..., // function to create a deferred promise
619 }
620
621 The ``environment`` object is opaque to GCLI. It can be used for providing
622 arbitrary data to your commands about their environment. It is most useful
623 when more than one command line exists on a page with similar commands in both
624 which should act in their own ways.
625 An example use for ``environment`` would be a page with several tabs, each
626 containing an editor with a command line. Commands executed in those editors
627 should apply to the relevant editor.
628 The ``environment`` object is passed to GCLI at startup (probably in the
629 ``createTerminal()`` function).
630
631 The ``document`` object is also passed to GCLI at startup. In some environments
632 (e.g. embedded in Firefox) there is no global ``document``. This object
633 provides a way to create DOM nodes.
634
635 ``defer()`` allows commands to execute asynchronously.
636
637
638 ## Returning data
639
640 The command meta-data specifies the type of data returned by the command using
641 the ``returnValue`` setting.
642
643 ``returnValue`` processing is currently functioning, but incomplete, and being
644 tracked in [Bug 657595](http://bugzil.la/657595). Currently you should specify
645 a ``returnType`` of ``string`` or ``html``. If using HTML, you can return
646 either an HTML string or a DOM node.
647
648 In the future, JSON will be strongly encouraged as the return type, with some
649 formatting functions to convert the JSON to HTML.
650
651 Asynchronous output is achieved using a promise created from the ``context``
652 parameter: ``context.defer()``.
653
654 Some examples of this is practice:
655
656 { returnType: "string" }
657 ...
658 return "example";
659
660 GCLI interprets the output as a plain string. It will be escaped before display
661 and available as input to other commands as a plain string.
662
663 { returnType: "html" }
664 ...
665 return "<p>Hello</p>";
666
667 GCLI will interpret this as HTML, and parse it for display.
668
669 { returnType: "dom" }
670 ...
671 return util.createElement(context.document, 'div');
672
673 ``util.createElement`` is a utility to ensure use of the XHTML namespace in XUL
674 and other XML documents. In an HTML document it's functionally equivalent to
675 ``context.document.createElement('div')``. If your command is likely to be used
676 in Firefox or another XML environment, you should use it. You can import it
677 with ``var util = require('util/util');``.
678
679 GCLI will use the returned HTML element as returned. See notes on ``context``
680 above.
681
682 { returnType: "number" }
683 ...
684 return 42;
685
686 GCLI will display the element in a similar way to a string, but it the value
687 will be available to future commands as a number.
688
689 { returnType: "date" }
690 ...
691 return new Date();
692
693 { returnType: "file" }
694 ...
695 return new File();
696
697 Both these examples return data as a given type, for which a converter will
698 be required before the value can be displayed. The type system is likely to
699 change before this is finalized. Please contact the author for more
700 information.
701
702 { returnType: "string" }
703 ...
704 var deferred = context.defer();
705 setTimeout(function() {
706 deferred.resolve("hello");
707 }, 500);
708 return deferred.promise;
709
710 Errors can be signaled by throwing an exception. GCLI will display the message
711 property (or the toString() value if there is no message property). (However
712 see *3 principles for writing commands* above for ways to avoid doing this).
713
714
715 ## Specifying Types
716
717 Types are generally specified by a simple string, e.g. ``'string'``. For most
718 types this is enough detail. There are a number of exceptions:
719
720 * Array types. We declare a parameter to be an array of things using ``[]``,
721 for example: ``number[]``.
722 * Selection types. There are 3 ways to specify the options in a selection:
723 * Using a lookup map
724
725 type: {
726 name: 'selection',
727 lookup: { one:1, two:2, three:3 }
728 }
729
730 (The boolean type is effectively just a selection that uses
731 ``lookup:{ 'true': true, 'false': false }``)
732
733 * Using given strings
734
735 type: {
736 name: 'selection',
737 data: [ 'left', 'center', 'right' ]
738 }
739
740 * Using named objects, (objects with a ``name`` property)
741
742 type: {
743 name: 'selection',
744 data: [
745 { name: 'Google', url: 'http://www.google.com/' },
746 { name: 'Microsoft', url: 'http://www.microsoft.com/' },
747 { name: 'Yahoo', url: 'http://www.yahoo.com/' }
748 ]
749 }
750
751 * Delegate type. It is generally best to inherit from Delegate in order to
752 provide a customization of this type. See settingValue for an example.
753
754 See below for more information.
755

mercurial