From ed163448f9dcaa23e4430cdff1b45fac2330e6df Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Wed, 3 Sep 2025 14:09:26 -0700 Subject: [PATCH] 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. --- .../src/finance/lib/internal/lib_root.bash | 234 ++++++++++++++++++ container/src/finance/lib/lib_finance.bash | 7 +- 2 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 container/src/finance/lib/internal/lib_root.bash diff --git a/container/src/finance/lib/internal/lib_root.bash b/container/src/finance/lib/internal/lib_root.bash new file mode 100644 index 0000000..7febbd4 --- /dev/null +++ b/container/src/finance/lib/internal/lib_root.bash @@ -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 . + +# +# "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 diff --git a/container/src/finance/lib/lib_finance.bash b/container/src/finance/lib/lib_finance.bash index 16da9b0..dbc734d 100644 --- a/container/src/finance/lib/lib_finance.bash +++ b/container/src/finance/lib/lib_finance.bash @@ -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 $? }