Skip to content
This repository has been archived by the owner on Jul 14, 2019. It is now read-only.

Command Modules

Alex Ford edited this page Jan 31, 2014 · 10 revisions

<< home

This article is outdated and will be updated very soon. 2/1/2014

Shotgun command modules are just Node.js modules. There isn't much special about them except that they must define a specific function called 'invoke'. As with any node module you are free to create a named "some-command.js" file or a directory "some-command" with an "index.js" file placed inside it. If your command is growing in complexity it is suggested that you put it in its own directory and split up some of the functionality for that command into separate files for readability.

// shotgun_cmds/echo.js

// The invoke function is where the command logic will go.
exports.invoke = function (shell, options) {
    var iterations = options.iterations;
    for (var count = 0; count < iterations; count++) {
        shell.log(options.message);
    }
};

Within the invoke function two arguments are passed in for you to use. The first is the shell instance, complete with helper functions for sending data back to the onData callback you defined. The second argument is an options object which simply stores all the user-supplied options when invoking your command.

Helper Functions

The shell helper functions are as follows:

  • log - Adds a line of text to the lines array on the result object.

warn, error, and debug are useful for giving context to lines of text so that the UI can apply different styling behaviors when displaying the text to the user. For example your command might do something like shell.warn('The value supplied is below the minimum threshold.'); within the invoke function. This adds a line object to the lines array on the result object that will be passed to the application using shotgun. When the application iterates over the lines array it will see that the line has a property called type that is set to "error" and it will know to display the text differently from the normal log type.

  • loadCommandModule - Accepts a path to a command module and loads it into the framework.

  • loadCommandModules - Same as the previous function except it loads all modules within a given directory.

  • send - Accepts a data object and sends it to the client application's onData callback function.

  • onData - Accepts a callback function to be invoked whenever shotgun has data for the application.

  • clearDisplay - Sets a property clearDisplay: true on a data object and sends it to the onData callback function. Optionally accepts a boolean resetContext that will cause the helper to also reset the context object.

  • exit - Sets a property exit: true on a data object and sends it to the onData callback function.

  • password - Sets a property password: true on a data object and sends it to the onData callback function.

  • resetContext - Removes all properties from the stored context object and invokes the onContextSave callback.

  • onContextSave - Accepts a callback function that will be invoked anytime a change is made to the context object within shotgun.

  • setVar - Accepts a string key and a value which can be of any type. This helper will store the value as a property on the context object and invoke the onContextSave callback.

  • getVar - Accepts a string key and returns the value of a previously stored context variable.

  • delVar - Accepts a string key and removes a previously stored context variable from the stored context, then invokes the onContextSave callback.

  • setPrompt - This is available on the API but it is mostly for internal use. It is exposed for advanced users to take advantage of. Unless you really know what you're doing it is not recommended that you use this helper function.

  • clearPrompt - This is the same story as setPrompt. Don't use it unless you know what you're doing.

Optional Command Properties

Command modules may also expose any of four other properties.

// cmds/echo.js continued

// A string containing a short description of the command.
exports.description = 'Displays the supplied message.';

// A string containing helpful usage syntax for the user.
exports.usage = '<message> [options]';

// If true, this command module will not show up in the help menu.
exports.hidden = false;

// Options is an object containing a comprehensive list of parameters that the command accepts and understands.
exports.options = {
    message: {
        noName: true,
        required: true,
        description: 'The message to be displayed.',
        prompt: true
    },
    iterations: {
        aliases: ['i'],
        required: true,
        default: 1,
        description: 'The number of times to display the message.',
        validate: /^[1-9]\d*$/
    }
};

Any options specified in the user input will be passed to the invoke() function of the command regardless of whether or not they appear in the command's options property. The options property is used to validate specific options that the command understands. For instance, in the above example we provided a regular expression to the validation property on the iterations option. You may also supply a function to the validate property if you need more customized validation.

iterations: {
    aliases: ['i'],
    required: true,
    default: 1,
    description: 'The number of times to display the message.',
    validate: function (value, options) {
        return value > 0;
    }
}

When you define options with noName set to true, such as the message option in the above example, that lets shotgun know that this option will have no hyphenated name provided in the user input. Options without names will be added to the options object that is passed to the command's invoke() function in the order they are found in the parsed user input. For example:

