summaryrefslogtreecommitdiffstats
path: root/devtools/shared/gcli/source/docs/writing-commands.md
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/gcli/source/docs/writing-commands.md')
-rw-r--r--devtools/shared/gcli/source/docs/writing-commands.md757
1 files changed, 757 insertions, 0 deletions
diff --git a/devtools/shared/gcli/source/docs/writing-commands.md b/devtools/shared/gcli/source/docs/writing-commands.md
new file mode 100644
index 000000000..e73050279
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/writing-commands.md
@@ -0,0 +1,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.