diff options
Diffstat (limited to 'devtools/shared/gcli/source/docs')
-rw-r--r-- | devtools/shared/gcli/source/docs/design.md | 102 | ||||
-rw-r--r-- | devtools/shared/gcli/source/docs/developing-gcli.md | 213 | ||||
-rw-r--r-- | devtools/shared/gcli/source/docs/index.md | 150 | ||||
-rw-r--r-- | devtools/shared/gcli/source/docs/running-tests.md | 60 | ||||
-rw-r--r-- | devtools/shared/gcli/source/docs/writing-commands.md | 757 | ||||
-rw-r--r-- | devtools/shared/gcli/source/docs/writing-tests.md | 20 | ||||
-rw-r--r-- | devtools/shared/gcli/source/docs/writing-types.md | 106 |
7 files changed, 1408 insertions, 0 deletions
diff --git a/devtools/shared/gcli/source/docs/design.md b/devtools/shared/gcli/source/docs/design.md new file mode 100644 index 000000000..5e3c0a2f3 --- /dev/null +++ b/devtools/shared/gcli/source/docs/design.md @@ -0,0 +1,102 @@ + +# The Design of GCLI + +## Design Goals + +GCLI should be: + +- primarily for technical users. +- as fast as a traditional CLI. It should be possible to put your head down, + and look at the keyboard and use GCLI 'blind' at full speed without making + mistakes. +- principled about the way it encourages people to build commands. There is + benefit from unifying the underlying concepts. +- automatically helpful. + +GCLI should not attempt to: + +- convert existing GUI users to a CLI. +- use natural language input. The closest we should get to natural language is + thinking of commands as ```verb noun --adjective```. +- gain a touch based interface. Whilst it's possible (even probable) that touch + can provide further benefits to command line users, that can wait while we + catch up with 1985. +- slavishly follow the syntax of existing commands, predictability is more + important. +- be a programming language. Shell scripts are mini programming languages but + we have JavaScript sat just next door. It's better to integrate than compete. + + +## Design Challenges + +What has changed since 1970 that might cause us to make changes to the design +of the command line? + + +### Connection limitations + +Unix pre-dates the Internet and treats almost everything as a file. Since the +Internet it could be more useful to use URIs as ways to identify sources of data. + + +### Memory limitations + +Modern computers have something like 6 orders of magnitude more memory than the +PDP-7 on which Unix was developed. Innovations like stdin/stdout and pipes are +ways to connect systems without long-term storage of the results. The ability +to store results for some time (potentially in more than one format) +significantly reduces the need for these concepts. We should make the results +of past commands addressable for re-use at a later time. + +There are a number of possible policies for eviction of items from the history. +We should investigate options other than a simple stack. + + +### Multi-tasking limitations + +Multi-tasking was a problem in 1970; the problem was getting a computer to do +many jobs on 1 core. Today the problem is getting a computer to do one job on +many cores. However we're stuck with this legacy in 2 ways. Firstly that the +default is to force everything to wait until the previous job is finished, but +more importantly that output from parallel jobs frequently collides + + $ find / -ctime 5d -print & + $ find / -uid 0 -print & + // good luck working out what came from where + + $ tail -f logfile.txt & + $ vi main.c + // have a nice time editing that file + +GCLI should allow commands to be asynchronous and will provide UI elements to +inform the user of job completion. It will also keep asynchronous command +output contained within it's own display area. + + +### Output limitations + +The PDP-7 had a teletype. There is something like 4 orders of magnitude more +information that can be displayed on a modern display than a 80x24 character +based console. We can use this flexibility to provide better help to the user +in entering their command. + +The additional display richness can also allow interaction with result output. +Command output can include links to follow-up commands, and even ask for +additional input. (e.g. "your search returned zero results do you want to try +again with a different search string") + +There is no reason why output must be static. For example, it could be +informative to see the results of an "ls" command alter given changes made by +subsequent commands. (It should be noted that there are times when historical +information is important too) + + +### Integration limitations + +In 1970, command execution meant retrieving a program from storage, and running +it. This required minimal interaction between the command line processor and +the program being run, and was good for resource constrained systems. +This lack of interaction resulted in the processing of command line arguments +being done everywhere, when the task was better suited to command line. +We should provide metadata about the commands being run, to allow the command +line to process, interpret and provide help on the input. diff --git a/devtools/shared/gcli/source/docs/developing-gcli.md b/devtools/shared/gcli/source/docs/developing-gcli.md new file mode 100644 index 000000000..113712655 --- /dev/null +++ b/devtools/shared/gcli/source/docs/developing-gcli.md @@ -0,0 +1,213 @@ + +# Developing GCLI + +## About the code + +The majority of the GCLI source is stored in the ``lib`` directory. + +The ``docs`` directory contains documentation. +The ``scripts`` directory contains RequireJS that GCLI uses. +The ``build`` directory contains files used when creating builds. +The ``mozilla`` directory contains the mercurial patch queue of patches to apply +to mozilla-central. +The ``selenium-tests`` directory contains selenium web-page integration tests. + +The source in the ``lib`` directory is split into 4 sections: + +- ``lib/demo`` contains commands used in the demo page. It is not needed except + for demo purposes. +- ``lib/test`` contains a small test harness for testing GCLI. +- ``lib/gclitest`` contains tests that run in the test harness +- ``lib/gcli`` contains the actual meat + +GCLI is split into a UI portion and a Model/Controller portion. + + +## The GCLI Model + +The heart of GCLI is a ``Requisition``, which is an AST for the input. A +``Requisition`` is a command that we'd like to execute, and we're filling out +all the inputs required to execute the command. + +A ``Requisition`` has a ``Command`` that is to be executed. Each Command has a +number of ``Parameter``s, each of which has a name and a type as detailed +above. + +As you type, your input is split into ``Argument``s, which are then assigned to +``Parameter``s using ``Assignment``s. Each ``Assignment`` has a ``Conversion`` +which stores the input argument along with the value that is was converted into +according to the type of the parameter. + +There are special assignments called ``CommandAssignment`` which the +``Requisition`` uses to link to the command to execute, and +``UnassignedAssignment``used to store arguments that do not have a parameter +to be assigned to. + + +## The GCLI UI + +There are several components of the GCLI UI. Each can have a script portion, +some template HTML and a CSS file. The template HTML is processed by +``domtemplate`` before use. + +DomTemplate is fully documented in [it's own repository] +(https://github.com/joewalker/domtemplate). + +The components are: + +- ``Inputter`` controls the input field, processing special keyboard events and + making sure that it stays in sync with the Requisition. +- ``Completer`` updates a div that is located behind the input field and used + to display completion advice and hint highlights. It is stored in + completer.js. +- ``Display`` is responsible for containing the popup hints that are displayed + above the command line. Typically Display contains a Hinter and a RequestsView + although these are not both required. Display itself is optional, and isn't + planned for use in the first release of GCLI in Firefox. +- ``Hinter`` Is used to display input hints. It shows either a Menu or an + ArgFetch component depending on the state of the Requisition +- ``Menu`` is used initially to select the command to be executed. It can act + somewhat like the Start menu on windows. +- ``ArgFetch`` Once the command to be executed has been selected, ArgFetch + shows a 'dialog' allowing the user to enter the parameters to the selected + command. +- ``RequestsView`` Contains a set of ``RequestView`` components, each of which + displays a command that has been invoked. RequestsView is a poor name, and + should better be called ReportView + +ArgFetch displays a number of Fields. There are fields for most of the Types +discussed earlier. See 'Writing Fields' above for more information. + + +## Testing + +GCLI contains 2 test suites: + +- JS level testing is run with the ``test`` command. The tests are located in + ``lib/gclitest`` and they use the test runner in ``lib/test``. This is fairly + comprehensive, however it does not do UI level testing. + If writing a new test it needs to be registered in ``lib/gclitest/index``. + For an example of how to write tests, see ``lib/gclitest/testSplit.js``. + The test functions are implemented in ``lib/test/assert``. +- Browser integration tests are included in ``browser_webconsole_gcli_*.js``, + in ``toolkit/components/console/hudservice/tests/browser``. These are + run with the rest of the Mozilla test suite. + + +## Coding Conventions + +The coding conventions for the GCLI project come from the Bespin/Skywriter and +Ace projects. They are roughly [Crockford] +(http://javascript.crockford.com/code.html) with a few exceptions and +additions: + +* ``var`` does not need to be at the top of each function, we'd like to move + to ``let`` when it's generally available, and ``let`` doesn't have the same + semantic twists as ``var``. + +* Strings are generally enclosed in single quotes. + +* ``eval`` is to be avoided, but we don't declare it evil. + +The [Google JavaScript conventions] +(https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) are +more detailed, we tend to deviate in: + +* Custom exceptions: We generally just use ``throw new Error('message');`` + +* Multi-level prototype hierarchies: Allowed; we don't have ``goog.inherits()`` + +* ``else`` begins on a line by itself: + + if (thing) { + doThis(); + } + else { + doThat(); + } + + +## Startup + +Internally GCLI modules have ``startup()``/``shutdown()`` functions which are +called on module init from the top level ``index.js`` of that 'package'. + +In order to initialize a package all that is needed is to require the package +index (e.g. ``require('package/index')``). + +The ``shutdown()`` function was useful when GCLI was used in Bespin as part of +dynamic registration/de-registration. It is not known if this feature will be +useful in the future. So it has not been entirely removed, it may be at some +future date. + + +## Running the Unit Tests + +Start the GCLI static server: + + cd path/to/gcli + node gcli.js + +Now point your browser to http://localhost:9999/localtest.html. When the page +loads the tests will be automatically run outputting to the console, or you can +enter the ``test`` command to run the unit tests. + + +## Contributing Code + +Please could you do the following to help minimize the amount of rework that we +do: + +1. Check the unit tests run correctly (see **Running the Unit Tests** above) +2. Check the code follows the style guide. At a minimum it should look like the + code around it. For more detailed notes, see **Coding Conventions** above +3. Help me review your work by using good commit comments. Which means 2 things + * Well formatted messages, i.e. 50 char summary including bug tag, followed + by a blank line followed by a more in-depth message wrapped to 72 chars + per line. This is basically the format used by the Linux Kernel. See the + [commit log](https://github.com/joewalker/gcli/commits/master) for + examples. The be extra helpful, please use the "shortdesc-BUGNUM: " if + possible which also helps in reviews. + * Commit your changes as a story. Make it easy for me to understand the + changes that you've made. +4. Sign your work. To improve tracking of who did what, we follow the sign-off + procedure used in the Linux Kernel. + The sign-off is a simple line at the end of the explanation for the + patch, which certifies that you wrote it or otherwise have the right to + pass it on as an open-source patch. The rules are pretty simple: if you + can certify the below: + + Developer's Certificate of Origin 1.1 + + By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + + then you just add a line saying + + Signed-off-by: Random J Developer <random@developer.example.org> + + using your real name (sorry, no pseudonyms or anonymous contributions.) + +Thanks for wanting to contribute code. + diff --git a/devtools/shared/gcli/source/docs/index.md b/devtools/shared/gcli/source/docs/index.md new file mode 100644 index 000000000..dce112e6d --- /dev/null +++ b/devtools/shared/gcli/source/docs/index.md @@ -0,0 +1,150 @@ + +# About GCLI + +## GCLI is a Graphical Command Line Interpreter. + +GCLI is a command line for modern computers. When command lines were invented, +computers were resource-limited, disconnected systems with slow multi-tasking +and poor displays. The design of the Unix CLI made sense in 1970, but over 40 +years on, considering the pace of change, there are many improvements we can +make. + +CLIs generally suffer from poor discoverability; It's hard when faced with a +blank command line to work out what to do. As a result the majority of programs +today use purely graphical user interfaces, however in doing so, they lose some +of the benefits of CLIs. CLIs are still used because generally, in the hands of +a skilled user they are faster, and have a wider range of available options. + +GCLI attempts to get the best of the GUI world and the CLI world to produce +something that is both easy to use and learn as well as fast and powerful. + +GCLI has a type system to help ensure that users are inputting valid commands +and to enable us to provide sensible context sensitive help. GCLI provides +integration with JavaScript rather than being an alternative (like CoffeeScript). + + +## History + +GCLI was born as part of the +[Bespin](http://ajaxian.com/archives/canvas-for-a-text-editor) project and was +[discussed at the time](http://j.mp/bespin-cli). The command line component +survived the rename of Bepsin to Skywriter and the merger with Ace, got a name +of it's own (Cockpit) which didn't last long before the project was named GCLI. +It is now being used in the Firefox's web console where it doesn't have a +separate identity but it's still called GCLI outside of Firefox. It is also +used in [Eclipse Orion](http://www.eclipse.org/orion/). + + +## Environments + +GCLI is designed to work in a number of environments: + +1. As a component of Firefox developer tools. +2. As an adjunct to Orion/Ace and other online editors. +3. As a plugin to any web-page wishing to provide its own set of commands. +4. As part of a standalone web browser extension with it's own set of commands. + + +## Related Pages + +Other sources of GCLI documentation: + +- [Writing Commands](writing-commands.md) +- [Writing Types](writing-types.md) +- [Developing GCLI](developing-gcli.md) +- [Writing Tests](writing-tests.md) / [Running Tests](running-tests.md) +- [The Design of GCLI](design.md) +- Source + - The most up-to-date source is in [this Github repository](https://github.com/joewalker/gcli/). + - When a feature is 'done' it's merged into the [Mozilla clone](https://github.com/mozilla/gcli/). + - From which it flows into [Mozilla Central](https://hg.mozilla.org/mozilla-central/file/tip/devtools/client/commandline). +- [Demo of GCLI](http://mozilla.github.com/gcli/) with an arbitrary set of demo + commands +- Other Documentation + - [Embedding docs](https://github.com/mozilla/gcli/blob/master/docs/index.md) + - [Status page](http://mozilla.github.com/devtools/2011/status.html#gcli) + + +## Accessibility + +GCLI uses ARIA roles to guide a screen-reader as to the important sections to +voice. We welcome [feedback on how these roles are implemented](https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Developer+Tools:+Graphic+Commandline+and+Toolbar&rep_platform=All&op_sys=All&short_desc=GCLI). + +The command line uses TAB as a method of completing current input, this +prevents use of TAB for keyboard navigation. Instead of using TAB to move to +the next field you can use F6. In addition to F6, ALT+TAB, CTRL+TAB, META+TAB +make an attempt to move the focus on. How well this works depends on your +OS/browser combination. + + +## Embedding GCLI + +There are 3 basic steps in using GCLI in your system. + +1. Import a GCLI JavaScript file. + For serious use of GCLI you are likely to be creating a custom build (see + below) however if you just want to have a quick play, you can use + ``gcli-uncompressed.js`` from [the gh-pages branch of GCLI] + (https://github.com/mozilla/gcli/tree/gh-pages) + Just place the following wherever you place your script files. + + <script src="path/to/gcli-uncompressed.js" type="text/javascript"></script> + +2. Having imported GCLI, we need to tell it where to display. The simplest + method is to include an elements with the id of ``gcli-input`` and + ``gcli-display``. + + <input id="gcli-input" type="text"/> + <div id="gcli-display"></div> + +3. Tell GCLI what commands to make available. See the sections on Writing + Commands, Writing Types and Writing Fields for more information. + + GCLI uses the CommonJS AMD format for it's files, so a 'require' statement + is needed to get started. + + require([ 'gcli/index' ], function(gcli) { + gcli.add(...); // Register custom commands/types/etc + gcli.createTerminal(); // Create a user interface + }); + + The createTerminal() function takes an ``options`` objects which allows + customization. At the current time the documentation of these object is left + to the source. + + +## Backwards Compatibility + +The goals of the GCLI project are: + +- Aim for very good backwards compatibility with code required from an + 'index' module. This means we will not break code without a cycle of + deprecation warnings. + + There are currently 3 'index' modules: + - gcli/index (all you need to get started with GCLI) + - demo/index (a number of demo commands) + - gclitest/index (GCLI test suite) + + Code from these modules uses the module pattern to prevent access to internal + functions, so in essence, if you can get to it from an index module, you + should be ok. + +- We try to avoid needless change to other modules, however we don't make any + promises, and don't provide a deprecation cycle. + + Code from other modules uses classes rather than modules, so member variables + are exposed. Many classes mark private members using the `_underscorePrefix` + pattern. Particular care should be taken if access is needed to a private + member. + + +## Creating Custom Builds + +GCLI uses [DryIce](https://github.com/mozilla/dryice) to create custom builds. +If dryice is installed (``npm install .``) then you can create a built +version of GCLI simply using ``node gcli.js standard``. DryIce supplies a custom +module loader to replace RequireJS for built applications. + +The build will be output to the ``built`` directory. The directory will be +created if it doesn't exist. diff --git a/devtools/shared/gcli/source/docs/running-tests.md b/devtools/shared/gcli/source/docs/running-tests.md new file mode 100644 index 000000000..378c40837 --- /dev/null +++ b/devtools/shared/gcli/source/docs/running-tests.md @@ -0,0 +1,60 @@ + +# Running Tests + +GCLI has a test suite that can be run in a number of different environments. +Some of the tests don't work in all environments. These should be automatically +skipped when not applicable. + + +## Web + +Running a limited set of test from the web is the easiest. Simply load +'localtest.html' and the unit tests should be run automatically, with results +displayed on the console. Tests can be re-run using the 'test' command. + +It also creates a function 'testCommands()' to be run at a JS prompt, which +enables the test commands for debugging purposes. + + +## Firefox + +GCLI's test suite integrates with Mochitest and runs automatically on each test +run. Dryice packages the tests to format them for the Firefox build system. + +For more information about running Mochitest on Firefox (including GCLI) see +[the MDN, Mochitest docs](https://developer.mozilla.org/en/Mochitest) + + +# Node + +Running the test suite under node can be done as follows: + + $ node gcli.js test + +Or, using the `test` command: + + $ node gcli.js + Serving GCLI to http://localhost:9999/ + This is also a limited GCLI prompt. + Type 'help' for a list of commands, CTRL+C twice to exit: + : test + + testCli: Pass (funcs=9, checks=208) + testCompletion: Pass (funcs=1, checks=139) + testExec: Pass (funcs=1, checks=133) + testHistory: Pass (funcs=3, checks=13) + .... + + Summary: Pass (951 checks) + + +# Travis CI + +GCLI check-ins are automatically tested by [Travis CI](https://travis-ci.org/joewalker/gcli). + + +# Test Case Generation + +GCLI can generate test cases automagically. Load ```localtest.html```, type a +command to be tested into GCLI, and the press F2. GCLI will output to the +console a template test case for the entered command. 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. diff --git a/devtools/shared/gcli/source/docs/writing-tests.md b/devtools/shared/gcli/source/docs/writing-tests.md new file mode 100644 index 000000000..4d42142cd --- /dev/null +++ b/devtools/shared/gcli/source/docs/writing-tests.md @@ -0,0 +1,20 @@ + +# Writing Tests + +There are several sources of GCLI tests and several environments in which they +are run. + +The majority of GCLI tests are stored in +[this repository](https://github.com/joewalker/gcli/) in files named like +```./lib/gclitest/test*.js```. These tests run in Firefox, Chrome, Opera, +and NodeJS/JsDom + +See [Running Tests](running-tests.md) for further details. + +GCLI comes with a generic unit test harness (in ```./lib/test/```) and a +set of helpers for creating GCLI tests (in ```./lib/gclitest/helpers.js```). + +# GCLI tests in Firefox + +The build process converts the GCLI tests to run under Mochitest inside the +Firefox unit tests. It also adds some diff --git a/devtools/shared/gcli/source/docs/writing-types.md b/devtools/shared/gcli/source/docs/writing-types.md new file mode 100644 index 000000000..ed2f75438 --- /dev/null +++ b/devtools/shared/gcli/source/docs/writing-types.md @@ -0,0 +1,106 @@ + +# Writing Types + +Commands are a fundamental building block because they are what the users +directly interacts with, however they are built on ``Type``s. There are a +number of built in types: + +* string. This is a JavaScript string +* number. A JavaScript number +* boolean. A JavaScript boolean +* selection. This is an selection from a number of alternatives +* delegate. This type could change depending on other factors, but is well + defined when one of the conversion routines is called. + +There are a number of additional types defined by Pilot and GCLI as +extensions to the ``selection`` and ``delegate`` types + +* setting. One of the defined settings +* settingValue. A value that can be applied to an associated setting. +* command. One of the defined commands + +Most of our types are 'static' e.g. there is only one type of 'string', however +some types like 'selection' and 'delegate' are customizable. + +All types must inherit from Type and have the following methods: + + /** + * Convert the given <tt>value</tt> to a string representation. + * Where possible, there should be round-tripping between values and their + * string representations. + */ + stringify: function(value) { return 'string version of value'; }, + + /** + * Convert the given <tt>str</tt> to an instance of this type. + * Where possible, there should be round-tripping between values and their + * string representations. + * @return Conversion + */ + parse: function(str) { return new Conversion(...); }, + + /** + * The plug-in system, and other things need to know what this type is + * called. The name alone is not enough to fully specify a type. Types like + * 'selection' and 'delegate' need extra data, however this function returns + * only the name, not the extra data. + * <p>In old bespin, equality was based on the name. This may turn out to be + * important in Ace too. + */ + name: 'example', + +In addition, defining the following function can be helpful, although Type +contains default implementations: + +* nudge(value, by) + +Type, Conversion and Status are all declared by commands.js. + +The values produced by the parse function can be of any type, but if you are +producing your own, you are strongly encouraged to include properties called +``name`` and ``description`` where it makes sense. There are a number of +places in GCLI where the UI will be able to provide better help to users if +your values include these properties. + + +# Writing Fields + +Fields are visual representations of types. For simple types like string it is +enough to use ``<input type=...>``, however more complex types we may wish to +provide a custom widget to allow the user to enter values of the given type. + +This is an example of a very simple new password field type: + + function PasswordField(doc) { + this.doc = doc; + } + + PasswordField.prototype = Object.create(Field.prototype); + + PasswordField.prototype.createElement = function(assignment) { + this.assignment = assignment; + this.input = dom.createElement(this.doc, 'input'); + this.input.type = 'password'; + this.input.value = assignment.arg ? assignment.arg.text : ''; + + this.onKeyup = function() { + this.assignment.setValue(this.input.value); + }.bind(this); + this.input.addEventListener('keyup', this.onKeyup, false); + + this.onChange = function() { + this.input.value = this.assignment.arg.text; + }; + this.assignment.onAssignmentChange.add(this.onChange, this); + + return this.input; + }; + + PasswordField.prototype.destroy = function() { + this.input.removeEventListener('keyup', this.onKeyup, false); + this.assignment.onAssignmentChange.remove(this.onChange, this); + }; + + PasswordField.claim = function(type) { + return type.name === 'password' ? Field.claim.MATCH : Field.claim.NO_MATCH; + }; |