container: finance: add lib_root impl (macro/plugin support)

- dfi's `root` now supports shell loading (and running) of
  macros/plugins:

  * An interactive ROOT.cern instance is no longer *always* needed:

    - For macros (non-server); a 'one-off' instance will start, load the
      given macro, run the macro and then the instance will exit.

      * Server(s) will maintain an interactive instance.

    - For plugins; an interactive instance will start, load the given
      plugin, and then leave the user to make their calls manually.

  * All repo macros and repo/custom plugins are available.

- By default, an interactive instance will start when no dfi `root`
  arguments are given.

See `root help` for details.
This commit is contained in:
2025-09-03 14:09:26 -07:00
parent ae32cfa229
commit ed163448f9
2 changed files with 236 additions and 5 deletions

View File

@@ -0,0 +1,234 @@
#!/usr/bin/env bash
# docker-finance | modern accounting for the power-user
#
# Copyright (C) 2025 Aaron Fiore (Founder, Evergreen Crypto LLC)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# "Libraries"
#
[ -z "$DOCKER_FINANCE_CONTAINER_REPO" ] && exit 1
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_utils.bash" || exit 1
#
# Facade
#
function lib_root::root()
{
lib_root::__parse_root "$@"
lib_root::__root "$@"
lib_utils::catch $?
}
#
# Implementation
#
function lib_root::__parse_root()
{
[ -z "$global_usage" ] && lib_utils::die_fatal
local -r _arg="$1"
local -r _usage="
\e[32mDescription:\e[0m
docker-finance ROOT.cern interpreter
\e[32mUsage:\e[0m
$ $global_usage [help | [TAB COMPLETION]] [args]
\e[32mArguments:\e[0m
None: start interactive interpreter w/out running any given macro or plugins
[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
[args]: arguments to macro or plugin
\e[32mExamples:\e[0m
\e[37;2m# See this usage help\e[0m
$ $global_usage help
\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
\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 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 repo example plugin, run within instance\e[0m
$ $global_usage plugins/repo/example.cc
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()
...
"
if [[ "$#" -gt 0 && ! "$_arg" =~ (^macro/|^plugins/) ]]; then
lib_utils::die_usage "$_usage"
fi
# Type determined by path stub
local -r _stub="${_arg#*/}"
#
# Macro
#
if [[ "$_arg" =~ ^macro ]]; then
local -r _macro="$_stub"
# Repo macros
[ ! -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="$_stub"
fi
#
# Plugin
#
if [[ "$_arg" =~ ^plugins ]]; then
local -r _plugin="${_stub#*/}"
# Repo plugins
if [[ "$_stub" =~ ^repo ]]; then
[ ! -f "${DOCKER_FINANCE_CONTAINER_REPO}/plugins/root/${_plugin}" ] \
&& lib_utils::die_fatal "repo plugin not found '${_plugin}'"
fi
# Custom plugins
if [[ "$_stub" =~ ^custom ]]; then
[ -z "$DOCKER_FINANCE_CONTAINER_PLUGINS" ] && lib_utils::die_fatal
[ ! -f "${DOCKER_FINANCE_CONTAINER_PLUGINS}/root/${_plugin}" ] \
&& lib_utils::die_fatal "custom plugin not found '${_plugin}'"
fi
# Per API (v1), requires path stub format
declare -gr global_arg_plugin="$_stub"
fi
}
function lib_root::__root()
{
lib_utils::deps_check "root"
local _exec=("root" "-l")
#
# Default
#
# Start interactive instance w/out loading/running macro or plugin
if [[ -z "$global_arg_macro" && -z "$global_arg_plugin" ]]; then
_exec+=("-e" "help()")
fi
#
# 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
#
local -r _file="${global_arg_macro##*/}"
local -r _stub="${_file^}"
local -r _class="${_stub%.*}"
local -r _caller="dfi::macro::${global_arg_macro%%/*}::${_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()"
fi
_exec+=("-e" "${_caller}::${_run}")
# NOTE: when called via shell, server(s) are implied to
# require an interactive ROOT instance (not a 'one-off').
if [[ "${_class%.*}" != "Server" ]]; then
_exec+=('-q')
fi
fi
#
# Plugin
#
if [ ! -z "$global_arg_plugin" ]; then
_exec+=("-e" "dfi::plugin::load(\"${global_arg_plugin}\")")
fi
#
# Execute
#
# NOTE:
#
# This *MUST* be executed from within the macro directory,
# regardless of what is loaded (macro or plugin), as ROOT.cern
# will automatically load rootlogon.C (which `dfi` needs).
#
# Any loading or macros of plugins from within an interactive
# instance can use be completed with the `dfi` API.
#
lib_utils::print_debug "${_exec[@]}"
pushd "${DOCKER_FINANCE_CONTAINER_REPO}/src/root/macro" 1>/dev/null \
&& "${_exec[@]}" \
|| lib_utils::die_fatal "could not pushd"
}
# vim: sw=2 sts=2 si ai et

View File

@@ -90,6 +90,7 @@ function lib_finance::finance()
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_meta.bash" || exit 1
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_plugins.bash" || exit 1
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_reports.bash" || exit 1
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_root.bash" || exit 1
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_taxes.bash" || exit 1
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_times.bash" || exit 1
}
@@ -216,11 +217,7 @@ function lib_finance::reports()
function lib_finance::root()
{
# TODO: lib_root, add extendable arguments
lib_utils::deps_check "root"
# NOTE: will automatically load rootlogon.C
pushd "${DOCKER_FINANCE_CONTAINER_REPO}/src/root/macro" 1>/dev/null \
&& root -l
lib_root::root "$@"
lib_utils::catch $?
}