Introduction
Although a graphical user interface (GUI) is more common those days, I found that the command-line interface (CLI) is better way to interact with a software to perform specific tasks. In this article I will show how to create expandable command-line interpreter that receives, parse, and executes commands.
Background
When designing the interpreter the following principles was followed
:
- Simplify adding or removing commands.
- Allow to reuse commands in different programs and contexts
- Allow to group commands and use them in different programs and contexts
- Show list of available commands
- Show detailed help about specific command
The Demo Program
public class Demo {
public static int Main(string[] sargs) {
Commander countCmd = new Commander("count",2,1);
countCmd.Add(new CountCommand());
countCmd.Add(new CountDownCommand());
Commander mainCmd = new Commander("demo",1,1);
mainCmd.Add(new PrintCommand());
mainCmd.Add(new HalloCommand());
mainCmd.Add(countCmd);
return (int) mainCmd.Run(sargs);
}
}
The added commands can be invoked by the demo program. Note that the help messages are based on the added commands.
E:\demo>demo help
demo Commands [version 1.01] :
help => Display available commands,'help CMD' for help on CMD
print => Print message count times
hallo => Print 'Hallo world!' count times
count => count Commands [version 2.01]. use count help for detail
E:\demo>demo help help
Usage:
1. help
* Display all available commands with summary
2. help <command>
* Display help on a specific <command>
E:\demo>demo help print
Usage: print --msg=<msg> --count=<count>
* print <msg> <count> times
Arguments:
* <msg> - The message to print
* <count> - How many times to print the message
E:\demo>demo print --msg=hallo --count=3
hallo
hallo
hallo
E:\demo>demo help hallo
Usage: hallo --count=<count>
* print 'Hallo world!' <count> times
Arguments:
* <count> - How many times to print 'Hallo world!'
E:\demo>demo hallo --count=3
Hallo world!
Hallo world!
Hallo world!
E:\demo>demo count help
count Commands [version 2.01] :
help => Display available commands,'help CMD' for help on CMD
count => Count up to the given <count>
count-down => Count from <count> down to 1
E:\demo>demo count help count
Usage: count <count>
* Count upto <count>
Arguments:
* <count> - The maximum to count
E:\demo>demo count count 3
001
002
003
E:\demo>demo count help count-down
Usage: count-down <count>
* ount from <count> down to 1
Arguments:
* <count> - The statring number
E:\demo>demo count count-down 3
003
002
001
About the code
The ICommand Interface
The ICommand
interface allowes to classes which implementes it to be invoked by a commander.
The RunStatus Run(CommandArgs args)
is the key method in this interface. This method is responsible for executing the command's related code. The method is given CommandArgs
object and should return the RunStatus
of the command's execution. (Success
, InvalidArgs
, Error
or NoCommand
).
Each command have a
Name
,
Help
and
Summary
.
- The
Name
should be a single word which used to identify the command. - The
Summary
should be a single line description about the command's purpose - The
Help
should be a detailed description about the command and its valid arguments
The CommandArgs
The arguments are given to command by CommandArgs
object. There are two types of arguments that can be accessed using this object:
- Named arguments are arguments that can be accessed by name and can have optional value. To access named argument the following methods can be used:
-
this[string name]
Finds the value of named argument if exist, otherwise return null
-
bool HasArg(string name,out string value)
Finds whether a named argument if exist and its value. -
bool HasArg(string name)
Finds whether a named argument if exist and its value.
- Ordered arguments are arguments that can be accessed by index (1 based index) .To access named argument the following metdos can be used :
-
this[int index]
Finds the index'th ordered argument.
The CommandName
property holds the command's name while the Count
property holds the number of ordered arguments.
Parsing from array of strings
The CommandArgs
can be parsed from array of strings. To keep things simple the format is of named argument is --ARGNAME[=VALUE]. In other words , all strings that start with double dash (--) are treated as name argument. The argument name is delimited by -- and the end of the string or the optional equal sign (=). In the former case the value will of named argument will be empty string. in the latter case the value will be delimited by (=) and end of string.
The first string does not match this format will be treated as the command's name. Any other string will be treated as ordered argument.
The following code should clarify how the parsing is done
CommandArgs args = new CommandArgs(new string[] {
"show","--count=10","--display","arg1","arg2","arg3"
});
System.Console.WriteLine(args.CommandName);
System.Console.WriteLine(args["count"]);
System.Console.WriteLine(args["display"] == "");
System.Console.WriteLine(args.Count);
System.Console.WriteLine(args[1]);
System.Console.WriteLine(args[2]);
System.Console.WriteLine(args[3]);
System.Console.WriteLine(args["none"] == null);
System.Console.WriteLine(args[0] == null);
System.Console.WriteLine(args[4] == null);
The ICommander Interface
ICommander
is special ICommand
that can store list of commands, invoked them on demand. When the command is invoked using the Run(CommandArgs args)
method. The commander invokes the command which matches args.CommandName
.
To add a command to the commander we use the Add(ICommand cmd)
. As ICommander extends the ICommand interface, it can be also added to commander. This can be useful to chain and reuse ICommanders.
Each Commander has built-in help command HelpCommand
. This command can be invoked without arguments or with command's name. In the former case the displays all available commands with summary. In the later case the a detailed help about this specific command.
History
- Version 1 - First version.