summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pointerlist.cc123
-rw-r--r--src/pointerlist.h74
-rw-r--r--test/ctor.cc17
-rw-r--r--test/pointerlist_test.cc320
4 files changed, 534 insertions, 0 deletions
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 <cstring>
+
+PointerList::PointerList(int argc, const char* const argv[])
+{
+ for(int i = 0; i < argc; ++i)
+ {
+ push_back(argv[i]);
+ }
+}
+
+std::pair<int, const char* const*> 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<std::string_view, std::string_view>& 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<int, const char* const*> 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 <string>
+#include <vector>
+#include <deque>
+#include <utility>
+#include <map>
+
+//! 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<std::string>
+{
+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<int, const char* const*> get();
+
+private:
+ std::vector<const char*> 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<std::string_view, std::string_view>& 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<int, const char* const*> get();
+
+private:
+ std::map<std::string, std::string> 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
@@ -12,6 +12,23 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
{
.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,
.target = "deps_test",
.sources = {
"deps_test.cc",
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 <uunit.h>
+
+#include <set>
+#include <algorithm>
+
+#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<std::string> 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<std::string> 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<std::string> 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;