#!/usr/bin/env bash
# docker-finance | modern accounting for the power-user
#
# Copyright (C) 2021-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 .
#
# "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_edit::edit()
{
lib_edit::__parse_args "$@"
lib_edit::__edit
lib_utils::catch $?
}
#
# Implementation
#
function lib_edit::__parse_args()
{
[ -z "$global_usage" ] && lib_utils::die_fatal
[ -z "$global_arg_delim_1" ] && lib_utils::die_fatal
[ -z "$global_arg_delim_2" ] && lib_utils::die_fatal
[ -z "$global_arg_delim_3" ] && lib_utils::die_fatal
local -r _usage="
\e[32mDescription:\e[0m
Edit container configuration files and journals
\e[32mUsage:\e[0m
$ $global_usage > [account${global_arg_delim_2}]
\e[32mArguments:\e[0m
Configuration type:
type${global_arg_delim_2}
Account:
account${global_arg_delim_2}
\e[32mExamples:\e[0m
\e[37;2m# Edit fetch configuration\e[0m
$ $global_usage type${global_arg_delim_2}fetch
\e[37;2m# Edit meta and subprofile configurations\e[0m
$ $global_usage type${global_arg_delim_2}meta${global_arg_delim_3}shell
\e[37;2m# Edit _manual_ journal for year 2023\e[0m
$ $global_usage type${global_arg_delim_2}manual year=2023
\e[37;2m# Edit subaccount rules for acccount${global_arg_delim_1}subaccount\e[0m
$ $global_usage type${global_arg_delim_2}rules account${global_arg_delim_2}gemini${global_arg_delim_1}exchange
\e[37;2m# Edit shared rules for multiple accounts\e[0m
$ $global_usage type${global_arg_delim_2}rules account${global_arg_delim_2}electrum${global_arg_delim_3}ethereum-based
\e[37;2m# Edit subaccount rules for multiple account${global_arg_delim_1}subaccount\e[0m
$ $global_usage account${global_arg_delim_2}ledger${global_arg_delim_1}nano${global_arg_delim_3}trezor${global_arg_delim_1}model type${global_arg_delim_2}rules
\e[37;2m# Edit subaccount preprocess and rules for multiple account${global_arg_delim_1}subaccount\e[0m
$ $global_usage account${global_arg_delim_2}coinbase${global_arg_delim_1}platform${global_arg_delim_3}electrum${global_arg_delim_1}wallet-1${global_arg_delim_3}coinomi${global_arg_delim_1}wallet-1 type${global_arg_delim_2}preprocess${global_arg_delim_3}rules
"
#
# Ensure supported arguments
#
[ $# -eq 0 ] && lib_utils::die_usage "$_usage"
for _arg in "$@"; do
[[ ! "$_arg" =~ ^type[s]?${global_arg_delim_2} ]] \
&& [[ ! "$_arg" =~ ^account[s]?${global_arg_delim_2} ]] \
&& [[ ! "$_arg" =~ ^year[s]?${global_arg_delim_2} ]] \
&& lib_utils::die_usage "$_usage"
done
#
# Parse arguments before testing
#
# Parse key for value
for _arg in "$@"; do
local _key="${_arg%${global_arg_delim_2}*}"
local _len="$((${#_key} + 1))"
if [[ "$_key" =~ ^type[s]?$ ]]; then
local _arg_type="${_arg:${_len}}"
[ -z "$_arg_type" ] && lib_utils::die_usage "$_usage"
fi
if [[ "$_key" =~ ^account[s]?$ ]]; then
local _arg_account="${_arg:${_len}}"
[ -z "$_arg_account" ] && lib_utils::die_usage "$_usage"
fi
if [[ "$_key" =~ ^year[s]?$ ]]; then
local _arg_year="${_arg:${_len}}"
[ -z "$_arg_year" ] && lib_utils::die_usage "$_usage"
fi
done
#
# Test for valid ordering/functionality of argument values
#
# Arg: account
if [ ! -z "$_arg_account" ]; then
if [ -z "$_arg_type" ]; then
lib_utils::die_usage "$_usage"
fi
fi
# Arg: year
if [ ! -z "$_arg_year" ]; then
if [[ -z "$_arg_type" ]]; then
lib_utils::die_usage "$_usage"
fi
fi
#
# Test argument values, set globals
#
IFS="$global_arg_delim_3"
# Arg: type
if [ ! -z "$_arg_type" ]; then
read -ra _read <<<"$_arg_type"
for _type in "${_read[@]}"; do
if [[ ! "$_type" =~ ^fetch$|^iadd$|^manual$|^meta$|^preprocess$|^rules$|^shell$ ]]; then
lib_utils::die_usage "$_usage"
fi
if [[ ! -z "$_arg_account" ]]; then
if [[ ! "$_type" =~ ^preprocess$|^rules$ ]]; then
lib_utils::die_usage "$_usage"
fi
fi
if [[ -z "$_arg_account" ]]; then
if [[ "$_type" =~ ^preprocess$|^rules$ ]]; then
lib_utils::die_usage "$_usage"
fi
fi
done
declare -gr global_arg_type=("${_read[@]}")
fi
# Arg: account
if [ ! -z "$_arg_account" ]; then
read -ra _read <<<"$_arg_account"
declare -gr global_arg_account=("${_read[@]}")
fi
# Arg: year
# TODO: implement range
if [ ! -z "$_arg_year" ]; then
# TODO: 20th century support
if [[ ! "$_arg_year" =~ ^20[0-9][0-9]$ || ! ${global_arg_type[*]} =~ ^manual$ ]]; then
lib_utils::die_usage "$_usage"
fi
declare -gr global_arg_year="$_arg_year"
else
global_arg_year="$(date +%Y)" # Set default current
declare -gr global_arg_year
fi
}
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"
;;
iadd | manual)
local _path="${global_child_profile_flow}/import/${global_child_profile}/_manual_/${global_arg_year}"
[ ! -d "$_path" ] && mkdir -p "$_path"
_path+="/post-import.journal"
[ ! -f "$_path" ] && touch "$_path"
# TODO: upstream request to provide comment(N) entries
[[ "$_type" == "iadd" ]] && /usr/bin/hledger-iadd -f "$_path"
# TODO: option for editor type
[[ "$_type" == "manual" ]] && $EDITOR "$_path"
;;
meta)
# 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"
# 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"
;;
preprocess | rules)
# Run all paths through one editor instance
local _paths=()
# Prepare account/account-shared.* or account/subaccount/account-subaccount.*
for _account in "${global_arg_account[@]}"; do
local _acct="${_account%${global_arg_delim_1}*}"
# Attempting to parse out an empty arg will result in dup account as sub
[[ "$_account" =~ ${global_arg_delim_1} ]] \
&& local _sub="${_account#*${global_arg_delim_1}}"
local _path="${global_child_profile_flow}/import/${global_child_profile}/"
[ ! -z "$_sub" ] && _path+="${_acct}/${_sub}/"
case "$_type" in
preprocess)
[ -z "$_sub" ] \
&& local _file="${_acct}-shared.bash" \
|| local _file="$_type"
;;
rules)
[ -z "$_sub" ] \
&& local _file="${_acct}-shared.${_type}" \
|| local _file="${_acct}-${_sub}.${_type}"
;;
esac
_path+="$_file"
if [ ! -f "$_path" ]; then
lib_utils::die_fatal "File not found: $_path"
else
_paths+=("$_path")
fi
done
# Execute
$EDITOR "${_paths[@]}"
;;
shell)
$EDITOR "$global_conf_shell"
;;
esac
done
}
# vim: sw=2 sts=2 si ai et