From 4d2abcf434b3ad2786607be41e2f72b32f70969c Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sun, 15 Oct 2023 17:09:31 -0700 Subject: [PATCH] Add command line flag processing. --- Arena.cc | 2 +- Buffer.cc | 2 +- CMakeDocs.txt | 1 + CMakeLists.txt | 18 ++-- Flag.cc | 235 +++++++++++++++++++++++++++++++++++++++++++------ Flag.h | 121 ++++++++++++++----------- Test.cc | 4 +- Test.h | 2 +- flagTest.cc | 59 +++++++++++++ 9 files changed, 356 insertions(+), 88 deletions(-) create mode 100644 flagTest.cc diff --git a/Arena.cc b/Arena.cc index 1c4dd6b..823b0f0 100644 --- a/Arena.cc +++ b/Arena.cc @@ -346,7 +346,7 @@ uint8_t & Arena::operator[](size_t index) { if (index > this->size) { -#if defined(SCSL_DESKTOP_BUILD) and !defined(SCSL_NO_ASSERT) +#if defined(SCSL_DESKTOP_BUILD) and !defined(SCSL_NOEXCEPT) throw std::range_error("index out of range"); #else abort(); diff --git a/Buffer.cc b/Buffer.cc index 230f6f2..39e34b9 100644 --- a/Buffer.cc +++ b/Buffer.cc @@ -330,7 +330,7 @@ uint8_t & Buffer::operator[](size_t index) { if (index > this->length) { -#if defined(SCSL_DESKTOP_BUILD) and !defined(SCSL_NO_ASSERT) +#if defined(SCSL_DESKTOP_BUILD) and !defined(SCSL_NOEXCEPT) throw std::range_error("array index out of bounds"); #else abort(); diff --git a/CMakeDocs.txt b/CMakeDocs.txt index 62bc8e4..a49bb53 100644 --- a/CMakeDocs.txt +++ b/CMakeDocs.txt @@ -5,6 +5,7 @@ if (${DOXYGEN_FOUND}) set(DOXYGEN_GENERATE_MAN YES) set(DOXYGEN_GENERATE_LATEX YES) #set(DOXYGEN_EXTRACT_ALL YES) +message(STATUS "Doxygen found, building docs.") doxygen_add_docs(scsl_docs ${HEADER_FILES} ${SOURCE_FILES} diff --git a/CMakeLists.txt b/CMakeLists.txt index da726b0..4bf5d4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.22) project(scsl LANGUAGES CXX - VERSION 0.1.0 + VERSION 0.1.1 DESCRIPTION "Shimmering Clarity Standard Library") set(CMAKE_CXX_STANDARD 14) @@ -33,6 +33,7 @@ set(HEADER_FILES scsl.h Buffer.h Dictionary.h Exceptions.h + Flag.h StringUtil.h TLV.h Test.h @@ -45,6 +46,7 @@ set(SOURCE_FILES Commander.h Dictionary.cc Exceptions.cc + Flag.cc StringUtil.cc TLV.cc Test.cc @@ -66,6 +68,10 @@ target_link_libraries(phonebook scsl) include(CTest) enable_testing() +add_executable(buffer_test bufferTest.cc) +target_link_libraries(buffer_test scsl) +add_test(bufferTest buffer_test) + add_executable(tlv_test tlvTest.cc) target_link_libraries(tlv_test scsl) add_test(tlvTest tlv_test) @@ -74,9 +80,9 @@ add_executable(dictionary_test dictionaryTest.cc) target_link_libraries(dictionary_test scsl) add_test(dictionaryTest dictionary_test) -add_executable(buffer_test bufferTest.cc) -target_link_libraries(buffer_test scsl) -add_test(bufferTest buffer_test) +add_executable(flag_test flagTest.cc) +target_link_libraries(flag_test scsl) +add_test(flagTest flag_test) add_executable(stringutil_test stringutil_test.cc) target_link_libraries(stringutil_test scsl) @@ -94,7 +100,7 @@ add_custom_target(cloc COMMAND cloc ${SOURCE_FILES} ${HEADER_FILES} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(deploy-docs - COMMAND rsync --progress -auvz ${CMAKE_CURRENT_BINARY_DIR}/html/* docs.shimmering-clarity.net:sites/cc/ + COMMAND rsync --delete-after --progress -auvz ${CMAKE_CURRENT_BINARY_DIR}/html/* docs.shimmering-clarity.net:sites/cc/${PROJECT_NAME}/ DEPENDS scsl_docs ) diff --git a/Flag.cc b/Flag.cc index 96c8499..a802f2c 100644 --- a/Flag.cc +++ b/Flag.cc @@ -30,18 +30,45 @@ #include #include "Flag.h" +#include "StringUtil.h" -namespace klib { +namespace scsl { -static const std::regex isFlag("^-[a-zA-Z0-9]+$", +static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$", std::regex_constants::nosubs); - -Flag::Flag(FlagType fType, std::string fName, std::string fDescription) - : Type(fType), WasSet(false), Name(fName), Description(fDescription) +std::string +ParseStatusToString(ParseStatus status) { + switch (status) { + case ParseStatus::OK: + return "OK"; + case ParseStatus::EndOfFlags: + return "end of flags"; + case ParseStatus::NotRegistered: + return "flag not registered"; + case ParseStatus::NotEnoughArgs: + return "not enough args passed to flags"; + default: + return "unknown/unspecified parse error"; + } +} + + +Flag * +NewFlag(FlagType fType, std::string fName, std::string fDescription) +{ + auto flag = new Flag; + + flag->Type = fType; + flag->WasSet = false; + flag->Name = fName; + flag->Description = fDescription; + flag->Value = FlagValue{}; + + return flag; } @@ -68,16 +95,79 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription) return false; } - auto flag = new Flag; - flag->Type = fType; - flag->WasSet = false; - flag->name = name; - this->desription = fDescription; + auto flag = NewFlag(fType, fName, fDescription); this->flags[fName] = flag; return true; } +bool +Flags::Register(std::string fName, bool defaultValue, std::string fDescription) +{ + if (!this->Register(fName, FlagType::Boolean, fDescription)) { + return false; + } + + this->flags[fName]->Value.b = defaultValue; + return true; +} + + +bool +Flags::Register(std::string fName, int defaultValue, std::string fDescription) +{ + if (!this->Register(fName, FlagType::Integer, fDescription)) { + return false; + } + + this->flags[fName]->Value.i = defaultValue; + return true; +} + + +bool +Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescription) +{ + if (!this->Register(fName, FlagType::UnsignedInteger, fDescription)) { + return false; + } + + this->flags[fName]->Value.u = defaultValue; + return true; +} + + +bool +Flags::Register(std::string fName, size_t defaultValue, std::string fDescription) +{ + if (!this->Register(fName, FlagType::SizeT, fDescription)) { + return false; + } + + this->flags[fName]->Value.size = defaultValue; + return true; +} + + +bool +Flags::Register(std::string fName, std::string defaultValue, std::string fDescription) +{ + if (!this->Register(fName, FlagType::String, fDescription)) { + return false; + } + + this->flags[fName]->Value.s = new std::string(defaultValue); + return true; +} + + +size_t +Flags::Size() +{ + return this->flags.size(); +} + + Flag * Flags::Lookup(std::string fName) { @@ -85,18 +175,19 @@ Flags::Lookup(std::string fName) return nullptr; } - + return this->flags[fName]; } -OptFlagValue -Flags::ValueOf(std::string fName) +bool +Flags::ValueOf(std::string fName, FlagValue &value) { if (this->flags.count(fName)) { - return std::nullopt; + return false; } - return OptFlagValue(this->flags[fName]->Value); + value = this->flags[fName]->Value; + return true; } @@ -106,6 +197,8 @@ Flags::parseArg(int argc, char **argv, int &index) { std::string arg(argv[index]); + U::S::TrimWhitespace(arg); + index++; if (!std::regex_search(arg, isFlag)) { return ParseStatus::EndOfFlags; @@ -116,15 +209,33 @@ Flags::parseArg(int argc, char **argv, int &index) } auto flag = flags[arg]; - if (flag->Type == FlagType::Boolean) { + switch (flag->Type) { + case FlagType::Boolean: flag->WasSet = true; flag->Value.b = true; return ParseStatus::OK; - } - - switch (flag->Type) { + case FlagType::Integer: + flag->WasSet = true; + flag->Value.i = std::stoi(argv[++index], 0, 0); + return ParseStatus::OK; + case FlagType::UnsignedInteger: + flag->WasSet = true; + flag->Value.u = static_cast(std::stoi(argv[index++], 0, 0)); + return ParseStatus::OK; + case FlagType::String: + flag->WasSet = true; + flag->Value.s = new std::string(argv[index++]); + return ParseStatus::OK; + case FlagType::SizeT: + flag->WasSet = true; + flag->Value.size = static_cast(std::stoull(argv[index++])); + return ParseStatus::OK; default: - throw std::runtime_error("not handled"); +#if defined(NDEBUG) or defined(SCSL_NOEXCEPT) + return ParseStatus::Unknown; +#else + throw std::runtime_error("unhandled type"); +#endif } return ParseStatus::OK; @@ -148,12 +259,16 @@ Flags::Parse(int argc, char **argv) index++; } continue; - case ParseStatus::EndOfFlags: case ParseStatus::NotEnoughArgs: + case ParseStatus::NotRegistered: // fall through // return result; default: - throw runtime_error("unhandled parse state"); +#if defined(NDEBUG) or defined(SCSL_NOEXCEPT) + return ParseStatus::Unknown; +#else + throw std::runtime_error("unhandled parse state"); +#endif } } @@ -176,16 +291,80 @@ Flags::Args() } -bool -Flags::GetBool(std::string name, bool &flagValue) +Flag * +Flags::checkGetArg(std::string fName, FlagType eType) { - if (this->flags[name] == 0) { - return false; + if (this->flags[fName] == 0) { + return nullptr; } - return std::get(this->flags[name]->Value); + auto flag = this->flags[fName]; + if (flag == nullptr) { + return nullptr; + } + + if (flag->Type != eType) { + return nullptr; + } + + return flag; } -} // namespace klib +bool +Flags::GetBool(std::string fName, bool &flagValue) +{ + auto flag = this->checkGetArg(fName, FlagType::Boolean); + + flagValue = flag->Value.b; + return flag->WasSet; +} + + +bool +Flags::GetInteger(std::string fName, int &flagValue) +{ + auto flag = this->checkGetArg(fName, FlagType::Integer); + + flagValue = flag->Value.i; + return flag->WasSet; +} + + +bool +Flags::GetUnsignedInteger(std::string fName, unsigned int &flagValue) +{ + auto flag = this->checkGetArg(fName, FlagType::UnsignedInteger); + + flagValue = flag->Value.u; + return flag->WasSet; +} + + + +bool +Flags::GetSizeT(std::string fName, std::size_t &flagValue) +{ + auto flag = this->checkGetArg(fName, FlagType::SizeT); + + flagValue = flag->Value.size; + return flag->WasSet; +} + + +bool +Flags::GetString(std::string fName, std::string &flagValue) +{ + auto flag = this->checkGetArg(fName, FlagType::String); + + if (flag->Value.s == nullptr) { + return false; + } + + flagValue = *(flag->Value.s); + return flag->WasSet; +} + + +} // namespace scsl diff --git a/Flag.h b/Flag.h index 78ff599..d009ac1 100644 --- a/Flag.h +++ b/Flag.h @@ -26,49 +26,56 @@ #include #include #include -#include +#include #include #include -namespace klib { - +namespace scsl { +/// FlagType indicates the value held in a FlagValue. enum class FlagType : uint8_t { - Unknown = 0, - Boolean = 1, - Integer = 2, - UnsignedInteger = 3, - String = 4, + Unknown = 0, + Boolean = 1, + Integer = 2, ///< int32_t + UnsignedInteger = 3, ///< uint32_t + SizeT = 4, ///< size_t + String = 5, }; enum class ParseStatus : uint8_t { - OK = 0, - EndOfFlags = 1, - NotRegistered = 2, - NotEnoughArgs = 3, + Unknown = 0, + OK = 1, + EndOfFlags = 2, + NotRegistered = 3, + NotEnoughArgs = 4, }; - -typedef std::variant< - std::string *, - bool *, - int64_t *, - uint64_t *> FlagValue; +std::string +ParseStatusToString(ParseStatus status); -struct Flag { - Flag(FlagType fType, std::string fName, std::string fDescription); +typedef union { + unsigned int u; + int i; + std::size_t size; + std::string *s; + bool b; +} FlagValue; - FlagType Type; - bool WasSet; - std::string Name; - std::string Description; - FlagValue Value; -}; -typedef std::optional OptFlagValue; + +typedef struct { + FlagType Type; + bool WasSet; + std::string Name; + std::string Description; + FlagValue Value; +} Flag; + +Flag * +NewFlag(FlagType fType, std::string fName, std::string fDescription); class Flags { @@ -76,36 +83,52 @@ public: Flags(std::string fName); Flags(std::string fName, std::string fDescription); - bool Register(std::string fName, - FlagType fType, - std::string fDescription); - Flag *Lookup(std::string fName); - OptFlagValue - ValueOf(std::string fName); + bool Register(std::string fName, + FlagType fType, + std::string fDescription); + bool Register(std::string fName, + bool defaultValue, + std::string fDescription); + bool Register(std::string fName, + int defaultValue, + std::string fDescription); + bool Register(std::string fName, + unsigned int defaultValue, + std::string fDescription); + bool Register(std::string fName, + size_t defaultValue, + std::string fDescription); + bool Register(std::string fName, + std::string defaultValue, + std::string fDescription); + size_t Size(); + Flag *Lookup(std::string fName); + bool ValueOf(std::string fName, FlagValue &value); - int Parse(int argc, char **argv); + ParseStatus Parse(int argc, char **argv); - void Usage(std::ostream &os, int exitCode); + void Usage(std::ostream &os, int exitCode); - size_t NumArgs(); - std::vector Args(); - std::string Arg(int index); + size_t NumArgs(); + std::vector Args(); + std::string Arg(int index); - bool GetBool(std::string name, bool &flagValue); - // bool GetUnsignedInteger(std::string name, uint64_t &flagValue) - // bool GetInteger(std::string name, int64_t &flagValue) - // bool GetString(std::string name, std::string &flagValue) + bool GetBool(std::string fName, bool &flagValue); + bool GetUnsignedInteger(std::string fName, unsigned int &flagValue); + bool GetInteger(std::string fName, int &flagValue); + bool GetString(std::string fName, std::string &flagValue); + bool GetSizeT(std::string fName, std::size_t &flagValue); private: - ParseStatus parseArg(int argc, char **argv, int &index); + ParseStatus parseArg(int argc, char **argv, int &index); + Flag *checkGetArg(std::string fName, FlagType eType); - std::string name; - std::string description; - std::vector args; - std::map flags; + std::string name; + std::string description; + std::vector args; + std::map flags; }; - -} // namespace klib +} // namespace scsl diff --git a/Test.cc b/Test.cc index 828513c..15605de 100644 --- a/Test.cc +++ b/Test.cc @@ -15,7 +15,7 @@ namespace scsl { void TestAssert(bool condition, std::string message) { -#if defined(NDEBUG) || defined(SCSL_NO_ASSERT) +#if defined(NDEBUG) || defined(SCSL_NOEXCEPT) if (!condition) { throw AssertionFailed(message); } @@ -35,7 +35,7 @@ TestAssert(bool condition) if (condition) { return; } -#if defined(SCSL_NO_ASSERT) +#if defined(SCSL_NOEXCEPT) std::cerr << "Assertion failed!\n"; #else std::stringstream msg; diff --git a/Test.h b/Test.h index d96388a..12f2c5b 100644 --- a/Test.h +++ b/Test.h @@ -29,7 +29,7 @@ void TestAssert(bool condition); /// If NDEBUG is set, TestAssert will throw an exception if condition is false. /// Otherwise, it calls assert after printing the message. /// -/// In addition to NDEBUG, SCSL_NO_ASSERT will suppress assertions. +/// In addition to NDEBUG, SCSL_NOEXCEPT will suppress assertions. /// /// \throws AssertionFailed /// diff --git a/flagTest.cc b/flagTest.cc new file mode 100644 index 0000000..77002a4 --- /dev/null +++ b/flagTest.cc @@ -0,0 +1,59 @@ +// +// Created by kyle on 10/15/23. +// + + +#include +#include "Flag.h" +#include "Test.h" + + +using namespace scsl; + + +int +main(int argc, char *argv[]) +{ + bool testFlag = false; + size_t testSize = 0; + unsigned int testUnsigned = 0; + int testInteger = 0; + std::string testString; + + auto flags = new Flags("flag_test", "this is a test of the flag functionality."); + flags->Register("-b", FlagType::Boolean, "test boolean"); + flags->Register("-s", FlagType::String, "test string"); + flags->Register("-u", (unsigned int)42, "test unsigned integer"); + flags->Register("-i", -42, "test integer"); + flags->Register("-size", FlagType::SizeT, "test size_t"); + TestAssert(flags->Size() == 5, "flags weren't registered"); + + auto status = flags->Parse(argc, argv); + + if (status != ParseStatus::OK) { + std::cerr << "failed to parse flags: " << ParseStatusToString(status) << "\n"; + exit(1); + } + + auto wasSet = flags->GetBool("-b", testFlag); + std::cout << " (bool) test flag was set: " << wasSet << "\n"; + std::cout << " (bool) test flag value: " << testFlag << "\n"; + + wasSet = flags->GetInteger("-i", testInteger); + std::cout << " (int) test flag was set: " << wasSet << "\n"; + std::cout << " (int) test flag value: " << testInteger << "\n"; + + wasSet = flags->GetUnsignedInteger("-u", testUnsigned); + std::cout << " (uint) test flag was set: " << wasSet << "\n"; + std::cout << " (uint) test flag value: " << testUnsigned << "\n"; + + wasSet = flags->GetSizeT("-size", testSize); + std::cout << "(size_t) test flag was set: " << wasSet << "\n"; + std::cout << "(size_t) test flag value: " << testSize << "\n"; + + wasSet = flags->GetString("-s", testString); + std::cout << "(string) test flag was set: " << wasSet << "\n"; + std::cout << "(string) test flag value: " << testString << "\n"; + + return 0; +} \ No newline at end of file