Add command line flag processing.

This commit is contained in:
Kyle Isom 2023-10-15 17:09:31 -07:00
parent e80f597ec8
commit 4d2abcf434
9 changed files with 356 additions and 88 deletions

View File

@ -346,7 +346,7 @@ uint8_t &
Arena::operator[](size_t index) Arena::operator[](size_t index)
{ {
if (index > this->size) { 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"); throw std::range_error("index out of range");
#else #else
abort(); abort();

View File

@ -330,7 +330,7 @@ uint8_t &
Buffer::operator[](size_t index) Buffer::operator[](size_t index)
{ {
if (index > this->length) { 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"); throw std::range_error("array index out of bounds");
#else #else
abort(); abort();

View File

@ -5,6 +5,7 @@ if (${DOXYGEN_FOUND})
set(DOXYGEN_GENERATE_MAN YES) set(DOXYGEN_GENERATE_MAN YES)
set(DOXYGEN_GENERATE_LATEX YES) set(DOXYGEN_GENERATE_LATEX YES)
#set(DOXYGEN_EXTRACT_ALL YES) #set(DOXYGEN_EXTRACT_ALL YES)
message(STATUS "Doxygen found, building docs.")
doxygen_add_docs(scsl_docs doxygen_add_docs(scsl_docs
${HEADER_FILES} ${SOURCE_FILES} ${HEADER_FILES} ${SOURCE_FILES}

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.22)
project(scsl LANGUAGES CXX project(scsl LANGUAGES CXX
VERSION 0.1.0 VERSION 0.1.1
DESCRIPTION "Shimmering Clarity Standard Library") DESCRIPTION "Shimmering Clarity Standard Library")
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 14)
@ -33,6 +33,7 @@ set(HEADER_FILES scsl.h
Buffer.h Buffer.h
Dictionary.h Dictionary.h
Exceptions.h Exceptions.h
Flag.h
StringUtil.h StringUtil.h
TLV.h TLV.h
Test.h Test.h
@ -45,6 +46,7 @@ set(SOURCE_FILES
Commander.h Commander.h
Dictionary.cc Dictionary.cc
Exceptions.cc Exceptions.cc
Flag.cc
StringUtil.cc StringUtil.cc
TLV.cc TLV.cc
Test.cc Test.cc
@ -66,6 +68,10 @@ target_link_libraries(phonebook scsl)
include(CTest) include(CTest)
enable_testing() 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) add_executable(tlv_test tlvTest.cc)
target_link_libraries(tlv_test scsl) target_link_libraries(tlv_test scsl)
add_test(tlvTest tlv_test) add_test(tlvTest tlv_test)
@ -74,9 +80,9 @@ add_executable(dictionary_test dictionaryTest.cc)
target_link_libraries(dictionary_test scsl) target_link_libraries(dictionary_test scsl)
add_test(dictionaryTest dictionary_test) add_test(dictionaryTest dictionary_test)
add_executable(buffer_test bufferTest.cc) add_executable(flag_test flagTest.cc)
target_link_libraries(buffer_test scsl) target_link_libraries(flag_test scsl)
add_test(bufferTest buffer_test) add_test(flagTest flag_test)
add_executable(stringutil_test stringutil_test.cc) add_executable(stringutil_test stringutil_test.cc)
target_link_libraries(stringutil_test scsl) target_link_libraries(stringutil_test scsl)
@ -94,7 +100,7 @@ add_custom_target(cloc
COMMAND cloc ${SOURCE_FILES} ${HEADER_FILES} COMMAND cloc ${SOURCE_FILES} ${HEADER_FILES}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
add_custom_target(deploy-docs 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 DEPENDS scsl_docs
) )

235
Flag.cc
View File

@ -30,18 +30,45 @@
#include <vector> #include <vector>
#include "Flag.h" #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); std::regex_constants::nosubs);
std::string
Flag::Flag(FlagType fType, std::string fName, std::string fDescription) ParseStatusToString(ParseStatus status)
: Type(fType), WasSet(false), Name(fName), Description(fDescription)
{ {
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; return false;
} }
auto flag = new Flag; auto flag = NewFlag(fType, fName, fDescription);
flag->Type = fType;
flag->WasSet = false;
flag->name = name;
this->desription = fDescription;
this->flags[fName] = flag; this->flags[fName] = flag;
return true; 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 * Flag *
Flags::Lookup(std::string fName) Flags::Lookup(std::string fName)
{ {
@ -85,18 +175,19 @@ Flags::Lookup(std::string fName)
return nullptr; return nullptr;
} }
return this->flags[fName];
} }
OptFlagValue bool
Flags::ValueOf(std::string fName) Flags::ValueOf(std::string fName, FlagValue &value)
{ {
if (this->flags.count(fName)) { 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]); std::string arg(argv[index]);
U::S::TrimWhitespace(arg);
index++; index++;
if (!std::regex_search(arg, isFlag)) { if (!std::regex_search(arg, isFlag)) {
return ParseStatus::EndOfFlags; return ParseStatus::EndOfFlags;
@ -116,15 +209,33 @@ Flags::parseArg(int argc, char **argv, int &index)
} }
auto flag = flags[arg]; auto flag = flags[arg];
if (flag->Type == FlagType::Boolean) { switch (flag->Type) {
case FlagType::Boolean:
flag->WasSet = true; flag->WasSet = true;
flag->Value.b = true; flag->Value.b = true;
return ParseStatus::OK; return ParseStatus::OK;
} case FlagType::Integer:
flag->WasSet = true;
switch (flag->Type) { flag->Value.i = std::stoi(argv[++index], 0, 0);
return ParseStatus::OK;
case FlagType::UnsignedInteger:
flag->WasSet = true;
flag->Value.u = static_cast<unsigned int>(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<size_t>(std::stoull(argv[index++]));
return ParseStatus::OK;
default: 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; return ParseStatus::OK;
@ -148,12 +259,16 @@ Flags::Parse(int argc, char **argv)
index++; index++;
} }
continue; continue;
case ParseStatus::EndOfFlags:
case ParseStatus::NotEnoughArgs: case ParseStatus::NotEnoughArgs:
case ParseStatus::NotRegistered:
// fall through // // fall through //
return result; return result;
default: 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 Flag *
Flags::GetBool(std::string name, bool &flagValue) Flags::checkGetArg(std::string fName, FlagType eType)
{ {
if (this->flags[name] == 0) { if (this->flags[fName] == 0) {
return false; return nullptr;
} }
return std::get<bool>(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

121
Flag.h
View File

@ -26,49 +26,56 @@
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <map> #include <map>
#include <optional> #include <string>
#include <variant> #include <variant>
#include <vector> #include <vector>
namespace klib { namespace scsl {
/// FlagType indicates the value held in a FlagValue.
enum class FlagType : uint8_t { enum class FlagType : uint8_t {
Unknown = 0, Unknown = 0,
Boolean = 1, Boolean = 1,
Integer = 2, Integer = 2, ///< int32_t
UnsignedInteger = 3, UnsignedInteger = 3, ///< uint32_t
String = 4, SizeT = 4, ///< size_t
String = 5,
}; };
enum class ParseStatus : uint8_t { enum class ParseStatus : uint8_t {
OK = 0, Unknown = 0,
EndOfFlags = 1, OK = 1,
NotRegistered = 2, EndOfFlags = 2,
NotEnoughArgs = 3, NotRegistered = 3,
NotEnoughArgs = 4,
}; };
std::string
typedef std::variant< ParseStatusToString(ParseStatus status);
std::string *,
bool *,
int64_t *,
uint64_t *> FlagValue;
struct Flag { typedef union {
Flag(FlagType fType, std::string fName, std::string fDescription); unsigned int u;
int i;
std::size_t size;
std::string *s;
bool b;
} FlagValue;
FlagType Type;
bool WasSet; typedef struct {
std::string Name; FlagType Type;
std::string Description; bool WasSet;
FlagValue Value; std::string Name;
}; std::string Description;
typedef std::optional<FlagValue> OptFlagValue; FlagValue Value;
} Flag;
Flag *
NewFlag(FlagType fType, std::string fName, std::string fDescription);
class Flags { class Flags {
@ -76,36 +83,52 @@ public:
Flags(std::string fName); Flags(std::string fName);
Flags(std::string fName, std::string fDescription); Flags(std::string fName, std::string fDescription);
bool Register(std::string fName, bool Register(std::string fName,
FlagType fType, FlagType fType,
std::string fDescription); std::string fDescription);
Flag *Lookup(std::string fName); bool Register(std::string fName,
OptFlagValue bool defaultValue,
ValueOf(std::string fName); 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(); size_t NumArgs();
std::vector<std::string> Args(); std::vector<std::string> Args();
std::string Arg(int index); std::string Arg(int index);
bool GetBool(std::string name, bool &flagValue); bool GetBool(std::string fName, bool &flagValue);
// bool GetUnsignedInteger(std::string name, uint64_t &flagValue) bool GetUnsignedInteger(std::string fName, unsigned int &flagValue);
// bool GetInteger(std::string name, int64_t &flagValue) bool GetInteger(std::string fName, int &flagValue);
// bool GetString(std::string name, std::string &flagValue) bool GetString(std::string fName, std::string &flagValue);
bool GetSizeT(std::string fName, std::size_t &flagValue);
private: 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 name;
std::string description; std::string description;
std::vector<std::string> args; std::vector<std::string> args;
std::map<std::string, Flag *> flags; std::map<std::string, Flag *> flags;
}; };
} // namespace scsl
} // namespace klib

View File

@ -15,7 +15,7 @@ namespace scsl {
void void
TestAssert(bool condition, std::string message) TestAssert(bool condition, std::string message)
{ {
#if defined(NDEBUG) || defined(SCSL_NO_ASSERT) #if defined(NDEBUG) || defined(SCSL_NOEXCEPT)
if (!condition) { if (!condition) {
throw AssertionFailed(message); throw AssertionFailed(message);
} }
@ -35,7 +35,7 @@ TestAssert(bool condition)
if (condition) { if (condition) {
return; return;
} }
#if defined(SCSL_NO_ASSERT) #if defined(SCSL_NOEXCEPT)
std::cerr << "Assertion failed!\n"; std::cerr << "Assertion failed!\n";
#else #else
std::stringstream msg; std::stringstream msg;

2
Test.h
View File

@ -29,7 +29,7 @@ void TestAssert(bool condition);
/// If NDEBUG is set, TestAssert will throw an exception if condition is false. /// If NDEBUG is set, TestAssert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message. /// 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 /// \throws AssertionFailed
/// ///

59
flagTest.cc Normal file
View File

@ -0,0 +1,59 @@
//
// Created by kyle on 10/15/23.
//
#include <iostream>
#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;
}