From ed163448f9dcaa23e4430cdff1b45fac2330e6df Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Wed, 3 Sep 2025 14:09:26 -0700 Subject: [PATCH 1/5] 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 $? } From 33486665a9fc14e8421ccb2135e9fe7240cc4727 Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Wed, 3 Sep 2025 14:10:24 -0700 Subject: [PATCH 2/5] container: root: rootlogon.C: remove default help() Help will automatically load when starting an interactive instance. --- container/src/root/macro/rootlogon.C | 3 --- 1 file changed, 3 deletions(-) diff --git a/container/src/root/macro/rootlogon.C b/container/src/root/macro/rootlogon.C index ab21aaa..2cb0c48 100644 --- a/container/src/root/macro/rootlogon.C +++ b/container/src/root/macro/rootlogon.C @@ -196,9 +196,6 @@ void rootlogon() gInterpreter->ProcessLine(".L ../src/hash.hh"); gInterpreter->ProcessLine(".L ../src/random.hh"); gInterpreter->ProcessLine(".L ../src/utility.hh"); - - // Help usage - help(); } #endif // CONTAINER_SRC_ROOT_MACRO_ROOTLOGON_C_ From 745a16e086c40f31ee3a15360b43fcb40203c483 Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Wed, 3 Sep 2025 14:11:03 -0700 Subject: [PATCH 3/5] container: finance: completion: add `root` --- container/src/finance/completion.bash | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/container/src/finance/completion.bash b/container/src/finance/completion.bash index d9c40d5..68d7018 100644 --- a/container/src/finance/completion.bash +++ b/container/src/finance/completion.bash @@ -135,7 +135,20 @@ function docker-finance::completion() mapfile -t _reply < <(compgen -W "help all${global_arg_delim_2} type${global_arg_delim_2} interval${global_arg_delim_2} year${global_arg_delim_2}" -- "$_cur") ;; root) - # TODO: currently no-op + compopt +o nospace + + [ -z "$DOCKER_FINANCE_CONTAINER_REPO" ] && lib_utils::die_fatal + [ -z "$DOCKER_FINANCE_CONTAINER_PLUGINS" ] && lib_utils::die_fatal + + local -r _args=("-type" "f" "-not" "-path" "*/internal/*" "-not" "-path" "*/impl/*" "-not" "-path" "*/common/*" "!" "-name" "rootlogon.C") + local _root + 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 + }) + declare -r _root + mapfile -t _reply < <(compgen -W "help ${_root[*]}" -- "$_cur") ;; taxes) mapfile -t _reply < <(compgen -W "help all${global_arg_delim_2} tag${global_arg_delim_2} account${global_arg_delim_2} year${global_arg_delim_2} write${global_arg_delim_2}" -- "$_cur") From c3364511b0ae34ea0889dd5530960fe04e40f9f1 Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Wed, 3 Sep 2025 14:11:21 -0700 Subject: [PATCH 4/5] container: finance: lib_plugins: update usage help - Clarify usage, add examples, note plugin support type * finance's `plugins` only supports shell-based plugins - To use `root` plugins, use `root` command (see `root help`) --- .../src/finance/lib/internal/lib_plugins.bash | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/container/src/finance/lib/internal/lib_plugins.bash b/container/src/finance/lib/internal/lib_plugins.bash index b9e8be2..78a8357 100644 --- a/container/src/finance/lib/internal/lib_plugins.bash +++ b/container/src/finance/lib/internal/lib_plugins.bash @@ -2,7 +2,7 @@ # docker-finance | modern accounting for the power-user # -# Copyright (C) 2024 Aaron Fiore (Founder, Evergreen Crypto LLC) +# Copyright (C) 2024-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 @@ -65,18 +65,39 @@ function lib_plugins::__parse_args() local -r _usage=" \e[32mDescription:\e[0m - Execute a categorical plugin + Execute a categorical shell plugin (non-\`root\`) + + NOTE: for \`root\` plugins, see \`root help\` \e[32mUsage:\e[0m - $ $global_usage [args] + $ $global_usage [help | [TAB COMPLETION]] [args] + +\e[32mArguments:\e[0m + + [None | help]: show this usage help + + [TAB COMPLETION]: run given shell plugin + + custom = custom plugins in custom plugin location + repo = repository plugins in repository location + + [args]: arguments to plugin \e[32mExamples:\e[0m - \e[37;2m# Execute a repository plugin in '${_repo}'\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 + custom/billing/invoice.bash help repo/timew_to_timeclock.bash + custom/billing/manage.bash repo/example.bash + + \e[37;2m# Execute a repository shell plugin in '${_repo}'\e[0m $ $global_usage repo${global_arg_delim_1}example.bash \"I'm in repo\" - \e[37;2m# Execute a custom plugin in '${_custom}'\e[0m + \e[37;2m# Execute a custom shell plugin in '${_custom}'\e[0m $ $global_usage custom${global_arg_delim_1}example.bash \"I'm in custom\" " From c68a6680efbbe83d49d2bc3b74e40ab21973754d Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Wed, 3 Sep 2025 15:21:22 -0700 Subject: [PATCH 5/5] container: finance: completion: add 'help' to `plugins` --- container/src/finance/completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/src/finance/completion.bash b/container/src/finance/completion.bash index 68d7018..ff622a4 100644 --- a/container/src/finance/completion.bash +++ b/container/src/finance/completion.bash @@ -129,7 +129,7 @@ function docker-finance::completion() find "${DOCKER_FINANCE_CONTAINER_REPO}"/plugins/finance "${_args[@]}" -printf 'repo/%P\n' 2>/dev/null }) declare -r _plugins - mapfile -t _reply < <(compgen -W "${_plugins[*]}" -- "$_cur") + mapfile -t _reply < <(compgen -W "help ${_plugins[*]}" -- "$_cur") ;; reports) mapfile -t _reply < <(compgen -W "help all${global_arg_delim_2} type${global_arg_delim_2} interval${global_arg_delim_2} year${global_arg_delim_2}" -- "$_cur")