echo "Dance monkey, dance!" -i 5

Using the sample 'echo' command we defined earlier the above sample user input would yield the following:

// cmds/echo.js

exports.invoke = function (shell, options) {
    options.iterations == 5; // true
    options.message === "Dance monkey, dance!"; // true
};

Since the message option has noName set to true shotgun simply parses the user input and adds first non-named option to the options object under message. The order matters if the option has noName enabled. If you have more than one option with noName: true then the user must supply them in the order they appear in your module. Defining a usage string really helps here so the user knows what format to supply their input to satisfy your command.

I stated earlier that named options are passed to the command even if they are not defined in the options property of that command. Thus, the following is valid:

echo "Dance monkey, dance!" -i 5 --verbose

would yield:

// cmds/echo.js

exports.invoke = function (shell, options) {
    options.verbose == true; // true
};

Despite verbose not being defined as part of the options property of the command module, it is still accessible if provided by the user. It will just be optional, won't undergo any validation, and won't show up in that command modules help information.

Defined Command Options

As explained above, the user can supply any option they wish and your command module could access that value via the supplied options object. Defining exports.options on your command module is just a way to tell shotgun what options your module understands and what rules to apply to those options if they are found. Here is a comprehensive list of available properties you can set for each option you define for your command module:

aliases

exports.options = {
    message: {
        aliases: ['m', 'msg']
    }
};

Sometimes you may not want the user to have to type --message as a parameter for your command every single time. You can supply aliases so that the user can supply one of the aliases instead. In the above example the user could supply -m or --msg instead of --message if they chose to.

default

exports.options = {
    message: {
        default: 'Hello World'
    }
};

Defining options with a default value ensures that the option will have a value even if the user does not supply one. In the above example the user could supply a message, but if they don't then the default value of "Hello World" would be used.

description

exports.options = {
    message: {
        description: "A message to be displayed."
    }
};

You don't have to supply a description, but if you do then it will show up when the user attempts to get help information for the command by typing help commandName.

noName

exports.options = {
    message: {
        noName: true
    }
};

Supplying noName: true tells shotgun that this option does not have to be specified by name. For example, the user could supply "some value" instead of --message "some value". Keep in mind that options with no name are evaluated in order. If you have two options with noName: true then the first user-supplied value without a name will be used for the first option you defined and the second user-supplied value with no name will be used for the second option. The order does not matter when options are supplied with a name, even if noName is true.

hidden

exports.options = {
    message: {
        hidden: true
    }
};

Supplying hidden: true will cause the default "help" command to hide this option when showing available options for the command. This is useful if you have set noName: true since you will likely include unnamed command options in the command's usage string rather than in the list of named options.

Example:

// login.js
var db = require('./db');
exports.description = "Allows the user to sign in with their username and password.";
exports.usage = "[username] [password]";
exports.options = {
    username: {
        noName: true,
        required: true,
        prompt: "Please enter your username.",
        validate: function (username) {
            return db.checkUserExists(username);
        },
        hidden: true
    },
    password: {
        noName: true,
        required: true,
        prompt: "Please enter your password.",
        validate: function (password, options) {
            var user = db.getUser(options.username);
            return user.password === password;
        },
        hidden: true,
        password: true
    }
};
exports.invoke = function (shell, options) {
    // Do authentication stuff.
    shell.log("Login successful :)");
};

In the above module there is no reason to display "--username" or "--password" in the help menu because users will almost never explicitly supply them. They will either include them with no names or be prompted for them.

prompt

exports.options = {
    message: {
        prompt: true // or prompt: 'Enter a message.'
    }
};

If you specify prompt: true on your option and required is also true then the user will be prompted for the value if they did not supply the option themselves. If prompt is true but required is not set to true and the user supplied the option with no value then they will be prompted for the value. If prompt is set to true then it will prompt the user with a default message like "Enter value for message." You also have the option to supply your own message by simply replacing true with a string. The supplied string will be displayed instead of the default message.

password

exports.options = {
    message: {
        password: true
    }
};

Setting password: true only has an effect if a prompt is also set. This tells shotgun to set data.password = true;. If the UI chooses to it could use this password property to modify the UI input field to be a password field for the prompt. This is useful for a login command where the command will prompt the user for their password.

