From 0badef2ccdf13a7f642069bbc44e220e75115b52 Mon Sep 17 00:00:00 2001 From: josh Date: Thu, 3 Jul 2025 01:18:22 -0400 Subject: [PATCH] Scoping out ArgsParser class. --- CMakeLists.txt | 31 ++++++++++++ include/ArgsParser.hpp | 85 +++++++++++++++++++++++++++++++++ main.cpp | 37 ++++++++++++++ src/ArgsParser.cpp | 106 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/ArgsParser.hpp create mode 100644 main.cpp create mode 100644 src/ArgsParser.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..be1686c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.18...3.28) +project(ArgsParser + VERSION 1.0 + LANGUAGES CXX) + +if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + message(FATAL_ERROR "In-source builds are not allowed!") +endif() + +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +file(GLOB_RECURSE HEADERS "include/*.hpp") + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +include_directories("include") + + +if (UNIX) + add_library(ArgsParser SHARED ${SOURCES}) +endif() + +if (WIN32) + add_library(ArgsParser STATIC ${SOURCES}) +endif() + +add_executable(args_demo main.cpp) + +target_link_libraries(args_demo PUBLIC ArgsParser) \ No newline at end of file diff --git a/include/ArgsParser.hpp b/include/ArgsParser.hpp new file mode 100644 index 0000000..ed622b7 --- /dev/null +++ b/include/ArgsParser.hpp @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include + +struct FlagInfo { + std::string canonical_name; + bool expects_arg; + std::optional arg; +}; + +/// A class that provides a convenient interface for parsing commands. +class ArgsParser { +public: + /// Constructor from traditional argc, argv + ArgsParser(int argc, char** argv, bool keep_program_name = false); + + /// @return True if there is another unconsumed token in the argument list. + bool has_next() const; + + /// Retrieves then next unconsumed token in the argument list. + /// @return The next token in the argument list. + std::string get_next() const; + + std::string consume_next(); + + /// Constructor from an existing vector of strings, + explicit ArgsParser(const std::vector& value): args(value) {} + + bool has_flag(const std::string& option); + + bool has_flag(const std::initializer_list& flags); + + /// Searches the arguments for all given flag tokens, and removes all occurrences of it. + /// @return true if the flag was found in the arguments. + bool consume_flag(const std::string& option); + + + /// Searches the arguments for all given flag tokens, and removes them. + /// @return True if the flag was found in the arguments. + bool consume_flag(const std::initializer_list& flags); + + /// Searches for a specific flag and its argument. + bool has_flag_arg(const std::string& option); + + /// Retrieves the argument for the given flag. + std::optional get_flag_arg(const std::string& option); + + /// Searches for a specific flag and its argument, removes both, and returns the argument. + std::optional consume_flag_arg(const std::string& flag); + + std::tuple consume_flag_arg(const std::initializer_list& flags); + + std::vector get_remaining_args() const; + + std::vector get_unmatched() const { return get_remaining_args(); } + + + + bool contains(const std::string& token) const; + bool has_flag_contains(const std::string& match); + + std::optional get_flag_contains(std::string& match) { + for (auto& token : args) { + if (token.find(match) != std::string::npos) + return token; + } + + return std::nullopt; + } + + bool has_flag_starts_with(const std::string& value) const; + std::string get_flag_starts_with(const std::string& prefix) const; + + + + + +protected: + /// The list of arguments that have yet to be consumed. + std::vector args{}; + /// The full list of arguments originally passed to the instance. This vector will not be modified. + std::vector original_args{}; +private: +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..8c60cd7 --- /dev/null +++ b/main.cpp @@ -0,0 +1,37 @@ +#include +#include + +int main(int argc, char* argv[]) { + + std::vector arguments (argv, argv+argc); + + + for (auto& entry : arguments) { + std::cout << entry << " "; + } + std::cout << std::endl; + + ArgsParser args(argc, argv); + args.consume_next(); // Consume file-name. + + if (args.has_flag("--help")) { + std::cout << "Help information displayed here!" << std::endl; + return 0; + } + + for (auto& entry : args.get_remaining_args()) { + std::cout << entry << " "; + } + std::cout << std::endl; + + while (args.has_next()) { + std::string token = args.consume_next(); + + std::cout << token << " "; + } + + std::cout << std::endl; + + + return 0; +} \ No newline at end of file diff --git a/src/ArgsParser.cpp b/src/ArgsParser.cpp new file mode 100644 index 0000000..cf34bca --- /dev/null +++ b/src/ArgsParser.cpp @@ -0,0 +1,106 @@ +#include +#include + +ArgsParser::ArgsParser(int argc, char **argv, bool keep_program_name) { + args = std::vector(argv, argv+argc); +} + +bool ArgsParser::has_next() const { + return !args.empty(); +} + +std::string ArgsParser::get_next() const { + return args[0]; +} + +std::string ArgsParser::consume_next() { + std::string value = args[0]; + + args.erase(args.begin()); + + return value; +} + +bool ArgsParser::has_flag(const std::string &option) { + return std::ranges::find(args, option) != args.end(); +} + +bool ArgsParser::has_flag(const std::initializer_list &flags) { + for (auto& flag : flags) + if (has_flag(flag)) + return true; + + return false; +} + +bool ArgsParser::consume_flag(const std::string &option) { + if (has_flag(option)) { + args.erase(std::remove(args.begin(), args.end(), option), args.end()); + return true; + } + return false; +} + +bool ArgsParser::consume_flag(const std::initializer_list &flags) { + bool had_any_flag = false; + for (auto& flag : flags) { + if (has_flag(flags)) { + had_any_flag = true; + args.erase(std::remove(args.begin(), args.end(), flag), args.end()); + } + } + return had_any_flag; +} + +bool ArgsParser::has_flag_arg(const std::string &option) { + if (has_flag(option)) { + auto it = std::ranges::find(args, option); + return it!=args.end() && ++it!=args.end(); + } +} + + +std::optional ArgsParser::get_flag_arg(const std::string &option) { + auto it = std::ranges::find(args, option); + ++it; + + return *it; +} + +std::optional ArgsParser::consume_flag_arg(const std::string &flag) { + +} + +std::tuple ArgsParser::consume_flag_arg(const std::initializer_list &flags) { + +} + +std::vector ArgsParser::get_remaining_args() const { + return args; +} + +bool ArgsParser::has_flag_contains(const std::string &match) { + for (auto& token : args) { + if (token.find(match) != std::string::npos) + return true; + } + + return false; +} + +bool ArgsParser::has_flag_starts_with(const std::string &value) const { + for (auto& token : args) { + if (token.starts_with(value)) + return true; + } + + return false; +} + +std::string ArgsParser::get_flag_starts_with(const std::string &prefix) const { + for (auto& token : this->args) { + if (token.starts_with(prefix)) + return token; + } +} +