summaryrefslogtreecommitdiffstats
path: root/devtools/shared/gcli/source/docs/writing-commands.md
blob: e73050279184fe70eb0446e6c4819f311a9ccb90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757

# Writing Commands

## Basics

GCLI has opinions about how commands should be written, and it encourages you
to do The Right Thing. The opinions are based on helping users convert their
intentions to commands and commands to what's actually going to happen.

- Related commands should be sub-commands of a parent command. One of the goals
  of GCLI is to support a large number of commands without things becoming
  confusing, this will require some sort of namespacing or there will be
  many people wanting to implement the ``add`` command. This style of
  writing commands has become common place in Unix as the number of commands
  has gone up.
  The ```context``` command allows users to focus on a parent command, promoting
  its sub-commands above others.

- Each command should do exactly and only one thing. An example of a Unix
  command that breaks this principle is the ``tar`` command

        $ tar -zcf foo.tar.gz .
        $ tar -zxf foo.tar.gz .

  These 2 commands do exactly opposite things. Many a file has died as a result
  of a x/c typo. In GCLI this would be better expressed:

        $ tar create foo.tar.gz -z .
        $ tar extract foo.tar.gz -z .

  There may be commands (like tar) which have enough history behind them
  that we shouldn't force everyone to re-learn a new syntax. The can be achieved
  by having a single string parameter and parsing the input in the command)

