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

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     2 # Writing Commands
     4 ## Basics
     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.
    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.
    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
    22         $ tar -zcf foo.tar.gz .
    23         $ tar -zxf foo.tar.gz .
    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:
    28         $ tar create foo.tar.gz -z .
    29         $ tar extract foo.tar.gz -z .
    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)
    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.
    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)
    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.
    49 Currently these concepts are not enforced at a code level, but they could be in
    50 the future.
    53 ## How commands work
    55 This is how to create a basic ``greet`` command:
    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     });
    73 This command is used as follows:
    75     : greet Joe
    76     Hello, Joe
    78 Some terminology that isn't always obvious: a function has 'parameters', and
    79 when you call a function, you pass 'arguments' to it.
    82 ## Internationalization (i18n)
    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.
    87 ### Firefox Embedding
    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()``
    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.
    99 Your command will then look something like this:
   101     gcli.addCommand({
   102       name: 'greet',
   103       description: gcli.lookup("greetDesc")
   104       ...
   105     });
   107 ### Web Commands
   109 There are 2 ways to provide translated strings for web use. The first is to
   110 supply the translated strings in the description:
   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     });
   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.
   130 More efficient is to supply a lookup key and ask GCLI to lookup the key from an
   131 appropriate localized strings file:
   133     gcli.addCommand({
   134       name: 'greet',
   135       description: { 'key': 'demoGreetingDesc' }
   136       ...
   137     });
   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.
   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.
   148 ## Default argument values
   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:
   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     });
   171 Now we can also use the ``greet`` command as follows:
   173     : greet
   174     Hello, World!
   177 ## Positional vs. named arguments
   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.
   183 For example, we can also invoke the greet command as follows:
   185     : greet --name Joe
   186     Hello, Joe
   189 ## Short argument names
   191 GCLI allows you to specify a 'short' character for any parameter:
   193     gcli.addCommand({
   194       name: 'greet',
   195       params: [
   196         {
   197           name: 'name',
   198           short: 'n',
   199           type: 'string',
   200           ...
   201         }
   202       ],
   203       ...
   204     });
   206 This is used as follows:
   208     : greet -n Fred
   209     Hello, Fred
   211 Currently GCLI does not allow short parameter merging (i.e. ```ls -la```)
   212 however this is planned.
   215 ## Parameter types
   217 Initially the available types are:
   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
   233 This list can be extended. See [Writing Types](writing-types.md) on types for
   234 more information.
   236 The following examples assume the following definition of the ```greet```
   237 command:
   239     gcli.addCommand({
   240       name: 'greet',
   241       params: [
   242         { name: 'name', type: 'string' },
   243         { name: 'repeat', type: 'number' }
   244       ],
   245       ...
   246     });
   248 Parameters can be specified either with named arguments:
   250     : greet --name Joe --repeat 2
   252 And sometimes positionally:
   254     : greet Joe 2
   256 Parameters can be specified positionally if they are considered 'important'.
   257 Unimportant parameters must be specified with a named argument.
   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:
   263     : greet --name Joe --repeat 2
   264     : greet --repeat 2 --name Joe
   266 However (obviously) these are not the same:
   268     : greet Joe 2
   269     : greet 2 Joe
   271 (The second would be an error because 'Joe' is not a number).
   273 Named arguments are assigned first, then the remaining arguments are assigned
   274 to the remaining parameters. So the following is valid and unambiguous:
   276     : greet 2 --name Joe
   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.
   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.
   285 For example, using:
   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     });
   296 Would mean that this is an error
   298     : greet Joe 2
   300 You would instead need to do the following:
   302     : greet Joe --repeat 2
   304 For more on parameter groups, see below.
   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.
   310 Parameters are optional if they either:
   311 - Have a ```defaultValue``` property
   312 - Are of ```type=boolean``` (boolean arguments automatically default to being false)
   314 There is currently no way to make parameters mutually exclusive.
   317 ## Selection types
   319 Parameters can have a type of ``selection``. For example:
   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     });
   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.
   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:
   343       type: {
   344         name: 'selection',
   345         lookup: {
   346           'en': Locale.EN,
   347           'fr': Locale.FR,
   348           ...
   349         }
   350       },
   352 Similarly, ``lookup`` can be a function returning the data of this type.
   355 ## Number types
   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:
   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     });
   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.
   378 ## Delegate types
   380 Delegate types are needed when the type of some parameter depends on the type
   381 of another parameter. For example:
   383     : set height 100
   384     : set name "Joe Walker"
   386 We can achieve this as follows:
   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     });
   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.
   411 ## Array types
   413 Parameters can have a type of ``array``. For example:
   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     });
   431 This would be used as follows:
   433     : greet Fred Jim Shiela
   434     Hello, Fred, Jim, Shiela.
   436 Or using named arguments:
   438     : greet --names Fred --names Jim --names Shiela
   439     Hello, Fred, Jim, Shiela.
   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.
   446 ## Sub-commands
   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``.
   453 Groups of commands are specified with the top level command not having an
   454 exec function:
   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     });
   474 ## Parameter groups
   476 Parameters can be grouped into sections.
   478 There are 3 ways to assign a parameter to a group.
   480 The simplest uses ```option: true``` to put a parameter into the default
   481 'Options' group:
   483     gcli.addCommand({
   484       name: 'greet',
   485       params: [
   486         { name: 'repeat', type: 'number', option: true }
   487       ],
   488       ...
   489     });
   491 The ```option``` property can also take a string to use an alternative parameter
   492 group:
   494     gcli.addCommand({
   495       name: 'greet',
   496       params: [
   497         { name: 'repeat', type: 'number', option: 'Advanced' }
   498       ],
   499       ...
   500     });
   502 An example of how this can be useful is 'git' which categorizes parameters into
   503 'porcelain' and 'plumbing'.
   505 Finally, parameters can be grouped together as follows:
   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     });
   522 This could be used as follows:
   524     : greet Joe --repeat 2 --debug
   525     About to send greeting
   526     Hello, Joe
   527     Hello, Joe
   528     Done!
   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.
   535 ## Command metadata
   537 Each command should have the following properties:
   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.
   545 And optionally the following extra properties:
   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.
   556 The ``params`` property is an array of objects, one for each parameter. Each
   557 parameter object should have the following 3 properties:
   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).
   563 Optionally each parameter can have these properties:
   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.
   585 ## The Command Function (exec)
   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).
   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     });
   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``.
   611 The ``context`` object has the following signature:
   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     }
   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).
   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.
   635 ``defer()`` allows commands to execute asynchronously.
   638 ## Returning data
   640 The command meta-data specifies the type of data returned by the command using
   641 the ``returnValue`` setting.
   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.
   648 In the future, JSON will be strongly encouraged as the return type, with some
   649 formatting functions to convert the JSON to HTML.
   651 Asynchronous output is achieved using a promise created from the ``context``
   652 parameter: ``context.defer()``.
   654 Some examples of this is practice:
   656     { returnType: "string" }
   657     ...
   658     return "example";
   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.
   663     { returnType: "html" }
   664     ...
   665     return "<p>Hello</p>";
   667 GCLI will interpret this as HTML, and parse it for display.
   669     { returnType: "dom" }
   670     ...
   671     return util.createElement(context.document, 'div');
   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');``.
   679 GCLI will use the returned HTML element as returned. See notes on ``context``
   680 above.
   682     { returnType: "number" }
   683     ...
   684     return 42;
   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.
   689     { returnType: "date" }
   690     ...
   691     return new Date();
   693     { returnType: "file" }
   694     ...
   695     return new File();
   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.
   702     { returnType: "string" }
   703     ...
   704     var deferred = context.defer();
   705     setTimeout(function() {
   706       deferred.resolve("hello");
   707     }, 500);
   708     return deferred.promise;
   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).
   715 ## Specifying Types
   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:
   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
   725             type: {
   726               name: 'selection',
   727               lookup: { one:1, two:2, three:3 }
   728             }
   730     (The boolean type is effectively just a selection that uses
   731     ``lookup:{ 'true': true, 'false': false }``)
   733   * Using given strings
   735             type: {
   736               name: 'selection',
   737               data: [ 'left', 'center', 'right' ]
   738             }
   740   * Using named objects, (objects with a ``name`` property)
   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             }
   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.
   754 See below for more information.

mercurial