12 Commits

Author SHA1 Message Date
3122ed6ac7 Add circleci config. 2023-10-18 17:57:19 -07:00
184d468b06 Dictionary conditions reversed.
During a code cleanup, a check for a TLV tag inverted the logic.
2023-10-18 17:35:27 -07:00
fa1cb59697 Add missing include header.
This was being included transitively.
2023-10-18 16:21:09 -07:00
a8387f010f Trying to get build working on Fedora. 2023-10-18 16:19:09 -07:00
00e9bc0f22 Format README. 2023-10-17 00:45:21 -07:00
567f5f9564 gitea doesn't support RST READMEs, so renamed this. 2023-10-17 00:44:40 -07:00
b7584b06cc Cleanup example program in Flag docs. 2023-10-16 23:58:49 -07:00
a0cd2ca866 Add trailing newline to cmake config. 2023-10-16 21:23:26 -07:00
a0edf915ad Remove test prints and fix cursor checks in dictionary. 2023-10-16 17:53:50 -07:00
930a2d68f4 Update docs; bump patch version. 2023-10-16 15:03:22 -07:00
40d92db968 Bump package version. 2023-10-16 04:02:46 -07:00
407ee1c85d 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.
2023-10-16 04:01:35 -07:00
33 changed files with 1049 additions and 257 deletions

26
.circleci/config.yml Normal file
View File

@@ -0,0 +1,26 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/configuration-reference
version: 2.1
# Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/configuration-reference/#jobs
jobs:
ctest:
# Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub.
# See: https://circleci.com/docs/configuration-reference/#executor-job
docker:
- image: git.wntrmute.dev/sc/dev:alpine
# Add steps to the job
# See: https://circleci.com/docs/configuration-reference/#steps
steps:
- checkout
- run:
name: Setup cmake build
command: setup-cmake.sh
# Orchestrate jobs using workflows
# See: https://circleci.com/docs/configuration-reference/#workflows
workflows:
ctest:
jobs:
- ctest

8
.idea/klib.iml generated
View File

@@ -1,2 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />
<module classpath="CMake" type="CPP_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python facet">
<configuration sdkName="Python 3.10 (scsl)" />
</facet>
</component>
</module>

3
.idea/misc.xml generated
View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (scsl)" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

View File

@@ -1,19 +1,37 @@
#include <cassert>
///
/// \file Arena.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief Memory management using an arena.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <cstdio>
#include <cstdlib>
#include <cstring>
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
#include "WinHelpers.h"
#pragma comment(lib, "advapi32.lib")
#endif
#include <ios>
@@ -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:

26
Arena.h
View File

@@ -1,11 +1,26 @@
///
/// \file Arena.h
/// \author K. Isom
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief Memory management using an arena.
///
/// Arena defines a memory management backend for pre-allocating memory.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <iostream>
#include <sys/stat.h>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <sys/stat.h>
#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

View File

@@ -2,14 +2,29 @@
/// \file Buffer.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Buffer implements basic line buffers.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <cassert>
#include <cstring>
#include <iomanip>
#include <ios>
#include <iostream>
#include <iomanip>
#include "Buffer.h"

View File

@@ -8,12 +8,27 @@
/// editing. It allocates memory in powers of two, and will grow or shrink
/// as needed.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <iostream>
#include <cstdint>
#include <iostream>
namespace scsl {

View File

@@ -1,64 +1,61 @@
cmake_minimum_required(VERSION 3.22)
project(scsl LANGUAGES CXX
VERSION 0.1.1
VERSION 0.2.5
DESCRIPTION "Shimmering Clarity Standard Library")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_VERBOSE_MAKEFILES TRUE)
set(VERBOSE YES)
if (MSVC)
add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/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"
"$<$<CONFIG:RELEASE>:-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"
"$<$<CONFIG:RELEASE>:-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()

View File

