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)
{
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();

View File

@ -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();

View File

@ -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}

View File

@ -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
)

235
Flag.cc
View File

@ -30,18 +30,45 @@
#include <vector>
#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<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:
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<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

81
Flag.h
View File

@ -26,49 +26,56 @@
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <string>
#include <variant>
#include <vector>
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,
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;
typedef struct {
FlagType Type;
bool WasSet;
std::string Name;
std::string Description;
FlagValue Value;
};
typedef std::optional<FlagValue> OptFlagValue;
} Flag;
Flag *
NewFlag(FlagType fType, std::string fName, std::string fDescription);
class Flags {
@ -79,11 +86,26 @@ public:
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);
OptFlagValue
ValueOf(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);
@ -91,14 +113,16 @@ public:
std::vector<std::string> 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);
Flag *checkGetArg(std::string fName, FlagType eType);
std::string name;
std::string description;
@ -107,5 +131,4 @@ private:
};
} // namespace klib
} // namespace scsl

View File

@ -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;

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.
/// 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
///

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;
}