From 407ee1c85d0d802269841a790dbf6d130e31be34 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 16 Oct 2023 02:18:09 -0700 Subject: [PATCH] Cleanup code and docs; add missing header. - The source and header files should have standard comment headers. - Windows support has been removed, as I've decomissioned my Windows desktop in favour of a Linux desktop. - Commander.h wasn't being added to the install directory. --- .idea/klib.iml | 8 +- .idea/misc.xml | 3 + Arena.cc | 30 ++- Arena.h | 26 ++- Buffer.cc | 18 +- Buffer.h | 17 +- CMakeLists.txt | 57 +++-- Commander.cc | 22 +- Commander.h | 22 +- Dictionary.cc | 25 ++- Dictionary.h | 25 ++- Exceptions.cc | 24 ++- Exceptions.h | 26 ++- Flag.cc | 146 +++++++++---- Flag.h | 281 +++++++++++++++++++++---- LICENSE | 13 ++ Makefile | 75 +++++-- README.rst | 27 +++ StringUtil.cc | 124 ++++++++--- StringUtil.h | 42 +++- TLV.cc | 36 ++++ TLV.h | 3 +- Test.cc | 25 ++- Test.h | 19 +- WinHelpers.cc | 142 ------------- WinHelpers.h | 39 ---- CMakeDocs.txt => cmake/docs.cmake | 2 +- CMakePack.txt => cmake/packaging.cmake | 4 +- flagTest.cc | 10 +- phonebook.cc | 25 ++- scsl.h | 22 +- stringutil_test.cc | 51 ++++- tlvTest.cc | 3 + 33 files changed, 988 insertions(+), 404 deletions(-) create mode 100644 LICENSE create mode 100644 README.rst delete mode 100644 WinHelpers.cc delete mode 100644 WinHelpers.h rename CMakeDocs.txt => cmake/docs.cmake (93%) rename CMakePack.txt => cmake/packaging.cmake (89%) diff --git a/.idea/klib.iml b/.idea/klib.iml index f08604b..d3f1e81 100644 --- a/.idea/klib.iml +++ b/.idea/klib.iml @@ -1,2 +1,8 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 79b3c94..ecaf8cc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ + + \ No newline at end of file diff --git a/Arena.cc b/Arena.cc index 823b0f0..e9f1c56 100644 --- a/Arena.cc +++ b/Arena.cc @@ -1,19 +1,37 @@ -#include +/// +/// \file Arena.cc +/// \author K. Isom +/// \date 2023-10-06 +/// \brief Memory management using an arena. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// + #include #include #include + #if defined(__posix__) || defined(__linux__) || defined(__APPLE__) #include -#include #include #include #include #define PROT_RW (PROT_WRITE|PROT_READ) -#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) -#include "WinHelpers.h" -#pragma comment(lib, "advapi32.lib") #endif #include @@ -246,7 +264,7 @@ Arena::Destroy() case ArenaType::Static: break; case ArenaType::Alloc: - delete this->store; + delete[] this->store; break; #if defined(__posix__) || defined(__linux__) || defined(__APPLE__) case ArenaType::MemoryMapped: diff --git a/Arena.h b/Arena.h index 97b16c9..f57858d 100644 --- a/Arena.h +++ b/Arena.h @@ -1,11 +1,26 @@ /// /// \file Arena.h -/// \author K. Isom +/// \author K. Isom /// \date 2023-10-06 /// \brief Memory management using an arena. /// /// Arena defines a memory management backend for pre-allocating memory. /// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// /// \section PLATFORM SUPPORT /// /// Arena will build on the major platforms, but memory-mapped files are only @@ -17,10 +32,10 @@ #define KIMODEM_ARENA_H -#include -#include #include #include +#include +#include #include "Exceptions.h" @@ -112,12 +127,7 @@ public: /// \param path The path to the file that should be created. /// \param fileSize The size of the file to create. /// \return Returns 0 on success and -1 on error. -#if defined(__posix__) || defined(__linux__) || defined(__APPLE__) int Create(const char *path, size_t fileSize); -#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) - int Create(const char *path, size_t fileSize); - -#endif /// Open reads a file into the arena; the file must already exist. On /// Unix-based platforms, the arena will be backed by a memory via diff --git a/Buffer.cc b/Buffer.cc index 39e34b9..e062351 100644 --- a/Buffer.cc +++ b/Buffer.cc @@ -2,14 +2,28 @@ /// \file Buffer.cc /// \author K. Isom /// \date 2023-10-09 +/// \brief Buffer implements basic line buffers. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. /// - #include #include #include #include -#include #include "Buffer.h" diff --git a/Buffer.h b/Buffer.h index ba3c3b4..78b4034 100644 --- a/Buffer.h +++ b/Buffer.h @@ -8,12 +8,27 @@ /// editing. It allocates memory in powers of two, and will grow or shrink /// as needed. /// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// #ifndef KGE_BUFFER_H #define KGE_BUFFER_H -#include #include +#include namespace scsl { diff --git a/CMakeLists.txt b/CMakeLists.txt index 99241bc..a7282b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,64 +1,61 @@ cmake_minimum_required(VERSION 3.22) project(scsl LANGUAGES CXX - VERSION 0.1.1 + VERSION 0.2.2 DESCRIPTION "Shimmering Clarity Standard Library") set(CMAKE_CXX_STANDARD 14) set(CMAKE_VERBOSE_MAKEFILES TRUE) set(VERBOSE YES) -if (MSVC) - add_compile_options("/W4" "$<$:/O2>") +# compile options: +# -Wall Default to all errors. +# -Wextra And a few extra. +# -Werror And require them to be fixed to build. +# -Wno-unused-function This is a library. Not every function is used here. +# -Wno-unused-parameter Some functions have parameters defined for compatibility, +# and aren't used in the implementation. +add_compile_options( + "-static" + "-Wall" + "-Wextra" + "-Werror" + "-Wno-unused-function" + "-Wno-unused-parameter" + "-g" + "$<$:-O2>" +) +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_compile_options("-stdlib=libc++") else () - # compile options: - # -Wall Default to all errors. - # -Wextra And a few extra. - # -Werror And require them to be fixed to build. - # -Wno-unused-function This is a library. Not every function is used here. - # -Wno-unused-parameter Some functions have parameters defined for compatibility, - # and aren't used in the implementation. - - add_compile_options( - "-static" - "-Wall" - "-Wextra" - "-Werror" - "-Wno-unused-function" - "-Wno-unused-parameter" - "$<$:-O2>" - ) - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - add_compile_options("-stdlib=libc++") - else () - # nothing special for gcc at the moment - endif () + # nothing special for gcc at the moment endif () + add_compile_definitions(SCSL_DESKTOP_BUILD) add_compile_definitions(SCSL_VERSION=${PROJECT_VERSION}) set(HEADER_FILES scsl.h Arena.h Buffer.h + Commander.h Dictionary.h Exceptions.h Flag.h StringUtil.h TLV.h Test.h - WinHelpers.h) +) set(SOURCE_FILES Arena.cc Buffer.cc Commander.cc - Commander.h Dictionary.cc Exceptions.cc Flag.cc StringUtil.cc TLV.cc Test.cc - WinHelpers.cc) +) if (APPLE) add_library(scsl @@ -119,6 +116,6 @@ install(FILES ${HEADER_FILES} DESTINATION include/scsl) install(FILES scslConfig.cmake DESTINATION share/scsl/cmake) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/scsl.pc DESTINATION lib/pkgconfig) -include(CMakePack.txt) -include(CMakeDocs.txt) +include(cmake/packaging.cmake) +include(cmake/docs.cmake) endif() diff --git a/Commander.cc b/Commander.cc index dce29d9..cea9020 100644 --- a/Commander.cc +++ b/Commander.cc @@ -1,12 +1,30 @@ /// /// \file Commander.cc -/// \author kyle +/// \author K. Isom /// \date 2023-10-10 +/// \brief Subprogram tooling. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. /// #include + #include "Commander.h" + namespace scsl { @@ -57,4 +75,4 @@ Commander::Run(std::string command, int argc, char **argv) } -} // scsl \ No newline at end of file +} // namespace scsl \ No newline at end of file diff --git a/Commander.h b/Commander.h index 4bfa7c0..280c3ad 100644 --- a/Commander.h +++ b/Commander.h @@ -12,9 +12,24 @@ /// $ some_tool subcommand args... /// ``` /// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// -#include #include +#include #include #include @@ -28,7 +43,7 @@ namespace scsl { /// CommanderFunc describes a function that can be run in Commander. /// /// It expects an argument count and a list of arguments. -typedef std::function CommanderFunc; +using CommanderFunc = std::function; /// Subcommands are the individual commands for the program. A Subcommand @@ -104,7 +119,8 @@ private: std::map cmap; }; -} // scsl + +} // namespace scsl #endif //SCSL_COMMANDER_H diff --git a/Dictionary.cc b/Dictionary.cc index 38f6d68..e491386 100644 --- a/Dictionary.cc +++ b/Dictionary.cc @@ -1,5 +1,28 @@ -#include +/// +/// \file Dictionary.cc +/// \author K.Isom +/// \date 2023-10-05 +/// \brief Key-value store built on top of Arena and TLV. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// + #include +#include + #include "Dictionary.h" #if defined(SCSL_DESKTOP_BUILD) diff --git a/Dictionary.h b/Dictionary.h index d259856..c28499c 100644 --- a/Dictionary.h +++ b/Dictionary.h @@ -1,7 +1,24 @@ /// -/// \file scsl.h -/// \author kyle -/// \date 2023-10-06 +/// \file Dictionary.h +/// \author kyle (kyle@imap.cc) +/// \date 2023-10-12 +/// \brief Key-value store built on top of Arena and TLV. +/// +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. /// @@ -9,6 +26,8 @@ #define SCSL_DICTIONARY_H +#include + #include "Arena.h" #include "TLV.h" diff --git a/Exceptions.cc b/Exceptions.cc index 3903b89..baf2d68 100644 --- a/Exceptions.cc +++ b/Exceptions.cc @@ -1,6 +1,24 @@ -// -// Created by kyle on 2023-10-10. -// +/// +/// \file Exceptions.cc +/// \author K. Isom +/// \date 2023-10-10 +/// \brief Custom exceptions used in writing test programs. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// #include "Exceptions.h" diff --git a/Exceptions.h b/Exceptions.h index e4c9aaa..c4bab16 100644 --- a/Exceptions.h +++ b/Exceptions.h @@ -1,7 +1,24 @@ -// -// Created by kyle on 2023-10-10. -// - +/// +/// \file Exceptions.h +/// \author K. Isom +/// \date 2023-10-10 +/// \brief Custom exceptions for use in SCSL used in writing test programs. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// #ifndef SCSL_EXCEPTIONS_H #define SCSL_EXCEPTIONS_H @@ -9,6 +26,7 @@ #include #include + namespace scsl { diff --git a/Flag.cc b/Flag.cc index a802f2c..e4cc56f 100644 --- a/Flag.cc +++ b/Flag.cc @@ -1,11 +1,9 @@ /// -/// \file Flag.h -/// \author kyle -/// \created 2023-10-12 +/// \file Flag.cc +/// \author K. Isom +/// \date 2023-10-12 /// \brief Flag defines a command-line flag parser. /// -/// \section COPYRIGHT -/// /// Copyright 2023 K. Isom /// /// Permission to use, copy, modify, and/or distribute this software for @@ -23,9 +21,7 @@ /// -#include -#include -#include +#include #include #include @@ -36,11 +32,11 @@ namespace scsl { -static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$", +static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$", std::regex_constants::nosubs); std::string -ParseStatusToString(ParseStatus status) +Flags::ParseStatusToString(ParseStatus status) { switch (status) { case ParseStatus::OK: @@ -58,22 +54,22 @@ ParseStatusToString(ParseStatus status) Flag * -NewFlag(FlagType fType, std::string fName, std::string fDescription) +NewFlag(std::string fName, FlagType fType, std::string fDescription) { auto flag = new Flag; - flag->Type = fType; - flag->WasSet = false; - flag->Name = fName; + flag->Type = fType; + flag->WasSet = false; + flag->Name = fName; flag->Description = fDescription; - flag->Value = FlagValue{}; + flag->Value = FlagValue{}; return flag; } Flags::Flags(std::string fName) - : name(fName), description("") + : name(fName), description("") { } @@ -84,10 +80,21 @@ Flags::Flags(std::string fName, std::string fDescription) } +Flags::~Flags() +{ + for (auto flag : this->flags) { + if (flag.second->Type == FlagType::String) { + delete flag.second->Value.s; + } + delete flag.second; + } +} + + bool Flags::Register(std::string fName, FlagType fType, std::string fDescription) { - if (!std::regex_search(fName, isFlag)) { + if (!std::regex_search(fName, isFlag) || fName == "-h") { return false; } @@ -95,7 +102,7 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription) return false; } - auto flag = NewFlag(fType, fName, fDescription); + auto flag = NewFlag(fName, fType, fDescription); this->flags[fName] = flag; return true; } @@ -191,12 +198,10 @@ Flags::ValueOf(std::string fName, FlagValue &value) } - -ParseStatus +Flags::ParseStatus Flags::parseArg(int argc, char **argv, int &index) { - std::string arg(argv[index]); - + std::string arg(argv[index]); U::S::TrimWhitespace(arg); index++; @@ -205,29 +210,36 @@ Flags::parseArg(int argc, char **argv, int &index) } if (this->flags.count(arg) == 0) { + if (arg == "-h" || arg == "--help") { + Usage(std::cout, 0); + } return ParseStatus::NotRegistered; } auto flag = flags[arg]; + if ((flag->Type != FlagType::Boolean) && index == argc) { + return ParseStatus::NotEnoughArgs; + } + switch (flag->Type) { case FlagType::Boolean: - flag->WasSet = true; + flag->WasSet = true; flag->Value.b = true; return ParseStatus::OK; case FlagType::Integer: - flag->WasSet = true; + flag->WasSet = true; flag->Value.i = std::stoi(argv[++index], 0, 0); return ParseStatus::OK; case FlagType::UnsignedInteger: - flag->WasSet = true; + flag->WasSet = true; flag->Value.u = static_cast(std::stoi(argv[index++], 0, 0)); return ParseStatus::OK; case FlagType::String: - flag->WasSet = true; + flag->WasSet = true; flag->Value.s = new std::string(argv[index++]); return ParseStatus::OK; case FlagType::SizeT: - flag->WasSet = true; + flag->WasSet = true; flag->Value.size = static_cast(std::stoull(argv[index++])); return ParseStatus::OK; default: @@ -242,10 +254,13 @@ Flags::parseArg(int argc, char **argv, int &index) } -ParseStatus -Flags::Parse(int argc, char **argv) +Flags::ParseStatus +Flags::Parse(int argc, char **argv, bool skipFirst) { - int index = 1; + int index = 0; + if (skipFirst) { + index = 1; + } while (index != argc) { auto result = this->parseArg(argc, argv, index); @@ -277,6 +292,55 @@ Flags::Parse(int argc, char **argv) } +void +Flags::Usage(std::ostream &os, int exitCode) +{ + os << this->name << ":\t"; + auto indent = this->name.size() + 7; + + U::S::WriteTabIndented(os, description, 72 - indent, indent / 8, false); + os << "\n\n"; + + for (const auto &pair : this->flags) { + auto argLine = "\t" + pair.first; + switch (pair.second->Type) { + case FlagType::Boolean: + argLine += "\t\t"; + break; + case FlagType::Integer: + argLine += "int\t\t"; + break; + case FlagType::UnsignedInteger: + argLine += "uint\t\t"; + break; + case FlagType::SizeT: + argLine += "size_t\t"; + break; + case FlagType::String: + argLine += "string\t"; + break; + case FlagType::Unknown: + // fallthrough + default: +#ifdef SCSL_NOEXCEPT + abort(); +#else + throw std::runtime_error("unhandled parsing error - this is a bug"); +#endif + break; + } + + os << argLine; + indent = argLine.size(); + U::S::WriteTabIndented(os, pair.second->Description, + 72-indent, (indent/8)+2, false); + } + + os << "\n"; + exit(exitCode); +} + + size_t Flags::NumArgs() { @@ -291,6 +355,17 @@ Flags::Args() } +std::string +Flags::Arg(size_t i) +{ + if (i >= this->args.size()) { + throw std::out_of_range("index is out of range"); + } + + return this->args[i]; +} + + Flag * Flags::checkGetArg(std::string fName, FlagType eType) { @@ -314,7 +389,7 @@ Flags::checkGetArg(std::string fName, FlagType eType) bool Flags::GetBool(std::string fName, bool &flagValue) { - auto flag = this->checkGetArg(fName, FlagType::Boolean); + auto flag = this->checkGetArg(fName, FlagType::Boolean); flagValue = flag->Value.b; return flag->WasSet; @@ -324,7 +399,7 @@ Flags::GetBool(std::string fName, bool &flagValue) bool Flags::GetInteger(std::string fName, int &flagValue) { - auto flag = this->checkGetArg(fName, FlagType::Integer); + auto flag = this->checkGetArg(fName, FlagType::Integer); flagValue = flag->Value.i; return flag->WasSet; @@ -334,18 +409,17 @@ Flags::GetInteger(std::string fName, int &flagValue) bool Flags::GetUnsignedInteger(std::string fName, unsigned int &flagValue) { - auto flag = this->checkGetArg(fName, FlagType::UnsignedInteger); + 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); + auto flag = this->checkGetArg(fName, FlagType::SizeT); flagValue = flag->Value.size; return flag->WasSet; @@ -355,7 +429,7 @@ Flags::GetSizeT(std::string fName, std::size_t &flagValue) bool Flags::GetString(std::string fName, std::string &flagValue) { - auto flag = this->checkGetArg(fName, FlagType::String); + auto flag = this->checkGetArg(fName, FlagType::String); if (flag->Value.s == nullptr) { return false; diff --git a/Flag.h b/Flag.h index d009ac1..cd50903 100644 --- a/Flag.h +++ b/Flag.h @@ -1,11 +1,9 @@ /// /// \file Flag.h -/// \author kyle -/// \created 2023-10-12 +/// \author K. Isom +/// \date 2023-10-12 /// \brief Flag declares a command-line flag parser. /// -/// \section COPYRIGHT -/// /// Copyright 2023 K. Isom /// /// Permission to use, copy, modify, and/or distribute this software for @@ -35,90 +33,297 @@ namespace scsl { /// FlagType indicates the value held in a FlagValue. +/// +/// \todo When C++17 support is more common, switch to `std::variant`. enum class FlagType : uint8_t { - Unknown = 0, - Boolean = 1, + Unknown = 0, ///< Unsupported value type. + Boolean = 1, ///< bool Integer = 2, ///< int32_t UnsignedInteger = 3, ///< uint32_t SizeT = 4, ///< size_t - String = 5, + String = 5, ///< std::string }; -enum class ParseStatus : uint8_t { - Unknown = 0, - OK = 1, - EndOfFlags = 2, - NotRegistered = 3, - NotEnoughArgs = 4, -}; - -std::string -ParseStatusToString(ParseStatus status); - - +/// FlagValue holds the value of a command line flag. typedef union { - unsigned int u; - int i; - std::size_t size; - std::string *s; - bool b; + unsigned int u; ///< FlagType::UnsignedInteger + int i; ///< FlagType::Integer + std::size_t size; ///< FlagType::SizeT + std::string *s; ///< FlagType::String + bool b; ///< FlagType::Boolean } FlagValue; +/// Flag describes an individual command-line flag. typedef struct { - FlagType Type; - bool WasSet; - std::string Name; - std::string Description; - FlagValue Value; + FlagType Type; ///< The type of the value in the flag. + bool WasSet; ///< The flag was set on the command-line. + std::string Name; ///< The name of the flag. + std::string Description; ///< A description of the flag. + FlagValue Value; ///< The flag's value. } Flag; -Flag * -NewFlag(FlagType fType, std::string fName, std::string fDescription); - +/// NewFlag is a helper function for constructing a new flag. +/// +/// \param fName The name of the flag. +/// \param fType The type of the flag. +/// \param fDescription A description of the flag. +/// \return A pointer to a flag. +Flag *NewFlag(std::string fName, FlagType fType, std::string fDescription); +/// Flags provides a basic facility for processing command line flags. +/// +/// Any remaining arguments after the args are added to the parser as +/// arguments that can be accessed with NumArgs, Args, and Arg. +/// +/// \note The parser automatically handles the -h and --help flags by +/// calling Usage(std::cout, 0). The user can override this flag +/// and handle providing help on their own. +/// +/// \details +/// +/// Arguments are named with their leading dash, e.g. "-f". For example, +/// +/// ```c++ +/// flags.Register("-f", FlagType::String, "Path to a configuration file."); +/// ``` +/// \section Usage +/// +/// A short example program is below: +/// +/// int +/// main(int argc, char *argv[]) +/// { +/// std::string server = "service.example.com"; +/// unsigned int port = 1234; +/// +/// auto flags = new scsl::Flags("example-client", +/// "This interacts with the example.com service."); +/// flags->Register("-p", port, "server port"); +/// flags->Register("-s", server, "hostname to connect to"); +/// +/// auto status = flags->Parse(argc, argv); +/// if (status != ParseStatus::OK) { +/// std::cerr << "failed to parse flags: " +/// << scsl::Flags::ParseStatusToString(status) +/// << "\n"; +/// exit(1); +/// } +/// +/// auto wasSet = flags->GetString("-s", server); +/// if (wasSet) { +/// std::cout << "hostname override: " << server << "\n"; +/// } +/// +/// wasSet = flags->GetUnsignedInteger("-p", port); +/// if (wasSet) { +/// std::cout << "port override: " << port << "\n"; +/// } +/// +/// std::cout << "connecting to " << server << ":" << port << "\n"; +/// for (size_t i = 0; i < flags.NumArgs(); i++) { +/// std::cout << "\tExecuting command " << flags.Arg(i) << "\n"; +/// } +/// return 0; +/// } +/// class Flags { public: + /// ParseStatus describes the result of parsing command-line + /// arguments. + enum class ParseStatus : uint8_t { + /// An unknown parsing error occurred. This is a bug, + /// and users should never see this. + Unknown = 0, + + /// Parsing succeeded. + OK = 1, + + /// This is an internal status marking the end of + /// command line flags. + EndOfFlags = 2, + + /// The command line flag provided isn't registered. + NotRegistered = 3, + + /// Not enough arguments were provided to a flag that + /// takes an argument. For example, if "-p" expects + /// a number, and the program was called with just + /// `./program -p`, this error will be reported. + NotEnoughArgs = 4, + }; + + /// ParseStatusToString returns a string message describing the + /// result of parsing command line args. + static std::string ParseStatusToString(ParseStatus status); + + /// Create a new flags parser for the named program. + /// + /// \param fName The program name, Flags(std::string fName); + + /// Create a new flags parser for the named program. + /// + /// \param fName The program name. + /// \param fDescription A short description of the program. Flags(std::string fName, std::string fDescription); + ~Flags(); + + /// Register a new command line flag. + /// + /// \param fName The name of the flag, including a leading dash. + /// \param fType The type of the argument to parse. + /// \param fDescription A description of the flag. + /// \return True if the flag was registered, false if the flag could + /// not be registered (e.g. a duplicate flag was registered). bool Register(std::string fName, FlagType fType, std::string fDescription); + + /// Register a new boolean command line flag with a default value. + /// + /// \note For booleans, it only makes sense to pass a false default + /// value, as there is no way to set a boolean flag to false. + /// This form is provided for compatibility with the other + /// variants on this method. + /// + /// \param fName The name of the flag, including a leading dash. + /// \param defaultValue The default value for the flag. + /// \param fDescription A short description of the flag. + /// \return True if the flag was registered, false if the flag could + /// not be registered (e.g. a duplicate flag was registered). bool Register(std::string fName, bool defaultValue, std::string fDescription); + + /// Register a new integer command line flag with a default value. + /// + /// \param fName The name of the flag, including a leading dash. + /// \param defaultValue The default value for the flag. + /// \param fDescription A short description of the flag. + /// \return True if the flag was registered, false if the flag could + /// not be registered (e.g. a duplicate flag was registered). bool Register(std::string fName, int defaultValue, std::string fDescription); + + /// Register a new unsigned integer command line flag with a default + /// value. + /// + /// \param fName The name of the flag, including a leading dash. + /// \param defaultValue The default value for the flag. + /// \param fDescription A short description of the flag. + /// \return True if the flag was registered, false if the flag could + /// not be registered (e.g. a duplicate flag was registered). bool Register(std::string fName, unsigned int defaultValue, std::string fDescription); + + /// Register a new size_t command line flag with a default value. + /// + /// \param fName The name of the flag, including a leading dash. + /// \param defaultValue The default value for the flag. + /// \param fDescription A short description of the flag. + /// \return True if the flag was registered, false if the flag could + /// not be registered (e.g. a duplicate flag was registered). bool Register(std::string fName, size_t defaultValue, std::string fDescription); + + /// Register a new string command line flag with a default value. + /// + /// \param fName The name of the flag, including a leading dash. + /// \param defaultValue The default value for the flag. + /// \param fDescription A short description of the flag. + /// \return True if the flag was registered, false if the flag could + /// not be registered (e.g. a duplicate flag was registered). bool Register(std::string fName, std::string defaultValue, std::string fDescription); + + /// Return the number of registered flags. size_t Size(); + + /// Lookup a flag. + /// + /// \param fName The flag name. + /// \return A pointer to flag if registered, or nullptr if the flag + /// wasn't registered. Flag *Lookup(std::string fName); + + /// ValueOf returns the value of the flag in the bool ValueOf(std::string fName, FlagValue &value); - ParseStatus Parse(int argc, char **argv); + /// Process a list of arguments into flags. + /// + /// \param argc The number of arguments. + /// \param argv The arguments passed to the program. + /// \param skipFirst Flags expects to receive arguments directly + /// from those passed to `main`, and defaults to skipping + /// the first argument. Set to false if this is not the + /// case. + /// \return + ParseStatus Parse(int argc, char **argv, bool skipFirst=true); + /// Print a usage message to the output stream. void Usage(std::ostream &os, int exitCode); + /// Return the number of arguments processed. These are any + /// remaining elements in argv that are not flags. size_t NumArgs(); + + /// Return the arguments as a vector. std::vector Args(); - std::string Arg(int index); + /// Return a particular argument. + /// + /// \param index The argument number to extract. + /// \return The argument at index i. If the index is greater than + /// the number of arguments present, an out_of_range + /// exception is thrown. + std::string Arg(size_t index); + + /// Retrieve the state of a boolean flag. + /// + /// \param fName The flag to look up. + /// \param flagValue The value to store. + /// \return True if the value was set, or false if the value isn't + /// a boolean or if it wasn't set. 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); + /// Retrieve the value of an unsigned integer flag. + /// + /// \param fName The flag to look up. + /// \param flagValue The value to store. + /// \return True if the value was set, or false if the value isn't + /// an unsigned integer or if it wasn't set. + bool GetUnsignedInteger(std::string fName, unsigned int &flagValue); + + /// Retrieve the value of an integer flag. + /// + /// \param fName The flag to look up. + /// \param flagValue The value to store. + /// \return True if the value was set, or false if the value isn't + /// an integer or if it wasn't set. + bool GetInteger(std::string fName, int &flagValue); + + /// Retrieve the value of a string flag. + /// + /// \param fName The flag to look up. + /// \param flagValue The value to store. + /// \return True if the value was set, or false if the value isn't + /// a string or if it wasn't set. + bool GetString(std::string fName, std::string &flagValue); + + /// Retrieve the value of a size_t flag. + /// + /// \param fName The flag to look up. + /// \param flagValue The value to store. + /// \return True if the value was set, or false if the value isn't + /// a size_t or if it wasn't set. + bool GetSizeT(std::string fName, std::size_t &flagValue); private: ParseStatus parseArg(int argc, char **argv, int &index); diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..56fb9c4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2023 K. Isom + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile index 8852cd0..d152258 100644 --- a/Makefile +++ b/Makefile @@ -1,39 +1,59 @@ -TARGET := klib.a -TESTS := tlv_test dictionary_test -HEADERS := $(wildcard *.h) -SOURCES := $(wildcard *.cc) -OBJS := Arena.o Dictionary.o TLV.o +HEADERS := scsl.h \ + Arena.h \ + Buffer.h \ + Commander.h \ + Dictionary.h \ + Exceptions.h \ + Flag.h \ + StringUtil.h \ + Test.h \ + TLV.h \ + WinHelpers.h +SOURCES := Arena.cc \ + Buffer.cc \ + Commander.cc \ + Dictionary.cc \ + Exceptions.cc \ + Flag.cc \ + StringUtil.cc \ + Test.cc \ + TLV.cc \ + WinHelpers.cc + +BUILD := DEBUG +OBJS := $(patsubst %.cc,%.o,$(SOURCES)) +LIBS := libscsl.a + +TARGETS := $(LIBS) phonebook +TESTS := bufferTest dictionaryTest flagTest tlvTest CXX := clang++ -CXXFLAGS := -g -std=c++14 -Werror -Wall -DSCSL_DESKTOP_BUILD +CXXFLAGS := -std=c++14 -Werror -Wall -Wextra -DSCSL_DESKTOP_BUILD \ + -DSCSL_BUILD_TYPE=${BUILD} +ifeq ($(BUILD),DEBUG) +CXXFLAGS += -g -fsanitize=address +else +CXXFLAGS += -O2 +endif .PHONY: all -all: $(TARGET) $(TESTS) tags run-tests +all: $(TARGETS) $(TESTS) tags run-tests tags: $(HEADERS) $(SOURCES) ctags $(HEADERS) $(SOURCES) -$(TARGET): $(OBJS) +libscsl.a: $(OBJS) $(AR) rcs $@ $(OBJS) -tlv_test: tlvTest.o $(TARGET) - $(CXX) -o $@ $(CXXFLAGS) tlvTest.o $(TARGET) - -dictionary_test: dictionaryTest.o $(TARGET) - $(CXX) -o $@ $(CXXFLAGS) dictionaryTest.o $(TARGET) - .PHONY: print-% print-%: ; @echo '$(subst ','\'',$*=$($*))' klib.a: $(OBJS) -%.o: %.cc - $(CXX) -o $@ -c $(CXXFLAGS) $< - .PHONY: clean clean: # build outputs - rm -f $(TARGET) $(TESTS) *.o + rm -f $(TARGETS) $(TESTS) *.o # test miscellaneous rm -f core core.* tags arena_test.bin @@ -45,3 +65,22 @@ run-tests: $(TESTS) echo "./$${testbin}" ; \ ./$${testbin}; \ done + +phonebook: phonebook.o $(LIBS) + $(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS) + +bufferTest: bufferTest.o $(LIBS) + $(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS) + +dictionaryTest: dictionaryTest.o $(LIBS) + $(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS) + +flagTest: flagTest.o $(LIBS) + $(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS) + +tlvTest: tlvTest.o $(LIBS) + $(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS) + +%.o: %.cc + $(CXX) -o $@ -c $(CXXFLAGS) $< + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..552001d --- /dev/null +++ b/README.rst @@ -0,0 +1,27 @@ +scsl : The Shimmering Clarity Standard C++ Library +================================================== + +scsl is a collection of software I found myself needing to use repeatedly. + +Full `Doxygen documentation`_ is available. + +.. _Doxygen documentation: https://docs.shimmering-clarity.net/scsl/ + + +License +------- + +Copyright 2023 K. Isom + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/StringUtil.cc b/StringUtil.cc index 778e444..c702fe8 100644 --- a/StringUtil.cc +++ b/StringUtil.cc @@ -1,3 +1,26 @@ +/// +/// \file StringUtil.cc +/// \author K. Isom +/// \date 2023-10-12 +/// \brief Utilities for working with strings. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// + +#include #include #include @@ -12,22 +35,21 @@ namespace U { namespace S { -/* std::vector SplitKeyValuePair(std::string line, std::string delimiter) { - std::string key; - std::string val; + auto pair = SplitN(line, delimiter, 2); - auto pos = line.find(delimiter); - if (pos == std::string::npos) { - key = line; - val = ""; - } else { - key = line.substr(0, pos); - val = line.substr(pos + 1, line.size() - pos - 2); + if (pair.size() == 0) { + return {"", ""}; + } else if (pair.size() == 1) { + return {pair[0], ""}; } + assert(pair.size() == 2); + auto key = pair[0]; + auto val = pair[1]; + TrimWhitespace(key); TrimWhitespace(val); return {key, val}; @@ -37,23 +59,11 @@ SplitKeyValuePair(std::string line, std::string delimiter) std::vector SplitKeyValuePair(std::string line, char delimiter) { - std::string key; - std::string val; + std::string sDelim; - auto pos = line.find(delimiter); - if (pos == std::string::npos) { - key = line; - val = ""; - } else { - key = line.substr(0, pos); - val = line.substr(pos + 1, line.size() - pos - 2); - } - - TrimWhitespace(key); - TrimWhitespace(val); - return {key, val}; + sDelim.push_back(delimiter); + return SplitKeyValuePair(line, sDelim); } -*/ void @@ -132,6 +142,66 @@ SplitN(std::string s, std::string delim, size_t maxCount) } +std::vector +WrapText(std::string line, size_t lineLength) +{ + std::vector wrapped; + auto parts = SplitN(line, " ", 0); + + for (auto &part: parts) { + TrimWhitespace(part); + + } + + std::string wLine; + for (auto word: parts) { + if (word.size() == 0) { + continue; + } + + if ((wLine.size() + word.size() + 1) > lineLength) { + wrapped.push_back(wLine); + wLine.clear(); + } + + if (wLine.size() > 0) { + wLine += " "; + } + wLine += word; + } + + if (wLine.size() > 0) { + wrapped.push_back(wLine); + } + + return wrapped; +} + + +void +WriteTabIndented(std::ostream &os, std::vector lines, + int tabStop, bool indentFirst) +{ + std::string indent(tabStop, '\t'); + + for (size_t i = 0; i < lines.size(); i++) { + if (i > 0 || indentFirst) { + os << indent; + } + os << lines[i] << "\n"; + } +} + + +void +WriteTabIndented(std::ostream &os, std::string line, size_t maxLength, + int tabStop, bool indentFirst) +{ + auto lines = WrapText(line, maxLength); + WriteTabIndented(os, lines, tabStop, indentFirst); +} + + std::ostream & VectorToString(std::ostream &os, const std::vector &svec) { @@ -141,7 +211,7 @@ VectorToString(std::ostream &os, const std::vector &svec) os << "{"; for (size_t i = 0; i < svec.size(); i++) { - if (i > 0) os << ", "; + if (i > 0) { os << ", "; } os << svec[i]; } @@ -153,7 +223,7 @@ VectorToString(std::ostream &os, const std::vector &svec) std::string VectorToString(const std::vector &svec) { - std::stringstream ss; + std::stringstream ss; VectorToString(ss, svec); return ss.str(); diff --git a/StringUtil.h b/StringUtil.h index a32b657..083cd50 100644 --- a/StringUtil.h +++ b/StringUtil.h @@ -1,10 +1,9 @@ /// /// \file StringUtil.h -/// \author kyle (kyle@midgard) -/// \created 2023-10-14 -/// \brief StringUtil contains string utilities. +/// \author K. Isom +/// \date 2023-10-14 +/// \brief Utilities for working with strings. /// -/// \section COPYRIGHT /// Copyright 2023 K. Isom /// /// Permission to use, copy, modify, and/or distribute this software for @@ -85,17 +84,44 @@ std::vector SplitKeyValuePair(std::string line, char delimiter); /// Split a string into parts based on the delimiter. /// +/// \param s The string that should be split. /// \param delimiter The string that delimits the parts of the string. -/// \param maxCount The maximum number of parts to split. If 0, there is no limit -/// to the number of parts. +/// \param maxCount The maximum number of parts to split. If 0, there is no +/// limit to the number of parts. /// \return A vector containing all the parts of the string. std::vector SplitN(std::string, std::string delimiter, size_t maxCount=0); -//std::vector SplitN(std::string, char delimiter, size_t size_t maxCount=0); +/// WrapText is a very simple wrapping function that breaks the line into +/// lines of at most lineLength characters. It does this by breaking the +/// line into individual words (splitting on whitespace). +std::vector WrapText(std::string line, size_t lineLength); + +/// Write out a vector of lines indented with tabs. +/// +/// \param os The output stream to write to. +/// \param lines The lines of text to write. +/// \param tabStop The number of tabs to indent. +/// \param indentFirst Whether the first line should be indented. +void WriteTabIndented(std::ostream &os, std::vector lines, + int tabStop, bool indentFirst); + +/// Wrap a line, then output it to a stream. +/// +/// \param os The output stream to write to. +/// \param line The line to wrap and output. +/// \param maxLength The maximum length of each section of text. +/// \param tabStop The number of tabs to indent. +/// \param indentFirst Whether the first line should be indented. +void WriteTabIndented(std::ostream &os, std::string line, size_t maxLength, + int tabStop, bool indentFirst); -/// Return a string represention of a string vector in the form [size]{"foo", "bar", ...}. +/// Write a string vector to the output stream in the same format as +/// VectorToString. std::ostream &VectorToString(std::ostream &os, const std::vector &svec); + +/// Return a string represention of a string vector in the form +/// [size]{"foo", "bar", ...}. std::string VectorToString(const std::vector &svec); diff --git a/TLV.cc b/TLV.cc index 44401b1..2fccf2a 100644 --- a/TLV.cc +++ b/TLV.cc @@ -1,9 +1,35 @@ +/// +/// \file TLV.cc +/// \author K. Isom +/// \date 2023-10-06 +/// \brief Tag-Length-Value records built on Arena. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// + +#include #include + #include "TLV.h" using namespace scsl; +/// REC_SIZE calculates the total length of a TLV record, including the +/// two byte header. #define REC_SIZE(x) ((std::size_t)x.Len + 2) @@ -90,6 +116,7 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec) { cursor = LocateTag(arena, cursor, rec); if (rec.Tag != TAG_EMPTY) { + std::cout << "skipping record\n"; cursor = SkipRecord(rec, cursor); } @@ -102,13 +129,22 @@ LocateTag(Arena &arena, uint8_t *cursor, Record &rec) { uint8_t tag, len; + if (!arena.CursorInArena(cursor)) { + cursor = nullptr; + } + if (cursor == nullptr) { + std::cout << "move cursor to arena start\n"; cursor = arena.Start(); } while ((tag = cursor[0]) != rec.Tag) { + assert(arena.CursorInArena(cursor)); + std::cout << "cursor is in arena\n"; len = cursor[1]; + std::cout << "record length" << len << "\n"; if (!spaceAvailable(arena, cursor, len)) { + std::cout << "no space available\n"; return nullptr; } cursor += len; diff --git a/TLV.h b/TLV.h index 0821217..50ec427 100644 --- a/TLV.h +++ b/TLV.h @@ -14,8 +14,8 @@ #ifndef KIMODEM_TLV_H #define KIMODEM_TLV_H -#include #include +#include #include "Arena.h" @@ -23,6 +23,7 @@ namespace scsl { namespace TLV { + #ifndef TLV_MAX_LEN static constexpr size_t TLV_MAX_LEN = 253; #endif diff --git a/Test.cc b/Test.cc index 15605de..44042c5 100644 --- a/Test.cc +++ b/Test.cc @@ -1,11 +1,28 @@ -// -// Created by kyle on 2023-10-09. -// +/// +/// \file Test.cc +/// \author K. Isom +/// \date 2023-10-09 +/// \brief Tooling to assist in building test programs.. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// #include "Exceptions.h" #include "Test.h" -#include #include #include diff --git a/Test.h b/Test.h index 12f2c5b..68a3393 100644 --- a/Test.h +++ b/Test.h @@ -2,11 +2,28 @@ /// \file Test.h /// \author K. Isom /// \date 2023-10-09 -/// \brief Test.h implements basic testing tools. +/// \brief Tooling to assist in building test programs.. /// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// + #ifndef SCSL_TEST_H #define SCSL_TEST_H + #include diff --git a/WinHelpers.cc b/WinHelpers.cc deleted file mode 100644 index 7420e12..0000000 --- a/WinHelpers.cc +++ /dev/null @@ -1,142 +0,0 @@ -// -// Created by kyle on 2023-10-10. -// - -#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) - -#include "WinHelpers.h" - - -namespace scsl { -namespace Windows { - - -int -DisplayWinError(LPTSTR lpszFunction, HANDLE handle) -{ - // Retrieve the system error message for the last-error code - DWORD dw = GetLastError(); -#ifndef NDEBUG - LPVOID lpMsgBuf; - LPVOID lpDisplayBuf; - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &lpMsgBuf, - 0, NULL); - - // Display the error message and exit the process - lpDisplayBuf = (LPVOID) LocalAlloc(LMEM_ZEROINIT, - (lstrlen((LPCTSTR) lpMsgBuf) + - lstrlen((LPCTSTR) lpszFunction) + - 40) * sizeof(TCHAR)); - StringCchPrintf((LPTSTR) lpDisplayBuf, - LocalSize(lpDisplayBuf) / sizeof(TCHAR), - TEXT("%s failed with error %d: %s"), - lpszFunction, dw, lpMsgBuf); - MessageBox(NULL, (LPCTSTR) lpDisplayBuf, TEXT("Error"), MB_OK); - - LocalFree(lpMsgBuf); - LocalFree(lpDisplayBuf); -#endif - if ((handle != NULL) && (handle != INVALID_HANDLE_VALUE)) { - CloseHandle(handle); - } - return dw; -} - - -BOOL SetPrivilege( - HANDLE hToken, // access token handle - LPCTSTR lpszPrivilege, // name of privilege to enable/disable - BOOL bEnablePrivilege // to enable or disable privilege -) -{ - TOKEN_PRIVILEGES tp; - LUID luid; - - if (!LookupPrivilegeValue( - NULL, // lookup privilege on local system - lpszPrivilege, // privilege to lookup - &luid)) // receives LUID of privilege - { - printf("LookupPrivilegeValue error: %u\n", GetLastError()); - return FALSE; - } - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - if (bEnablePrivilege) - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - else - tp.Privileges[0].Attributes = 0; - - // Enable the privilege or disable all privileges. - - if (!AdjustTokenPrivileges( - hToken, - FALSE, - &tp, - sizeof(TOKEN_PRIVILEGES), - (PTOKEN_PRIVILEGES) NULL, - (PDWORD) NULL)) { - printf("AdjustTokenPrivileges error: %u\n", GetLastError()); - return FALSE; - } - - if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { - printf("The token does not have the specified privilege. \n"); - return FALSE; - } - - return TRUE; -} - - -HANDLE -CreateFileWindows(const char *path) -{ - HANDLE fHandle; - - return CreateFileA( - (LPSTR) path, - (GENERIC_READ | GENERIC_WRITE), - (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE), - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); -} - - -int -CreateFixedSizeFile(const char *path, size_t size) -{ - _LARGE_INTEGER fileSize; - - fileSize.QuadPart = size; - - HANDLE fHandle = CreateFileWindows(path); - if (SetFilePointerEx(fHandle, fileSize, nullptr, FILE_BEGIN) != TRUE) { - return DisplayWinError("SetFilePointerEx", fHandle); - } - - if (SetEndOfFile(fHandle) != TRUE) { - return DisplayWinError("SetEndOfFile", fHandle); - } - - CloseHandle(fHandle); - return 0; -} - - -} // namespace Windows -} // namespace scsl - - -#endif \ No newline at end of file diff --git a/WinHelpers.h b/WinHelpers.h deleted file mode 100644 index e00b98d..0000000 --- a/WinHelpers.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Created by kyle on 2023-10-10. -// - -#ifndef SCSL_WINHELPERS_H -#define SCSL_WINHELPERS_H - -#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) - -#include -#include -#include -#include - -namespace scsl { -namespace Windows { - - -int DisplayWinError(LPTSTR lpszFunction, HANDLE handle); - -BOOL SetPrivilege( - HANDLE hToken, // access token handle - LPCTSTR lpszPrivilege, // name of privilege to enable/disable - BOOL bEnablePrivilege // to enable or disable privilege -); - -HANDLE CreateFileWindows(const char *path); - -int CreateFixedSizeFile(const char *path, size_t size); - - - - -} // namespace Windows -} // namespace scsl - -#endif // Windows-only guards. - -#endif //SCSL_WINHELPERS_H diff --git a/CMakeDocs.txt b/cmake/docs.cmake similarity index 93% rename from CMakeDocs.txt rename to cmake/docs.cmake index a49bb53..6fa44db 100644 --- a/CMakeDocs.txt +++ b/cmake/docs.cmake @@ -4,7 +4,7 @@ find_package(Doxygen) if (${DOXYGEN_FOUND}) set(DOXYGEN_GENERATE_MAN 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 diff --git a/CMakePack.txt b/cmake/packaging.cmake similarity index 89% rename from CMakePack.txt rename to cmake/packaging.cmake index d0fbbd2..5e620ec 100644 --- a/CMakePack.txt +++ b/cmake/packaging.cmake @@ -7,6 +7,9 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_PACKAGE_FILE_NAME + ${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_ARCH}${CMAKE_HOST_SYSTEM_PROCESSOR}) + # Debian settings set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Shimmering Clarity") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The Shimmering Clarity standard C++ library") @@ -35,4 +38,3 @@ set(CPACK_SOURCE_IGNORE_FILES include (CPack) add_custom_target(package_docs DEPENDS SCSL_docs package package_source) - diff --git a/flagTest.cc b/flagTest.cc index 77002a4..a465893 100644 --- a/flagTest.cc +++ b/flagTest.cc @@ -20,18 +20,19 @@ main(int argc, char *argv[]) int testInteger = 0; std::string testString; - auto flags = new Flags("flag_test", "this is a test of the flag functionality."); + auto flags = new Flags("flag_test", "this is a test of the flag functionality. This line is particularly long to make sure the wrapping works."); 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("-u", (unsigned int)42, "test unsigned integer with a long description line. This should trigger multiline text-wrapping."); 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"; + if (status != Flags::ParseStatus::OK) { + std::cerr << "failed to parse flags: " + << Flags::ParseStatusToString(status) << "\n"; exit(1); } @@ -55,5 +56,6 @@ main(int argc, char *argv[]) std::cout << "(string) test flag was set: " << wasSet << "\n"; std::cout << "(string) test flag value: " << testString << "\n"; + delete flags; return 0; } \ No newline at end of file diff --git a/phonebook.cc b/phonebook.cc index 46223ab..8434be8 100644 --- a/phonebook.cc +++ b/phonebook.cc @@ -1,6 +1,24 @@ -// -// Created by kyle on 2023-10-10. -// +/// +/// \file phonebook.cc +/// \author K. Isom +/// \date 2023-10-10 +/// \brief Commandline tools for interacting with dictionary data file. +/// +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that +/// the above copyright notice and this permission notice appear in all /// copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +/// PERFORMANCE OF THIS SOFTWARE. +/// #include #include @@ -9,6 +27,7 @@ using namespace std; #include "Arena.h" #include "Commander.h" #include "Dictionary.h" +#include "Flag.h" using namespace scsl; static const char *defaultPhonebook = "pb.dat"; diff --git a/scsl.h b/scsl.h index cb0d763..d1bee0b 100644 --- a/scsl.h +++ b/scsl.h @@ -1,10 +1,13 @@ /// /// \file scsl.h -/// \author kyle -/// \created 2023-10-10 +/// \author kyle (kyle@imap.cc) +/// \date 2023-10-10 /// \brief scsl is my collection of C++ data structures and code. /// +/// scsl.h is a utility header that includes all of SCSL. +/// /// \section COPYRIGHT +/// /// Copyright 2023 K. Isom /// /// Permission to use, copy, modify, and/or distribute this software for @@ -25,12 +28,15 @@ #define SCSL_SCSL_H -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /// scsl is the top-level namespace containing all the code in this library. diff --git a/stringutil_test.cc b/stringutil_test.cc index 9f7d2ea..3740817 100644 --- a/stringutil_test.cc +++ b/stringutil_test.cc @@ -1,10 +1,9 @@ /// /// \file stringutil_test.cc /// \author kyle -/// \created 10/14/23 +/// \date 10/14/23 /// \brief Ensure the stringutil functions work. /// -/// \section COPYRIGHT /// Copyright 2023 K. Isom /// /// Permission to use, copy, modify, and/or distribute this software for @@ -27,24 +26,26 @@ #include "StringUtil.h" #include "Test.h" + + using namespace scsl; static void TestTrimming(std::string line, std::string lExpected, std::string rExpected, std::string expected) { - std::string result; - std::string message; + std::string result; + std::string message; - result = U::S::TrimLeadingWhitespaceDup(line); + result = U::S::TrimLeadingWhitespaceDup(line); message = "TrimLeadingDup(\"" + line + "\"): '" + result + "'"; TestAssert(result == lExpected, message); - result = U::S::TrimTrailingWhitespaceDup(line); + result = U::S::TrimTrailingWhitespaceDup(line); message = "TrimTrailingDup(\"" + line + "\"): '" + result + "'"; TestAssert(result == rExpected, message); - result = U::S::TrimWhitespaceDup(line); + result = U::S::TrimWhitespaceDup(line); message = "TrimDup(\"" + line + "\"): '" + result + "'"; TestAssert(result == expected, message); @@ -68,7 +69,7 @@ TestTrimming(std::string line, std::string lExpected, std::string rExpected, std static std::string vec2string(std::vector v) { - std::stringstream ss; + std::stringstream ss; ss << "("; ss << v.size(); @@ -76,7 +77,7 @@ vec2string(std::vector v) ss << "{"; for (size_t i = 0; i < v.size(); i++) { - if (i > 0) ss << ", "; + if (i > 0) { ss << ", "; } ss << v[i]; } @@ -100,6 +101,36 @@ TestSplit(std::string line, std::string delim, size_t maxCount, std::vector expected{ + "A much longer", + "line, something", + "that can be", + "tested with", + "WrapText. Does", + "it handle", + "puncuation? I", + "hope so.", + }; + + auto wrapped = U::S::WrapText(testLine, 16); + TestAssert(wrapped.size() == expected.size(), + U::S::VectorToString(wrapped) + " != " + U::S::VectorToString(expected)); + + for (size_t i = 0; i < wrapped.size(); i++) { + TestAssert(wrapped[i] == expected[i], + "\"" + wrapped[i] + "\" != \"" + expected[i] + "\""); + } + + U::S::WriteTabIndented(std::cout, wrapped, 4, true); +} + + int main() { @@ -116,4 +147,6 @@ main() std::vector{"abc:def:ghij:klm"}); TestSplit("abc::def:ghi", ":", 0, std::vector{"abc", "", "def", "ghi"}); + + TestWrapping(); } \ No newline at end of file diff --git a/tlvTest.cc b/tlvTest.cc index 36782ba..f274ca1 100644 --- a/tlvTest.cc +++ b/tlvTest.cc @@ -48,13 +48,16 @@ tlvTestSuite(Arena &backend) assert(cursor != nullptr); assert(cmpRecord(rec3, rec4)); + std::cout << "\tSetRecord 1\n"; TLV::SetRecord(rec4, 3, TEST_STRLEN3, TEST_STR3); assert(TLV::WriteToMemory(backend, nullptr, rec4)); + std::cout << "FindTag 3\n"; rec4.Tag = 2; cursor = TLV::FindTag(backend, nullptr, rec4); assert(cursor != nullptr); + std::cout << "DeleteRecord\n"; TLV::DeleteRecord(backend, cursor); assert(cursor[0] == 3); }