container: finance: lib_root/completion: add Pluggable support

- Adds Pluggable auto-(un)load support
- Adds plugin dispatcher for interpreter
- Refactors to support new Pluggable system
- Updates usage help and improves documentation
This commit is contained in:
2025-12-19 10:59:30 -08:00
parent 2a6504f872
commit e77c25f0c9
2 changed files with 100 additions and 63 deletions

View File

@@ -145,7 +145,7 @@ function docker-finance::completion()
mapfile -t _root < <({
find "${DOCKER_FINANCE_CONTAINER_PLUGINS}"/root "${_args[@]}" -printf 'plugins/custom/%P\n' 2>/dev/null
find "${DOCKER_FINANCE_CONTAINER_REPO}"/plugins/root "${_args[@]}" -printf 'plugins/repo/%P\n' 2>/dev/null
find "${DOCKER_FINANCE_CONTAINER_REPO}"/src/root/macro "${_args[@]}" -printf 'macro/%P\n' 2>/dev/null
find "${DOCKER_FINANCE_CONTAINER_REPO}"/src/root/macro "${_args[@]}" -printf 'macros/repo/%P\n' 2>/dev/null
})
declare -r _root
mapfile -t _reply < <(compgen -W "help ${_root[*]}" -- "$_cur")

View File