required

exports.options = {
    message: {
        required: true
    }
};

This is one of the simpler options. If you set required: true on an option then shotgun will display an error to the user if they do not supply a value. One caveat is if you supply a default value or a prompt, either the default value will be used if there is no user-supplied value or the user will be prompted for the value.

validate

// Regular expression.
exports.options = {
    message: {
        validate: /^[a-z0-9]$/i // Only alpha-numeric characters are allowed.
    }
};

// Validation Function
exports.options = {
    message: {
        validate: function (value, shell, options) {
            return value === 'Hello world!';
        }
    }
};

Validate allows you to specify a regular expression or a function that will inform shotgun that the supplied value should be validated. If the value does not pass validation then an error will be displayed to the user and they will have to supply valid input before the command will be invoked. If you use a validation function it passes in three parameters. The first is the value supplied by the user for that option. The second is all of the supplied options the user passed in.

NOTE: Keep in mind that command options are validated in order. If you access any user-supplied options in your validation function, any options that appear after the one you're currently validating will not have been validated yet so be careful with them.

You will rarely (if ever) have to use the options parameter; if your validation logic is dependent upon other options then you most likely want to do that work in the invoke function after all options have been validated. The third option is the shell instance, giving you access to the same functions available in your invoke function.

Our example 'echo' command

What we did in a previous example is create a simple command called 'echo' that will be plugged into the shotgun shell framework simply by placing the module in the 'shotgun_cmds' directory (or the directory you passed into the shell).

The example command we just wrote is a pretty simple command. It performs a small task and only accepts one option. The nice thing about shotgun is that you don't have to do any pre-parsing of user input before passing it along to the module. Shotgun does all the legwork for you, allowing you to focus on creating well-designed command modules instead of worrying about user input, context, etc.

shell.onData(function (data) {
    console.log(data);
}).execute('echo -i 5 "Hello world!"');

The onData callback would be invoked five times and the console would yield:

{ line: { options: {}, type: 'log', text: 'Hello world!' } }
{ line: { options: {}, type: 'log', text: 'Hello world!' } }
{ line: { options: {}, type: 'log', text: 'Hello world!' } }
{ line: { options: {}, type: 'log', text: 'Hello world!' } }
{ line: { options: {}, type: 'log', text: 'Hello world!' } }

Each line object contains the text that will be displayed for that line and an options object containing meta information about the line such as bold, italic, underline, etc. The UI can then apply whatever display options it is capable of providing, but the options can be safely ignored if necessary. For example, if you were writing a console application then bold, italics, and underline wouldn't be possible. The options are just metadata that does not have to be used.

The invoke function.

Within your command modules the invoke function is the main entry point for your module. It is passed two arguments. The first is the shell instance which contains helper methods for modifying context and sending data back to the onData callback. The second is an options object containing the user-supplied options.

The text helper functions such as log(), warn(), and error() accept an options object as a second argument if needed. This options object is added to the line object before the line object is sent to the onData callback function.

exports.invoke = function (shell, options) {
    shell.log('If possible, the UI should display this line bolded, italicized, and underlined.', {
        bold: true,
        italic: true,
        underline: true
    });
};

It is possible to send your own custom data to the onData callback function. To do so just use the shell.send() helper function.

// cmds/mycommand.js

exports.invoke = function (shell, options) {
    var data = { customValue: 'This is a custom value.' };
    shell.send(data);
};

// app.js

shell.onData(function (data) {
    console.log(data.customValue);
}).execute('mycommand');

shell.execute also takes an options object in case you need to make values available to a command without them needing to be supplied as user input.

// app.js

shell.execute('mycommand', context, { someValue: true });

// cmds/mycommand.js

exports.invoke = function (shell, options) {
    shell.log('Custom value: ' + options.someValue);
};

Values supplied in this manner will override user input that matches it, so be mindful of the options you pass in. For example:

// app.js

shell.execute('mycommand --someValue "pizza"', context, { someValue: 'bacon' });

will yield:

// cmds/mycommand.js

exports.invoke = function (shell, options) {
    options.someValue === 'bacon'; // true
};

<< home

Clone this wiki locally