Development/CLI

From Soar Wiki

Jump to: navigation, search

Contents

General notes on commands

  • Everything on the command line gets tokenized by spaces, matching double quotes, or matching braces.
simple                         ... 1 token
more complex                   ... 2 tokens
some-flags -ld *               ... 3 tokens
sp {a soar production}         ... 2 tokens
print -d 0 -i s1               ... 5 tokens
echo "Initializing components" ... 2 tokens
excise    weird*spacing        ... 2 tokens
  • Commands should be one word, or multiple words separated by dashes.
foo
foo-bar
  • Commands can have short options, long options, and arguments.
  • Short options are a token that starts with a dash followed by a character.
-s
-l
  • Long options are a token that starts with two dashes followed by a word, or multiple words separated by dashes.
--word
--multiple-words
  • Arguments are any other token on the line not preceeded by one or two dashes.
  • Options themselves can have required arguments, optional arguments, or no argument.
  • Options with required arguments must have an argument directly following the option. This argument is tied to the option.
  • Options with optional arguments may have an argument directly following them, or more options, or nothing. If an argument follows the option, it is tied to the option.
  • Options with no arguments can be bundled together with one dash.
-abcde
  • Options with required or optional arguments may not be bundled together.
  • Long options may not be bundled together.
  • Long options may be abbreviated, so long as the amount of characters typed resolves any ambiguities with other options.
    • For example, if a command has options --foo --foobar --bar, the following are all legal:
command --foo
command --foobar
command --foob   ... resolves to --foobar
command --bar
command --b      ... resolves to --bar

How to add a new command

I will explain how to create a new, ficticious command called name-version that concatenates the agent's name and the kernel version.

Create and Add the Source File

Easiest way to do this is to copy an existing file with similar functionality and then rename it for the new command.

A good simple file to copy is cli_quit.cpp.

Add the file to the project and version control.

  • For the name-version command, we copy cli_quit.cpp and rename it cli_nameversion.cpp.

Before the parse function

The first line of any SML .cpp file must be

#include <portability.h>

Next, include the cli header and some other things, as necessary:

#include "cli_CommandLineInterface.h"
#include "sml_Names.h"

using namespace cli;
using namespace sml;

ParseX

Next, create (rename) the Parse function.

  • Our command's parse function ParseNameVersion.

The ParseX function's purpose is to convert the command line into a function call.

The parse function will always have the same function signature:

bool( std::vector<std::string>& argv )
  • The first parameter is the tokenized command line.
    • argv[0] is always the name of the command.
    • argv.size() is always the total number of arguments on the command line.

Errors

If there is an error, set the error code using SetError() then return false.

The SetError() function always returns false, so here's a short cut:

if ( thereIsAnError ) return SetError( CLIError::kErrorCode );

Error codes are defined in cli_CLIError.h.

Parsing Options

  • If your command doesn't need options, skip this section.

Start by defining the options that the command accepts.

Here is an example from ParseExcise:

Options optionsData[] = {
    {'a', "all",	0},
    {'c', "chunks",	0},
    {'d', "default",	0},
    {'t', "task",	0},
    {'u', "user",	0},
    {0, 0, 0}
};

The first column is the short option. This is case sensative. This must be unique among options for the command.

The second column is the long option. This must be unique among options for the command.

The third column is whether or not the option has an argument, and if said argument is required or not.

  • 0 indicates no option,
  • 1 indicates required option,
  • 2 indicates optional argument.

See ParseWatch for a complex example. An example of an option requiring an argument is:

watch --level 2

The --level argument makes no sense without a numerical argument.

The last entry in the array must be all zeros: {0, 0, 0}

  • Here is another example, for our ficticious command:
Options optionsData[] = {
	{'t', "twice", 0},
	{0, 0, 0}
};
  • With options defined, loop forever with the following command:
for (;;) {
    if (!ProcessOptions(argv, optionsData)) return false;
    if (m_Option == -1) break;

    switch (m_Option) {
        ...
        default:
            return SetError(CLIError::kGetOptError);
    }
}

Fill in the ... with case statements corresponding to the short options in your table, such as:

case 't':
    // Do something
    break;

DoX

By the end of the ParseX function, you will be able to call the DoX function to actually do the work.

DoX should take the same name as ParseX with Do substituted for Parse.

  • Our ficiticious function becomes DoNameVersion

DoX functions always return true if successful, false if unsuccessful.

The function signiture depends on the work that needs to be done in the function.

Do the work and return true on success.

If the function is supposed to return more than just a success code, there are two ways to return data: raw and structured output.

Raw Output

Raw output is a simple string that gets sent back to the client, presumably displayed as-is. You can print to m_Result string as you would to cout:

int angle;
...
m_Result << '*' << " the angle of the dangle is " << angle;

Structured output

Use AddArgTag(). Here are some examples:

AppendArgTagFast(sml_Names::kParamVersionMajor, sml_Names::kTypeInt, Int2String(m_KernelVersion.major, buf, kMinBufferSize));
AppendArgTagFast(sml_Names::kParamLearnForceLearnStates, sml_Names::kTypeString, output.c_str());
AppendArgTagFast(sml_Names::kParamTimers, sml_Names::kTypeBoolean, pSysparams[TIMERS_ENABLED] ? sml_Names::kTrue : sml_Names::kFalse);

Add Prototypes to CLI Header

Add the prototypes for the Parse and Do functions to the appropriate places in cli_CommandLineInterface.h. Add the command alphabetacally with the other commands. Include doxygen documentation block above the Do function and document your parameters.

/*************************************************************
* @brief name-version command
* @param pAgent The pointer to the gSKI agent interface
* @param twice True to print the agent twice
*************************************************************/
bool DoNameVersion(bool twice);

Add the Command to the Command List

Modify cli_Commands.h/cpp to add the command name alphabetacally as a constant:

static char const* kCLINameVersion;
char const* Commands::kCLINameVersion = "name-version";

This is the command as the user will type it. The command name must be unique.

Map the Command

In the CLI constructor (cli_CommandLineInterface.cpp), add an entry mapping the command name constant to the parse function pointer:

m_CommandMap[Commands::kCLINameVersion] = &cli::CommandLineInterface::ParseNameVersion;

Finally, below this block, indicate if you would like the command to be echoed in a team debugging session (most commands should be echoed):

m_EchoMap[Commands::kCLINameVersion ]	 = true ;

Command Help

In order for online help generation to work for commands:

  • The command must have a wiki help/usage page.
Personal tools