@@ -1,12 +1,30 @@
///
/// \file Commander.cc
/// \author kyle
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Subprogram tooling.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <iostream>
#include "Commander.h"
namespace scsl {
@@ -57,4 +75,4 @@ Commander::Run(std::string command, int argc, char **argv)
}
} // scsl
} // namespace scsl

View File

@@ -12,9 +12,25 @@
/// $ some_tool subcommand args...
/// ```
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <map>
#include <cstdint>
#include <functional>
#include <map>
#include <string>
#include <vector>
@@ -28,7 +44,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<bool(int, char **)> CommanderFunc;
using CommanderFunc = std::function<bool (int, char **)>;
/// Subcommands are the individual commands for the program. A Subcommand
@@ -36,7 +52,7 @@ typedef std::function<bool(int, char **)> CommanderFunc;
class Subcommand {
public:
/// Status describes the results of running a Subcommand.
enum class Status : uint8_t {
enum class Status : int8_t {
/// The subcommand executed correctly.
OK = 0,
/// Not enough arguments were supplied to the subcommand.
@@ -104,7 +120,8 @@ private:
std::map<std::string, Subcommand *> cmap;
};
} // scsl
} // namespace scsl
#endif //SCSL_COMMANDER_H

View File

@@ -1,5 +1,29 @@
#include <cstring>
///
/// \file Dictionary.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2023-10-05
/// \brief Key-value store built on top of Arena and TLV.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <cassert>
#include <cstdlib>
#include <cstring>
#include "Dictionary.h"
#if defined(SCSL_DESKTOP_BUILD)
@@ -20,16 +44,13 @@ Dictionary::Lookup(const char *key, uint8_t klen, TLV::Record &res)
if ((klen == res.Len) &&
(memcmp(res.Val, key, klen) == 0)) {
TLV::ReadFromMemory(res, cursor);
if (res.Tag != this->vTag) {
abort();
}
return true;
assert(res.Tag == this->vTag);
return res.Tag == this->vTag;
}
cursor = TLV::FindTag(this->arena, cursor, res);
}
return false;
}

View File

@@ -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 <kyle@imap.cc>
///
/// 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 <cstdint>
#include "Arena.h"
#include "TLV.h"
@@ -26,12 +45,6 @@ namespace scsl {
*/
/// Dictionary implements a key-value store on top of Arena and TLV::Record.
///
/// phonebook of SSIDs and WPA keys on a microcontroller. This phonebook had to
/// be stored in persistent NVRAM storage, preëmpting the use of std::map or
/// similar. The hardware in use was also not conducive to more expensive
/// options. It was originally named Phonebook until it was adapted to a more
/// general-purpose data structure.
///
/// Keys and vales are stored as sequential pairs of TLV records; they are
/// expected to contain string values but this isn't necessarily the case. The
/// tag values default to a tag of DICTIONARY_TAG_KEY, and values to a tag of

View File

@@ -1,6 +1,24 @@
//
// Created by kyle on 2023-10-10.
//
///
/// \file Exceptions.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Custom exceptions used in writing test programs.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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"
@@ -8,9 +26,8 @@
namespace scsl {
AssertionFailed::AssertionFailed(std::string message) : msg(message)
{
}
AssertionFailed::AssertionFailed(std::string message) : msg(message) {}
const char *
AssertionFailed::what() const throw()
@@ -19,6 +36,4 @@ AssertionFailed::what() const throw()
}
}

View File