- Avoid errors. We try to avoid the user having to start again with a command
  due to some problem. The majority of problems are simple typos which we can
  catch using command metadata, but there are 2 things command authors can do
  to prevent breakage.

  - Where possible avoid the need to validate command line parameters in the
    exec function. This can be done by good parameter design (see 'do exactly
    and only one thing' above)

  - If there is an obvious fix for an unpredictable problem, offer the
    solution in the command output. So rather than use request.error (see
    Request Object below) output some HTML which contains a link to a fixed
    command line.

Currently these concepts are not enforced at a code level, but they could be in
the future.


## How commands work

This is how to create a basic ``greet`` command:

    gcli.addItems([{
      name: 'greet',
      description: 'Show a greeting',
      params: [
        {
          name: 'name',
          type: 'string',
          description: 'The name to greet'
        }
      ],
      returnType: 'string',
      exec: function(args, context) {
        return 'Hello, ' + args.name;
      }
    }]);

This command is used as follows:

    : greet Joe
    Hello, Joe

Some terminology that isn't always obvious: a function has 'parameters', and
when you call a function, you pass 'arguments' to it.


## Internationalization (i18n)

There are several ways that GCLI commands can be localized. The best method
depends on what context you are writing your command for.

### Firefox Embedding

GCLI supports Mozilla style localization. To add a command that will only ever
be used embedded in Firefox, this is the way to go. Your strings should be
stored in ``toolkit/locales/en-US/chrome/global/devtools/gclicommands.properties``,
And you should access them using ``let l10n = require("gcli/l10n")`` and then
``l10n.lookup(...)`` or ``l10n.lookupFormat()``

For examples of existing commands, take a look in
``devtools/client/webconsole/GcliCommands.jsm``, which contains most of the
current GCLI commands. If you will be adding a number of new commands, then
consider starting a new JSM.

Your command will then look something like this:

    gcli.addItems([{
      name: 'greet',
      description: gcli.lookup("greetDesc")
      ...
    }]);

### Web Commands

There are 2 ways to provide translated strings for web use. The first is to
supply the translated strings in the description:

    gcli.addItems([{
      name: 'greet',
      description: {
        'root': 'Show a greeting',
        'fr-fr': 'Afficher un message d'accueil',
        'de-de': 'Zeige einen Gruß',
        'gk-gk': 'Εμφάνιση ένα χαιρετισμό',
        ...
      }
      ...
    }]);

Each description should contain at least a 'root' entry which is the
default if no better match is found. This method has the benefit of being
compact and simple, however it has the significant drawback of being wasteful
of memory and bandwidth to transmit and store a significant number of strings,
the majority of which will never be used.

More efficient is to supply a lookup key and ask GCLI to lookup the key from an
appropriate localized strings file:

    gcli.addItems([{
      name: 'greet',
      description: { 'key': 'demoGreetingDesc' }
      ...
    }]);

For web usage, the central store of localized strings is
``lib/gcli/nls/strings.js``. Other string files can be added using the
``l10n.registerStringsSource(...)`` function.

This method can be used both in Firefox and on the Web (see the help command
for an example). However this method has the drawback that it will not work
with DryIce built files until we fix bug 683844.


## Default argument values

The ``greet`` command requires the entry of the ``name`` parameter. This
parameter can be made optional with the addition of a ``defaultValue`` to the
parameter:

    gcli.addItems([{
      name: 'greet',
      description: 'Show a message to someone',
      params: [
        {
          name: 'name',
          type: 'string',
          description: 'The name to greet',
          defaultValue: 'World!'
        }
      ],
      returnType: 'string',
      exec: function(args, context) {
        return "Hello, " + args.name;
      }
    }]);

Now we can also use the ``greet`` command as follows:

    : greet
    Hello, World!


## Positional vs. named arguments

Arguments can be entered either positionally or as named arguments. Generally
users will prefer to type the positional version, however the named alternative
can be more self documenting.

For example, we can also invoke the greet command as follows:

    : greet --name Joe
    Hello, Joe


## Short argument names

GCLI allows you to specify a 'short' character for any parameter:

    gcli.addItems([{
      name: 'greet',
      params: [
        {
          name: 'name',
          short: 'n',
          type: 'string',
          ...
        }
      ],
      ...
    }]);

This is used as follows:

    : greet -n Fred
    Hello, Fred

Currently GCLI does not allow short parameter merging (i.e. ```ls -la```)
however this is planned.


## Parameter types

Initially the available types are:

- string
- boolean
- number
- selection
- delegate
- date
- array
- file
- node
- nodelist
- resource
- command
- setting

This list can be extended. See [Writing Types](writing-types.md) on types for
more information.

The following examples assume the following definition of the ```greet```
command:

    gcli.addItems([{
      name: 'greet',
      params: [
        { name: 'name', type: 'string' },
        { name: 'repeat', type: 'number' }
      ],
      ...
    }]);

Parameters can be specified either with named arguments:

    : greet --name Joe --repeat 2

And sometimes positionally:

    : greet Joe 2

Parameters can be specified positionally if they are considered 'important'.
Unimportant parameters must be specified with a named argument.

Named arguments can be specified anywhere on the command line (after the
command itself) however positional arguments must be in order. So
these examples are the same:

    : greet --name Joe --repeat 2
    : greet --repeat 2 --name Joe

However (obviously) these are not the same:

    : greet Joe 2
    : greet 2 Joe

(The second would be an error because 'Joe' is not a number).

Named arguments are assigned first, then the remaining arguments are assigned
to the remaining parameters. So the following is valid and unambiguous:

    : greet 2 --name Joe

Positional parameters quickly become unwieldy with long parameter lists so we
recommend only having 2 or 3 important parameters. GCLI provides hints for
important parameters more obviously than unimportant ones.

Parameters are 'important' if they are not in a parameter group. The easiest way
to achieve this is to use the ```option: true``` property.

For example, using:

    gcli.addItems([{
      name: 'greet',
      params: [
        { name: 'name', type: 'string' },
        { name: 'repeat', type: 'number', option: true, defaultValue: 1 }
      ],
      ...
    }]);

Would mean that this is an error

    : greet Joe 2

You would instead need to do the following:

    : greet Joe --repeat 2

For more on parameter groups, see below.

In addition to being 'important' and 'unimportant' parameters can also be
optional. If is possible to be important and optional, but it is not possible
to be unimportant and non-optional.

Parameters are optional if they either:
- Have a ```defaultValue``` property
- Are of ```type=boolean``` (boolean arguments automatically default to being false)

There is currently no way to make parameters mutually exclusive.


## Selection types

Parameters can have a type of ``selection``. For example:

    gcli.addItems([{
      name: 'greet',
      params: [
        { name: 'name', ... },
        {
          name: 'lang',
          description: 'In which language should we greet',
          type: { name: 'selection', data: [ 'en', 'fr', 'de', 'es', 'gk' ] },
          defaultValue: 'en'
        }
      ],
      ...
    }]);

GCLI will enforce that the value of ``arg.lang`` was one of the values
specified. Alternatively ``data`` can be a function which returns an array of
strings.

The ``data`` property is useful when the underlying type is a string but it
doesn't work when the underlying type is something else. For this use the
``lookup`` property as follows:

      type: {
        name: 'selection',
        lookup: {
          'en': Locale.EN,
          'fr': Locale.FR,
          ...
        }
      },

Similarly, ``lookup`` can be a function returning the data of this type.


## Number types

Number types are mostly self explanatory, they have one special property which
is the ability to specify upper and lower bounds for the number:

    gcli.addItems([{
      name: 'volume',
      params: [
        {
          name: 'vol',
          description: 'How loud should we go',
          type: { name: 'number', min: 0, max: 11 }
        }
      ],
      ...
    }]);

You can also specify a ``step`` property which specifies by what amount we
should increment and decrement the values. The ``min``, ``max``, and ``step``
properties are used by the command line when up and down are pressed and in
the input type of a dialog generated from this command.


## Delegate types

Delegate types are needed when the type of some parameter depends on the type
of another parameter. For example:

    : set height 100
    : set name "Joe Walker"

We can achieve this as follows:

    gcli.addItems([{
      name: 'set',
      params: [
        {
          name: 'setting',
          type: { name: 'selection', values: [ 'height', 'name' ] }
        },
        {
          name: 'value',
          type: {
            name: 'delegate',
            delegateType: function() { ... }
          }
        }
      ],
      ...
    }]);

Several details are left out of this example, like how the delegateType()
function knows what the current setting is. See the ``pref`` command for an
example.


## Array types

Parameters can have a type of ``array``. For example:

    gcli.addItems([{
      name: 'greet',
      params: [
        {
          name: 'names',
          type: { name: 'array', subtype: 'string' },
          description: 'The names to greet',
          defaultValue: [ 'World!' ]
        }
      ],
      ...
      exec: function(args, context) {
        return "Hello, " + args.names.join(', ') + '.';
      }
    }]);

This would be used as follows:

    : greet Fred Jim Shiela
    Hello, Fred, Jim, Shiela.

Or using named arguments:

    : greet --names Fred --names Jim --names Shiela
    Hello, Fred, Jim, Shiela.

There can only be one ungrouped parameter with an array type, and it must be
at the end of the list of parameters (i.e. just before any parameter groups).
This avoids confusion as to which parameter an argument should be assigned.


## Sub-commands

It is common for commands to be groups into those with similar functionality.
Examples include virtually all VCS commands, ``apt-get``, etc. There are many
examples of commands that should be structured as in a sub-command style -
``tar`` being the obvious example, but others include ``crontab``.

Groups of commands are specified with the top level command not having an
exec function:

    gcli.addItems([
      {
        name: 'tar',
        description: 'Commands to manipulate archives',
      },
      {
        name: 'tar create',
        description: 'Create a new archive',
        exec: function(args, context) { ... },
        ...
      },
      {
        name: 'tar extract',
        description: 'Extract from an archive',
        exec: function(args, context) { ... },
        ...
      }
    ]);


## Parameter groups

Parameters can be grouped into sections.

There are 3 ways to assign a parameter to a group.

The simplest uses ```option: true``` to put a parameter into the default
'Options' group:

    gcli.addItems([{
      name: 'greet',
      params: [
        { name: 'repeat', type: 'number', option: true }
      ],
      ...
    }]);

The ```option``` property can also take a string to use an alternative parameter
group:

    gcli.addItems([{
      name: 'greet',
      params: [
        { name: 'repeat', type: 'number', option: 'Advanced' }
      ],
      ...
    }]);

An example of how this can be useful is 'git' which categorizes parameters into
'porcelain' and 'plumbing'.

Finally, parameters can be grouped together as follows:

    gcli.addItems([{
      name: 'greet',
      params: [
        { name: 'name', type: 'string', description: 'The name to greet' },
        {
          group: 'Advanced Options',
          params: [
            { name: 'repeat', type: 'number', defaultValue: 1 },
            { name: 'debug', type: 'boolean' }
          ]
        }
      ],
      ...
    }]);

This could be used as follows:

    : greet Joe --repeat 2 --debug
    About to send greeting
    Hello, Joe
    Hello, Joe
    Done!

Parameter groups must come after non-grouped parameters because non-grouped
parameters can be assigned positionally, so their index is important. We don't
want 'holes' in the order caused by parameter groups.


## Command metadata

Each command should have the following properties:

- A string ``name``.
- A short ``description`` string. Generally no more than 20 characters without
  a terminating period/fullstop.
- A function to ``exec``ute. (Optional for the parent containing sub-commands)
  See below for more details.

And optionally the following extra properties:

- A declaration of the accepted ``params``.
- A ``hidden`` property to stop the command showing up in requests for help.
- A ``context`` property which defines the scope of the function that we're
  calling. Rather than simply call ``exec()``, we do ``exec.call(context)``.
- A ``manual`` property which allows a fuller description of the purpose of the
  command.
- A ``returnType`` specifying how we should handle the value returned from the
  exec function.

The ``params`` property is an array of objects, one for each parameter. Each
parameter object should have the following 3 properties:

- A string ``name``.
- A short string ``description`` as for the command.
- A ``type`` which refers to an existing Type (see Writing Types).

Optionally each parameter can have these properties:

- A ``defaultValue`` (which should be in the type specified in ``type``).
  The defaultValue will be used when there is no argument supplied for this
  parameter on the command line.
  If the parameter has a ``defaultValue``, other than ``undefined`` then the
  parameter is optional, and if unspecified on the command line, the matching
  argument will have this value when the function is called.
  If ``defaultValue`` is missing, or if it is set to ``undefined``, then the
  system will ensure that a value is provided before anything is executed.
  There are 2 special cases:
  - If the type is ``selection``, then defaultValue must not be undefined.
    The defaultValue must either be ``null`` (meaning that a value must be
    supplied by the user) or one of the selection values.
  - If the type is ``boolean``, then ``defaultValue:false`` is implied and
    can't be changed. Boolean toggles are assumed to be off by default, and
    should be named to match.
- A ``manual`` property for parameters is exactly analogous to the ``manual``
  property for commands - descriptive text that is longer than than 20
  characters.


## The Command Function (exec)

The parameters to the exec function are designed to be useful when you have a
large number of parameters, and to give direct access to the environment (if
used).

    gcli.addItems([{
      name: 'echo',
      description: 'The message to display.',
      params: [
        {
          name: 'message',
          type: 'string',
          description: 'The message to display.'
        }
      ],
      returnType: 'string',
      exec: function(args, context) {
        return args.message;
      }
    }]);

The ``args`` object contains the values specified on the params section and
provided on the command line. In this example it would contain the message for
display as ``args.message``.

The ``context`` object has the following signature:

    {
      environment: ..., // environment object passed to createTerminal()
      exec: ...,        // function to execute a command
      update: ...,      // function to alter the text of the input area
      createView: ...,  // function to help creating rich output
      defer: ...,       // function to create a deferred promise
    }

The ``environment`` object is opaque to GCLI. It can be used for providing
arbitrary data to your commands about their environment. It is most useful
when more than one command line exists on a page with similar commands in both
which should act in their own ways.
An example use for ``environment`` would be a page with several tabs, each
containing an editor with a command line. Commands executed in those editors
should apply to the relevant editor.
The ``environment`` object is passed to GCLI at startup (probably in the
``createTerminal()`` function).

The ``document`` object is also passed to GCLI at startup. In some environments
(e.g. embedded in Firefox) there is no global ``document``. This object
provides a way to create DOM nodes.

``defer()`` allows commands to execute asynchronously.


## Returning data

The command meta-data specifies the type of data returned by the command using
the ``returnValue`` setting.

``returnValue`` processing is currently functioning, but incomplete, and being
tracked in [Bug 657595](http://bugzil.la/657595). Currently you should specify
a ``returnType`` of ``string`` or ``html``. If using HTML, you can return
either an HTML string or a DOM node.

In the future, JSON will be strongly encouraged as the return type, with some
formatting functions to convert the JSON to HTML.

Asynchronous output is achieved using a promise created from the ``context``
parameter: ``context.defer()``.

Some examples of this is practice:

    { returnType: "string" }
    ...
    return "example";

GCLI interprets the output as a plain string. It will be escaped before display
and available as input to other commands as a plain string.

    { returnType: "html" }
    ...
    return "<p>Hello</p>";

GCLI will interpret this as HTML, and parse it for display.

    { returnType: "dom" }
    ...
    return util.createElement(context.document, 'div');

``util.createElement`` is a utility to ensure use of the XHTML namespace in XUL
and other XML documents. In an HTML document it's functionally equivalent to
``context.document.createElement('div')``. If your command is likely to be used
in Firefox or another XML environment, you should use it. You can import it
with ``var util = require('util/util');``.

GCLI will use the returned HTML element as returned. See notes on ``context``
above.

    { returnType: "number" }
    ...
    return 42;

GCLI will display the element in a similar way to a string, but it the value
will be available to future commands as a number.

    { returnType: "date" }
    ...
    return new Date();

    { returnType: "file" }
    ...
    return new File();

Both these examples return data as a given type, for which a converter will
be required before the value can be displayed. The type system is likely to
change before this is finalized. Please contact the author for more
information.

    { returnType: "string" }
    ...
    var deferred = context.defer();
    setTimeout(function() {
      deferred.resolve("hello");
    }, 500);
    return deferred.promise;

Errors can be signaled by throwing an exception. GCLI will display the message
property (or the toString() value if there is no message property). (However
see *3 principles for writing commands* above for ways to avoid doing this).


## Specifying Types

Types are generally specified by a simple string, e.g. ``'string'``. For most
types this is enough detail. There are a number of exceptions:

* Array types. We declare a parameter to be an array of things using ``[]``,
  for example: ``number[]``.
* Selection types. There are 3 ways to specify the options in a selection:
  * Using a lookup map

            type: {
              name: 'selection',
              lookup: { one:1, two:2, three:3 }
            }

    (The boolean type is effectively just a selection that uses
    ``lookup:{ 'true': true, 'false': false }``)

  * Using given strings

            type: {
              name: 'selection',
              data: [ 'left', 'center', 'right' ]
            }

  * Using named objects, (objects with a ``name`` property)

            type: {
              name: 'selection',
              data: [
                { name: 'Google', url: 'http://www.google.com/' },
                { name: 'Microsoft', url: 'http://www.microsoft.com/' },
                { name: 'Yahoo', url: 'http://www.yahoo.com/' }
              ]
            }

* Delegate type. It is generally best to inherit from Delegate in order to
  provide a customization of this type. See settingValue for an example.

See below for more information.