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

@@ -25,7 +25,9 @@
#include <initializer_list>
#include <string>
#include <utility>
#include "../../common/type.hh"
#include "../../common/utility.hh"
//! \namespace dfi
@@ -35,7 +37,7 @@ namespace dfi
//! \namespace dfi::plugin
//! \brief docker-finance plugins
//! \warning All plugins (repo/custom) must exist within this namespace
//! and work within their own inner namespace
//! and work within their own inner namespace
//! \since docker-finance 1.0.0
namespace plugin
{
@@ -44,94 +46,544 @@ namespace plugin
//! \since docker-finance 1.1.0
namespace common
{
//! \brief Load plugin by pseudo-paths
//! \details Wrapper to load from directory outside of default tree
//! \param path Must be of string "repo/file" or "custom/file" where file is
//! a plugin filename that exists in repository plugin path or
//! a client-side end-user's custom plugin path.
//! \ingroup cpp_plugin_impl
//! \details
//! Example:
//! <br>&emsp; root [0] dfi::plugin::load("repo/example.cc")<br>
//!
//! Will load:
//! <br>&emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example.cc<br>
//!
//! Example:
//! <br>&emsp; root [0] dfi::plugin::load("custom/example.cc")<br>
//!
//! Will load:
//! <br>&emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example.cc<br>
//!
//! \note Parent directory for all plugins are outside of repository's `root` directory
void load(const std::string& path)
//! \brief Plugin's pseudo-path handler
//! \note Parent directory for all plugins are outside of repository's `root` directory
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
class PluginPath final : public ::dfi::common::PluggablePath
{
const std::string type{path.substr(0, path.find("/"))};
public:
//! \param pseudo Must be string "repo/<relative>" or "custom/<relative>"
//! where "repo" is a repository plugin, "custom" is a custom plugin,
//! and where <relative> is a pseudo-path relative to plugin's
//! operating system location.
explicit PluginPath(const std::string& pseudo)
: ::dfi::common::PluggablePath(
pseudo,
::dfi::common::type::PluggablePath(
{::dfi::common::get_env("DOCKER_FINANCE_CONTAINER_REPO")
+ "/plugins/root/",
::dfi::common::get_env("DOCKER_FINANCE_CONTAINER_PLUGINS")
+ "/root/"}))
{
}
};
std::string file{path};
file.erase(0, file.find("/") + 1);
//! \brief Plugin's namespace handler
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
class PluginSpace final : public ::dfi::common::PluggableSpace
{
public:
explicit PluginSpace(const std::string& inner)
: ::dfi::common::PluggableSpace(
::dfi::common::type::PluggableSpace({"plugin", inner}))
{
}
};
if (type == "repo")
{
file.insert(
0,
::dfi::common::get_env("DOCKER_FINANCE_CONTAINER_REPO")
+ "/plugins/root/");
}
else if (type == "custom")
{
file.insert(
0,
::dfi::common::get_env("DOCKER_FINANCE_CONTAINER_PLUGINS")
+ "/root/");
}
else
{
::dfi::common::throw_ex<::dfi::common::type::RuntimeError>(
"must be of type 'repo/<file>' or 'custom/<file>'");
}
//! \brief Plugin's arguments handler
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
class PluginArgs final : public ::dfi::common::PluggableArgs
{
public:
explicit PluginArgs(const std::string& load, const std::string& unload)
: ::dfi::common::PluggableArgs(
::dfi::common::type::PluggableArgs({load, unload}))
{
}
};
::dfi::common::Command::load(file);
//! \brief Plugin handler
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
class Plugin final
: public ::dfi::common::Pluggable<PluginPath, PluginSpace, PluginArgs>
{
public:
explicit Plugin(
::dfi::common::type::Pluggable<PluginPath, PluginSpace, PluginArgs>
plugin)
: ::dfi::common::Pluggable<PluginPath, PluginSpace, PluginArgs>(plugin)
{
}
};
} // namespace common
//
// Loader
//
//! \brief Loads a single plugin
//!
//! \param path common::PluginPath
//! \param args common::PluginArgs
//!
//! \ref dfi::plugin::common::Plugin
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::load({"repo/example/example.cc"}, {"using foo = int; foo f;"})
//!
//! Will load:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc
//!
//! and pass `using foo = int; foo f;` to the plugin's loader (plugin-implementation defined).
//!
//!
//! Example 2:
//!
//! &emsp; root [0] dfi::plugin::load({"custom/example/example.cc"}, {"using bar = char; bar b;"})
//!
//! Will load:
//!
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! and pass `using bar = char; bar b;` to the plugin's loader (plugin-implementation defined).
//!
//! \warning
//! To utilize plugin auto-loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void load(const common::PluginPath& path, const common::PluginArgs& args)
{
::dfi::common::type::
Pluggable<common::PluginPath, common::PluginSpace, common::PluginArgs>
type{path, common::PluginSpace{path.parent()}, args};
common::Plugin plugin{type};
plugin.load();
}
//! \brief Wrapper to load plugins by list of pseudo-paths
//! \ingroup cpp_plugin_impl
//! \param paths List must consist of string "repo/file" or "custom/file" where file is
//! a plugin filename that exists in repository plugin path or
//! a client-side end-user's custom plugin path.
//! \brief Convenience wrapper to load a list of single plugins with argument
//!
//! \ref load(const common::PluginPath& path, const common::PluginArgs& args)
//!
//! \param plugins List of single plugins
//!
//! \details
//!
//! Example:
//! <br>&emsp; root [0] dfi::plugin::load({"repo/example.cc", "custom/example.cc"})<br>
//!
//! &emsp; root [0] dfi::plugin::load({{"repo/example/example.cc", "using foo = int; foo f;"}, {"custom/example/example.cc", "using bar = char; bar b;"}})
//!
//! Will load:
//! <br>&emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example.cc
//! and ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example.cc<br>
//!
//! \note Parent directory for all plugins are outside of repository's `root` directory
//! &emsp; `${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc`\n
//! &emsp; `${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc`
//!
//! And pass plugin-implementation defined auto-loader arguments:
//!
//! &emsp; `using foo = int; foo f;` for `${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc`\n
//! &emsp; `using bar = char; bar b;` for `${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc`
//!
//! \warning
//! To utilize plugin auto-loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void load(
const std::initializer_list<
std::pair<common::PluginPath, common::PluginArgs>>& plugins)
{
for (const auto& plugin : plugins)
load(plugin.first, plugin.second);
}
//! \brief Convenience wrapper to load a single plugin with optional argument
//!
//! \ref load(const common::PluginPath& path, const common::PluginArgs& args)
//!
//! \param path Must be of string "repo/<relative>" or "custom/<relative>" to plugin location, as described by common::PluginPath
//!
//! \param arg Optional string argument to pass to plugin's auto-loader (plugin-implementation defined)
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::load("repo/example.cc", "using foo = int; foo f;")
//!
//! Will load:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc
//!
//! and pass `using foo = int; foo f;` to the plugin's loader (plugin-implementation defined).
//!
//!
//! Example 2:
//!
//! &emsp; root [0] dfi::plugin::load("custom/example/example.cc", "using bar = char; bar b;")
//!
//! Will load:
//!
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! and pass `using bar = char; bar b;` to the plugin's loader (plugin-implementation defined).
//!
//! \warning
//! To utilize plugin auto-loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void load(const std::string& path, const std::string& arg = {})
{
load(common::PluginPath{path}, common::PluginArgs{arg, ""});
}
//! \brief Convenience wrapper to load a list of plugins with no arguments
//!
//! \ref load(const std::string& path, const std::string& arg)
//!
//! \param path Must be of string "repo/<relative>" or "custom/<relative>" to plugin location, as described by common::PluginPath
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::load({"repo/example/example.cc", "custom/example/example.cc"})
//!
//! Will load both:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc\n
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! \warning
//! To utilize plugin auto-loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.0.0
void load(const std::initializer_list<std::string>& paths)
{
for (const auto& path : paths)
::dfi::plugin::common::load(path);
load(path);
}
} // namespace common
//! \brief Convenience wrapper to inner common loader
//
// Unloader
//
//! \brief Unloads a single plugin
//!
//! \param path common::PluginPath
//! \param args common::PluginArgs
//!
//! \ref dfi::plugin::common::Plugin
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::unload({"repo/example/example.cc"}, {"using foo = int; foo f;"})
//!
//! Will unload:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc
//!
//! and pass `using foo = int; foo f;` to the plugin's unloader (plugin-implementation defined).
//!
//!
//! Example 2:
//!
//! &emsp; root [0] dfi::plugin::unload({"custom/example/example.cc"}, {"using bar = char; bar b;"})
//!
//! Will unload:
//!
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! and pass `using bar = char; bar b;` to the plugin's unloader (plugin-implementation defined).
//!
//! \warning
//! To utilize plugin auto-unloader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-unloader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void load(const std::string& path)
void unload(const common::PluginPath& path, const common::PluginArgs& args)
{
common::load(path);
::dfi::common::type::
Pluggable<common::PluginPath, common::PluginSpace, common::PluginArgs>
type{path, common::PluginSpace{path.parent()}, args};
common::Plugin plugin{type};
plugin.unload();
}
//! \brief Convenience wrapper to inner common loader
//! \brief Convenience wrapper to unload a list of single plugins with arguments
//!
//! \ref unload(const common::PluginPath& path, const common::PluginArgs& args)
//!
//! \param plugins List of single plugins
//!
//! \details
//!
//! Example:
//!
//! &emsp; root [0] dfi::plugin::unload({{"repo/example/example.cc", "using foo = int; foo f;"}, {"custom/example/example.cc", "using bar = char; bar b;"}})
//!
//! Will unload:
//!
//! &emsp; `${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc`\n
//! &emsp; `${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc`
//!
//! And pass plugin-implementation defined auto-unloader arguments:
//!
//! &emsp; `using foo = int; foo f;` for `${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc`\n
//! &emsp; `using bar = char; bar b;` for `${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc`
//!
//! \warning
//! To utilize plugin auto-unloader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-unloader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void load(const std::initializer_list<std::string>& paths)
void unload(
const std::initializer_list<
std::pair<common::PluginPath, common::PluginArgs>>& plugins)
{
common::load(paths);
for (const auto& plugin : plugins)
unload(plugin.first, plugin.second);
}
// TODO(afiore): unload
//! \brief Convenience wrapper to unload a single plugin with optional argument
//!
//! \ref unload(const common::PluginPath& path, const common::PluginArgs& args)
//!
//! \param path Must be of string "repo/<relative>" or "custom/<relative>" to plugin location, as described by common::PluginPath
//!
//! \param arg Optional string argument to pass to plugin's auto-unloader (plugin-implementation defined)
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::unload("repo/example/example.cc", "using foo = int; foo f;")
//!
//! Will unload:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc
//!
//! and pass `using foo = int; foo f;` to the plugin's unloader (plugin-implementation defined).
//!
//!
//! Example 2:
//!
//! &emsp; root [0] dfi::plugin::unload("custom/example/example.cc", "using bar = char; bar b;")
//!
//! Will unload:
//!
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! and pass `using bar = char; bar b;` to the plugin's unloader (plugin-implementation defined).
//!
//! \warning
//! To utilize plugin auto-unloader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-unloader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void unload(const std::string& path, const std::string& arg = {})
{
unload(common::PluginPath{path}, common::PluginArgs{"", arg});
}
//! \brief Convenience wrapper to unload a list of plugins with no arguments
//!
//! \ref unload(const std::string& path, const std::string& arg)
//!
//! \param path Must be of string "repo/<relative>" or "custom/<relative>" to plugin location, as described by common::PluginPath
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::unload({"repo/example/example.cc", "custom/example/example.cc"})
//!
//! Will unload both:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc\n
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! \warning
//! To utilize plugin auto-unloader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-unloader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void unload(const std::initializer_list<std::string>& paths)
{
for (const auto& path : paths)
unload(path);
}
//
// Reloader
//
//! \brief Reloads a single plugin
//!
//! \param path common::PluginPath
//! \param args common::PluginArgs
//!
//! \ref dfi::plugin::common::Plugin
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::reload({"repo/example/example.cc"}, {"using foo = int; foo f;", "using bar = char; bar b;"})
//!
//! Will reload:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc
//!
//! and pass `using bar = char; bar b;` to the plugin's unloader (plugin-implementation defined)
//! and pass `using foo = int; foo f;` to the plugin's loader (plugin-implementation defined)
//!
//! \warning
//! To utilize plugin auto-(un)loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-(un)loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void reload(const common::PluginPath& path, const common::PluginArgs& args)
{
::dfi::common::type::
Pluggable<common::PluginPath, common::PluginSpace, common::PluginArgs>
type{path, common::PluginSpace{path.parent()}, args};
common::Plugin plugin{type};
plugin.reload();
}
//! \brief Convenience wrapper to unload a list of single plugins with arguments
//!
//! \ref reload(const common::PluginPath& path, const common::PluginArgs& args)
//!
//! \param plugins List of populated pair of common::PluginPath and common::PluginArgs
//!
//! \details
//!
//! Example:
//! &emsp; root [0] dfi::plugin::reload({{"repo/example/example.cc", "using foo = int; foo f;"}, {"custom/example/example.cc", "using bar = char; bar b;"}})
//!
//! Will reload:
//!
//! &emsp; `${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc`\n
//! &emsp; `${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc`
//!
//! And pass plugin-implementation defined auto-loader arguments:
//!
//! &emsp; `using foo = int; foo f;` for `${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc`\n
//! &emsp; `using bar = char; bar b;` for `${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc`
//!
//! \warning
//! To utilize plugin auto-(un)loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-(un)loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void reload(
const std::initializer_list<
std::pair<common::PluginPath, common::PluginArgs>>& plugins)
{
for (const auto& plugin : plugins)
reload(plugin.first, plugin.second);
}
//! \brief Convenience wrapper to reload a single plugin with optional arguments
//!
//! \ref reload(const common::PluginPath& path, const common::PluginArgs& args)
//!
//! \param path Must be of string "repo/<relative>" or "custom/<relative>" to plugin location, as described by common::PluginPath
//!
//! \param args Optional pair of arguments to pass to plugin's auto-loader/unloader (plugin-implementation defined)
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::reload("repo/example/example.cc", {"using foo = int; foo f;", ""})
//!
//! Will reload:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc
//!
//! and pass `using foo = int; foo f;` to the plugin's auto-loader (plugin-implementation defined).
//!
//!
//! Example 2:
//!
//! &emsp; root [0] dfi::plugin::reload("custom/example/example.cc", {"", "using bar = char; bar b;"})
//!
//! Will reload:
//!
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! and pass `using bar = char; bar b;` to the plugin's auto-unloader (plugin-implementation defined).
//!
//! \warning
//! To utilize plugin auto-(un)loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-(un)loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void reload(
const std::string& path,
const std::pair<std::string, std::string>& args = {})
{
reload(common::PluginPath{path}, common::PluginArgs{args.first, args.second});
}
//! \brief Convenience wrapper to reload a list of plugins with no arguments
//!
//! \ref reload(const std::string& path, const std::string& arg)
//!
//! \param path Must be of string "repo/<relative>" or "custom/<relative>" to plugin location, as described by common::PluginPath
//!
//! \details
//!
//! Example 1:
//!
//! &emsp; root [0] dfi::plugin::reload({"repo/example/example.cc", "custom/example/example.cc"})
//!
//! Will reload both:
//!
//! &emsp; ${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc\n
//! &emsp; ${DOCKER_FINANCE_CLIENT_PLUGINS}/root/example/example.cc
//!
//! \warning
//! To utilize plugin auto-(un)loader functionality, the plugin's parent directory *MUST* align with the plugin's namespace;\n
//! e.g., "repo/example/example.cc" requires the plugin's auto-(un)loader to exist in the namespace `dfi::plugin::example`.\n
//! To avoid this requisite, though ill-advised, use a `dfi::common` file loader instead.
//!
//! \ingroup cpp_plugin
//! \since docker-finance 1.1.0
void reload(const std::initializer_list<std::string>& paths)
{
for (const auto& path : paths)
reload(path);
}
} // namespace plugin
} // namespace dfi