diff --git a/client/docker-finance.d/container/hledger/hledger.conf.in b/client/docker-finance.d/container/hledger/hledger.conf.in new file mode 100644 index 0000000..ef3e290 --- /dev/null +++ b/client/docker-finance.d/container/hledger/hledger.conf.in @@ -0,0 +1,29 @@ +# docker-finance | modern accounting for the power-user +# +# Copyright (C) 2024 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 . + +# NOTE: +# - File is treated as a plain configuration file (non-executable) +# - Only functional for `hledger 1.34.99-gcf0c7c2ef-20240702` or later +# - See sample: https://github.com/simonmichael/hledger/blob/master/hledger.conf.sample + +[balance] not:desc:balances not:equity:balances not:archive +[print] not:desc:balances not:equity:balances not:archive + +[ui] assets liabilities not:desc:balances not:equity:balances not:archive +[web] --serve --host=0.0.0.0 --base-url http://127.0.0.1:5000 --capabilities=view assets liabilities not:desc:balances not:equity:balances not:archive + +# vim: syn=bash sw=2 sts=2 si ai et diff --git a/client/docker-finance.d/container/shell/subprofile.bash.in b/client/docker-finance.d/container/shell/subprofile.bash.in index 80217b6..bd77453 100644 --- a/client/docker-finance.d/container/shell/subprofile.bash.in +++ b/client/docker-finance.d/container/shell/subprofile.bash.in @@ -26,6 +26,7 @@ [ -z "$DOCKER_FINANCE_CONTAINER_CMD" ] && echo "DOCKER_FINANCE_CONTAINER_CMD not set, check installation" >&2 # NOTE: ledger-based commands ending in `\`: the escape is needed so you can add more arguments as needed +# TODO: remove default 'not:' arguments to `ledger{-ui,-web}` once distributions support hledger 1.34.99-gcf0c7c2ef-20240702 or higher (--conf support) # CLI ledger alias @DOCKER_FINANCE_SUBPROFILE@_ledger='$DOCKER_FINANCE_CONTAINER_CMD @DOCKER_FINANCE_PROFILE@/@DOCKER_FINANCE_SUBPROFILE@ ledger' diff --git a/client/src/docker/lib/internal/lib_gen.bash b/client/src/docker/lib/internal/lib_gen.bash index dc1686b..aedb759 100644 --- a/client/src/docker/lib/internal/lib_gen.bash +++ b/client/src/docker/lib/internal/lib_gen.bash @@ -322,7 +322,7 @@ function lib_gen::__gen_container() lib_utils::print_debug "Generating container" # - # Check to proceed + # Check to proceed with profile-specific # lib_utils::print_custom " \e[32m│\e[0m\n" @@ -377,7 +377,7 @@ function lib_gen::__gen_container() lib_utils::print_custom " \e[32m│ │ └─\e[34m Using subprofile:\e[0m ${_subprofile}\n" # - # Execute + # Execute profile-specific # lib_utils::print_custom " \e[32m│ │\e[0m\n" @@ -394,7 +394,7 @@ function lib_gen::__gen_container() fi lib_utils::print_custom " \e[32m│ │\e[0m\n" - lib_utils::print_custom " \e[32m│ ├─\e[34;1m Generate (or update) container hledger-flow configs and/or accounts? [Y/n] \e[0m" + lib_utils::print_custom " \e[32m│ ├─\e[34;1m Generate (or update) hledger configuration file? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then @@ -403,6 +403,19 @@ function lib_gen::__gen_container() lib_utils::print_debug "_gen_path=${_gen_path}" lib_utils::print_debug "_gen_conf_path=${_gen_conf_path}" + lib_gen::__gen_hledger "$_gen_path" "$_gen_conf_path" + fi + + lib_utils::print_custom " \e[32m│ │\e[0m\n" + lib_utils::print_custom " \e[32m│ ├─\e[34;1m Generate (or update) container hledger-flow configs and/or accounts? [Y/n] \e[0m" + read -p "" _read + _confirm="${_read:-y}" + if [[ "$_confirm" == [yY] ]]; then + local _gen_path="${DOCKER_FINANCE_CLIENT_FLOW}/profiles/${_profile}/${_subprofile}" + local _gen_conf_path="${_gen_path}/docker-finance.d" + lib_utils::print_debug "_gen_path=${_gen_path}" + lib_utils::print_debug "_gen_conf_path=${_gen_conf_path}" + lib_gen::__gen_flow "$_gen_path" "$_gen_conf_path" fi } @@ -475,6 +488,53 @@ function lib_gen::__gen_shell_write() "${global_repo_conf_dir}/container/shell/superscript.bash.in" >"$_file" } +# +# Generate hledger configuration file +# + +function lib_gen::__gen_hledger() +{ + local -r _gen_path="$1" + local -r _gen_conf_path="$2" + + local _dir="${_gen_conf_path}/hledger" + [ ! -d "$_dir" ] && mkdir -p "$_dir" + + local _file="${_dir}/hledger.conf" + if [ -f "$_file" ]; then + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m hledger configuration found, backup then generate new one? [Y/n] \e[0m" + read -p "" _read + _confirm="${_read:-y}" + + if [[ "$_confirm" == [yY] ]]; then + cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal + + lib_gen::__gen_hledger_write "$_file" + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) hledger configuration now? [Y/n] \e[0m" + fi + else + lib_gen::__gen_hledger_write "$_file" + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) hledger configuration now? [Y/n] \e[0m" + fi + + lib_utils::print_custom "$_print_custom" + read -p "" _read + _confirm="${_read:-y}" + [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" +} + +function lib_gen::__gen_hledger_write() +{ + local _file="$1" + [ -z "$_file" ] && lib_utils::die_fatal + + [ -z "$DOCKER_FINANCE_VERSION" ] && lib_utils::die_fatal + [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal + sed \ + -e "s:@DOCKER_FINANCE_VERSION@:${DOCKER_FINANCE_VERSION}:g" \ + "${global_repo_conf_dir}/container/hledger/hledger.conf.in" >"$_file" +} + # # Generate hledger-flow # diff --git a/container/src/finance/lib/internal/lib_edit.bash b/container/src/finance/lib/internal/lib_edit.bash index 7027a3d..5078cda 100644 --- a/container/src/finance/lib/internal/lib_edit.bash +++ b/container/src/finance/lib/internal/lib_edit.bash @@ -59,7 +59,7 @@ function lib_edit::__parse_args() Configuration type: - type${global_arg_delim_2} + type${global_arg_delim_2} Account: @@ -70,6 +70,9 @@ function lib_edit::__parse_args() \e[37;2m# Edit fetch configuration\e[0m $ $global_usage type${global_arg_delim_2}fetch + \e[37;2m# Edit hledger configuration\e[0m + $ $global_usage type${global_arg_delim_2}hledger + \e[37;2m# Edit meta and subprofile configurations\e[0m $ $global_usage type${global_arg_delim_2}meta${global_arg_delim_3}shell @@ -157,7 +160,7 @@ function lib_edit::__parse_args() read -ra _read <<<"$_arg_type" for _type in "${_read[@]}"; do - if [[ ! "$_type" =~ ^fetch$|^iadd$|^manual$|^meta$|^preprocess$|^rules$|^shell$ ]]; then + if [[ ! "$_type" =~ ^fetch$|^hledger$|^iadd$|^manual$|^meta$|^preprocess$|^rules$|^shell$ ]]; then lib_utils::die_usage "$_usage" fi if [[ ! -z "$_arg_account" ]]; then @@ -198,18 +201,45 @@ function lib_edit::__parse_args() function lib_edit::__edit() { [ -z "$EDITOR" ] && lib_utils::die_fatal - [ -z "$global_child_profile" ] && lib_utils::die_fatal - [ -z "$global_child_profile_flow" ] && lib_utils::die_fatal - [ -z "$global_conf_fetch" ] && lib_utils::die_fatal - [ -z "$global_conf_meta" ] && lib_utils::die_fatal - [ -z "$global_conf_shell" ] && lib_utils::die_fatal for _type in "${global_arg_type[@]}"; do case "$_type" in fetch) - $EDITOR "$global_conf_fetch" + [ -z "$global_conf_fetch" ] && lib_utils::die_fatal + + local _dir + _dir="$(dirname $global_conf_fetch)" + [ ! -d "$_dir" ] && mkdir -p "$_dir" + + local _file + _file="$(basename $global_conf_fetch)" + + local _path + _path="${_dir}/${_file}" + [ ! -f "$_path" ] && touch "$_path" + + $EDITOR "$_path" || lib_utils::die_fatal + ;; + hledger) + [ -z "$global_conf_hledger" ] && lib_utils::die_fatal + + local _dir + _dir="$(dirname $global_conf_hledger)" + [ ! -d "$_dir" ] && mkdir -p "$_dir" + + local _file + _file="$(basename $global_conf_hledger)" + + local _path + _path="${_dir}/${_file}" + [ ! -f "$_path" ] && touch "$_path" + + $EDITOR "$_path" || lib_utils::die_fatal ;; iadd | manual) + [ -z "$global_child_profile" ] && lib_utils::die_fatal + [ -z "$global_child_profile_flow" ] && lib_utils::die_fatal + local _path="${global_child_profile_flow}/import/${global_child_profile}/_manual_/${global_arg_year}" [ ! -d "$_path" ] && mkdir -p "$_path" @@ -223,16 +253,29 @@ function lib_edit::__edit() [[ "$_type" == "manual" ]] && $EDITOR "$_path" ;; meta) + [ -z "$global_conf_meta" ] && lib_utils::die_fatal + + local _dir + _dir="$(dirname $global_conf_meta)" + [ ! -d "$_dir" ] && mkdir -p "$_dir" + + local _file + _file="$(basename $global_conf_meta)" + + local _path + _path="${_dir}/${_file}" + [ ! -f "$_path" ] && touch "$_path" + # NOTE: # - Expects comments to begin with format: //! # - If saved to original, opening with `--skip` will clobber the # original file's comments. - local -r _skip="$(grep -E "^//!" $global_conf_meta | wc -l)" - visidata --quitguard --motd-url file:///dev/null --filetype csv --skip "$_skip" "$global_conf_meta" + local -r _skip="$(grep -E "^//!" $_path | wc -l)" + visidata --quitguard --motd-url file:///dev/null --filetype csv --skip "$_skip" "$_path" # TODO: HACK: visidata saves w/ DOS-style carriage... # ...but there seems to be no option out of this. - sed -i 's:\r::g' "$global_conf_meta" + sed -i 's:\r::g' "$_path" ;; preprocess | rules) # Run all paths through one editor instance @@ -271,10 +314,23 @@ function lib_edit::__edit() done # Execute - $EDITOR "${_paths[@]}" + $EDITOR "${_paths[@]}" || lib_utils::die_fatal ;; shell) - $EDITOR "$global_conf_shell" + [ -z "$global_conf_shell" ] && lib_utils::die_fatal + + local _dir + _dir="$(dirname $global_conf_shell)" + [ ! -d "$_dir" ] && mkdir -p "$_dir" + + local _file + _file="$(basename $global_conf_shell)" + + local _path + _path="${_dir}/${_file}" + [ ! -f "$_path" ] && touch "$_path" + + $EDITOR "$_path" || lib_utils::die_fatal ;; esac done diff --git a/container/src/finance/lib/internal/lib_ledger.bash b/container/src/finance/lib/internal/lib_ledger.bash index 7eaf7d9..7c68ba3 100644 --- a/container/src/finance/lib/internal/lib_ledger.bash +++ b/container/src/finance/lib/internal/lib_ledger.bash @@ -30,30 +30,35 @@ source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_utils.bash function lib_ledger::ledger-import() { + lib_ledger::__ledger "$@" lib_ledger::__ledger-import "$@" lib_utils::catch $? } function lib_ledger::ledger-cli() { + lib_ledger::__ledger "$@" lib_ledger::__ledger-cli "$@" lib_utils::catch $? } function lib_ledger::ledger-ui() { + lib_ledger::__ledger "$@" lib_ledger::__ledger-ui "$@" lib_utils::catch $? } function lib_ledger::ledger-vui() { + lib_ledger::__ledger "$@" lib_ledger::__ledger-vui "$@" lib_utils::catch $? } function lib_ledger::ledger-web() { + lib_ledger::__ledger "$@" lib_ledger::__ledger-web "$@" lib_utils::catch $? } @@ -62,7 +67,38 @@ function lib_ledger::ledger-web() # Implementation # -function lib_ledger::__ledger-import() +# Constructor +function lib_ledger::__ledger() +{ + # Base arguments to hledger before end-user added + [ -z "$global_child_profile_journal" ] && lib_utils::die_fatal + declare -g global_base_args=("-f" "$global_child_profile_journal") + + # + # Apply features to given hledger version. + # + # CLI version formats: + # + # hledger 1.34, linux-x86_64 + # hledger 1.34.99-gcf0c7c2ef-20240702, linux-x86_64 + # + # TODO: hope that container platforms will package 1.35 before 1.40 rolls out + # + + # 1.34.99 and above + hledger --version \ + | gawk '{ if ($2 !~ /^1.3(4.99|([5-9][\.[0-99]?))/) { exit 1 } }' FS=' ' + + if [ $? -eq 0 ]; then + # --conf is supported + [ -z "$global_conf_hledger" ] && lib_utils::die_fatal + global_base_args+=("--conf" "$global_conf_hledger") + fi + + lib_utils::print_debug "${global_base_args[*]}" "$@" +} + +function lib_ledger::__parse_ledger-import() { [ -z "$global_usage" ] && lib_utils::die_fatal [ -z "$global_arg_delim_1" ] && lib_utils::die_fatal @@ -80,7 +116,7 @@ function lib_ledger::__ledger-import() \e[32mArguments:\e[0m - Fetch year: + Import year: year${global_arg_delim_2} @@ -94,13 +130,11 @@ function lib_ledger::__ledger-import() lib_utils::die_usage "$_usage" fi - # Get/Set year if [ ! -z "$_arg" ]; then if [[ ! "$_arg" =~ ^year[s]?${global_arg_delim_2} ]]; then lib_utils::die_usage "$_usage" fi - # Parse key for value local _key="${_arg%${global_arg_delim_2}*}" local _len="$((${#_key} + 1))" if [[ "$_key" =~ ^year[s]?$ ]]; then @@ -122,31 +156,44 @@ function lib_ledger::__ledger-import() global_arg_year="$(date +%Y)" declare -gr global_arg_year fi +} - [ -z "$global_child_profile_journal" ] && lib_utils::die_fatal - time /usr/local/bin/hledger-flow import \ - "$(dirname $global_child_profile_journal)" --start-year "$global_arg_year" +function lib_ledger::__ledger-import() +{ + lib_ledger::__parse_ledger-import "$@" + + time hledger-flow import \ + "$(dirname $global_child_profile_journal)" \ + --start-year "$global_arg_year" } function lib_ledger::__ledger-cli() { - /usr/bin/hledger -f "$global_child_profile_journal" "$@" + [ -z "${global_base_args[*]}" ] && lib_utils::die_fatal + + hledger "${global_base_args[@]}" "$@" } function lib_ledger::__ledger-ui() { - /usr/bin/hledger-ui -f "$global_child_profile_journal" "$@" + [ -z "${global_base_args[*]}" ] && lib_utils::die_fatal + + hledger-ui "${global_base_args[@]}" "$@" } function lib_ledger::__ledger-vui() { - /usr/bin/hledger -f "$global_child_profile_journal" print -O csv "$@" \ + [ -z "${global_base_args[*]}" ] && lib_utils::die_fatal + + hledger "${global_base_args[@]}" print -O csv "$@" \ | visidata --motd-url file:///dev/null --filetype csv } function lib_ledger::__ledger-web() { - /usr/bin/hledger-web -f "$global_child_profile_journal" "$@" + [ -z "${global_base_args[*]}" ] && lib_utils::die_fatal + + hledger-web "${global_base_args[@]}" "$@" } # 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 022afbc..3c872b1 100644 --- a/container/src/finance/lib/lib_finance.bash +++ b/container/src/finance/lib/lib_finance.bash @@ -78,6 +78,7 @@ function lib_finance::finance() # Globals (configuration) declare -grx global_conf_fetch="${global_child_profile_flow}/docker-finance.d/fetch/fetch.yaml" + declare -grx global_conf_hledger="${global_child_profile_flow}/docker-finance.d/hledger/hledger.conf" declare -grx global_conf_meta="${global_child_profile_flow}/docker-finance.d/meta/meta.csv" declare -grx global_conf_shell="${global_child_profile_flow}/docker-finance.d/shell/subprofile.bash"