From 69d66ac41ab66a5a5da007ccfacc1d5a9d45d819 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Sat, 1 Feb 2025 16:24:07 +0100 Subject: Add PointerList and EnvMap classes for working with, and propagating, argc/argv and env strings --- src/pointerlist.cc | 123 ++++++++++++++++++ src/pointerlist.h | 74 +++++++++++ test/ctor.cc | 17 +++ test/pointerlist_test.cc | 320 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 534 insertions(+) create mode 100644 src/pointerlist.cc create mode 100644 src/pointerlist.h create mode 100644 test/pointerlist_test.cc diff --git a/src/pointerlist.cc b/src/pointerlist.cc new file mode 100644 index 0000000..c0242f7 --- /dev/null +++ b/src/pointerlist.cc @@ -0,0 +1,123 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "pointerlist.h" + +#include + +PointerList::PointerList(int argc, const char* const argv[]) +{ + for(int i = 0; i < argc; ++i) + { + push_back(argv[i]); + } +} + +std::pair PointerList::get() +{ + argptrs.clear(); + for(const auto& arg : *this) + { + argptrs.push_back(arg.data()); + } + argptrs.push_back(nullptr); + + return {argptrs.size() - 1, // size not counting the nullptr at the end + argptrs.data()}; +} + +EnvMap::EnvMap(const char* const env[]) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(std::string_view(*ptr)); + ++ptr; + } +} + +EnvMap::EnvMap(const char* env) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(ptr); + ptr += strlen(ptr) + 1; + } +} + +std::string EnvMap::operator[](const std::string& key) const +{ + try + { + return data.at(key); + } + catch(...) + { + return {}; + } +} + +void EnvMap::clear() +{ + data.clear(); +} + +void EnvMap::insert(std::string_view key_value) +{ + auto equals_sign = key_value.find('='); + if(equals_sign == std::string::npos) + { + insert({key_value, ""}); + return; + } + std::string key{key_value.substr(0, equals_sign)}; + std::string value{key_value.substr(equals_sign + 1)}; // skip '=' + insert({key, value}); +} + +void EnvMap::insert(const std::pair& item) +{ + data[std::string(item.first)] = item.second; +} + +bool EnvMap::contains(std::string_view key) const +{ + return data.contains(std::string{key}); +} + +std::size_t EnvMap::size() const +{ + return data.size(); +} + +std::string EnvMap::stringify() const +{ + std::string str; + for(const auto& [key, value] : data) + { + str += key + "=" + value + '\0'; + } + str += '\0'; + return str; +} + +std::pair EnvMap::get() +{ + pointerlist.clear(); + for(const auto& [key, value] : data) + { + pointerlist.push_back(key + "=" + value + '\0'); + } + return pointerlist.get(); +} diff --git a/src/pointerlist.h b/src/pointerlist.h new file mode 100644 index 0000000..988bb26 --- /dev/null +++ b/src/pointerlist.h @@ -0,0 +1,74 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include +#include +#include +#include +#include + +//! Maintains an (owning) list of string args and converts them to argc/argv +//! compatible arguments on request. +//! The returned pointers are guaranteed to be valid as long as the PointerList +//! object lifetime is not exceeded. +class PointerList + : public std::deque +{ +public: + PointerList() = default; + PointerList(int argc, const char* const argv[]); + + //! Returns argc/argv pair from the current list of args + //! The argv entry after the last is a nullptr (not included in the argc) + std::pair get(); + +private: + std::vector argptrs; +}; + + +//! Maintains an owning map of strings representing the env. +class EnvMap +{ +public: + EnvMap() = default; + + //! Initialize from an array of pointers to key=value\0 strings terminated + //! by \0 + EnvMap(const char* const env[]); + + //! Initialize from a string of the format + //! key1=val\0key2=val\0...keyN=val\0\0 + EnvMap(const char* env); + + std::string operator[](const std::string& key) const; + + //! Clear all items in the map + void clear(); + + //! Insert string from format: key=value + void insert(std::string_view key_value); + + //! Regular map insert + void insert(const std::pair& item); + + //! Checks if the container contains element with specific key + bool contains(std::string_view key) const; + + std::size_t size() const; + + //! Return string with the following format: + //! key1=val\0key2=val\0...keyN=val\0\0 + std::string stringify() const; + + //! Returns the map as argc/argv pair where each pointer points to a string + //! of the format key=value\0 and is terminated with a nullptr + std::pair get(); + +private: + std::map data{}; + + PointerList pointerlist; +}; diff --git a/test/ctor.cc b/test/ctor.cc index d69b1cf..acb232f 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -9,6 +9,23 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) { return { + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, + .target = "pointerlist_test", + .sources = { + "pointerlist_test.cc", + "testmain.cc", + "../src/pointerlist.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"pointerlist\"", + }, + }, + }, { .type = ctor::target_type::unit_test, .system = ctor::output_system::build, diff --git a/test/pointerlist_test.cc b/test/pointerlist_test.cc new file mode 100644 index 0000000..4274473 --- /dev/null +++ b/test/pointerlist_test.cc @@ -0,0 +1,320 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include + +#include +#include + +#include "pointerlist.h" + +class PointerListTest + : public uUnit +{ +public: + PointerListTest() + { + uTEST(PointerListTest::test_zom_pointerlist_push); + uTEST(PointerListTest::test_zom_pointerlist_from_args); + uTEST(PointerListTest::test_zom_envmap_insert); + uTEST(PointerListTest::test_zom_envmap_from_env); + uTEST(PointerListTest::test_exceptional_env); + } + + void test_zom_pointerlist_push() + { + using namespace std::string_literals; + + { // Zero + PointerList args; + uASSERT_EQUAL(0u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One + PointerList args; + args.push_back("hello"); + uASSERT_EQUAL(1u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + } + + { // Many + PointerList args; + args.push_back("hello"); + args.push_back("dear"); + args.push_back("world"); + uASSERT_EQUAL(3u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(3, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + uASSERT_EQUAL("dear"s, std::string(argv[1])); + uASSERT_EQUAL("world"s, std::string(argv[2])); + } + } + + void test_zom_pointerlist_from_args() + { + using namespace std::string_literals; + + { // Zero + PointerList args(0, nullptr); + uASSERT_EQUAL(0u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One + int _argc{1}; + const char* _argv[] = { "hello" }; + PointerList args(_argc, _argv); + uASSERT_EQUAL(1u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + } + + { // Many + int _argc{3}; + const char* _argv[] = { "hello", "dear", "world" }; + PointerList args(_argc, _argv); + uASSERT_EQUAL(3u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(3, argc); + // order must be preserved + uASSERT_EQUAL("hello"s, std::string(argv[0])); + uASSERT_EQUAL("dear"s, std::string(argv[1])); + uASSERT_EQUAL("world"s, std::string(argv[2])); + } + } + + void test_zom_envmap_insert() + { + using namespace std::string_literals; + + { // Zero + EnvMap env; + uASSERT_EQUAL(0u, env.size()); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + + auto str = env.stringify(); + uASSERT_EQUAL(1u, str.size()); + uASSERT_EQUAL('\0', str[0]); + } + + { // One (key only) + EnvMap env; + env.insert("hello"); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + { // One (with value) + EnvMap env; + env.insert("hello=world"); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // Overwrite one + EnvMap env; + env.insert("hello=world"); + uASSERT_EQUAL(1u, env.size()); + uASSERT_EQUAL("world"s, env["hello"s]); + + env.insert("hello=foo"); + uASSERT_EQUAL(1u, env.size()); + uASSERT_EQUAL("foo"s, env["hello"s]); + } + + { // Many + EnvMap env; + env.insert("hello=world"); + env.insert("world=leader"); + env.insert("dear=boar"); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + } + + void test_zom_envmap_from_env() + { + using namespace std::string_literals; + + { // Zero + const char* penv = nullptr; + EnvMap env(penv); + uASSERT_EQUAL(0u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // Zero + const char** ppenv = nullptr; + EnvMap env(ppenv); + uASSERT_EQUAL(0u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One (key only) + const char* ptr = "hello\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + + { // One (key only) + const char* ptr[] = {"hello", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + + { // One (with value) + const char* ptr = "hello=world\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // One (with value) + const char* ptr[] = {"hello=world\0", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // Many + const char* ptr = "hello=world\0world=leader\0dear=boar\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + + { // Many + const char* ptr[] = + {"hello=world\0", "world=leader\0", "dear=boar\0", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + } + + void test_exceptional_env() + { + using namespace std::string_literals; + + { // Zero + EnvMap env; + uASSERT_EQUAL(0u, env.size()); + uASSERT_EQUAL(""s, env["foo"]); // lookup of non-existing key + } + } +}; + +// Registers the fixture into the 'registry' +static PointerListTest test; -- cgit v1.2.3