Extending the command-line

This topic contains information about extending the command-line by providing additional commands.

The Overview of command-line interface article provides general command-line information. This article provides the following information about extending the command line:

You might want to extend the command-line to add support for new features or enhance the existing ones. You might want to add a new build command that compiles, packages and publishes a module. Or maybe you want to add a command that validates all of the html files in the public folder. You can easily extend the command-line interface in IBM® WebSphere® sMash as each module has the ability to provide new commands. The command-line uses the features of WebSphere sMash like event processing and virtual directories, so any module can provide additional commands.

Creating a new command

You can create a command-line command by registering a handler for the cliTask event and providing an implementation of the onCliTask method. The easiest way to register a cliTask handler is to put a Groovy script in the app/tasks directory that allows implicit registration of the handler. The virtual directory feature of WebSphere sMash automatically finds the new cliTask handlers. The Groovy script must provide an implementation of the onCliTask method to be invoked when the cliTask event is fired. The onCliTask method can access the event zone to obtain the arguments passed on invocation and the name of the event. The following directories are also used.

/event/task
Contains the name of the invoked task.
/event/args
Contains a List of the command-line arguments; none if not specified.

The following code snippet shows how the task is invoked.

def onCliTask() {
   def task = zget("/event/task")
   System.out.println("task name is " + task)
   zget("/event/args").each{System.out.println("arg is " + it)}
}	

If the task handler is written in Java™, then the handler must be explicitly registered for the cliTask event providing the task name.

# MyTask
/config/handlers += [{
   "events": "cliTask",
   "handler" : "zero.simple.task.MyTask.class",
   "conditions" : "/event/task =~ mytask"	
}]

The Java-based task must provide an implementation of the onCliTask method and the event zone, available through the GlobalContext, can be used to obtain the event arguments.

public void onCliTask() {
   List<String> args = GlobalContext.zget("/event/args");
   ... 
}	

The task implementation might need to access the location from which the task was invoked and the location where the WebSphere sMash command-line is installed. The system property user.dir can be used to determine the location from which the task was invoked, but this might not be desired location. The command-line supports a walk up feature in which tasks for a module can be invoked from any directory under the module. The apphome directory is the root directory of the module and can be obtained from the event zone, event/apphome. The root directory of the module is probably more useful to find artifacts like the config directory.

static public File getAppRoot() {
    File app = zget("/event/apphome");
    return app;
}

The location where the command-line was installed, also referred to as zerohome, might need to be determined. The command-line scripts set zerohome as an environment property.

String zerohome = System.getenv("ZERO_HOME");

A task should implement the -json option. This option allows a structured output that can be reliably parsed by applications like the Application Builder. In general, a task displays messages with the LogHelper with level INFO, which writes to stderr. When -json is specified, a json structured result is written to stdout. This enables you to programmatically invoke commands and use the exit code and -json option to determine the results.

The version command in the zero.cli.tasks module has a -full option that displays more comprehensive results for problem determination. The -full option fires a collectInfo event that allows other components to provide additional detailed information to be aggregated. The collectInfo task implementors check for the -json option and, if specified, add their results to the map that can be obtained from /event/collectResults. The results are placed in the map under a module label, for example, zero.cli.tasks. If -json is not specified, then the results are displayed using LogHelper.

The map found in the /event/collectResults event zone can not be replaced. The version task requires that this map is updated with the additional information.

The following snippets show registering a handler for the collectInfo event, and providing results.

/config/handlers += [{
    "events": "cliTask",
    "handler" : "my.component.Dump.class",
    "conditions" : "/event/task =~ collectInfo"
}]
public void onCliTask() {
    Object obj = zget("/event/collectResults");
    if (obj != null && obj instanceof Map ) {
       ((Map)obj).put("my.component", "my.results");
    }

The -json option can be tested by parsing the args passed in /event/args.

Creating a group of tasks

With command-line task support, you can also group a set of tasks, referred to as subtasks. One of the benefits of using subtasks is that users can request help for a single subtask and see all of the available commands. This grouping makes the command-line easier to use for a set of commands that all operate on a single instance, like a module group or a repository. The following snippet shows explicitly registering a subtask for a Java-based implementation.

# Mytask
/config/handlers += [{
    "events": "cliTask",
    "handler" : "zero.cli.tasks.commands.SubCommand.class",
    "conditions" : "/event/task =~ mytask"
}]
	
# MySubTask
/config/handlers += [{
    "events": "cliSubTask",
    "handler" : "zero.simple.task.MySubTask.class",
    "conditions" : "(/event/task =~ mytask) && (/event/subTask =~ mysubtask)"
}]

In the preceding example, specifying event/task defines the task name, and event/subTask defines the subtask. The condition match invokes the onCliSubTask method of the handler.

Invoking the task directly, zero mytask, displays the help for the task and all of the subtasks. A subtask is invoked by specifying the task name and then the subtask name, for example, zero mytask mysubtask. The subtask must implement the onCliSubTask method, as shown in the following example.

public void onCliSubTask() {
   System.out.println("subtask was invoked");
}

A Groovy subtask can also be explicitly registered using the SubCommand class. In this case, the Groovy subtask script should also be explicitly registered as a handler.

Currently, only explicit registration is supported for all subtasks, implicit registration of a Groovy-based subtask is not supported at this time. Only implicit registration of groovy tasks is supported.

The following code sample shows explicitly registering a Groovy task named testing and a subtask named hello. These tasks can be invoke as zero testing hello.

# testing
/config/handlers += [{
    "events": "cliTask",
    "handler" : "zero.cli.tasks.commands.SubCommand.class",
    "conditions" : "/event/task =~ testing"
}]

/config/handlers += [{
    "events": "cliSubTask",
    "handler" : "hello.groovy",
    "conditions" : "(/event/task =~ testing) && (/event/subTask =~ hello)"
}]

The implementation for hello.groovy must be in app/scripts directory.

def onCliSubTask() {
   System.out.println("hello from subtask");
}	

Providing help for a task

You can use the task support help component to display help text for each of the tasks. The help component is implemented using property files that are placed in the app/tasks/help folder. The naming convention for the help property file is taskname.properties. A task can also supply help for all supported locales by providing properties files with language, country and variants specified. Each task should provide help, and zero help is the preferred way of determining which commands are available to a module.

The following list is the search order used to find the appropriate help property file for a task using the user’s locale:

As an example, to provide a help properties file for the resolve task for English (default) and Japanese, you could provide the following properties files.

resolve_ja.properties
resolve.properties

Additional property files can be supplied to support other locale variants.

You can use the help component to display help for subtasks by placing those help property files under the app/tasks/help/${subtask} folder, where ${subtask} is the name of the subtask. The help property file supports both short and full help text. The short help is displayed when help for all tasks, including subtasks, is requested. Full help is displayed when the help is requested for a single task.

Short text should be limited to one line and is specified in help property file for a task with the label short. The full text is specified with a label of full and allows multiple lines. Each specified line is printed with a new line to allow some control over formatting.

The following example shows a help properties file for a task.

short=Prints the documentation for the given command.
full.1=Usage:
full.2=
full.3=zero help [<command>]
full.4=
full.5=The command will print the documentation for the given command. If no command is provided, it will print a short summary of all commands currently available to the command-line.