Click here to Skip to main content
Click here to Skip to main content

Make Your Applications Extendable With Text Commands

, 5 Sep 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
This is a small part of a Command Line Interpreter I made. This class-library is the core command interpreter where you can insert commands and then execute them by their name.
Screenshot - CommandProcessor.jpg

Introduction

This is a small part of a Command Line Interpreter I made. This class-library is the core command interpreter where you can insert commands and then execute them by their name.

Background

I was developing a Command Line Interface and decided that I needed a library where I could define new commands and their implementation.

I call it a command processor but I guess it is simply a command line interpreter.
Personally I've used this class library in a CLI, in a simple chat-program and a remote controlling application.

Even though it is a very small piece of code, it touches many interesting aspects of .NET programming like generics, collections (mainly List & Dictionary), delegates and exceptions.

Going Straight for the Gravy

Now, what we need is some way for a user to add, remove and execute commands from a central list of available commands. So first, we need a class to define a command, like this:

internal class CCommand
{
     public CCommand(string commandName, commandCallback callbackMethod)
     {
         callback = callbackMethod;
         this.name = commandName;
     }

     private commandCallback callback;

     private string name;
     public string Name
     {
         get
         {
             return name;
         }
     } 

     public string execute(object sender, string[] args, string completeCommandString)
     {
         return callback.Invoke(sender, args, completeCommandString);
     } 
}
public delegate string commandCallback
    (object sender, string[] args, string completeCommandstring);

We first have a constructor that takes the name of the new command and a callback to the method to be executed when processing this command. The way you tell the class library to execute some method you have is via something called a delegate. A delegate is simply a wrapper around your callback method, that strictly defines how the callback method should look, like what parameters and their types must be. This way the compiler (at compile-time) can catch many hard-to-find bugs by making you strictly adhere to the delegate.

The commandCallback delegate is defined at the bottom OUTSIDE the CCommand class because it must be visible from other code.

Then there is a variable to hold the delegate and a property with the name of the command and finally a method to execute the command. This implementation is one way of executing a delegate.

Next we take a look at the rest of the code which is managing the list of commands.

public class CCmdProcessor
{
    #region Attributes

    // Key is the name of the command and value is the CCommand
     private Dictionary<string, CCommand> userCommand = 
            new Dictionary<string, CCommand>();

     private Tokenizer tok = new Tokenizer("");

     // The number of commands in the commandlist
     public int numberOfCommands
     {
         get
         {
             return userCommand.Count;
         }
     }

     #endregion

     #region Methods

     public void InsertCommand(string name, commandCallback methodToCall)
     {
         name = name.ToUpper();

         if(userCommand.ContainsKey(name))
             throw new CExceptionAlreadydefined(name);

         CCommand co = new CCommand(name, methodToCall);
         userCommand.Add(name, co);
         return;
     }

     public bool PurgeCommand(string name)
     {
         name = name.ToUpper();
         return userCommand.Remove(name);
     }

     public void PurgeAllCommands()
     {
         userCommand.Clear();
     }

     public string ExecuteCommand(string commandString)
     {
         tok.initialize(commandString);
         string result = tok.getNextToken().ToUpper();

         CCommand _tmp;
         if (userCommand.TryGetValue(result, out _tmp) == false)
             throw new CExceptionCommandUndefined("Command undefined " + commandString);
         else
         {
             //First tokenize to a list
             string ret;
             List args = new List();
             while ((ret = tok.getNextToken()) != new string(Tokenizer.EOF, 1))
             {
                 args.Add(ret);
             }
             //Then copy list to a string array
             string[] ls = args.ToArray(); //new string[args.Count];

             //then execute the command
             ret = _tmp.execute(this, ls, commandString);

             return ret;
         }
     }

     public string ExecuteCommand(string commandname, string[] arguments)
     {
         commandname = commandname.ToUpper();

         string _args = "";
         for (int n = 0; n < arguments.Length; n++)
         {
             _args += arguments[n] + " ";
         }

         CCommand _tmp;
         if (userCommand.TryGetValue(commandname, out _tmp) == false)
         {
             throw new CExceptionCommandUndefined("Command undefined " + commandname);
         }
         else
         {
             string ret;
             //then execute the command
             ret = _tmp.execute(this, arguments, commandname + " " + _args);

             return ret;
         }

     }

     #endregion
}

public class CExceptionCommandUndefined : ApplicationException
{
     public CExceptionCommandUndefined(string message)
     :base(message)
     {
     }

     public override string Message
     {
         get
         {
             return "Command undefined. " + base.Message;
         }
     }
}

public class CExceptionAlreadydefined : ApplicationException
{
     public CExceptionAlreadydefined(string message)
     : base(message)
     {
     }

     public override string Message
     {
         get
         {
             return "Command already defined. " + base.Message;
         }
     }
}

First thing off, we have some attributes like the list of the commands and a tokenizer which I won't get further into, except that it is a class that takes a string and splits it up into manageable tokens.

The list of commands is an object of the Dictionary class. This class is like an ordinary list except it takes a key together with the data you want to put into the list (like a Hashtable). This makes it possible to quickly find and sort the data in the dictionary, and we want the commands to be executed as fast as possible, otherwise you could just use a normal List.

Besides being a Dictionary class, it is also a generic-class/template-class/parameterized-class ..... you pick your favourite word. A generics class is nothing more than a class that takes one or more parameters - just like a method. The parameters for generics can only be types and not specific variables like with a method.

So for the Dictionary we want the command name as the key and the instance of a CCommand as the data, therefore we get:

private Dictionary<string, CCommand> userCommand = new Dictionary<string, CCommand>();

Then we have a method for inserting new commands. It first makes the command into only capital letters (all commands are non case-sensitive) and then checks to see if it's already defined in the list by checking for the key (name) to that command. If it is, we throw an exception otherwise we just add it into the list.

Then we can purge commands and execute them by checking for them in the list and then using the tokenizer, we make sure the commands get the correct arguments. Very simple piece of code but potentially quite powerful...IMHO.

Using the Code

I will, in later articles, show what this can be made to do, but I've just made a small app that demonstrates how to practically use it.

The command processor is made as a class library so you have to add a reference to the command processor DLL to use it in your own code.
You just make an instance of the CCmdProcessor and use this object to insert and execute your commands.

In the sample application, you can easily add additional commands. It is a pretty small app and should be self-explanatory. If you look at the picture at the top, you will see the sample application. It has a textbox where you type in your commands just like at a command prompt - starting with the command name and then the arguments separated by space.
Then you press the execute button and wait for the result in the Results box.

The source code should be pretty well commented!

History

  • 5th September, 2007 - First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jesper Olsen
Engineer
Denmark Denmark
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermdebug7-Oct-12 11:45 
QuestionNever seen MSDN??? PinmemberNirosh20-Jan-10 1:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 5 Sep 2007
Article Copyright 2007 by Jesper Olsen
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid