Options handling rewritten with usage of popt.h. fixed #1 with stringtoargcargv.cpp. The command arguments are now passed as struct mshconsole::Params. Readme

updated in oreder to mention stringtoargcargv.cpp.
This commit is contained in:
Claudio Maggioni 2016-04-16 19:21:39 +02:00
parent 709a2e6c1c
commit f6c3135c27
14 changed files with 425 additions and 272 deletions

View file

@ -3,11 +3,6 @@ Library that provides a bash-like interface for CLI C++ programs
### Credits
Code based on "Write a Shell in C" - by Stephen Brennan (http://brennan.io/2015/01/16/write-a-shell-in-c/)
The code has been slightly modified.
The code contains stringtoargcargv.cpp, a set of functions written by Bernhard Eder (http://web.archive.org/web/20121030075237/http://bbgen.net/blog/2011/06/string-to-argc-argv) for parsing a string into argc and argv.
### The library
The library can be compiled as shared library with the CMakeLists.txt file already in msh-console-library/, or it can be compiled with these commands:
```
cd msh-console-library
g++ -Wall -fPIC -std=c++11 -c command.cpp commands.cpp shell.cpp commandexecutor.cpp
g++ -shared -Wl,-soname,libmshconsole.so.1.0.0 -o libmshconsole.so.1.0.0 commandexecutor.o command.o commands.o shell.o
```
The library can be compiled as shared library with the CMakeLists.txt file already in msh-console-library/.

View file

@ -4,7 +4,7 @@
project(msh-console-library)
cmake_minimum_required(VERSION 2.8)
SET(CMAKE_CXX_FLAGS "-std=c++11")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -lpopt")
FILE(GLOB sources *.cpp)
FILE(GLOB headers *.h)

View file

@ -1,137 +1,77 @@
#include "shell.h"
namespace mshconsole {
Command::Command(const string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const vector<char*> argv)) :
name(n), funcCommand(funcptr){
Command::Command(const string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const vector<const char*> argv)) :
name(n), funcCommand(funcptr), optionNum(0){
checkObj();
}
void Command::addOption(Option *a){
if(a->getOptionName().length()==1){
shortOpts.push_back(a);
}
else longOpts.push_back(a);
void Command::addOption(const poptOption& a){
if(a.arg!=NULL&&a.arg!=nullptr&&a.arg!=0) throw InvalidOptionException{};
for(int i=0; i<opts.size(); i++)
if(strcmp((opts[i].longName),(a.longName))==0||
opts[i].shortName==a.shortName)
throw DuplicatedOptionException();
opts.push_back(a);
optionNum++;
}
int Command::execute(const vector<string>* args, CommandExecutor* c){
if(args==nullptr) return -1;
int Command::execute(const struct Params& p, CommandExecutor* c){
if(p.argc<=0) return -1;
//filling longData
Datas longData;
for(size_t i=0; i<longOpts.size(); i++){
longData.push_back(new Data(longOpts[i]));
// options a, b, c take integer arguments
// options f and g take no arguments
poptContext pc;
opts.push_back(POPT_TERMINATOR);
Datas datas;
for(int i=0; i<optionNum; i++){
Data* tmp = new Data(&opts[i]);
opts[i].arg=&(tmp->d);
datas.push_back(tmp);
}
//filling string for getopt and shortData
string getoptarg;
Datas shortData;
for(size_t i=0; i<shortOpts.size(); i++){
shortData.push_back(new Data(shortOpts[i]));
getoptarg.append(1,shortOpts[i]->getOptionName()[0]);
}
getoptarg+=" -:";
//creating argv as char** for getopt compatibility
vector<char *> argv(args->size());
size_t i;
for (i = 0; i < args->size(); ++i)
{
argv[i] = const_cast<char*>(&(args->operator[](i)[0]));
}
int argc=i;
//TODO: quotes for long options
try{
int c;
opterr=0;
optind=1;
while ((c = getopt (argc, argv.data(), getoptarg.c_str())) != -1){
if((c>='a'&&c<='z')||(c>='A'&&c>='Z')){
for(size_t i=0; i<shortOpts.size(); i++){
if(c==shortOpts[i]->getOptionName()[0]){
shortData[i]->set(true);
break;
}
}
}
else if(c=='-'){
string l(optarg);
bool getout = false;
try{
for(size_t i=0; i<longOpts.size(); i++){
if(l.find(longOpts[i]->getOptionName()+"=")==0){
if(!(longOpts[i]->hasFlag(Option::STRING))){
throw NotRightArgException();
}
longData[i]->set(l.substr(longOpts[i]->getOptionName().size()+1,l.size()));
getout = true;
break;
}
if(l==longOpts[i]->getOptionName()){
longData[i]->set(true);
getout=true;
break;
}
}
}
catch(Data::MultipleDefinitionException){
getout=false;
}
if(!getout) throw OptionNotParsedCorrectlyException(static_cast<string>("--") + optarg);
}
else if(c=='?'){
if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf (stderr, "Unknown option character `\\x%x'.\n",optopt);
return -1;
}
else //abort();
break;
// pc is the context for all popt-related functions
pc = poptGetContext(NULL, p.argc, const_cast<const char**>(p.argv), opts.data(), 0);
poptSetOtherOptionHelp(pc, "[ARG...]");
if (p.argc < 2 && opts.size()==0) {
poptPrintUsage(pc, stderr, 0);
return 1;
}
//demo of all options for debug (code to check)
/*
for(size_t i=0; i<longOpts.size(); i++){
cout << longOpts[i]->getOptionName() << ": ";
LongData* now = data[i];
if(now->getState()==LongData::States::STRING) cout << now->getLongData();
else cout << now->getBoolState();
cout << "\n";
}
for(size_t i=0; i<shortOpts.size(); i++){
cout << shortOptions[i] << ": " << sLongData[i] << "\n";
}*/
}
catch(OptionNotParsedCorrectlyException e){
cout << "Error parsing option " << e.getOptarg() << "\n";
return -1;
}
catch(NotRightArgException){
cout << "Error on options argument type\n";
return -1;
}
// process options and handle each val returned
int val;
while ((val = poptGetNextOpt(pc)) >= 0);
//making one big Data vector
shortOpts.insert(
shortOpts.end(),
std::make_move_iterator(longOpts.begin()),
std::make_move_iterator(longOpts.end())
);
//deleting options and command name from argv
argv.erase(argv.begin()); //argv[0] = command name
for(int i=0; i<argv.size(); i++){
if(argv[0][0]=='-'){
argv.erase(argv.begin()+i);
i--;
// poptGetNextOpt returns -1 when the final argument has been parsed
// otherwise an error occured
if (val != -1) {
cerr << name << ": ";
switch(val) {
case POPT_ERROR_NOARG:
cerr << "argument missing for an option\n";
return 1;
case POPT_ERROR_BADOPT:
cerr << "option not found\n";
return 1;
case POPT_ERROR_BADNUMBER:
case POPT_ERROR_OVERFLOW:
cerr << "option could not be converted to number\n";
return 1;
default:
cerr << "unknown error in option processing\n";
return 1;
}
}
return (*funcCommand)(c,shortData, argv);
vector<const char*> nonOptionArgs;
while (poptPeekArg(pc) != NULL)
nonOptionArgs.push_back(const_cast<const char*>(poptGetArg(pc)));
return (*funcCommand)(c,datas,nonOptionArgs);
}
string Command::getName(){
const string& Command::getName(){
return this->name;
}
@ -146,5 +86,5 @@ namespace mshconsole {
}
}
}
const string Command::Data::EMPTY("");
const struct poptOption Command::POPT_TERMINATOR = {NULL,'\0',POPT_ARG_NONE,NULL,-1,NULL,NULL};
}

View file

@ -1,9 +1,12 @@
#ifndef COMMAND_H
#define COMMAND_H
#pragma once
#include <string>
#include <vector>
//#include "shell.h"
#include "option.h"
#include <unistd.h>
extern "C"{
#include <popt.h>
}
#include "commandexecutor.h"
using std::string;
@ -12,71 +15,67 @@ using std::vector;
namespace mshconsole{
class Command
{
class OptionNotParsedCorrectlyException {
string optarg;
public:
OptionNotParsedCorrectlyException(const string& s) : optarg(s) {}
string getOptarg(){
return optarg;
}
};
class CommandNameNotValidException {};
class NotRightArgException {};
const string name;
void checkObj();
vector<Option*> longOpts;
vector<Option*> shortOpts;
vector<poptOption> opts;
static const struct poptOption POPT_TERMINATOR;
size_t optionNum;
public:
class Data{
public:
enum States{
STRING=4,
TRUE=2,
FALSE=1
union Arg{
int i;
char* s;
long l;
float f;
double d;
};
enum Types{
STRING,
INT,
LONG,
FLOAT,
DOUBLE
};
class MultipleDefinitionException{};
private:
string sData;
const Option* of;
enum States state;
static const string EMPTY;
bool isSDataValorized;
Data(const Option* o) : sData(), state(Data::FALSE), isSDataValorized(false), of(o) {}
union Arg d;
poptOption* of;
Data(poptOption* o) : of(o), d() {}
friend class mshconsole::Command;
public:
void set(bool b);
void set(const string& s);
enum States getState() const;
const string& getData() const;
bool getBoolState() const;
friend class Command;
friend class Datas;
const union Arg& getArg() const;
enum Types getType() const;
int getInt() const;
char* getString() const;
long getLong() const;
float getFloat() const;
double getDouble() const;
};
class Datas : public vector<Data*>{
public:
Data* getOptData(const string& optionName) const;
Data* getOptData(char opt) const;
Data* operator[](const string& opt) const;
};
Command(const string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const vector<char*> argv));
class DuplicatedOptionException{};
class CommandNameNotValidException{};
class InvalidOptionException{};
Command(const string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const vector<const char*> argv));
Command(const Command&);
~Command();
string getName();
int execute(const vector<string>*, CommandExecutor*);
void addOption(Option* a);
const string& getName();
int execute(const struct Params&, CommandExecutor*);
void addOption(const poptOption& option);
private:
int (*funcCommand)(CommandExecutor* whoExecuted,const Datas& data, const vector<char*> argv);
int (*funcCommand)(CommandExecutor* whoExecuted,const Datas& data, const vector<const char*> argv);
};
}
#endif // COMMAND_H

View file

@ -1,5 +1,6 @@
#ifndef COMMANDEXECUTOR_H
#define COMMANDEXECUTOR_H
#pragma once
#include <vector>
#include <string>
@ -16,12 +17,16 @@ namespace mshconsole{
ExitException(int c=0) : code(c){}
};
public:
virtual int executeCmd(std::vector<std::string>* args) = 0;
virtual int executeCmd(const struct Params& args) = 0;
virtual int executeCmd(const std::string& args) = 0;
virtual size_t howManyCmds() const = 0;
void exit(int code=0);
CommandExecutor();
};
struct Params{
int argc;
char** argv;
};
}
#endif // COMMANDEXECUTOR_H

View file

@ -14,10 +14,10 @@ namespace mshconsole {
return commands.size();
}
int Shell::Commands::launch(const vector<string>* args, bool launchThread){
int Shell::Commands::launch(const struct Params& p, bool launchThread){
for(unsigned int i=0; i<(launchThread ? threadCommands.size() : commands.size()); i++){
if((launchThread ? threadCommands[i]->getName() : commands[i]->getName())==args->operator [](0)){
return (launchThread ? threadCommands[i]->execute(args, parent) : commands[i]->execute(args, parent));
if((launchThread ? threadCommands[i]->getName() : commands[i]->getName())==p.argv[0]){
return (launchThread ? threadCommands[i]->execute(p, parent) : commands[i]->execute(p, parent));
}
}
throw CommandNotFoundException();

View file

@ -1,30 +1,40 @@
#include "command.h"
namespace mshconsole{
void Command::Data::set(bool b){
if(isSDataValorized) throw MultipleDefinitionException();
b ? state=Data::TRUE : state=Data::FALSE;
isSDataValorized=true;
inline const union Command::Data::Arg& Command::Data::getArg() const{
return d;
}
void Command::Data::set(const string& s){
if(isSDataValorized) throw MultipleDefinitionException();
state=Data::STRING;
sData=s;
isSDataValorized=true;
enum Command::Data::Types Command::Data::getType() const{
if(of->argInfo & POPT_ARG_INT || of->argInfo & POPT_ARG_VAL || of->argInfo == POPT_ARG_NONE){
return INT;
}
enum Command::Data::States Command::Data::getState() const{
return state;
else if(of->argInfo & POPT_ARG_STRING){
return STRING;
}
const string& Command::Data::getData() const{
if(state!=Data::STRING) return EMPTY;
else return sData;
else if(of->argInfo & POPT_ARG_LONG){
return LONG;
}
bool Command::Data::getBoolState() const{
return state!=Data::FALSE;
else if(of->argInfo & POPT_ARG_FLOAT){
return FLOAT;
}
Command::Data* Command::Datas::getOptData(const string& opt) const{
for(size_t i=0; i<size(); i++){
if(operator[](i)->of->getOptionName()==opt) return operator[](i);
else if(of->argInfo & POPT_ARG_DOUBLE){
return DOUBLE;
}
//else return NULL;
}
int Command::Data::getInt() const{
return d.i;
}
char* Command::Data::getString() const{
return d.i ? d.s : const_cast<char*>("");
}
long Command::Data::getLong() const{
return d.l;
}
float Command::Data::getFloat() const{
return d.f;
}
double Command::Data::getDouble() const{
return d.d;
}
}

View file

@ -0,0 +1,19 @@
#include "command.h"
namespace mshconsole {
Command::Data* Command::Datas::getOptData(const string& opt) const{
for(size_t i=0; i<this->size(); i++){
if(opt == this->at(i)->of->longName) return this->at(i);
}
}
Command::Data* Command::Datas::getOptData(char opt) const{
for(size_t i=0; i<this->size(); i++){
if(opt == this->at(i)->of->shortName) return this->at(i);
}
}
Command::Data* Command::Datas::operator [](const string& opt) const{
if(opt.length()>2) return getOptData(opt);
if(opt.length()==1) return getOptData(opt[0]);
else return nullptr;
}
}

View file

@ -1,17 +0,0 @@
#include "option.h"
namespace mshconsole{
Option::Option(const string &s, States b): optionName(s), want(b){
if(optionName.length()<1){
throw OptionNameEmptyException{};
}
}
const string& Option::getOptionName() const{
return optionName;
}
bool Option::hasFlag(enum States s) const{
return want & s;
}
}

View file

@ -1,20 +0,0 @@
#include <string>
using std::string;
namespace mshconsole{
class Option{
string optionName;
class OptionNameEmptyException{};
public:
enum States{
STRING=2,
BOOL=1
};
private:
enum States want;
public:
Option(const string& s, enum States b);
const string& getOptionName() const;
bool hasFlag(enum States s) const;
};
}

View file

@ -43,7 +43,7 @@ namespace mshconsole {
//launch loop
string* line;
vector<string>* args;
struct Params p;
int exitCode;
try{
do {
@ -59,9 +59,8 @@ namespace mshconsole {
readSuccess = false;
}
}while(!readSuccess);
args = split_line(line);
executeCmd(args);
delete args;
p = split_line(line);
executeCmd(p);
} while (1);
} catch(CommandExecutor::ExitException c) {
exitCode=c.getCode();
@ -74,7 +73,7 @@ namespace mshconsole {
return exitCode;
}
int Shell::launchCmd(vector<string>* args)
int Shell::launchCmd(const struct Params& p)
{
using std::exit;
int status;
@ -86,19 +85,12 @@ namespace mshconsole {
//execute threadCommand
int a;
try {
a=cmds.launch(args, true);
a=cmds.launch(p, true);
}
catch (CommandNotFoundException){
//execute bash command or program
vector<char *> argv(args->size() + 1);
size_t i;
for (i = 0; i != args->size()-1; ++i)
{
argv[i] = &(args->operator[](i)[0]);
}
argv[i] = NULL;
if((a=execvp(argv[0], argv.data())) == -1) {
cerr << name <<": command " << args->operator [](0) << " not found\n";
if((a=execvp(p.argv[0], p.argv)) == -1) {
cerr << name <<": command " << p.argv[0] << " not found\n";
}
exit(EXIT_FAILURE);
}
@ -118,18 +110,17 @@ namespace mshconsole {
return 1;
}
int Shell::executeCmd(vector<string>* args)
int Shell::executeCmd(const struct Params& p)
{
if (args->operator [](0) == "\0") {
// An empty command was entered.
if (!p.argc) { //empty line
return 1;
}
int ret;
try {
ret = cmds.launch(args, false);
ret = cmds.launch(p, false);
}
catch(CommandNotFoundException) {
ret = launchCmd(args);
ret = launchCmd(p);
}
return ret;
}
@ -181,17 +172,10 @@ namespace mshconsole {
return buffer;
}
vector<string>* Shell::split_line(const string* line)
{
vector<string>* tokens = new vector<string>();
string ln = *line;
istringstream is(ln);
int i;
for(i=0; getline(is, ln, ' '); i++){
tokens->push_back(ln);
}
tokens->push_back("\0");
return tokens;
struct Params Shell::split_line(const string* line){
struct Params p;
stringToArgcArgv(*line, &(p.argc), &(p.argv));
return p;
}
bool Shell::undoingLine = false;

View file

@ -1,18 +1,19 @@
#ifndef SHELL_H
#define SHELL_H
#pragma once
#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <sstream>
#include <string>
#include <cstring>
#include <vector>
#include <sstream>
#include <cstddef>
#include <csignal>
#include "command.h"
//#include "commands.h"
#include <wordexp.h>
#include "commandexecutor.h"
#include "stringtoargcargv.h"
using std::string;
using std::cin;
using std::cout;
@ -35,7 +36,7 @@ namespace mshconsole {
Commands(Shell*);
void add(Command*, bool isthread=false);
size_t howMany() const;
int launch(const vector<string>* args, bool launchThread=false);
int launch(const struct Params& args, bool launchThread=false);
};
static bool undoingLine;
@ -46,10 +47,10 @@ namespace mshconsole {
void (*shellPostSetup)(Shell *);
bool notLoop;
int launchCmd(vector<string>* args);
int launchCmd(const struct Params& args);
static void EofHandler(int);
string* read_line();
vector<string>* split_line(const string* line);
struct Params split_line(const string* line);
class IsUndoingLineException {};
class CommandNotFoundException {};
@ -69,7 +70,7 @@ namespace mshconsole {
//for in-shell commands
void addCmd(Command *cmd, bool isThread=false);
size_t howManyCmds() const;
int executeCmd(vector<string>* args);
int executeCmd(const struct Params& args);
int executeCmd(const string& args);
};
}

View file

@ -0,0 +1,192 @@
/* stringtoargcargv.cpp -- Parsing a string to std::vector<string>
Copyright (C) 2011 Bernhard Eder
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Bernhard Eder blog_at_bbgen.net
*/
#include "stringtoargcargv.h"
/*
* Usage:
* int argc;
* char** argv;
* stringToArgcArgv("foo bar", &argc, &argv);
*/
void stringToArgcArgv(const std::string& str, int* argc, char*** argv)
{
std::vector<std::string> args = parse(str);
*argv = (char**)std::malloc(args.size() * sizeof(char*));
int i=0;
for(std::vector<std::string>::iterator it = args.begin();
it != args.end();
++it, ++i)
{
std::string arg = *it;
(*argv)[i] = (char*)std::malloc((arg.length()+1) * sizeof(char));
std::strcpy((*argv)[i], arg.c_str());
}
*argc = args.size();
}
std::vector<std::string> parse(const std::string& args)
{
std::stringstream ain(args); // used to iterate over input string
ain >> std::noskipws; // do not skip white spaces
std::vector<std::string> oargs; // output list of arguments
std::stringstream currentArg("");
currentArg >> std::noskipws;
// current state
enum State {
InArg, // currently scanning an argument
InArgQuote, // currently scanning an argument (which started with quotes)
OutOfArg // currently not scanning an argument
};
State currentState = OutOfArg;
char currentQuoteChar = '\0'; // to distinguish between ' and " quotations
// this allows to use "foo'bar"
char c;
while(!ain.eof() && (ain >> c)) { // iterate char by char
if(_isQuote(c)) {
switch(currentState) {
case OutOfArg:
currentArg.str(std::string());
case InArg:
currentState = InArgQuote;
currentQuoteChar = c;
break;
case InArgQuote:
if(c == currentQuoteChar)
currentState = InArg;
else
currentArg << c;
break;
}
}
else if(_isWhitespace(c)) {
switch(currentState) {
case InArg:
oargs.push_back(currentArg.str());
currentState = OutOfArg;
break;
case InArgQuote:
currentArg << c;
break;
case OutOfArg:
// nothing
break;
}
}
else if(_isEscape(c)) {
switch(currentState) {
case OutOfArg:
currentArg.str(std::string());
currentState = InArg;
case InArg:
case InArgQuote:
if(ain.eof())
{
#ifdef WIN32
// Windows doesn't care about an escape character at the end.
// It just adds \ to the arg.
currentArg << c;
#else
throw(std::runtime_error("Found Escape Character at end of file."));
#endif
}
else
{
#ifdef WIN32
// Windows only escapes the " character.
// Every other character is just printed and the \ is added itself.
char c1 = c;
ain >> c;
if(c != '\"')
currentArg << c1; // only ignore \ when next char is "
ain.unget();
#else
ain >> c;
currentArg << c;
#endif
}
break;
}
}
else {
switch(currentState) {
case InArg:
case InArgQuote:
currentArg << c;
break;
case OutOfArg:
currentArg.str(std::string());
currentArg << c;
currentState = InArg;
break;
}
}
}
if(currentState == InArg)
oargs.push_back(currentArg.str());
else if(currentState == InArgQuote)
throw(std::runtime_error("Starting quote has no ending quote."));
return oargs;
}
bool _isQuote(char c)
{
if(c == '\"')
return true;
else if(c == '\'')
return true;
return false;
}
bool _isEscape(char c)
{
if(c == '\\')
return true;
return false;
}
bool _isWhitespace(char c)
{
if(c == ' ')
return true;
else if(c == '\t')
return true;
return false;
}

View file

@ -0,0 +1,45 @@
#ifndef STRINGTOARGCARGV_H
#define STRINGTOARGCARGV_H
#pragma once
/* stringtoargcargv.cpp -- Parsing a string to std::vector<string>
Copyright (C) 2011 Bernhard Eder
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Bernhard Eder blog_at_bbgen.net
*/
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
bool _isQuote(char c);
bool _isEscape(char c);
bool _isEscape(char c);
bool _isWhitespace(char c);
std::vector<std::string> parse(const std::string& args);
void stringToArgcArgv(const std::string& str, int* argc, char*** argv);
#endif //STRINGTOARGCARGV_H