From 1cb3ecd9a0e00250a085e054445ca0de76646bfb Mon Sep 17 00:00:00 2001 From: Jerome Date: Fri, 10 Dec 2021 23:39:26 +0100 Subject: [PATCH] Help string generation done. Boolean parsing done. --- Makefile | 72 +++++++++++++ include/ArgParseCpp.hpp | 114 ++++++++++++++++++++ include/utils.hpp | 10 ++ src/ArgParseCpp.cpp | 226 +++++++++++++++++++++++++++++++++++++++ src/test_ArgParseCpp.cpp | 71 ++++++++++++ src/utils.cpp | 1 + 6 files changed, 494 insertions(+) create mode 100644 Makefile create mode 100644 include/ArgParseCpp.hpp create mode 100644 include/utils.hpp create mode 100644 src/ArgParseCpp.cpp create mode 100644 src/test_ArgParseCpp.cpp create mode 100644 src/utils.cpp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..617b4ca --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +CXX ?= g++ + +# path # +SRC_PATH = src +BUILD_PATH = build +BIN_PATH = $(BUILD_PATH)/bin + +# executable # +BIN_NAME = argParseCpp + +# extensions # +SRC_EXT = cpp + +# code lists # +# Find all source files in the source directory, sorted by +# most recently modified +SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' | sort -k 1nr | cut -f2-) +# Set the object file names, with the source directory stripped +# from the path, and the build path prepended in its place +OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o) +# Set the dependency files that will be used to add header dependencies +DEPS = $(OBJECTS:.o=.d) + +# flags # +COMPILE_FLAGS = -std=c++11 -Wall -Wextra -g +INCLUDES = -I include/ -I /usr/local/include +# Space-separated pkg-config libraries used by this project +LIBS = + +.PHONY: default_target +default_target: release + +.PHONY: release +release: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS) +release: dirs + @$(MAKE) all + +.PHONY: dirs +dirs: + @echo "Creating directories" + @mkdir -p $(dir $(OBJECTS)) + @mkdir -p $(BIN_PATH) + +.PHONY: clean +clean: + @echo "Deleting $(BIN_NAME) symlink" + @$(RM) $(BIN_NAME) + @echo "Deleting directories" + @$(RM) -r $(BUILD_PATH) + @$(RM) -r $(BIN_PATH) + +# checks the executable and symlinks to the output +.PHONY: all +all: $(BIN_PATH)/$(BIN_NAME) + @echo "Making symlink: $(BIN_NAME) -> $<" + @$(RM) $(BIN_NAME) + @ln -s $(BIN_PATH)/$(BIN_NAME) $(BIN_NAME) + +# Creation of the executable +$(BIN_PATH)/$(BIN_NAME): $(OBJECTS) + @echo "Linking: $@" + $(CXX) $(OBJECTS) -o $@ ${LIBS} + +# Add dependency files, if they exist +-include $(DEPS) + +# Source file rules +# After the first compilation they will be joined with the rules from the +# dependency files to provide header dependencies +$(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT) + @echo "Compiling: $< -> $@" + $(CXX) $(CXXFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@ diff --git a/include/ArgParseCpp.hpp b/include/ArgParseCpp.hpp new file mode 100644 index 0000000..9e49c3f --- /dev/null +++ b/include/ArgParseCpp.hpp @@ -0,0 +1,114 @@ +#ifndef DEF_ArgParseCpp +#define DEF_ArgParseCpp + +#include +#include +#include +#include +#include + +#include + +std::vector SplitString(const std::string & str, char sep); +std::string RemovePrefixDashes(const std::string & str); + +namespace ArgParseCpp +{ + +/// Used to describe the parser parameter type. +enum ParameterType +{ + StandaloneParam, // Represents all parameters such as "--param" + SingleValueParam, // Represents all parameters such as "--param value" + EqualParam // Represents all parameters such as "--param=value" + // ListParam // Represents all parameters such as "--param=value1,value2,..,valueN" +}; + +/// Used to describe the parser parameter value type. +enum ParameterValueType +{ + Bool, + Int, + Double, + String +}; + +/// Converts a ParameterValueType into a string for display. +std::string ParameterValueType2Str(const ParameterValueType & paramValueType); + +/// This class defines an argument parser parameter, with its short and long name, its type, its value type, and its docstring. +class ParserParam +{ + public: + /// Builds a new parser param object with the specified parameters. + ParserParam(const std::string & shortName_, const std::string & longName_, ParameterType type_, ParameterValueType valueType_ = ArgParseCpp::Bool, const std::string & docString_ = ""); + + /// Generates automatically a doc string based on the parameter type and value type, and repaces the current doc string with the newly generated one. + const std::string & GenerateDocString(); + + /// If no doc string has been specified, it is automatically generated. + void AutoGenerateDocString(); + + /// Returns either the full name without prefix dashes, or the short name without prefix dashes. + std::string SimpleName() const; + + /// Returns the help entry string : "-p --param value Parameter "param" with value "value_param" of type integer number" + std::string GetHelpEntryStr() const; + + public: + std::string shortName; + std::string longName; + ParameterType type; + ParameterValueType valueType; + std::string docString; +}; + +/// This class implements a simple argument parser in C++. It can be used to parse a variety of input arguments to programs. +class Parser +{ + public: + /// Creates an empty parser. + Parser(); + + /// Adds a parameter to be parsed. + void AddParameter(ParserParam param); + + /// Parses the arguments passed as parameters. + void Parse(int argc, char **argv); + + /// Parses the arguments passed as a single string. + void Parse(const std::string & argString); + + /// Parses the arguments passed as parameters. + void Parse(std::vector args); + + /// Prints the custom help message if it has been defined, or an automatically constructed one. + void PrintHelp() const; + + /// Prints the parsed arguments and their associated values. + void PrintParsedArguments() const; + + /// Returns the value of the specified argument. A conversion is done between the input text and the output value type. + template T GetArgumentValue(const std::string & argName) + { + + } + + protected: + /// Cleans the argument list passed as parameter. + void CleanArgs(std::vector & args) const; + + protected: + std::vector parserParams; + + std::map boolParameters; + std::map intParameters; + std::map doubleParameters; + std::map stringParameters; + + std::string customHelpMessage; +}; + +} // namespace ArgParseCpp + +#endif diff --git a/include/utils.hpp b/include/utils.hpp new file mode 100644 index 0000000..ddb2967 --- /dev/null +++ b/include/utils.hpp @@ -0,0 +1,10 @@ +#ifndef DEF_Utils +#define DEF_Utils + +#include +#include + +#define PRINT_VAR(v); std::cout << #v << "\t = "; std::cout << (v) << "\n"; +#define PRINT_VEC(v); std::cout << #v << "\t =\n"; for(unsigned int i_PRINT_VEC = 0 ; i_PRINT_VEC < (v).size() ; i_PRINT_VEC++) { std::cout << (v)[i_PRINT_VEC] << "\n"; } + +#endif diff --git a/src/ArgParseCpp.cpp b/src/ArgParseCpp.cpp new file mode 100644 index 0000000..ed8df08 --- /dev/null +++ b/src/ArgParseCpp.cpp @@ -0,0 +1,226 @@ +#include + +std::vector SplitString(const std::string & str, char sep) +{ + std::vector elems; + std::string elem; + for(unsigned int i = 0 ; i < str.size() ; i++) + { + if(str[i] != sep) + elem += str[i]; + else + { + elems.push_back(elem); + elem = ""; + } + } + if(elem.size()) + elems.push_back(elem); + return elems; +} + +std::string RemovePrefixDashes(const std::string & str) +{ + std::string res; + bool inPrefix = true; + for(unsigned int i = 0 ; i < str.size() ; i++) + { + if(str[i] == '-') + continue; + else + inPrefix = false; + if(!inPrefix) + res += str[i]; + } + return res; +} + +// ------------------- ArgParseCpp ------------------- + +namespace ArgParseCpp +{ + +std::string ParameterValueType2Str(const ParameterValueType & paramValueType) +{ + if(paramValueType == ArgParseCpp::Bool) + return "bool"; + else if(paramValueType == ArgParseCpp::Int) + return "integer number"; + else if(paramValueType == ArgParseCpp::Double) + return "floating-point number"; + else if(paramValueType == ArgParseCpp::String) + return "string"; + return ""; +} + +// ------------------- ArgParseCpp::ParserParam ------------------- + +ParserParam::ParserParam(const std::string & shortName_, const std::string & longName_, ParameterType type_, ParameterValueType valueType_, const std::string & docString_) + : shortName(shortName_), + longName(longName_), + type(type_), + valueType(valueType_), + docString(docString_) +{} + +const std::string & ParserParam::GenerateDocString() +{ + if(type == ArgParseCpp::StandaloneParam) + docString = "Activates the option \"" + SimpleName() + "\" when present."; + else if(type == ArgParseCpp::SingleValueParam || type == ArgParseCpp::EqualParam) + docString = "Sets the parameter \"" + SimpleName() + "\" at the value \"value_" + SimpleName() + "\" of type " + ParameterValueType2Str(valueType) + "."; + + return docString; +} + +void ParserParam::AutoGenerateDocString() +{ + if(!docString.size()) + GenerateDocString(); +} + +std::string ParserParam::SimpleName() const { return RemovePrefixDashes((longName.size()) ? longName : shortName); } + +std::string ParserParam::GetHelpEntryStr() const +{ + std::string res; + if(type == ArgParseCpp::StandaloneParam) + res = " " + shortName + "\t" + longName + "\t" + docString + "\n"; + else if(type == ArgParseCpp::SingleValueParam) + res = " " + shortName + "\t" + longName + " value_" + SimpleName() + "\t" + docString + "\n"; + else if(type == ArgParseCpp::EqualParam) + res = " " + shortName + "\t" + longName + "=value_" + SimpleName() + "\t" + docString + "\n"; + return res; +} + +// ------------------- ArgParseCpp::Parser ------------------- + +Parser::Parser() {} + +void Parser::AddParameter(ParserParam param) +{ + param.AutoGenerateDocString(); + parserParams.push_back(param); +} + +void Parser::Parse(int argc, char **argv) +{ + std::vector args; + for(int i = 0 ; i < argc ; i++) + args.push_back(argv[i]); + Parse(args); +} + +void Parser::Parse(const std::string & argString) { Parse(SplitString(argString, ' ')); } + +void Parser::Parse(std::vector args) +{ + CleanArgs(args); + + if(args.size() <= 1) + { + std::cerr << "Error : no arguments passed to the program.\n"; + PrintHelp(); + return;// exit(1); + } + else + { + // DEBUG + PRINT_VAR(args.size()); + PRINT_VEC(args); + + // Prepare all the boolean arguments + for(ParserParam const& param : parserParams) // loop over all parameters + if(param.type == ArgParseCpp::StandaloneParam) // if the parameter is a boolean type + boolParameters[param.SimpleName()] = false; + + for(std::string const& arg : args) + { + if(arg == "-h" || arg == "--help") + { + PrintHelp(); + return;// exit(1); + } + + for(unsigned int iParam = 0 ; iParam < parserParams.size() ; iParam++) // loop over all parameters + { + ParserParam const& param = parserParams[iParam]; + PRINT_VAR(arg); + PRINT_VAR(param.shortName); + PRINT_VAR(param.longName); + std::cout << "----\n"; + if(arg == param.shortName || arg == param.longName) // if the argument corresponds to this parameter + { + if(param.type == ArgParseCpp::StandaloneParam) + boolParameters[param.SimpleName()] = true; + else if(param.type == ArgParseCpp::SingleValueParam) + { + if(iParam+1 >= parserParams.size()) + { + std::cerr << "Error : parameter " << arg << " must be followed by a single value."; + return; + } + + if(param.valueType == ArgParseCpp::Int) + intParameters[param.SimpleName()] = atoi(parserParams[++iParam]);// read next parameter as the value of this argument, and skip the next iteration, as the next argument has already been processed + if(param.valueType == ArgParseCpp::Double) + doubleParameters[param.SimpleName()] = strtod(parserParams[++iParam]);// read next parameter as the value of this argument, and skip the next iteration, as the next argument has already been processed + if(param.valueType == ArgParseCpp::String) + stringParameters[param.SimpleName()] = parserParams[++iParam];// read next parameter as the value of this argument, and skip the next iteration, as the next argument has already been processed + } + } + } + } + } +} + +void Parser::PrintHelp() const +{ + if(customHelpMessage.size()) + std::cout << customHelpMessage << "\n"; + else + { + // Generate a help message + std::cout << "Usage : \n"; + std::cout << " -h, -help\t\tPrints this help message and exits.\n"; + + for(ParserParam param : parserParams) + std::cout << param.GetHelpEntryStr(); + } +} + +void Parser::PrintParsedArguments() const +{ + std::cout << "\nBoolean parameters\n------------------\n"; + for(auto const& arg : boolParameters) + std::cout << arg.first << "\t= " << arg.second << "\n"; + std::cout << "\nInteger parameters\n------------------\n"; + for(auto const& arg : intParameters) + std::cout << arg.first << "\t= " << arg.second << "\n"; + std::cout << "\nFloating-point parameters\n-------------------------\n"; + for(auto const& arg : doubleParameters) + std::cout << arg.first << "\t= " << arg.second << "\n"; + std::cout << "\nString parameters\n------------------\n"; + for(auto const& arg : stringParameters) + std::cout << arg.first << "\t= " << arg.second << "\n"; +} + +void Parser::CleanArgs(std::vector & args) const +{ + for(std::vector::iterator it = args.end()-1 ; it != args.begin() ; --it) + { + if((*it).size() == 0) // remove empty arguments + args.erase(it); + else // clean individual arguments + { + char c = (*it)[(*it).size()-1]; + while(c == ' ' || c == '\t' || c == '\n') + { + (*it).erase(*((*it).end())); + c = (*it)[(*it).size()-1]; + } + } + } +} + +} // namespace ArgParseCpp diff --git a/src/test_ArgParseCpp.cpp b/src/test_ArgParseCpp.cpp new file mode 100644 index 0000000..86f59f0 --- /dev/null +++ b/src/test_ArgParseCpp.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +using ArgListT = std::vector; +using ArgParseCpp::Parser; + +template std::vector & operator<<(std::vector &vec, const T & val) +{ + vec.push_back(val); + return vec; +} + +int main(int argc, char *argv[]) +{ + // Test the parser on the arguments provided in main() + if(argc > 1) + { + Parser parser; + parser.Parse(argc, argv); + } + + // Test the parser in various predefined scenarios + else + { + if(0) + {// automatic help message + Parser parser; + parser.AddParameter(ArgParseCpp::ParserParam("-v", "--verbose", ArgParseCpp::StandaloneParam, ArgParseCpp::Bool, "Makes the program more verbose.")); + parser.AddParameter(ArgParseCpp::ParserParam("-a", "--all", ArgParseCpp::StandaloneParam, ArgParseCpp::Bool, "Does all the things.")); + parser.AddParameter(ArgParseCpp::ParserParam("-n", "--nope", ArgParseCpp::StandaloneParam)); + parser.AddParameter(ArgParseCpp::ParserParam("-d", "--decimate", ArgParseCpp::SingleValueParam, ArgParseCpp::Int, "Decimates the signal by a factor d.")); + parser.AddParameter(ArgParseCpp::ParserParam("-b", "--babar", ArgParseCpp::SingleValueParam, ArgParseCpp::Int)); + parser.AddParameter(ArgParseCpp::ParserParam("-c", "", ArgParseCpp::SingleValueParam, ArgParseCpp::Double)); + parser.AddParameter(ArgParseCpp::ParserParam("-o", "", ArgParseCpp::SingleValueParam, ArgParseCpp::String)); + parser.AddParameter(ArgParseCpp::ParserParam("-e", "", ArgParseCpp::EqualParam, ArgParseCpp::Int)); + parser.PrintHelp(); + } + if(0) + {// sanitize the input arguments and display help whenever -h or --help is seen + std::string argString = "programName -h blabla hehe hoho lalaland"; + Parser parser; + parser.Parse(argString); + } + if(0) + {// parse some boolean arguments + std::string argString = "programName -v --all"; + Parser parser; + parser.AddParameter(ArgParseCpp::ParserParam("-v", "--verbose", ArgParseCpp::StandaloneParam, ArgParseCpp::Bool, "Makes the program more verbose.")); + parser.AddParameter(ArgParseCpp::ParserParam("-a", "--all", ArgParseCpp::StandaloneParam)); + parser.AddParameter(ArgParseCpp::ParserParam("-b", "--brief", ArgParseCpp::StandaloneParam)); + parser.AddParameter(ArgParseCpp::ParserParam("-z", "", ArgParseCpp::StandaloneParam)); + parser.PrintHelp(); + parser.Parse(argString); + parser.PrintParsedArguments(); + } + // if(0) + {// parse single value arguments + std::string argString = "programName -a 5 -b 12.654 -s blabla_mon_negro hehe"; + Parser parser; + parser.AddParameter(ArgParseCpp::ParserParam("-a", "--all", ArgParseCpp::SingleValueParam, ArgParseCpp::Int)); + parser.AddParameter(ArgParseCpp::ParserParam("-b", "--brief", ArgParseCpp::SingleValueParam, ArgParseCpp::Double)); + parser.AddParameter(ArgParseCpp::ParserParam("-s", "", ArgParseCpp::SingleValueParam, ArgParseCpp::String)); + parser.PrintHelp(); + parser.Parse(argString); + parser.PrintParsedArguments(); + } + } + + return 0; +} diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..efcec79 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1 @@ +#include