@@ -1,7 +1,24 @@
//
// Created by kyle on 2023-10-10.
//
///
/// \file Exceptions.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Custom exceptions for use in SCSL used in writing test programs.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <exception>
#include <string>
namespace scsl {

146
Flag.cc
View File

@@ -1,11 +1,9 @@
///
/// \file Flag.h
/// \author kyle
/// \created 2023-10-12
/// \file Flag.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag defines a command-line flag parser.
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
@@ -23,9 +21,7 @@
///
#include <cstdint>
#include <functional>
#include <map>
#include <iostream>
#include <regex>
#include <vector>
@@ -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<unsigned int>(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<size_t>(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;

283
Flag.h
View File

@@ -1,11 +1,9 @@
///
/// \file Flag.h
/// \author kyle
/// \created 2023-10-12
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag declares a command-line flag parser.
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
@@ -35,90 +33,299 @@ 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:
///
/// ```c++
/// 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<std::string> 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);

View File

@@ -1,16 +1,3 @@
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 <kyle@imap.cc>
Permission to use, copy, modify, and/or distribute this software for any
@@ -23,5 +10,4 @@ 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.
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

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

57
README.md Normal file
View File

@@ -0,0 +1,57 @@
# scsl : The Shimmering Clarity Standard C++ Library
scsl is a collection of software I found myself needing to use repeatedly.
Full [Doxygen documentation](https://docs.shimmering-clarity.net/scsl/)
is available.
## Introduction
This is a collection of C++ code that I find useful in building things.
It arose from two main use cases.
### The modem
On the one hand, I was building a wireless modem for some Z80 computers I
have. I needed to be able to store a phonebook of SSIDs and WPA keys, as
well as short names to host:port descriptors. I had a limited amount of
persistent NVRAM storage and no SD card or other removeable media, so
typical desktop-oriented serialization mechanisms weren't going to really
work well. Furthermore, when working with microcontrollers, I prefer not to
dynamically allocate memory as much as possible. This led to building out
Arena, TLV::Record to store the records, and finally Dictionary to make use
of both of them.
Closely related to this, I've been working on building an ARM-based handheld
computer, for which I would also need a memory arena.
### The text editors
Some time ago, I wrote a console text editor of my own; then later, started
working on a graphical editor. For this, I needed some data structures to
manage memory in the editor. Thus, Buffer was born.
### Finally
I'd been writing Go professionally for a while, but C was my first love. I
recently started a job that is mostly in C++, and the best way for me to
learn is to build a bunch of stuff with it. So, I took a bunch of micro-
controller stuff I'd been writing and started building out some other stuff.
## License
Copyright 2023 K. Isom <kyle@imap.cc>
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.

View File

@@ -1,3 +1,26 @@
///
/// \file StringUtil.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Utilities for working with strings.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <cassert>
#include <iostream>
#include <sstream>
@@ -12,22 +35,21 @@ namespace U {
namespace S {
/*
std::vector<std::string>
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<std::string>
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<std::string>
WrapText(std::string line, size_t lineLength)
{
std::vector<std::string> 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<std::string> 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<std::string> &svec)
{
@@ -141,7 +211,7 @@ VectorToString(std::ostream &os, const std::vector<std::string> &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<std::string> &svec)
std::string
VectorToString(const std::vector<std::string> &svec)
{
std::stringstream ss;
std::stringstream ss;
VectorToString(ss, svec);
return ss.str();

View File

@@ -1,10 +1,9 @@
///
/// \file StringUtil.h
/// \author kyle (kyle@midgard)
/// \created 2023-10-14
/// \brief StringUtil contains string utilities.
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-14
/// \brief Utilities for working with strings.
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
@@ -85,17 +84,44 @@ std::vector<std::string> 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<std::string> SplitN(std::string, std::string delimiter, size_t maxCount=0);
//std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> &svec);
/// Return a string represention of a string vector in the form
/// [size]{"foo", "bar", ...}.
std::string VectorToString(const std::vector<std::string> &svec);

41
TLV.cc
View File

@@ -1,9 +1,35 @@
///
/// \file TLV.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief Tag-Length-Value records built on Arena.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <cassert>
#include <cstring>
#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)
@@ -91,6 +117,9 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
cursor = LocateTag(arena, cursor, rec);
if (rec.Tag != TAG_EMPTY) {
cursor = SkipRecord(rec, cursor);
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
}
return cursor;
@@ -102,11 +131,17 @@ LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
{
uint8_t tag, len;
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
if (cursor == nullptr) {
cursor = arena.Start();
}
while ((tag = cursor[0]) != rec.Tag) {
while (((tag = cursor[0]) != rec.Tag) &&
(arena.CursorInArena(cursor))) {
assert(arena.CursorInArena(cursor));
len = cursor[1];
if (!spaceAvailable(arena, cursor, len)) {
return nullptr;
@@ -115,6 +150,10 @@ LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
cursor += 2;
}
if (!arena.CursorInArena(cursor)) {
return nullptr;
}
if (tag != rec.Tag) {
return nullptr;
}

3
TLV.h
View File

@@ -14,8 +14,8 @@
#ifndef KIMODEM_TLV_H
#define KIMODEM_TLV_H
#include <cstdint>
#include <array>
#include <cstdint>
#include "Arena.h"
@@ -23,6 +23,7 @@
namespace scsl {
namespace TLV {
#ifndef TLV_MAX_LEN
static constexpr size_t TLV_MAX_LEN = 253;
#endif

24
Test.cc
View File

@@ -1,6 +1,24 @@
//
// Created by kyle on 2023-10-09.
//
///
/// \file Test.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Tooling to assist in building test programs..
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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"

19
Test.h
View File

@@ -2,11 +2,28 @@
/// \file Test.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Test.h implements basic testing tools.
/// \brief Tooling to assist in building test programs..
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <string>

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,24 @@
//
// Created by kyle on 2023-10-10.
//
///
/// \file phonebook.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Commandline tools for interacting with dictionary data file.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <iostream>
#include <string>
@@ -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";

24
scsl.h
View File

@@ -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 <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
@@ -25,12 +28,15 @@
#define SCSL_SCSL_H
#include <klib/Arena.h>
#include <klib/Buffer.h>
#include <klib/Dictionary.h>
#include <klib/Exceptions.h>
#include <klib/TLV.h>
#include <klib/Test.h>
#include <scsl/Arena.h>
#include <scsl/Buffer.h>
#include <scsl/Commander.h>
#include <scsl/Dictionary.h>
#include <scsl/Exceptions.h>
#include <scsl/Flag.h>
#include <scsl/StringUtil.h>
#include <scsl/TLV.h>
#include <scsl/Test.h>
/// scsl is the top-level namespace containing all the code in this library.
@@ -49,7 +55,7 @@ namespace scsl {
///
/// On the one hand, I was building a wireless modem for some Z80 computers I
/// have. I needed to be able to store a phonebook of SSIDs and WPA keys, as
/// well as short names to host:port descriptors. I had a limited amount of of
/// well as short names to host:port descriptors. I had a limited amount of
/// persistent NVRAM storage and no SD card or other removeable media, so
/// typical desktop-oriented serialization mechanisms weren't going to really
/// work well. Furthermore, when working with microcontrollers, I prefer not to

View File

@@ -1,2 +1,3 @@
set(SCSL_INCLUDE_DIRS include/scsl)
set(SCSL_LIBRARIES libscsl.a)
set(SCSL_LIBRARIES libscsl.a)

View File

@@ -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 <kyle@imap.cc>
///
/// 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<std::string> v)
{
std::stringstream ss;
std::stringstream ss;
ss << "(";
ss << v.size();
@@ -76,7 +77,7 @@ vec2string(std::vector<std::string> 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<std:
}
static void
TestWrapping()
{
std::string testLine = "A much longer line, something that can be tested with WrapText. ";
testLine += "Does it handle puncuation? I hope so.";
std::vector<std::string> 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<std::string>{"abc:def:ghij:klm"});
TestSplit("abc::def:ghi", ":", 0,
std::vector<std::string>{"abc", "", "def", "ghi"});
TestWrapping();
}

View File

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