From 1214adfbd12dc00cd0b39e767c58ee62d8e83b1f Mon Sep 17 00:00:00 2001 From: Cameron Reed Date: Mon, 25 Jul 2022 20:57:57 -0600 Subject: [PATCH] Initial Commit --- .gitignore | 4 + Inc/argparser.h | 66 ++++++++++++++++ Makefile | 66 ++++++++++++++++ Src/argparser.cpp | 196 ++++++++++++++++++++++++++++++++++++++++++++++ test.cpp | 48 ++++++++++++ 5 files changed, 380 insertions(+) create mode 100644 .gitignore create mode 100644 Inc/argparser.h create mode 100644 Makefile create mode 100644 Src/argparser.cpp create mode 100644 test.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b91c6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +bin/ +.kdev4/ +*.kdev4 diff --git a/Inc/argparser.h b/Inc/argparser.h new file mode 100644 index 0000000..cbfefba --- /dev/null +++ b/Inc/argparser.h @@ -0,0 +1,66 @@ +#pragma once +#include + +namespace Cam +{ +namespace Arguments +{ + +enum OPTTYPE +{ + FLAG = 0, + STRING, + INT +}; + +class Option +{ +public: + Option(const char* name, const char* description, OPTTYPE type); + Option(const char* name, const char* short_name, const char* description, OPTTYPE type); + bool found(); + void* data; +private: + const char* m_name; + const char* m_short_name; + const char* m_description; + OPTTYPE m_type; + bool m_found; + + friend class Parser; +}; + +class PositionalArgument +{ +public: + PositionalArgument(const char* name, bool required, OPTTYPE type = STRING); + bool found(); + void* data; +private: + const char* m_name; + OPTTYPE m_type; + bool m_req; + bool m_found; + + friend class Parser; +}; + +class Parser +{ +public: + Parser(const char* program_name); + void set_description(const char* description); + void add_option(Option* opt); + void add_positional_argument(PositionalArgument* arg); + int parse(int argc, char** argv); +private: + void print_help_message(); +private: + const char* m_program_name; + const char* m_description; + std::vector m_options; + std::vector m_positional_args; +}; + +} // namespace Arguments +} // namespace Cam diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1614bf1 --- /dev/null +++ b/Makefile @@ -0,0 +1,66 @@ +BUILD_DIR = bin +SOURCE_DIR = Src +HEADER_DIR = Inc +HEADER_INSTALL_DIR = /usr/local/include +INSTALL_DIR = /usr/local/lib + +LIB_NAME = argParser +LIB_FILE_NAME = lib$(LIB_NAME).a + +OPT = -O2 + +CC = gcc +CXX = g++ + +INCS = \ +-IInc + +C_SOURCES = $(wildcard $(SOURCE_DIR)/*.c) +CXX_SOURCES = $(wildcard $(SOURCE_DIR)/*.cpp) + +OBJECTS = $(addprefix $(BUILD_DIR)/, $(notdir $(C_SOURCES:.c=.o))) +OBJECTS += $(addprefix $(BUILD_DIR)/, $(notdir $(CXX_SOURCES:.cpp=.o))) + +CFLAGS = $(OPT) $(INCS) -Wall +CXXFLAGS = -std=c++17 $(OPT) $(INCS) -Wall + +HEADERS = $(wildcard $(HEADER_DIR)/*.h) + +CP = cp +MKDIR = mkdir -p +ARFLAGS = rvc + +all: $(BUILD_DIR)/$(LIB_FILE_NAME) + +$(BUILD_DIR)/$(LIB_FILE_NAME): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c | $(BUILD_DIR) + $(CC) -c $< $(CFLAGS) -o $@ + +$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.cpp | $(BUILD_DIR) + $(CXX) -c $< $(CXXFLAGS) -o $@ + +$(BUILD_DIR): + $(MKDIR) $@ + +test: all + $(CXX) test.cpp $(CXXFLAGS) -l$(LIB_NAME) -L$(BUILD_DIR) -o $(BUILD_DIR)/test + $(BUILD_DIR)/test + +test_installed: + $(CXX) test.cpp $(CXXFLAGS) -l$(LIB_NAME) -o $(BUILD_DIR)/test + $(BUILD_DIR)/test + +install: + $(CP) $(BUILD_DIR)/$(LIB_FILE_NAME) $(INSTALL_DIR) + $(CP) $(HEADERS) $(HEADER_INSTALL_DIR) + +uninstall: + $(RM) $(INSTALL_DIR)/$(LIB_FILE_NAME) + $(RM) $(addprefix $(HEADER_INSTALL_DIR)/, $(notdir $(HEADERS))) + +clean: + $(RM) -r $(BUILD_DIR) + +.PHONY: test install uninstall clean diff --git a/Src/argparser.cpp b/Src/argparser.cpp new file mode 100644 index 0000000..e9f216b --- /dev/null +++ b/Src/argparser.cpp @@ -0,0 +1,196 @@ +#include "argparser.h" +#include +#include +#include + +int stringToInt(const char* input) +{ + int value = 0; + for (int i = 0; input[i] != 0; i++) + { + if (input[i] < '0' || input[i] > '9') + return -1; + value = (value * 10) + input[i] - '0'; + } + + return value; +} + +namespace Cam +{ +namespace Arguments +{ + +Option::Option(const char* name, const char* description, OPTTYPE type) + : m_name(name), m_short_name(nullptr), m_description(description), m_type(type), m_found(false) {} + +Option::Option(const char* name, const char* short_name, const char* description, OPTTYPE type) + : m_name(name), m_short_name(short_name), m_description(description), m_type(type), m_found(false) {} + +bool Option::found() +{ + return m_found; +} + +PositionalArgument::PositionalArgument(const char* name, bool required, OPTTYPE type /* = STRING */) + : m_name(name), m_type(type), m_req(required) +{ + if (type == FLAG) { + std::cout << "Warning OPTTYPE 'FLAG' is not meant to be used with positional arguments" << std::endl; + std::cout << "Assuming type 'STRING' instead" << std::endl; + m_type = STRING; + } +} + +bool PositionalArgument::found() +{ + return m_found; +} + +Parser::Parser(const char* program_name) + : m_program_name(program_name), m_description(nullptr), m_options() {} + +void Parser::set_description(const char* description) +{ + m_description = description; +} + +void Parser::add_option(Option* opt) +{ + m_options.push_back(opt); +} + +void Parser::add_positional_argument(PositionalArgument* arg) +{ + m_positional_args.push_back(arg); +} + +int Parser::parse(int argc, char ** argv) +{ + uint8_t next_pos_arg = 0; + bool all_pos_args_found = m_positional_args.size() == 0; + for (uint16_t i = 1; i < argc; i++) + { + if (argv[i][0] == '-') { + bool match_found = false; + bool use_long_name = argv[i][1] == '-'; + Option* matched_arg; + const char* option_name; + + for (Option* arg: m_options) + { + if ((use_long_name ? strcmp(argv[i] + 2, arg->m_name) : strcmp(argv[i] + 1, arg->m_short_name)) == 0) { + arg->m_found = true; + match_found = true; + option_name = (use_long_name ? arg->m_name : arg->m_short_name); + matched_arg = arg; + + break; + } + } + + if (!match_found) { + if (use_long_name && strcmp(argv[i] + 2, "help") == 0) { + print_help_message(); + return 1; + } + std::cout << m_program_name << ": invalid option: " << argv[i] << std::endl << std::endl; + std::cout << "See " << m_program_name << " --help" << std::endl; + + return -1; + } else { + if (matched_arg->m_type != FLAG) { + i++; + if (i < argc && argv[i][0] != '-') { + if (matched_arg->m_type == STRING) { + matched_arg->data = argv[i]; + } else if (matched_arg->m_type == INT) { + int* val = new int; + *val = stringToInt(argv[i]); + if (*val == -1) { + std::cout << m_program_name << ": option '" << option_name << "' expects an integer as an argument, got " << argv[i] << " instead" << std::endl; + return -4; + } + matched_arg->data = val; + } else { + std::cout << "You appear to have forgotten to add a handler for this type of argument: " << matched_arg->m_type << std::endl; + } + } else { + std::cout << m_program_name << ": Missing argument for option '" << option_name << '\'' << std::endl; + return -3; + } + } + } + } else if (!all_pos_args_found) { + PositionalArgument* arg = m_positional_args[next_pos_arg]; + if (arg->m_type == INT) { + int* val = new int; + *val = stringToInt(argv[i]); + if (*val == -1) { + std::cout << m_program_name << ": argument '" << arg->m_name << "' expects an integer as an argument, got " << argv[i] << " instead" << std::endl; + return -4; + } + arg->data = val; + } else { + arg->data = argv[i]; + } + arg->m_found = true; + next_pos_arg++; + all_pos_args_found = m_positional_args.size() <= next_pos_arg; + } else { + std::cout << m_program_name << ": invalid option '" << argv[i] << '\'' << std::endl << std::endl; + std::cout << "See " << m_program_name << " --help" << std::endl; + + return -2; + } + } + + if (!all_pos_args_found) { + for (; next_pos_arg < m_positional_args.size(); next_pos_arg++) { + if (m_positional_args[next_pos_arg]->m_req) { + std::cout << m_program_name << ": Missing required positional argument '" << m_positional_args[next_pos_arg]->m_name << "'" << std::endl << std::endl; + std::cout << "See " << m_program_name << " --help" << std::endl; + + return -5; + } + } + } + + return 0; +} + +void Parser::print_help_message() +{ + std::cout << "Usage: " << m_program_name << (m_options.size() > 0 ? " [OPTIONS]" : ""); + for (PositionalArgument* arg: m_positional_args) + { + std::cout << " " << arg->m_name; + } + std::cout << std::endl << std::endl << std::endl; + + + std::cout << "Options:" << std::endl; + for (Option* opt: m_options) + { + std::cout << " -"; + if (opt->m_short_name != nullptr) + std::cout << opt->m_short_name << ", -"; + + std::cout << '-' << opt->m_name; + + if (opt->m_short_name != nullptr) { + for (uint8_t i = 0; i < 20 - strlen(opt->m_name) - strlen(opt->m_short_name); i++) + std::cout << ' '; + } else { + for (uint8_t i = 0; i < 20 - strlen(opt->m_name); i++) + std::cout << ' '; + } + + std::cout << opt->m_description << std::endl; + } + + std::cout << std::endl << std::endl << m_description << std::endl; +} + +} // namespace Arguments +} // namespace Cam diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..a9da054 --- /dev/null +++ b/test.cpp @@ -0,0 +1,48 @@ +#include +#include "argparser.h" + +using namespace Cam; + +int main(int argc, char **argv) { + Arguments::Parser parser = Arguments::Parser("test"); + + parser.set_description("A test of argument parsing :)"); + + Arguments::Option first("first", "f", "First arg", Arguments::STRING); + Arguments::Option second("second", "s", "Second arg", Arguments::INT); + Arguments::Option third("third", "t", "Third arg", Arguments::FLAG); + + Arguments::PositionalArgument name("name", true, Arguments::STRING); + Arguments::PositionalArgument age("age", false, Arguments::INT); + + parser.add_option(&first); + parser.add_option(&second); + parser.add_option(&third); + + parser.add_positional_argument(&name); + parser.add_positional_argument(&age); + + if (parser.parse(argc, argv) != 0) { + return -1; + } + + std::cout << "Found:" << std::endl; + std::cout << "\tFirst: " << first.found() << std::endl; + std::cout << "\tSecond: " << second.found() << std::endl; + std::cout << "\tThird: " << third.found() << std::endl; + std::cout << "\tName: " << name.found() << std::endl; + std::cout << "\tAge: " << age.found() << std::endl; + + if (first.found() || second.found() || name.found() || age.found()) + std::cout << std::endl << "Values:" << std::endl; + if (first.found()) + std::cout << "\tFirst: " << (char*) first.data << std::endl; + if (second.found()) + std::cout << "\tSecond: " << *((int*) second.data) << std::endl; + if (name.found()) + std::cout << "\tName: " << (char*) name.data << std::endl; + if (age.found()) + std::cout << "\tAge: " << *((int*) age.data) << std::endl; + + return 0; +}