// $Id: cmd.h,v 1.1 2014/09/24 06:39:16 querbach Exp $

// cmd.h			   Copyright (C) 2009, Real-Time Systems Inc.
//-------------------------------------------- All Rights Reserved ----------
//
//	Command-Line Interface
//
//---------------------------------------------------------------------------

#ifndef CMD_H
#define CMD_H

#include "ctype.h"
#include "string.h"
#include "iomanip.h"
#include "strstream.h"


// A CmdBuf wraps the streambuf that is the source of commands and the
// destination for responses.  It provides prompting, command-line editing,
// and uniform error display.

class CmdBuf
{
  istream& is;		// input stream for commands
  ostream& os;		// output stream for results and error messages

  const char* prompt;	// prompt string
  bool pendNL;		// newline pending

protected:

  char line[80];	// input line buffer
  istrstream iss;	// strstream for access to input line

public:

  // Create a command buffer.  We need a streambuf for character I/O and the
  // string to print at the tail end of the prompt.

  CmdBuf(istream& i, ostream& o, const char* p)
  : is(i), os(o), prompt(p), pendNL(false), iss(line, 1)
  { 
    strcpy(line, " ");
  }

  virtual ~CmdBuf() { }
  
  // Fill the command line buffer from the streambuf.
  
  virtual bool fill();  

  // Return the raw input istream.
  
  istream& raw() { return is; }
  
  // Return an istream containing whatever remains at this point in the
  // parsing of the input line.
  
  istream& in() { return iss; }

  // Return an ostream that the command can use to communicate its results
  // to the user.  If this is the first invocation since fill() set the
  // pendNL flag and we want the newline sent, do so.
  
  ostream& out(bool sendNL = true)
  { 
    if (pendNL && sendNL)
      os << '\n';
    
    pendNL = false;
    return os; 
  }

  // Halt parsing and return an ostream that the command can use to complain
  // about parsing errors.  Prepend the output with an error leadin string.
  
  virtual ostream& err() { iss.set(ios::failbit);  return out(); }

  // Still OK to continue parsing this input line?
  
  operator bool() const { return iss; }

  // Parse the first token in the command buffer, then search a command list
  // for it, and if found, return the list entry's object.  If not found,
  // show the help string of the last list entry as an error string.  If no
  // command is given, show the error string, but only if "complain" is
  // true.

  template <typename T>
  const T& search(T* list, bool complain = true)
  {
    char token[20];
    in() >> setw(sizeof(token)) >> token;

    // Look for matching command in list.
    
    const T* cmd;
    for (cmd = list; cmd->name; cmd++)
      if (strcasecmp(cmd->name, token) == 0)
        break;

    // Help requested?  Show each entry then halt parsing.
    
    if (strcmp(token, "?") == 0)
    {
      for (const T* cmd = list; cmd->name; cmd++)
        if (cmd->info)
          out() << "  " << cmd->name << cmd->info << endl;
      in().set(ios::failbit);
    }

    // Show error message if appropriate.
         
    else if (!cmd->name && (token[0] || complain))
      err() << cmd->info << endl;

    return *cmd;
  }
};


// Command list element class.
//
// An object of this struct contains name and help strings and a constructor
// to initialize them.  A menu, or "command list", is a simple array of
// objects of this or a derived class, terminated by an object with an null
// name pointer.

struct CmdBase
{
  const char* const name;	// command name
  const char* const info;	// help string

  // Create a command list element given name and help strings.
  
  CmdBase(const char* n, const char* i) : name(n), info(i) { }
};


// Functor command.
//
// An object of this class contains a target object to operate on and a
// parser method to apply to that object.
//
// A client can do something like the following:
//
//	CmdBuf buf;
//
//	CmdFunc cmds[] = 
//	{
//	  CmdFunc("foo", "  -- help for foo", foo, &Foo::parse),
//	  CmdFunc("bar", "  -- help for bar", bar, &Bar::show),
//	  CmdFunc(0, "Unknown command")
//	};
//
//	buf.search(cmds).parse(buf);
//
//	"buf.search(cmds)" will search cmds[] for a match to the first token
//	in the CmdBuf, and will return the associated CmdFunc object, or the
//	list terminator object if no entry matches.
//
//	".parse(buf)" will apply the CmdFunc's method to the CmdFunc's
//	object.  The method can then obtain further parameters from the
//	CmdBuf if desired, and can display its output using the CmdBuf's
//	output and error streams.
//
class CmdFunc : public CmdBase
{
  // Placeholder class.  No object of this class is ever created; instead
  // all target object references are cast to pointers to objects of this
  // class before storing.
  
  class Object;
  Object& object;	// object to operate on
  
  // Parser method on placeholder class.  All parser methods are cast to
  // method pointers of this type before storing.
  
  typedef void (Object::*Parser)(CmdBuf&) const;
  Parser parser;	// parse command arguments, operate on object

  // Note that in order to allow Parsers to operate on const objects, we
  // declare all objects and their Parsers const.  Obviously, many are not
  // const, but we ignore this fact with the reinterpret_casts in the
  // constructors.

  // Special null parser method for documentation-only and list-terminator
  // entries.
  
  void null(CmdBuf&) { }

protected:

  template <typename T, typename Buf>
  CmdFunc(const char* n, const char* i, T& t, void (T::*p)(Buf&))
  : CmdBase(n, i),
    object(reinterpret_cast<Object&>(t)),
    parser(reinterpret_cast<Parser>(p))
  { }

public:

  // Normal argument handler; has an object to operate on and an argument
  // parser method to apply to that object.
  
  template <typename T>
  CmdFunc(const char* n, const char* i, T& t, void (T::*p)(CmdBuf&))
  : CmdBase(n, i),
    object(reinterpret_cast<Object&>(t)),
    parser(reinterpret_cast<Parser>(p))
  { }

  // Special command list element for documentation-only or list-terminator
  // entries.  We point to this CmdFunc object and to a special parse method
  // which does nothing.  This type of entry then only exists to be
  // shown in a help display or as an error message.
  
  CmdFunc(const char* n, const char* i)
  : CmdBase(n, i),
    object(reinterpret_cast<Object&>(*this)),
    parser(reinterpret_cast<Parser>(&CmdFunc::null))
  { }

  // Run our parser method on our object.

  void parse(CmdBuf& buf) const { (object.*parser)(buf); }
};


// Enumerator command.
//
// An object of this class contains a reference to target object to return
// if the object's name is given on the command line.
//
// A client can do something like the following:
//
//	CmdBuf buf;
//	int i;
//
//	Cmd<int> cmds[] = 
//	{
//	  Cmd<int>("foo", "  -- help for foo", 42),
//	  Cmd<int>("bar", "  -- help for bar", 57),
//	  Cmd<int>(0, "Unknown option", i)
//	};
//
//	i = buf.search(cmds);
//
//	"buf.search(cmds)" will search cmds[] for a match to the first token
//	in the CmdBuf, and will return the associated Cmd<int> object, or
//	the list terminator object if no entry matches.
//
//	The assignment to "i" will invoke the conversion operator to T to
//	obtain the list entry's "int" object.
//
  
template <typename T> class Cmd : public CmdBase
{
  const T& object;	// object associated with this list entry

public:

  // Command list element, has name and help strings and an object. 
  
  Cmd(const char* n, const char* i, const T& t) 
  : CmdBase(n, i), object(t)
  { }

  // Get the entry's object.
  
  operator const T&() const { return object; }
};  


#endif // CMD_H

