1.3. Advanced YaST2 command line parsing

1.3.1. Important features

  • simple specification in the YaST module

  • automatic help

  • automatic checking of arguments (types, format)

  • interactive session without UI

1.3.2. Basic usage of module CommandLine

The aim of the module is to provide as automatic interface as possible for controlling the module. To support interactive sessions, the YaST module needs to provide command-handling loop similar to concept of event-handling in GUIs.

If the module does not need to do any special handling of the actions, it can use the "commandline" include (a wrapper for CommandLine). The include defines a single function "CommandLineRun()", which implements a standard event-loop and returns true on success. The module just needs to specify handlers for actions, user-interface, initialization and finishing.

Example 1.2. Simple CommandLine definition

{

define void deleteHandler( map options ) ``{
    string dev = options["device"]:"";
    CommandLine::Print("Deleting: "+dev);

    if(Lan::Delete(dev) && Lan::Commit())
	CommandLine::Print("Success");
    else
	CommandLine::Print("Error");
}

...

map cmdline = $[
    "help"	: "Configuration of network cards",
    "id"	: "lan",
    "guihandler": ``(LanSequence()),
    "initialize": ``(Lan::Read()),
    "finish"	: ``(Lan::Finish()),

    "actions"	: $[
	"list" : $[
	    "help"	: "display configuration summary",
	    "example"	: "lan list configured",
	    "handler"	: ``(listHandler())
	],
	"add" : $[
	    "help"	: "add a network card",
	    "handler"	: ``(addHandler())
	],
	"delete" : $[
	    "help"	: "delete a network card",
	    "handler"	: ``(deleteHandler())
	]
    ],
    ...
];

import "Lan";
include "commandline/commandline.ycp";

CommandLineRun(cmdline);

/* EOF */
}
    

The UI handler is specified in the "guihandler" key of the command line description map. It must take no arguments and return boolean, true on success.

The initialize resp. finish handler is specified by the "initialize" resp. "finish" key of the description map. They do not take any arguments and must return boolean, true on success. Notice that "initialize" and "finish" handlers are not used if a user asked for GUI (guihandler is used instead and therefore it must do initializing and finishing on its own). The handler for an action is specified in the "handler" key of the action description map. Each handler must take a single argument containing the options entered by the user and return a boolean, true on success. If the handler returns "false", the command line will abort for non-interactive handling. This is useful for handling error states. However, a handler must use CommandLine::Abort() to stop event-handling in the interactive mode.

The CommandLine module is stateful, i.e., it contains a current state of command line parsing/interactive console control of a YaST module. Therefore, the CommandLineRun() handles the commands as follows:

  1. standard UI start of a module - CommandLine::StartGUI will return true in this case

  2. command given as an argument - the inner while loop will be done only once

  3. interactive controling of a module - the while loop will be done as long as the user does not enter "exit" or "quit"

1.3.3. Internally handled commands

help

shows the help text for the command

interactive

starts interactive session without UI

<command> help

shows the command-specific help

<command> quiet

option to supress the progress messages

These are available in interactive mode only:

quit

quits interactive session, sets CommandLine::Aborted() flag to true

exit

exits interactive session, sets CommandLine::Aborted() flag to false

1.3.4. Specification of the supported commands in YaST module

[Note]Note

If the map does not follow the following rules, an error will be emitted into log.

The map describing the command line interface of a module must contain "id" entry containing the name of the module. The map can contain global help text, a list of actions, a list of options and a list for mapping of options to actions (which optionscan be used for which actions). Each action and option must have its help text.

Actions is a map with the action name as a key. For each action, it isnecessary to provide the help text. Optionally, action can have defined an example of usage.

A list of flags can be specified to change the default behavior of the parameter checker. It is a list with key "options". Currently known flags:

non_strict

for unknown parameters, do not check their validity can be used for passing unknown options into the handler

Example 1.3. Actions map definition

	"actions"	: $[
			"list"	: $[
				"help": "display configuration summary",
				"example": "lan list configured",
				"options": [ "non_strict" ]
			],
			"add"	: $[
				"help": "add a network card" ],
			...
                        ],
        

Options is a map with the option name as a key. For each option, it is necessary to provide a description text in "help" key, and a "type" key (for options with arguments only). Optionally, an option can contain an example.

There are two kinds of options: flags and options, which require an argument.For flags ommit type key, or specify type as "". A type is a string describing the type.basic types supported are string, boolean, integer.

Special types:

regex

In this case, you need to specify "typespec" key containing the regular expression the argument should be matched against.

enum

In this case, the typespec key must contain a list of possible values as strings

