container: root: new pluggable framework

- Implements pluggable auto-(un|re)loading
- Refactors, adds/updates documentation
This commit is contained in:
2025-12-03 12:41:46 -08:00
parent 8a3b44d727
commit 8055a9494f
4 changed files with 1157 additions and 82 deletions

View File

@@ -29,6 +29,7 @@
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "./type.hh"
@@ -273,6 +274,305 @@ std::string make_timestamp()
std::strftime(time.data(), time.size(), "%FT%TZ", std::gmtime(&t));
return std::string{time.data()};
}
//! \brief Base pseudo-path handler for various pluggables (plugins and macros)
//!
//! \details Handles pluggable paths, typically used by pluggable
//! implementations when auto-(un|re)loading.
//!
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
class PluggablePath
{
public:
//! \brief Parses (or re-parses) constructed types
//! \warning Only call this function after constructing if underlying type::PluggablePath has been changed (post-construction)
//! \return NPI reference
auto& parse()
{
// Parse out pseudo tag
const std::string type{m_pseudo.substr(0, m_pseudo.find('/'))};
std::string pruned{m_pseudo};
pruned.erase(0, pruned.find('/') + 1);
// Set family group
m_family = pruned;
// Set parent directory
m_parent = pruned.substr(0, pruned.find('/'));
// Set child (filename)
m_child = pruned.substr(pruned.find_last_of('/') + 1);
// Set absolute path
std::string absolute{pruned};
if (type == "repo")
{
m_is_repo = true;
absolute.insert(0, m_path.repo());
}
else if (type == "custom")
{
m_is_custom = true;
absolute.insert(0, m_path.custom());
}
else
{
throw_ex<type::RuntimeError>(
"must be of type 'repo/<relative>' or 'custom/<relative>'");
}
m_absolute = std::move(absolute);
return *this;
}
protected:
//! \brief Sets pluggable absolute path from given pseudo path
//! \param pseudo Pseudo-path ('repo/<relative>' or 'custom/<relative>')
//! \param base Operating system absolute paths to pluggable locations
//! \exception type::RuntimeError If not a valid pseudo-path
PluggablePath(const std::string& pseudo, const type::PluggablePath& path)
: m_pseudo(pseudo), m_path(path), m_is_repo(false), m_is_custom(false)
{
parse();
}
~PluggablePath() = default;
PluggablePath(const PluggablePath&) = default;
PluggablePath& operator=(const PluggablePath&) = default;
PluggablePath(PluggablePath&&) = default;
PluggablePath& operator=(PluggablePath&&) = default;
public:
//! \brief Mutator to underlying type::PluggablePath
//! \warning Use with caution: underlying implementation may change
auto& operator()() { return m_path; }
//! \brief Accessor to underlying type::PluggablePath
//! \warning Use with caution: underlying implementation may change
const auto& operator()() const { return m_path; }
public:
//! \return The pluggable's pseudo-path
const std::string& pseudo() const { return m_pseudo; }
//! \return The pluggable's operating system absolute path
const std::string& absolute() const { return m_absolute; }
// TODO(unassigned): relative() to current working dir
//! \return The pluggable's parent directory
//! \note This also represents the expected namespace used for pluggable auto-loading
const std::string& parent() const { return m_parent; }
//! \return The pluggable's child (filename)
const std::string& child() const { return m_child; }
//! \return The pluggable's group of parent member and child filename
const std::string& family() const { return m_family; }
//! \return true if pseudo-path describes a repository location
bool is_repo() const { return m_is_repo; }
//! \return true if pseudo-path describes a custom location
bool is_custom() const { return m_is_custom; }
private:
type::PluggablePath m_path;
std::string m_pseudo, m_absolute;
std::string m_parent, m_family, m_child;
bool m_is_repo, m_is_custom;
};
//! \brief Base namespace handler for various pluggables (plugins and macros)
//!
//! \details Handles pluggable namespace, typically used by pluggable
//! implementations when auto-(un|re)loading.
//!
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
class PluggableSpace
{
protected:
explicit PluggableSpace(const type::PluggableSpace& space) : m_space(space) {}
~PluggableSpace() = default;
PluggableSpace(const PluggableSpace&) = default;
PluggableSpace& operator=(const PluggableSpace&) = default;
PluggableSpace(PluggableSpace&&) = default;
PluggableSpace& operator=(PluggableSpace&&) = default;
public:
//! \brief Mutator to underlying type::PluggableSpace
//! \warning Use with caution: underlying implementation may change
auto& operator()() { return m_space; }
//! \brief Accessor to underlying type::PluggableSpace
//! \warning Use with caution: underlying implementation may change
const auto& operator()() const { return m_space; }
public:
//! \return The pluggable's outer namespace
const std::string& outer() const { return m_space.outer(); }
//! \return The pluggable's inner namespace
const std::string& inner() const { return m_space.inner(); }
//! \return true if the outer namespace was set
bool has_outer() const { return !m_space.outer().empty(); }
//! \return true if the inner namespace was set
bool has_inner() const { return !m_space.inner().empty(); }
private:
type::PluggableSpace m_space;
};
//! \brief Base argument handler for various pluggables (plugins and macros)
//!
//! \details Handles pluggable arguments, typically used by pluggable
//! implementations when auto-(un|re)loading.
//!
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
class PluggableArgs
{
protected:
explicit PluggableArgs(const type::PluggableArgs& args) : m_args(args) {}
~PluggableArgs() = default;
PluggableArgs(const PluggableArgs&) = default;
PluggableArgs& operator=(const PluggableArgs&) = default;
PluggableArgs(PluggableArgs&&) = default;
PluggableArgs& operator=(PluggableArgs&&) = default;
public:
//! \brief Mutator to underlying type::PluggableArgs
//! \warning Use with caution: underlying implementation may change
auto& operator()() { return m_args; }
//! \brief Accessor to underlying type::PluggableArgs
//! \warning Use with caution: underlying implementation may change
const auto& operator()() const { return m_args; }
public:
//! \return The pluggable's loader argument
const std::string& load() const { return m_args.load(); }
//! \return The pluggable's unloader argument
const std::string& unload() const { return m_args.unload(); }
//! \return true if the load argument was set
bool has_load() const { return !m_args.load().empty(); }
//! \return true if the unload argument was set
bool has_unload() const { return !m_args.unload().empty(); }
private:
type::PluggableArgs m_args;
};
//! \concept dfi::common::PPath
//! \brief Pluggable base implementation constraint (PluggablePath)
//! \ref dfi::common::PluggablePath
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
template <typename t_path>
concept PPath = std::derived_from<t_path, PluggablePath>;
//! \concept dfi::common::PSpace
//! \brief Pluggable base implementation constraint (PluggableSpace)
//! \ref dfi::common::PluggableSpace
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
template <typename t_space>
concept PSpace = std::derived_from<t_space, PluggableSpace>;
//! \concept dfi::common::PArgs
//! \brief Pluggable base implementation constraint (PluggableArgs)
//! \ref dfi::common::PluggableArgs
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
template <typename t_args>
concept PArgs = std::derived_from<t_args, PluggableArgs>;
//! \brief Base pluggable handler
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
template <PPath t_path, PSpace t_space, PArgs t_args>
class Pluggable
{
protected:
explicit Pluggable(const type::Pluggable<t_path, t_space, t_args>& plug)
: m_plug(plug)
{
}
~Pluggable() = default;
Pluggable(const Pluggable&) = default;
Pluggable& operator=(const Pluggable&) = default;
Pluggable(Pluggable&&) = default;
Pluggable& operator=(Pluggable&&) = default;
public:
//! \brief Mutator to underlying type::Pluggable
//! \warning Use with caution: underlying implementation may change
auto& operator()() { return m_plug; }
//! \brief Accessor to underlying type::Pluggable
//! \warning Use with caution: underlying implementation may change
const auto& operator()() const { return m_plug; }
public:
//! \brief Loads a single pluggable
//! \return NPI reference
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
const auto& load() const
{
// Load pluggable file
::dfi::common::load(m_plug.path().absolute());
// Execute pluggable's loader
const std::string s{
"dfi::" + m_plug.space().outer() + "::" + m_plug.space().inner()};
::dfi::common::line(s + "::load(\"" + m_plug.args().load() + "\")");
return *this;
}
//! \brief Unloads a single pluggable
//! \return NPI reference
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
const auto& unload() const
{
// Execute pluggable's unloader
const std::string s{
"dfi::" + m_plug.space().outer() + "::" + m_plug.space().inner()};
::dfi::common::line(s + "::unload(\"" + m_plug.args().unload() + "\")");
// Unload pluggable file
::dfi::common::unload(m_plug.path().absolute());
return *this;
}
//! \brief Reloads a single pluggable
//! \return NPI reference
//! \ingroup cpp_plugin_impl cpp_macro_impl
//! \since docker-finance 1.1.0
const auto& reload() const { return unload().load(); }
private:
type::Pluggable<t_path, t_space, t_args> m_plug;
};
} // namespace common
} // namespace dfi