\e[32mExamples:\e[0m
\e[37;2m# Generate all type and all intervals for current year\e[0m
$ $global_usage all${global_arg_delim_2}all
\e[37;2m# Generate all types for annual report for year 2022\e[0m
$ $global_usage all${global_arg_delim_2}type interval${global_arg_delim_2}annual year${global_arg_delim_2}2022
\e[37;2m# Generate income statements and balance sheets, quarterly and monthly for current year\e[0m
$ $global_usage type${global_arg_delim_2}is${global_arg_delim_3}bs interval${global_arg_delim_2}quarterly${global_arg_delim_3}monthly
"
#
# Ensure supported arguments
#
[ $# -eq 0 ] && lib_utils::die_usage "$_usage"
for _arg in "$@"; do
[[ ! "$_arg" =~ ^all${global_arg_delim_2} ]] \
&& [[ ! "$_arg" =~ ^type[s]?${global_arg_delim_2} ]] \
&& [[ ! "$_arg" =~ ^interval[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" =~ ^all$ ]]; then
local _arg_all="${_arg:${_len}}"
[ -z "$_arg_all" ] && lib_utils::die_usage "$_usage"
fi
if [[ "$_key" =~ ^type[s]?$ ]]; then
local _arg_type="${_arg:${_len}}"
[ -z "$_arg_type" ] && lib_utils::die_usage "$_usage"
fi
if [[ "$_key" =~ ^interval[s]?$ ]]; then
local _arg_interval="${_arg:${_len}}"
[ -z "$_arg_interval" ] && 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: all
if [ ! -z "$_arg_all" ]; then
# Can't use with non-years activated
if [[ ! -z "$_arg_type" && ! -z "$_arg_interval" ]]; then
lib_utils::die_usage "$_usage"
fi
fi
# Arg: type
if [ ! -z "$_arg_type" ]; then
[[ ! -z "$_arg_all" && ! -z "$_arg_interval" ]] \
|| [[ -z "$_arg_all" && -z "$_arg_interval" ]] \
&& lib_utils::die_usage "$_usage"
fi
# Arg: interval
if [ ! -z "$_arg_interval" ]; then
[[ ! -z "$_arg_all" && ! -z "$_arg_type" ]] \
|| [[ -z "$_arg_all" && -z "$_arg_type" ]] \
&& lib_utils::die_usage "$_usage"
fi
# Arg: year
if [ ! -z "$_arg_year" ]; then
[[ -z "$_arg_all" && -z "$_arg_type" && -z "$_arg_interval" ]] \
&& lib_utils::die_usage "$_usage"
fi
#
# Test argument values, set globals
#
IFS="$global_arg_delim_3"
# Arg: all
if [ ! -z "$_arg_all" ]; then
# If all= {type,interval} or {interval,type} then set to all=all
[[ "${#_arg_all[@]}" -eq 1 && "${_arg_all[*]}" =~ type[s]? && "${_arg_all[*]}" =~ interval[s]? ]] \
&& _arg_all="all"
# Read args from all
read -ra _read <<<"$_arg_all"
for _arg in "${_read[@]}"; do
# Support values
[[ ! "$_arg" =~ ^all$|^type[s]?$|^interval[s]?$ ]] \
&& lib_utils::die_usage "$_usage"
# If all=all then no need for all={type,interval} and type= or interval=
[[ "$_arg" == "all" && (! -z "$_arg_type" || ! -z "$_arg_interval") ]] \
|| [[ "$_arg" == "all" && "${#_read[@]}" -gt 1 ]] \
&& lib_utils::die_usage "$_usage"
# If all=type then no need for type= and if all=interval then no need for interval=
[[ "$_arg" =~ ^type[s]?$ && ! -z "$_arg_type" ]] \
|| [[ "$_arg" =~ ^interval[s]?$ && ! -z "$_arg_interval" ]] \
&& lib_utils::die_usage "$_usage"
# If all=type then need interval= or if all=interval then need type=
if [[ "$_arg" != "all" ]]; then
[[ "${#_read[@]}" -lt 2 && "$_arg" =~ ^type[s]?$ && -z "$_arg_interval" ]] \
|| [[ "${#_read[@]}" -lt 2 && "$_arg" =~ ^interval[s]?$ && -z "$_arg_type" ]] \
&& lib_utils::die_usage "$_usage"
fi
done
declare -gr global_arg_all=("${_read[@]}")
fi
# Arg: type
# TODO: acc should not require (nor produce based upon) interval
if [ ! -z "$_arg_type" ]; then
read -ra _read <<<"$_arg_type"
for _arg in "${_read[@]}"; do
[[ ! "$_arg" =~ ^acc$|^bs$|^bse$|^cf$|^is$ ]] && lib_utils::die_usage "$_usage"
done
declare -gr global_arg_type=("${_read[@]}")
elif [[ "${global_arg_all[*]}" =~ (all|type) ]]; then
declare -gr global_arg_type=("acc" "bs" "bse" "cf" "is")
fi
# Arg: interval
if [ ! -z "$_arg_interval" ]; then
read -ra _read <<<"$_arg_interval"
for _arg in "${_read[@]}"; do
[[ ! "$_arg" =~ ^daily$|^weekly$|^monthly$|^quarterly$|^yearly$|^annual$ ]] \
&& lib_utils::die_usage "$_usage"
done
declare -gr global_arg_interval=("${_read[@]}")
elif [[ "${global_arg_all[*]}" =~ (all|interval) ]]; then
declare -gr global_arg_interval=("daily" "weekly" "monthly" "quarterly" "annual")
fi
# Arg: year
# TODO: implement range
# TODO: implement all years
if [ ! -z "$_arg_year" ]; then
# TODO: 20th century support
if [[ ! "$_arg_year" =~ ^20[0-9][0-9]$ ]]; then
lib_utils::die_usage "$_usage"
fi
declare -gr global_arg_year="$_arg_year"
else
global_arg_year="$(date +%Y)"
declare -gr global_arg_year
fi
}
function lib_reports::__reports()
{
[ -z "$global_parent_profile" ] && lib_utils::die_fatal
[ -z "$global_child_profile" ] && lib_utils::die_fatal
[ -z "$global_child_profile_journal" ] && lib_utils::die_fatal
local -r _year="$global_arg_year"
local -r _out_dir="$(dirname $global_child_profile_journal)/reports/${_year}"
[ ! -d "$_out_dir" ] && mkdir -p "$_out_dir"
local -r _indv_out_dir="${_out_dir}/individual"
[ ! -d "$_indv_out_dir" ] && mkdir -p "$_indv_out_dir"
lib_utils::print_custom "\n"
lib_utils::print_info "Generating financial reports in year '${_year}' for '${global_parent_profile}/${global_child_profile}' ..."
lib_utils::print_custom "\n"
lib_utils::print_normal " ─ Reports"
lib_utils::print_custom " \e[32m│\e[0m\n"
local _username
_username="$(basename "$(dirname $global_child_profile_journal)" | tr '[:lower:]' '[:upper:]')"
for _type in "${global_arg_type[@]}"; do
case "$_type" in
acc)
local _echo="accounts"
local _desc="ACCOUNTS"
local _base_file="${_year}_${_username}_accounts"
local _opts=("not:equity:balances\$")
;;
bs)
local _echo="balance sheet"
local _desc="BALANCE SHEET"
local _base_file="${_year}_${_username}_balance-sheet"
local _opts=("")
;;
bse)
local _echo="balance sheet w/ equity"
local _desc="BALANCE SHEET w/ EQUITY"
local _base_file="${_year}_${_username}_balance-sheet-equity"
# TODO: finer rule for equity-specific deposit/withdrawal
local _opts=("not:equity:balances\$") #"not:deposit" "not:withdrawal")
;;
cf)
local _echo="cashflow"
local _desc="CASHFLOW"
local _base_file="${_year}_${_username}_cashflow"
local _opts=("")
;;
is)
local _echo="income statement"
local _desc="INCOME STATEMENT"
local _base_file="${_year}_${_username}_income-statement"
local _opts=("")
;;
*)
lib_utils:die_fatal "Type not implemented"
;;
esac
# Interval
for _interval in "${global_arg_interval[@]}"; do
case "$_interval" in
yearly | annual)
local _new_base_file="${_base_file}_01_yearly"
local _new_opts=("${_opts[@]}")
;;
quarterly)
local _new_base_file="${_base_file}_02_${_interval}"
local _new_opts=("${_opts[@]}" "--${_interval}")
;;
monthly)
local _new_base_file="${_base_file}_03_${_interval}"
local _new_opts=("${_opts[@]}" "--${_interval}")
;;
weekly)
local _new_base_file="${_base_file}_04_${_interval}"
local _new_opts=("${_opts[@]}" "--${_interval}")
;;
daily)
local _new_base_file="${_base_file}_05_${_interval}"
local _new_opts=("${_opts[@]}" "--${_interval}")
;;
*)
lib_utils:die_fatal "Interval not implemented"
;;
esac
# Execute
local _new_echo="$_echo (${_interval})"
local _new_desc="$_desc (${_interval})"
lib_utils::print_custom " \e[32m├─\e[34m\e[1;3m ${_new_echo}\e[0m\n"
(lib_reports::__reports_write \
"$_type" \
"$_new_desc" \
"$_new_base_file" \
"${_new_opts[@]}") &
done
wait
lib_utils::print_custom " \e[32m│\e[0m\n"
done
# TODO: more once upstream adds more features
# Index of all reports
lib_utils::print_normal " ─ Generating index of all reports ..."
local _index="index"
lib_reports::__reports_write \
"$_index" \
"$_index" \
"$_index" \
""
[ -z "$DOCKER_FINANCE_CLIENT_FLOW" ] && lib_utils::die_fatal
local -r _client_out_dir="${DOCKER_FINANCE_CLIENT_FLOW}/profiles/${global_parent_profile}/${global_child_profile}/reports/${_year}"
lib_utils::print_custom "\n Reports are located:\n\n"
lib_utils::print_custom "\tclient (host): ${_client_out_dir}/\n\n"
lib_utils::print_custom "\tcontainer: ${_out_dir}/\n"
lib_utils::print_custom "\n On your client (host), open your browser to:\n\n"
lib_utils::print_custom "\tfile://${_client_out_dir}/${_index}.html\n\n"
lib_utils::print_info "Done!"
}
function lib_reports::__reports_write()
{
local _type="$1"
local _description="$2"
local _base_file="$3"
local _opts=("${@:4}")
# Generate HTML
local _ext="html"
local _file="${_base_file}.${_ext}"
# HTML tag for search/replacement
local _html_tag="DOCKER_FINANCE_HTML_TAG"
# Generate individual files
# NOTE: all valued periods are appended
if [ "$_type" != "index" ]; then
# Individual report
local _individual="${_indv_out_dir}/${_file}"
## Base hledger command
local _period=("--period" "$_year")
if [ "$_year" == "$(date +%Y)" ]; then
local _period=("-b" "${_year}/01/01" "-e" "$(date +%Y/%m/%d)")
fi
# TODO: instead of `not:desc` for opening/closing, add opening/closing tags and use `not:tag`
local _base_hledger=("${global_hledger_cmd[@]}" "${_period[@]}" "$_type" "${_opts[@]}" "not:desc:balances\$" "not:archive")
## Divided sections, and the linkable section
echo -e "${_year} $_username | ${_description}" >"$_individual"
echo -e "
" >>"$_individual"
echo -e "${_year} $_username | ${_description}
" >>"$_individual"
echo -e "
" >>"$_individual"
## Brief view
echo "BRIEF VIEW" >>"$_individual"
local _hledger=("${_base_hledger[@]}")
if [ "$_type" == "acc" ]; then
# Text -> HTML
_hledger+=("--depth" "2")
"${_hledger[@]}" | sed 's:$:
:g' >>"$_individual"
lib_utils::catch $?
echo -e "
" >>"$_individual"
else
# HTML
_hledger+=("--depth" "2" "-O" "html")
"${_hledger[@]}" >>"$_individual"
lib_utils::catch $?
echo -e "
" >>"$_individual"
_hledger+=("--value=end")
"${_hledger[@]}" >>"$_individual"
lib_utils::catch $?
fi
echo -e "
" >>"$_individual"
## Full view
echo "FULL VIEW" >>"$_individual"
local _hledger=("${_base_hledger[@]}") # Reset to base
if [ "$_type" == "acc" ]; then
# Text -> HTML
"${_hledger[@]}" | sed 's:$:
:g' >>"$_individual"
lib_utils::catch $?
echo -e "
" >>"$_individual"
else
# HTML
_hledger+=("-O" "html")
"${_hledger[@]}" >>"$_individual"
lib_utils::catch $?
echo -e "
" >>"$_individual"
_hledger+=("--value=end")
"${_hledger[@]}" >>"$_individual"
lib_utils::catch $?
fi
## Back to top
echo -e "" >>"$_individual"
echo -e "
" >>"$_individual"
return
fi
# Combines HTML lines from each individual report (that has _html_tag) into links within an single index
local _index="${_out_dir}/${_file}"
echo "" >"$_index"
## Grab individuals
ls "${_indv_out_dir}"/*.${_ext} \
| sort -r \
| while read _line1; do
basename -s ."${_ext}" "$(echo "$_line1")" \
| while read _line2; do # Pull out the description to use as link
sed -i "1i$(echo "
$(grep $_html_tag $_line1 | cut -d'>' -f3 | cut -d'<' -f1 | cut -d'|' -f2)
")" "$_index"
done
done
# Additional stylings
sed -i "1i \
\
\
$_year $_username REPORTS \
\
\
\
\
\
$_year $_username REPORTS
\
\
" "$_index"
echo "" >>"$_index"
}
# vim: sw=2 sts=2 si ai et