Example 1.4. Options map definition

    	"options"	: $[
			"propose" : $[
				"help": "propose a configuration",
				"example": "lan add propose",
				"type": ""
			],
			"device": $[
				"help": "device ID",
				"type": "string",
				"example": "lan add device=eth0"
			],
			"blem"	: $[
				"help": "other argument (without spaces)",
				"type": "regex",
				"typespec": "^[^ ]+$"
			],
			"atboot": $[
				"help": "should be brought up at boot?",
				"type": "enum",
				"typespec": ["yes","no"]
                                }

                            

The actions and options are grouped together using mappings. Currently, you can mapan action to a set of options. You can't specify required/optional options, all ofthem are optional.

Example 1.5. Mappings between actions and their options

	"mappings"	$[
			"list"	: [ "configured", "unconfigured" ],
			"add"	: [ "device", "ip", "netmask", "blem" ],
			"delete": [ "device" ]
                        ]

        

1.3.5. Advanced API

If you need to write your own event loop, this is a part of the CommandLine API useful for this:

boolean CommandLine::StartGUI()

returns true, if the user asked to start up the module GUI

list CommandLine::Scan()

reads a new command line in interactive mode, splits the arguments into a list

map CommandLine::Command()

parse (and scan if needed) next command and return its map

map CommandLine::Parse( list commandline )

lower-level function to parse the command line, check the validity

boolean CommandLine::Done()

returns true, if the last command was already returned

boolean CommandLine::Aborted()

returns true, if the user asked to cancel the changes

void CommandLine::Error(string)

prints the string and then a message how to obtain the help

void CommandLine::Print(string)

prints the string

void CommandLine::PrintVerbose(string)

same as CommandLine::Print, but the string is printed only in verbose mode

Example 1.6. Example of an event-loop

	import "CommandLine";

	if( ! CommandLine::Init( description_of_commands, Args() ) ) return;

	if( CommandLine::StartGUI() ) {

		<do standard GUI module>

		return;
	}
	
	<initialize call>

	while( ! CommandLine::Done() )
	{
		map command = CommandLine::Command();

		<handle commands here>
	}
	
	<finish call>
    

The CommandLine::Init() returns boolean whether the module has something to do. If the user only requested help or the arguments passed were not correct, the module should stop processing.

The CommandLine::Command() will do the user interaction if necessary. Also, it will handle all supported"system" commands, like "help", "exit" and "quit".

[Note]Note

In interactive mode, the communication uses /dev/tty. In non-interactive commandline mode it prints everything to standard error output.

1.3.6. Example usage in YaST module

For an example without using the event-loop provided by the CommandLine module, see lan-simple.ycp.

/**
 * File:	clients/lan.ycp
 * Package:	Network configuration
 * Summary:	Network cards main file
 * Authors:	Michal Svec <msvec@suse.cz>
 *
 * $Id: lan-simple.ycp 10158 2003-06-23 12:48:40Z visnov $
 *
 * Main file for network card configuration.
 * Uses all other files.
 */

{

/***
 * <h3>Network configuration</h3>
 */

import "CommandLine";
include "network/lan/wizards.ycp";

/**
 * Command line definition
 */
map cmdline = $[
    "help"	: "Configuration of network cards",
    "id"	: "lan",
    "actions"	: $[
	"list" : $[
	    "help"	: "display configuration summary",
	    "example"	: "lan list configured"
	],
	"add" : $[
	    "help"	: "add a network card" ],
	"delete" : $[
	    "help"	: "delete a network card" ]
    ],
    "options" : $[
	"propose" : $[
	    "help"	: "propose a configuration",
	    "example"	: "lan add propose",
	    "type"	: ""
	],
	"configured" : $[
	    "help"	: "list only configured cards"
	],
	"unconfigured" : $[
	    "help"	: "list only not configured cards"
	],
	"device" : $[
	    "help"	: "device ID",
	    "type"	: "string",
	    "example"	: "lan add device=eth0"
	],
	"ip" : $[
	    "help": "device address",
	    "type": "ip"
	],
	"netmask" : $[
	    "help": "network mask",
	    "type": "netmask"
	],
    ],
    "mappings" : $[
	"list"	: [ "configured", "unconfigured" ],
	"add"	: [ "device", "ip", "netmask" ],
	"delete": [ "device" ],
    ]
];


/* The main () */
y2milestone("----------------------------------------");
y2milestone("Lan module started");

/* Initialize the arguments */
if(!CommandLine::Init(cmdline, Args())) {
    y2error("Commandline init failed");
    return false;
}

/* Start GUI */
if(CommandLine::StartGUI()) {
    any ret = nil;
    ret = LanSequence();
    y2debug("ret=%1", ret);
    y2milestone("Lan module finished");
    y2milestone("----------------------------------------");
    return ret;
}

/* Init */
CommandLine::Print("Initializing");
import "Lan";
import "Progress";
CommandLine::Print("Reading");
Progress::off();
Lan::Read();

/* Init variables */
string command = "";
list flags = [];
map options = $[];
string exit = "";
list l = [];

while(!CommandLine::Done()) {
    map m = CommandLine::Command();
    command = m["command"]:"exit";
    options = m["options"]:$[];

    /* list */
    if(command == "list") {
	CommandLine::Print("\nSummary\n");
	string summary = sformat("%1\n", Lan::Summary(false));
	CommandLine::Print(summary);
    }
    /* del */
    else if(command == "delete") {
	string dev = options["device"]:"";
	CommandLine::Print("Deleting: "+dev);

	if(Lan::Delete(dev) && Lan::Commit())
	CommandLine::Print("Success");
	else
	CommandLine::Print("Error");
    }
    /* add */
    else if(command == "add") {
	string dev = options["device"]:"";
    }
    else {
	/* maybe we got "exit" or "quit" */
	if( !CommandLine::Done() ) {
	    CommandLine::Print("Unknown command (should not happen)");
	    continue;
	}
    }
}

if(!CommandLine::Aborted()) {
    CommandLine::Print("Writing");
    Lan::Write();
    CommandLine::Print("Finish");
}
else {
    CommandLine::Print("Quit (without saving)");
}

/* Finish */
y2milestone("Lan module finished");
y2milestone("----------------------------------------");

/* EOF */
}

For an example with the standard event-loop provided by the commandline.ycp include, see lan-simpler.ycp.

/**
 * File:	clients/lan.ycp
 * Package:	Network configuration
 * Summary:	Network cards main file
 * Authors:	Michal Svec <msvec@suse.cz>
 *
 * $Id: lan-simpler.ycp 24831 2005-08-11 15:51:55Z visnov $
 *
 * Main file for network card configuration.
 * Uses all other files.
 */

{

/***
 * <h3>Network configuration</h3>
 */

import "CommandLine";

include "network/lan/wizards.ycp";

/**
 * Command line definition
 */
map cmdline = $[
    "help"	: "Configuration of network cards",
    "id"	: "lan",
    "guihandler": ``(LanSequence()),
    "initialize": ``(Lan::Read()),
    "finish"	: ``(Lan::Write()),
    "actions"	: $[
	"list" : $[
	    "help"	: "display configuration summary",
	    "example"	: "lan list configured",
	    "handler"	: ``(listHandler())
	],
	"add" : $[
	    "help"	: "add a network card",
	    "handler"	: ``(addHandler())
	],
	"delete" : $[
	    "help"	: "delete a network card",
	    "handler"	: ``(deleteHandler())
	]
    ],
    "options" : $[
	"propose" : $[
	    "help"	: "propose a configuration",
	    "example"	: "lan add propose",
	    "type"	: ""
	],
	"configured" : $[
	    "help"	: "list only configured cards"
	],
	"unconfigured" : $[
	    "help"	: "list only not configured cards"
	],
	"device" : $[
	    "help"	: "device ID",
	    "type"	: "string",
	    "example"	: "lan add device=eth0"
	],
	"ip" : $[
	    "help": "device address",
	    "type": "ip"
	],
	"netmask" : $[
	    "help": "network mask",
	    "type": "netmask"
	],
    ],
    "mappings" : $[
	"list"	: [ "configured", "unconfigured" ],
	"add"	: [ "device", "ip", "netmask" ],
	"delete": [ "device" ],
    ]
];

/** handler for action "list" */
define void listHandler( map options ) ``{
    CommandLine::Print("\nSummary\n");
    string summary = CommandLine::Rich2Plain( sformat("%1\n", mergestring( Lan::Summary(false), "") ) );
    CommandLine::Print(summary);
}

/** handler for action "add" */
define void addHandler( map options ) ``{
    string dev = options["device"]:"";
}

/** handler for action "delete" */
define void deleteHandler( map options ) ``{
    string dev = options["device"]:"";
    CommandLine::Print("Deleting: "+dev);

    if(Lan::Delete(dev) && Lan::Commit())
	CommandLine::Print("Success");
    else
	CommandLine::Print("Error");
}

import "Lan";

CommandLineRun( cmdline );

/* Finish */
y2milestone("Lan module finished");
y2milestone("----------------------------------------");

/* EOF */
}