@@ -59,9 +59,10 @@ function lib_root::__parse_root()
[TAB COMPLETION]: load (and run) given macro/plugin when starting interpreter
macro = repository macros in repository location
plugins/custom = custom plugins in custom plugin location
plugins/repo = repository plugins in repository location
macros/repo = macros in repository location
macros/custom = WIP (not currently supported)
plugins/repo = plugins in repository location
plugins/custom = plugins in custom plugin location
[args]: arguments to macro or plugin
@@ -72,30 +73,49 @@ function lib_root::__parse_root()
\e[37;2m# The output of tab completion\e[0m
$ $global_usage \\\t\\\t
help macro/crypto/random.C macro/test/unit.C plugins/custom/example.cc
macro/crypto/hash.C macro/test/benchmark.C macro/web/server.C plugins/repo/example.cc
help macros/repo/test/benchmark.C plugins/custom/example.cc
macros/repo/crypto/hash.C macros/repo/test/unit.C plugins/repo/bitcoin/bitcoin.cc
macros/repo/crypto/random.C macros/repo/web/server.C plugins/repo/example/example.cc
\e[37;2m# Load and run the repo's Web server macro\e[0m
\e[37;2m# NOTE: this macro will maintain a running ROOT instance\e[0m
$ $global_usage macro/web/server.C
\e[37;2m# Load and run repo's Web server macro\e[0m
\e[37;2m# NOTE: only server macros will maintain a running ROOT instance (others will 'one-off')\e[0m
$ $global_usage macros/repo/web/server.C
\e[37;2m# Load and run the repo's Hash macro with an argument\e[0m
\e[37;2m# NOTE: this macro will 'one-off' the ROOT instance\e[0m
$ $global_usage macro/crypto/hash.C 'this is a test message'
\e[37;2m# Load and run repo's Hash macro with loader argument 'this is a test message'\e[0m
$ $global_usage macros/repo/crypto/hash.C 'this is a test message'
\e[37;2m# Load repo example plugin, run within instance\e[0m
$ $global_usage plugins/repo/example.cc
\e[37;2m# Load and run unit tests for only Pluggable types, using filter as loader argument\e[0m
$ $global_usage macros/repo/test/unit.C 'Pluggable*:Macro*:Plugin*'
\e[37;2m# Load and use repo's example plugin, dispatching with trivial loader and unloader arguments\e[0m
$ $global_usage plugins/repo/example/example.cc 'int i{123}; i' 'std::cout << --i << std::endl;'
root [0]
Interpreting: '.L /home/business/docker-finance/plugins/root/example.cc'
root [1] dfi::plugin::my_plugin_namespace::example\\\t
example1
example2
example3
root [1] dfi::plugin::my_plugin_namespace::example2()
Processing macro/rootlogon.C...
...
Loading: '${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/example/example.cc'
Interpreting: 'dfi::plugin::example::example_cc::load(\"int i{123}; i\")'
...
Interpreting: 'int i{123}; i'
(int) 123
root [3] dfi::plugin::example::Example e; // Plugin's Example class is now available to use
root [4] e.example2()
...
root [5] g_plugin_example_cc.unload() // Plugin is a global instance based on Pluggable entrypoint name
Interpreting: 'dfi::plugin::example::example_cc::unload(\"std::cout << --i << std::endl;\")'
Interpreting: 'std::cout << --i << std::endl;'
122
...
root [6] e.example2() // Since plugin has been unloaded, this is expected to not compile
input_line_27:2:3: error: use of undeclared identifier 'e'
(e.example2())
^
Error in <HandleInterpreterException>: Error evaluating expression (e.example2())
Execution of your code was aborted.
root [7] .quit
$
"
if [[ "$#" -gt 0 && ! "$_arg" =~ (^macro/|^plugins/) ]]; then
if [[ "$#" -gt 0 && ! "$_arg" =~ (^macros/|^plugins/) ]]; then
lib_utils::die_usage "$_usage"
fi
@@ -106,19 +126,18 @@ function lib_root::__parse_root()
# Macro
#
if [[ "$_arg" =~ ^macro ]]; then
# TODO: utilize stub when 'repo/' and 'custom/' is implemented (like plugins)
local -r _macro="$_arg"
if [[ "$_arg" =~ ^macros ]]; then
local -r _macro="${_stub#*/}"
# Repo macros
[ ! -f "${DOCKER_FINANCE_CONTAINER_REPO}/src/root/${_macro}" ] \
[ ! -f "${DOCKER_FINANCE_CONTAINER_REPO}/src/root/macro/${_macro}" ] \
&& lib_utils::die_fatal "repo macro not found '${_macro}'"
# TODO: currently, only repo macros are supported
# (no custom macros, use custom plugins instead)
# Per API (v1), requires path stub format
declare -gr global_arg_macro="$_macro"
declare -gr global_arg_macro="$_stub"
fi
#
@@ -181,64 +200,82 @@ function lib_root::__root()
fi
#
# Macro: non-interactive opts
# Pluggable's arguments
#
local _load_arg="${*:2:1}"
local _unload_arg="${*:3}"
#
# Pluggable: macro
#
if [ ! -z "$global_arg_macro" ]; then
_exec+=("-e" "dfi::macro::load(\"${global_arg_macro}\")")
# NOTE:
#
# - Currently, only repo macros are supported
#
# * Per API (v1):
#
# - filename is named after class
# * classes begin uppercase
# * filename begins lowercase
#
# - run() static function is the entry point for all macros
#
# TODO: modify substring when 'repo/' and 'custom/' is implemented (like plugins)
local _namespace="${global_arg_macro#*/}"
_namespace="${_namespace%/*}"
declare -r _namespace
local _class="${global_arg_macro##*/}"
_class="${_class^}"
_class="${_class%.*}"
declare -r _class
local -r _caller="dfi::macro::${_namespace}::${_class}"
# Per-API signatures (v1)
if [[ ${_class} == "Hash" ]]; then
# If no message is given, default to unique profile/subprofile
[[ -z "$global_parent_profile" || -z "$global_arg_delim_1" || -z "$global_child_profile" ]] && lib_utils::die_fatal
[ -z "${*:2}" ] \
&& _run="run(\"${global_parent_profile}${global_arg_delim_1}${global_child_profile}\")" \
|| _run="run(\"${*:2}\")"
else
_run="run()"
# NOTE: for benchmarks, google benchmark does not have a gtest equivalent of
# `GTEST_FLAG(filter)`, but will process shell environment `BENCHMARK_FILTER`
if [[ "${_class%.*}" == "Benchmark" ]]; then
declare -grx BENCHMARK_FILTER="$_load_arg"
# Current impl will treat the load arg as code which, in this case, is not
unset _load_arg
fi
_exec+=("-e" "${_caller}::${_run}")
_exec+=("-e" "dfi::macro::load(\"${global_arg_macro}\", \"${_load_arg}\")")
# NOTE: when called via shell, server(s) are implied to
# require an interactive ROOT instance (not a 'one-off').
# NOTE: when calling via shell, server(s) are implied to
# require an interactive ROOT instance (not a 'one-off')
if [[ "${_class%.*}" != "Server" ]]; then
_exec+=('-q')
fi
# NOTE: currently no need to "dfi::macro::unload()" from here
fi
#
# Plugin: non-interactive opts
# Pluggable: plugin
#
if [ ! -z "$global_arg_plugin" ]; then
_exec+=("-e" "dfi::plugin::load(\"${global_arg_plugin}\")")
#
# Instead of using one-off wrappers, the following leaves the plugin's state
# intact during interpreter runtime.
#
# WARNING:
#
# The dispatcher (or any declarations outside of the dispatcher)
# will appear in the global namespace of the interpreter!
#
# Gives plugin a global instance based on Pluggable entrypoint name
local _name="${global_arg_plugin##*/}"
_name="g_plugin_${_name%.*}_${_name#*.}"
declare -r _name
local _code
_code+="using t_path = dfi::plugin::common::PluginPath;"
_code+="t_path path{\"${global_arg_plugin}\"};"
_code+="using t_space = dfi::plugin::common::PluginSpace;"
_code+="t_space space{path.parent(), path.child()};"
_code+="using t_args = dfi::plugin::common::PluginArgs;"
_code+="t_args args{\"${_load_arg}\", \"${_unload_arg}\"};"
_code+="dfi::common::type::Pluggable<t_path, t_space, t_args> type{path, space, args};"
_code+="dfi::plugin::common::Plugin plugin{type};"
local _dispatch="auto dispatch = []() -> auto { $_code; return plugin; };"
_dispatch+="auto ${_name} = dispatch();"
# NOTE: unload() at will, inside the interpreter
_exec+=("-e" "$_dispatch" "-e" "${_name}.load();")
fi
#