docker-finance | modern accounting for the power-user

Dedicated to Michael Morgan: a beautiful, beautiful soul.

---

Internal signing keys:

  Aaron Fiore (sole author)

    - 518A22F85BEFD32BCC99C48603F90C4F35E0213E
    - 31ECA5C347A0CC0815EDE730A3EACCFCDA7E685E
    - C8187C585CB07A4DA81CC0F37318B50EBE9C0DA8

Internal repositories (rebased from):

  Staging:

    $ git log -n1 --pretty=format:"%H"
    c8e0cd66f6c89fa7b3c62f72fb524a4cc454b7b6

    $ git rev-list --max-parents=0 HEAD
    ac3863b8c234755855f1aea3a07a853122decdf2

  Private:

    $ git log -n1 --pretty=format:"%H"
    69bb3591eaa2990a9637832bb484690e00c4f926

    $ git rev-list --max-parents=0 HEAD
    a5c1cc9fb593c4cf09bc0adfef6cb6d2964511ae
This commit is contained in:
2024-03-04 03:12:40 -08:00
commit b621e87df2
505 changed files with 37687 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
#!/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 <https://www.gnu.org/licenses/>.
#
# "Libraries"
#
source "$(dirname "$(realpath -s $0)")/src/docker/lib/internal/lib_docker.bash" || exit 1
source "$(dirname "$(realpath -s $0)")/../container/src/finance/lib/internal/lib_utils.bash" || exit 1
#
# Implementation
#
function lib_doxygen::__parse_args()
{
[ -z "$global_usage" ] && lib_utils::die_fatal
[ -z "$global_arg_delim_2" ] && lib_utils::die_fatal
[ -z "$global_arg_delim_3" ] && lib_utils::die_fatal
# TODO: pass `doxygen` arguments to gen
local -r _usage="
\e[32mDescription:\e[0m
Manage Doxygen documentation
\e[32mUsage:\e[0m
$ $global_usage <gen|rm>
\e[32mExamples:\e[0m
\e[37;2m# Generate Doxygen\e[0m
$ $global_usage gen
\e[37;2m# Remove previously generated Doxygen\e[0m
$ $global_usage rm
"
[[ $# -eq 0 || $# -gt 1 ]] && lib_utils::die_usage "$_usage"
for _arg in "$@"; do
[[ ! "$_arg" =~ ^gen$|^rm$ ]] \
&& lib_utils::die_usage "$_usage"
done
declare -gr global_arg_cmd="$1"
}
function lib_doxygen::__doxygen()
{
[ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal
local -r _dir="${DOCKER_FINANCE_CLIENT_REPO}/client/Doxygen"
case "$global_arg_cmd" in
gen)
lib_docker::__run "doxygen ${_dir}/Doxyfile \
&& echo -e \"\nNow, open your browser to:\n\n file://${_dir}/html/index.html\n\""
;;
rm)
lib_docker::__run "rm --verbose -fr ${_dir}/html"
;;
esac
}
function lib_doxygen::doxygen()
{
lib_doxygen::__parse_args "$@"
lib_doxygen::__doxygen
}
# vim: sw=2 sts=2 si ai et

View File

@@ -0,0 +1,256 @@
#!/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 <https://www.gnu.org/licenses/>.
#
# "Libraries"
#
source "$(dirname "$(realpath -s $0)")/../container/src/finance/lib/internal/lib_utils.bash" || exit 1
#
# Implementation
#
function lib_license::__parse_args()
{
[ -z "$global_usage" ] && 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
Add license text to a given file
\e[32mUsage:\e[0m
$ $global_usage file${global_arg_delim_2}<file>
\e[32mArguments:\e[0m
File path:
file${global_arg_delim_2}<path to file>
\e[32mExamples:\e[0m
\e[37;2m# Add license to given file\e[0m
$ $global_usage file${global_arg_delim_2}/path/to/file.ext
"
#
# Ensure supported arguments
#
[ $# -eq 0 ] && lib_utils::die_usage "$_usage"
for _arg in "$@"; do
[[ ! "$_arg" =~ ^file[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" =~ ^file[s]?$ ]]; then
local _arg_file="${_arg:${_len}}"
[ -z "$_arg_file" ] && lib_utils::die_usage "$_usage"
fi
done
#
# Test argument values, set globals
#
IFS="$global_arg_delim_3"
# Arg: file
if [ ! -z "$_arg_file" ]; then
readonly global_arg_file="$_arg_file" # TODO: make array when multiple files implemented
fi
}
function lib_license::__get_file_type()
{
local -r _file="$(basename $1)"
local -r _file_type="${_file##*.}"
if [ -z "$_file_type" ]; then
lib_utils::die_fatal "File type not detected for '${_file}'"
elif [[ "$_file_type" == "in" ]]; then
# Dockerfile may have OS/platform in filename
if [[ "$_file" =~ Dockerfile ]]; then
readonly global_file_type="docker"
else
# Get real extension of .in file
# TODO: optimize: remove cut, use bash
global_file_type="$(echo $_file | cut -d'.' -f2)"
readonly global_file_type
fi
else
readonly global_file_type="$_file_type"
fi
case "$global_file_type" in
bash | yaml | yml | docker | rules | clang-format)
readonly global_comment_type="#"
;;
cc | hh | C | php) # .C = ROOT macro file
readonly global_comment_type="//"
;;
csv)
readonly global_comment_type="//!"
;;
md)
# NOTE: impl MUST complete this comment (see below)
readonly global_comment_type="[//]: # ("
;;
*)
lib_utils::die_fatal "Unsupported file type '${global_file_type}' for '${_file}'"
;;
esac
}
function lib_license::__license()
{
local _deps=("gawk")
lib_utils::deps_check "${_deps[@]}"
[ ! -f "$global_arg_file" ] \
&& lib_utils::die_fatal "File not found '${global_arg_file}'"
lib_license::__get_file_type "$global_arg_file"
# TODO: add year= and author= arg options
local -r _text="docker-finance | modern accounting for the power-user
Copyright (C) <year> <author>
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 <https://www.gnu.org/licenses/>."
# NOTE: docker-finance `.in` files will have docker-finance version on 1st line
case "$global_file_type" in
cc | hh | C | yaml | yml | docker | rules | clang-format)
# Has no expectations of input
local _sed_args=("-e" "s\^\\$global_comment_type \g" "-e" "s: *$::g")
local _awk_args=('BEGIN{print text"\n"}{print}')
;;
bash | php | csv)
local _sed_args=("-e" "s\^\\$global_comment_type \g" "-e" "s: *$::g")
local _awk_args=(
'{
# Expects first 3 input lines as:
#
# 1 file tag (bash shebang, php opening tag, docker-finance version info, etc.)
# 2
# 3 code or text
# Save first line if needed
if (NR==1 && NF) {first = $0}
# First line then license text
if (NR<2)
{
if (global_file_type == "csv")
{
print text
print first
}
else
{
print first
print "\n"text"\n"
}
}
# Remaining file
if (NR>2) {print}
}')
;;
md)
# - Create empty first line
# - Replace pre-existing (' and ')' with '[' and ']'
# - Add ')' to EOL
local _sed_args=("-e" "s:(:[:g" "-e" "s:):]:g" "-e" "s\^\\${global_comment_type}\g" "-e" "s: *$:):g")
local _awk_args=(
'{
# Save first line if needed
if (NR==1 && NF) {first = $0}
# Uncomment if text itself does not have newline at beginning
#if (NR==1 && NF) {print ""}
# License text then first line
if (NR<2) {print "\n"text"\n"; print first"\n"}
# Remaining file
if (NR>2) {print}
}')
;;
*)
lib_utils::die_fatal "Unsupported file type '${global_file_type}' for '${global_arg_file}'"
;;
esac
# Setup temp
local -r _tmp_dir="$(mktemp -d -p /tmp docker-finance_XXX)"
local -r _tmp_file="$(mktemp -p $_tmp_dir license_XXX)"
# Add text
lib_utils::print_normal "Writing to '${global_arg_file}' as type '${global_file_type}'"
# Execute
gawk \
-v global_file_type="$global_file_type" \
-v text="$(echo -e $_text | sed "${_sed_args[@]}")" \
"${_awk_args[@]}" "$global_arg_file" >"$_tmp_file" \
&& mv -f "$_tmp_file" "$global_arg_file"
# Cleanup
[ -d "$_tmp_dir" ] && rmdir "$_tmp_dir"
if [ $? -ne 0 ]; then
lib_utils::die_fatal "Did not exist successfully"
fi
}
function lib_license::license()
{
lib_license::__parse_args "$@"
lib_license::__license && lib_utils::print_info "License written successfully!"
}
# vim: sw=2 sts=2 si ai et

View File

@@ -0,0 +1,266 @@
#!/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 <https://www.gnu.org/licenses/>.
#
# "Libraries"
#
source "$(dirname "$(realpath -s $0)")/src/docker/lib/internal/lib_docker.bash" || exit 1
source "$(dirname "$(realpath -s $0)")/../container/src/finance/lib/internal/lib_utils.bash" || exit 1
#
# Implementation
#
function lib_linter::__parse_args()
{
[ -z "$global_usage" ] && 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
Lint docker-finance source files
\e[32mUsage:\e[0m
$ $global_usage type${global_arg_delim_2}<type> [file${global_arg_delim_2}<file>]
\e[32mArguments:\e[0m
File type:
type${global_arg_delim_2}<bash|php|c++>
File path:
file${global_arg_delim_2}<path to file>
\e[32mNotes:\e[0m
Cannot use multiple types with file argument present
\e[32mExamples:\e[0m
\e[37;2m# Lint only PHP files\e[0m
$ $global_usage type${global_arg_delim_2}php
\e[37;2m# Lint *all* development files\e[0m
$ $global_usage type${global_arg_delim_2}bash${global_arg_delim_3}php${global_arg_delim_3}c++
\e[37;2m# Lint given C++ files\e[0m
$ $global_usage type${global_arg_delim_2}c++ file${global_arg_delim_2}path/file_1.hh${global_arg_delim_3}path/file_2.hh
"
#
# Ensure supported arguments
#
[ $# -eq 0 ] && lib_utils::die_usage "$_usage"
for _arg in "$@"; do
[[ ! "$_arg" =~ ^type[s]?${global_arg_delim_2} ]] \
&& [[ ! "$_arg" =~ ^file[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" =~ ^file[s]?$ ]]; then
local _arg_file="${_arg:${_len}}"
[ -z "$_arg_file" ] && lib_utils::die_usage "$_usage"
fi
done
#
# 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" =~ ^bash$|^shell$|^c\+\+$|^cpp$|^php$ ]]; then
lib_utils::die_usage "$_usage"
fi
done
declare -gr global_arg_type=("${_read[@]}")
fi
# Arg: file
if [ ! -z "$_arg_file" ]; then
read -ra _read <<<"$_arg_file"
if [ "${#global_arg_type[@]}" -gt 1 ]; then
# Multiple types not supported if file is given
lib_utils::die_usage "$_usage"
fi
declare -gr global_arg_file=("${_read[@]}")
fi
}
function lib_linter::__lint_bash()
{
local -r _path=("$@")
local -r _exts=("bash" "bash.in")
local -r _shfmt="shfmt -ln bash -i 2 -ci -bn -fn -w"
local -r _shellcheck="shellcheck --color=always --norc --enable=all --shell=bash --severity=warning"
if [ -z "${_path[*]}" ]; then
# Do all
for _ext in "${_exts[@]}"; do
lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \
"find ${DOCKER_FINANCE_CLIENT_REPO}/ -type f -name \*.${_ext} \
| while read _file
do echo Linting \'\${_file}\'
$_shfmt \$_file \$_file && $_shellcheck \${_file}
done"
done
else
# Do only given file(s)
for _p in "${_path[@]}"; do
local _file
_file="$(dirs +1)/${_p}"
lib_utils::print_normal "Linting '${_file}'"
lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \
"$_shfmt $_file $_file && $_shellcheck $_file"
done
fi
}
function lib_linter::__lint_cpp()
{
local -r _path=("$@")
local -r _exts=("hh" "C")
local -r _clang_format="clang-format -i"
local -r _cpplint="cpplint --root=${DOCKER_FINANCE_CLIENT_REPO} --filter=-whitespace/braces,-whitespace/newline,-whitespace/line_length,-build/c++11 --headers=hh --extensions=hh,C"
local -r _cppcheck="cppcheck --enable=warning,style,performance,portability --inline-suppr --std=c++17"
if [ -z "${_path[*]}" ]; then
# Do all
for _ext in "${_exts[@]}"; do
lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \
"find ${DOCKER_FINANCE_CLIENT_REPO}/ -type f -name \*.${_ext} \
| while read _file
do echo Linting \'\${_file}\'
$_clang_format \$_file && $_cpplint \$_file && $_cppcheck \$_file
done"
done
else
# Do only given file(s)
for _p in "${_path[@]}"; do
local _file
_file="$(dirs +1)/${_p}"
lib_utils::print_normal "Linting '${_file}'"
lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \
"$_clang_format $_file && $_cpplint $_file && $_cppcheck $_file"
done
fi
}
function lib_linter::__lint_php()
{
local -r _path=("$@")
local -r _ext="php"
local -r _php_cs_fixer="time php-cs-fixer fix --rules=@PSR12 --verbose"
local -r _phpstan="time phpstan analyse --debug --autoload-file /usr/local/lib/php/vendor/autoload.php --level 6" # TODO: incrementally increase to 9
if [ -z "${_path[*]}" ]; then
# Do all
lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \
"$_php_cs_fixer $DOCKER_FINANCE_CLIENT_REPO \
&& $_phpstan $DOCKER_FINANCE_CLIENT_REPO"
else
# Do only given file(s)
for _p in "${_path[@]}"; do
local _file
_file="$(dirs +1)/${_p}"
lib_utils::print_normal "Linting '${_file}'"
lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \
"$_php_cs_fixer $_file && $_phpstan $_file"
done
fi
}
function lib_linter::__linter()
{
[ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal
[[ ! "$PWD" =~ ^$DOCKER_FINANCE_CLIENT_REPO && ! -z "${global_arg_file[*]}" ]] \
&& lib_utils::die_fatal "Sorry, you must work (and lint) from within parent directory '${DOCKER_FINANCE_CLIENT_REPO}'"
lib_utils::print_debug "Bringing up network and container"
[ -z "$global_network" ] && lib_utils::die_fatal
local -r _no_funny_business="--internal=true"
docker network create "$_no_funny_business" "$global_network" 2>/dev/null
lib_docker::__docker_compose up -d docker-finance || lib_utils::die_fatal
# Transparent bind-mount: although it's the container repo, its path is identical to client repo
pushd "$DOCKER_FINANCE_CLIENT_REPO" 1>/dev/null || lib_utils::die_fatal
for _type in "${global_arg_type[@]}"; do
case "$_type" in
bash | shell)
lib_linter::__lint_bash "${global_arg_file[@]}"
;;
c++ | cpp)
lib_linter::__lint_cpp "${global_arg_file[@]}"
;;
php)
lib_linter::__lint_php "${global_arg_file[@]}"
;;
esac
lib_utils::print_custom "\n"
done
popd 1>/dev/null || lib_utils::die_fatal
lib_docker::__docker_compose down
docker network rm "$global_network" 1>/dev/null
}
function lib_linter::linter()
{
lib_linter::__parse_args "$@"
lib_linter::__linter && lib_utils::print_info "Linting completed successfully!"
}
# vim: sw=2 sts=2 si ai et

View File

@@ -0,0 +1,323 @@
#!/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 <https://www.gnu.org/licenses/>.
#
# "Libraries"
#
# Utilities (a container library (but container is never exposed to client code))
source "$(dirname "$(realpath -s $0)")/../container/src/finance/lib/internal/lib_utils.bash" || exit 1
#
# Implementation
#
# Dependencies
deps=("docker" "sed")
lib_utils::deps_check "${deps[@]}"
if ! docker compose version 1>/dev/null; then
lib_utils::die_fatal "Docker compose plugin not installed"
fi
if [ -z "$EDITOR" ]; then
editors=("vim" "vi" "emacs" "nano")
for editor in "${editors[@]}"; do
hash "$editor" &>/dev/null && export EDITOR="$editor" && break
done
if [ $? -ne 0 ]; then
lib_utils::die_fatal "Shell EDITOR is not set, export EDITOR in your shell"
fi
fi
#
# `docker compose`
#
function lib_docker::__docker_compose()
{
[ -z "$global_env_file" ] && lib_utils::die_fatal
[ -z "$global_dockerfile_path" ] && lib_utils::die_fatal
[ ! -f "$global_env_file" ] \
&& lib_utils::die_fatal "$global_env_file not found! Run the gen command!"
pushd "$global_dockerfile_path" 1>/dev/null \
|| return $? \
&& docker compose -f docker-compose.yml --env-file "$global_env_file" "$@"
popd 1>/dev/null || return $?
}
function lib_docker::__build()
{
time lib_docker::__docker_compose build --pull docker-finance "$@"
}
function lib_docker::__up()
{
# NOTE: for openvpn support, input this into /etc/docker/daemon.json and restart docker:
# {
# "default-address-pools" : [
# {
# "base" : "172.31.0.0/16",
# "size" : 24
# }
# ]
# }
lib_utils::print_debug "Bringing up network and container"
docker network create "$global_network" 2>/dev/null
lib_docker::__docker_compose up -d docker-finance "$@" \
&& docker exec -it "$global_container" /bin/bash
}
function lib_docker::__down()
{
[ -z "$global_network" ] && lib_utils::die_fatal
lib_utils::print_debug "Bringing down container and network"
lib_docker::__docker_compose down "$@" \
&& docker network rm "$global_network" 1>/dev/null
}
function lib_docker::__start()
{
lib_utils::print_debug "Starting container"
lib_docker::__docker_compose start "$@"
}
function lib_docker::__stop()
{
lib_utils::print_debug "Stopping container"
lib_docker::__docker_compose stop "$@"
}
function lib_docker::__backup()
{
# NOTE: does not export to tar, simply tags
local -r _backup="${global_image}:backup_$(date +%s)"
if docker tag "${global_image}:${global_tag}" "$_backup"; then
lib_utils::print_info "Created backup: ${global_image}:${global_tag} -> $_backup"
else
lib_utils::die_fatal "Could not create backup -> $_backup"
fi
}
function lib_docker::__rm()
{
[ -z "$global_image" ] && lib_utils::die_fatal
[ -z "$global_tag" ] && lib_utils::die_fatal
lib_utils::print_debug "Removing image"
docker image rm "${global_image}:${global_tag}" "$@"
}
function lib_docker::__run()
{
[ -z "$global_network" ] && lib_utils::die_fatal
lib_utils::print_debug "Creating network"
docker network create "$global_network" &>/dev/null
# NOTE: runs bash as interactive so .bashrc doesn't `return` (sources/aliases are needed)
lib_utils::print_debug "Spawning container with command '$*'"
lib_docker::__docker_compose run -it --rm --entrypoint='/bin/bash -i -c' docker-finance "$@"
local -r _return=$?
lib_utils::print_debug "Removing network"
docker network rm "$global_network" &>/dev/null # Don't force, if in use
return $_return
}
function lib_docker::__shell()
{
[ -z "$global_usage" ] && lib_utils::die_fatal
[ -z "$global_arg_delim_2" ] && lib_utils::die_fatal
[ -z "$global_arg_delim_3" ] && lib_utils::die_fatal
[ -z "$global_user" ] && lib_utils::die_fatal
local -r _arg="$1"
local -r _usage="
\e[32mDescription:\e[0m
Open a shell as given container user
\e[32mUsage:\e[0m
$ $global_usage user${global_arg_delim_2}<user>
\e[32mArguments:\e[0m
Container user:
user${global_arg_delim_2}<${global_user}|root|...>
\e[32mExamples:\e[0m
\e[37;2m# Open shell as current user (no arg)\e[0m
$ $global_usage
\e[37;2m# Open shell as current user (with arg)\e[0m
$ $global_usage user${global_arg_delim_2}${global_user}
\e[37;2m# Open root shell\e[0m
$ $global_usage user${global_arg_delim_2}root
"
if [ $# -gt 1 ]; then
lib_utils::die_usage "$_usage"
fi
# Get/Set user
if [ ! -z "$_arg" ]; then
if [[ ! "$_arg" =~ ^user[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" =~ ^user[s]?$ ]]; then
local _arg_user="${_arg:${_len}}"
if [ -z "$_arg_user" ]; then
lib_utils::die_usage "$_usage"
fi
fi
else
# Use default user
local _arg_user="${global_user}"
fi
if [ -z "$_arg_user" ]; then
lib_utils::die_usage "$_usage"
fi
[ -z "$global_container" ] && lib_utils::die_fatal
lib_utils::print_debug "Spawning shell in container '${global_container}'"
docker exec -it --user "$_arg_user" --workdir / "$global_container" /bin/bash
}
function lib_docker::__edit()
{
[ -z "$global_usage" ] && 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 client configuration files
\e[32mUsage:\e[0m
$ $global_usage type${global_arg_delim_2}<type>
\e[32mArguments:\e[0m
Configuration type:
type${global_arg_delim_2}<env|{shell|superscript}>
\e[32mExamples:\e[0m
\e[37;2m# Edit client/container environment variables\e[0m
$ $global_usage type${global_arg_delim_2}env
\e[37;2m# Edit client/container shell (superscript) \e[0m
$ $global_usage type${global_arg_delim_2}shell
\e[37;2m# Edit client/container shell (w/ alternate wording) and environment variables\e[0m
$ $global_usage type${global_arg_delim_2}superscript${global_arg_delim_3}env
"
if [ $# -ne 1 ]; then
lib_utils::die_usage "$_usage"
fi
# Get/Set type
local -r _arg="$1"
if [ ! -z "$_arg" ]; then
if [[ ! "$_arg" =~ ^type[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" =~ ^type[s]?$ ]]; then
local _arg_type="${_arg:${_len}}"
if [ -z "$_arg_type" ]; then
lib_utils::die_usage "$_usage"
fi
fi
fi
IFS="$global_arg_delim_3"
# Arg: type
if [ ! -z "$_arg_type" ]; then
read -ra _read <<<"$_arg_type"
for _type in "${_read[@]}"; do
if [[ ! "$_type" =~ ^env$|^shell$|^superscript$ ]]; then
lib_utils::die_usage "$_usage"
fi
done
readonly _edit_arg_type=("${_read[@]}")
fi
[ -z "$EDITOR" ] \
&& lib_utils::die_fatal "Export EDITOR to your preferred editor"
[ -z "$global_env_file" ] && lib_utils::die_fatal
[ ! -f "$global_env_file" ] \
&& lib_utils::die_fatal "Environment file now found"
[ -z "$global_shell_file" ] && lib_utils::die_fatal
[ ! -f "$global_shell_file" ] \
&& lib_utils::die_fatal "Shell (superscript) file now found"
# Run all files through one editor instance
local _paths=()
for _type in "${_edit_arg_type[@]}"; do
case "$_type" in
env)
_paths+=("$global_env_file")
;;
shell | superscript)
[ -z "$global_platform" ] && lib_utils::die_fatal
[[ "$global_platform" == "dev-tools" ]] \
&& lib_utils::die_fatal "Invalid platform, use finance image"
_paths+=("$global_shell_file")
;;
esac
done
# Execute
lib_utils::print_debug "Editing '${_paths[*]}'"
$EDITOR "${_paths[@]}"
}
# vim: sw=2 sts=2 si ai et

View File

@@ -0,0 +1,712 @@
#!/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 <https://www.gnu.org/licenses/>.
#
# "Libraries"
#
source "$(dirname "$(realpath -s $0)")/../container/src/finance/lib/internal/lib_utils.bash" || exit 1
#
# Implementation
#
if [ $UID -lt 1000 ]; then
lib_utils::die_fatal "Do not run as root or system user!"
fi
# IMPORTANT: keep umask for security
umask o-rwx
if ! docker compose version 1>/dev/null; then
lib_utils::die_fatal "Docker compose plugin not installed"
fi
if [ -z "$EDITOR" ]; then
editors=("vim" "vi" "emacs" "nano")
for editor in "${editors[@]}"; do
hash "$editor" &>/dev/null && export EDITOR="$editor" && break
done
if [ $? -ne 0 ]; then
lib_utils::die_fatal "Shell EDITOR is not set, export EDITOR in your shell"
fi
fi
#
# "Constructor" for environment generation
#
# Sets client-side environment with defaults or use existing environment
#
function lib_gen::gen()
{
lib_utils::print_debug "Constructing environment"
# Repository-provided (not user-defined) default environment
global_repo_conf_dir="$(dirname "$(realpath -s $0)")/docker-finance.d"
declare -gr global_repo_conf_dir
declare -gr global_repo_env_file="${global_repo_conf_dir}/client/env/gen.bash"
[ ! -f "$global_repo_env_file" ] \
&& lib_utils::die_fatal "Missing environment defaults! ($global_repo_env_file)"
#
# If empty environment, bootstrap with defaults
#
if [ -z "$DOCKER_FINANCE_CLIENT_CONF" ]; then
# shellcheck source=/dev/null
source "$global_repo_env_file"
fi
[ -z "$DOCKER_FINANCE_CLIENT_CONF" ] \
&& lib_utils::die_fatal "Defaults not generated! (${global_repo_env_file})"
[ -z "$DOCKER_FINANCE_USER" ] && lib_utils::die_fatal
global_env_filename="${DOCKER_FINANCE_USER}@$(uname -n)"
declare -gr global_env_filename
lib_gen::__set_client_globals
#
# Reset environment with user-provided (user-defined) existing file (if available)
#
[ -z "$global_platform" ] && lib_utils::die_fatal
[ -z "$global_tag" ] && lib_utils::die_fatal
local -r _env_dir="${DOCKER_FINANCE_CLIENT_CONF}/client/env/$(uname -s)-$(uname -m)/${global_platform}/${global_tag}"
local -r _env_file="${_env_dir}/${global_env_filename}"
if [ -f "$_env_file" ]; then
if [ -s "$_env_file" ]; then
# Re-bootstrap with (new) environment
lib_utils::print_debug "Environment found! Using '${_env_file}'"
lib_gen::__read_env "$_env_file"
lib_gen::__set_client_globals
else
lib_utils::print_warning \
"Client environment '${_env_file}' is empty! Writing defaults"
lib_gen::__write_env "$_env_file"
fi
else
[ -z "$global_basename" ] && lib_utils::die_fatal
if [[ -z "$global_command" || "$global_command" != "gen" ]]; then
lib_utils::die_fatal \
"Client environment not found! Run gen command'"
fi
lib_utils::print_info \
"Client environment not found, writing defaults to '${_env_file}'"
lib_gen::__write_env "$_env_file"
fi
}
#
# Set client globals from environment
#
function lib_gen::__set_client_globals()
{
lib_utils::print_debug "Setting client globals"
# Appending all client-side conf paths
local _client_path_tag
_client_path_tag="$(uname -s)-$(uname -m)/${global_platform}/${global_tag}"
# Client-side environment file (if available)
local _client_env_dir="${DOCKER_FINANCE_CLIENT_CONF}/client/env/${_client_path_tag}"
[ ! -d "$_client_env_dir" ] && mkdir -p "$_client_env_dir"
global_env_file="${_client_env_dir}/${global_env_filename}"
lib_utils::print_debug "global_env_file=${global_env_file}"
# NOTE:
#
# Client env tag format is avoided because:
#
# - We copy over static bash_aliases in Dockerfile,
# and superscript must be referenced by that static path
#
# - The needed dynamicness appears to not be satisfied via docker-compose
local _client_shell_dir="${DOCKER_FINANCE_CLIENT_CONF}/container/shell"
[ ! -d "$_client_shell_dir" ] && mkdir -p "$_client_shell_dir"
global_shell_file="${_client_shell_dir}/superscript.bash"
lib_utils::print_debug "global_shell_file=${global_shell_file}"
# Client view of client portion of repository
[ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal
global_repo_client="${DOCKER_FINANCE_CLIENT_REPO}/client"
[ ! -d "$global_repo_client" ] && lib_utils::die_fatal "Repository '${global_repo_client}' not found!"
lib_utils::print_debug "global_repo_client=${global_repo_client}"
# Backup-file extension
# TODO: make configurable
global_suffix="$(date +%Y-%m-%d_%H:%M:%S)"
lib_utils::print_debug "global_suffix=${global_suffix}"
}
#
# Get/Set client-side environment with given file
#
function lib_gen::__read_env()
{
lib_utils::print_debug "Get/Set environment"
# Unset
unset "$(printenv | grep -Eo "^DOCKER_FINANCE[^=]+")"
# Set, if a script that generates env
if [[ "$(head -n1 $1)" =~ (bin|env|sh|bash) ]]; then
# shellcheck source=/dev/null
source "$global_repo_env_file"
return $?
fi
# Set, if env file format (docker / bash)
while read _line; do
export "${_line?}" # SC2163
lib_utils::print_debug "$_line"
done <"$1"
}
#
# Write client-side environment to given file
#
function lib_gen::__write_env()
{
lib_utils::print_debug "Writing environment"
printenv | grep "DOCKER_FINANCE" | sort >"$1"
}
#
# Generate new configurations
#
function lib_gen::generate()
{
lib_utils::print_custom "\n"
lib_utils::print_info "Generating client/container environment"
# WARNING: client generation MUST be done before container generation
lib_gen::__gen_client
if [ $? -eq 0 ]; then
if [ "$global_platform" != "dev-tools" ]; then
lib_gen::__gen_container
fi
fi
if [ $? -eq 0 ]; then
lib_utils::print_info "Congratulations, environment sucessfully generated!"
lib_utils::print_custom "\n"
else
lib_utils::die_fatal "Environment not fully generated! Try again"
fi
}
function lib_gen::__gen_client()
{
lib_utils::print_debug "Generating client"
# Backup existing client-side environment file
if [ -f "$global_env_file" ]; then
lib_utils::print_custom " \e[32m│\e[0m\n"
lib_utils::print_custom " \e[32m├─\e[34;1m Client-side environment found, backup then generate new one? [Y/n] \e[0m"
read -p "" _read
local _confirm="${_read:-y}"
if [[ "$_confirm" == [yY] ]]; then
cp -a "$global_env_file" "${global_env_file}_${global_suffix}" \
|| lib_utils::die_fatal
# Get/Set with repository defaults
lib_gen::__read_env "$global_repo_env_file"
lib_gen::__write_env "$global_env_file"
fi
fi
lib_utils::print_custom " \e[32m│ └─\e[34m Edit (new) environment now? [Y/n] \e[0m"
read -p "" _read
local _confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] && $EDITOR "$global_env_file"
# Get/Set new (edited) environment variables
lib_gen::__read_env "$global_env_file"
lib_gen::__set_client_globals
}
function lib_gen::__gen_container()
{
lib_utils::print_debug "Generating container"
#
# Check to proceed
#
lib_utils::print_custom " \e[32m│\e[0m\n"
lib_utils::print_custom " \e[32m├─\e[34;1m Generate (or update) container profile configs and/or accounts? [Y/n] \e[0m"
read -p "" _read
local _confirm="${_read:-y}"
if [[ "$_confirm" != [yY] ]]; then
lib_utils::print_custom " \e[32m│\e[0m\n"
lib_utils::print_normal ""
return 0
fi
[ -z "$DOCKER_FINANCE_CLIENT_FLOW" ] && lib_utils::die_fatal
if [ -d "$DOCKER_FINANCE_CLIENT_FLOW" ]; then
lib_utils::print_custom " \e[32m│ ├─\e[34m hledger-flow path exists, continue with backup and generation? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
if [[ "$_confirm" != [yY] ]]; then
lib_utils::print_custom " \e[32m│\e[0m\n"
lib_utils::print_normal ""
return 0
fi
fi
#
# Development setup
#
lib_utils::print_custom " \e[32m│ │\e[0m\n"
lib_utils::print_custom " \e[32m│ ├─\e[34;1m Will this profile be used for development and/or demonstration? [N/y] \e[0m"
read -p "" _read
local _is_testing="${_read:-false}"
if [[ "$_read" == [yY] ]]; then
_is_testing=true
fi
lib_utils::print_debug "_is_testing=${_is_testing}"
#
# Construct full path to subprofile
#
lib_utils::print_custom " \e[32m│ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ ├─\e[34m Enter profile name (e.g., family in 'family/alice'): \e[0m"
read -p "" _read
local -r _profile="${_read:-default}"
lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Using profile:\e[0m ${_profile}\n"
lib_utils::print_custom " \e[32m│ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ └─\e[34m Enter subprofile name (e.g., alice in 'family/alice'): \e[0m"
read -p "" _read
local -r _subprofile="${_read:-${global_user}}"
lib_utils::print_custom " \e[32m│ │ └─\e[34m Using subprofile:\e[0m ${_subprofile}\n"
#
# Execute
#
lib_utils::print_custom " \e[32m│ │\e[0m\n"
lib_utils::print_custom " \e[32m│ ├─\e[34;1m Generate (or update) joint client/container shell script (superscript)? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
if [[ "$_confirm" == [yY] ]]; then
local _gen_path="\${DOCKER_FINANCE_CONTAINER_FLOW}/profiles/${_profile}/${_subprofile}" # \$ for container view
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_shell "$_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}" # client's view
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
}
#
# Generate client-side container superscript
#
function lib_gen::__gen_shell()
{
local -r _gen_path="$1"
local -r _gen_conf_path="$2"
# Subprofile source added to superscript
local -r _source="source \"${_gen_conf_path}/shell/subprofile.bash\""
local _file="$global_shell_file"
if [ -f "$_file" ]; then
# Backup
lib_utils::print_custom " \e[32m│ │ └─\e[34m Shell superscript found, backup before continuing? [Y/n] \e[0m"
read -p "" _read
local _confirm="${_read:-y}"
if [[ "$_confirm" == [yY] ]]; then
# Backup
local -r _backup=("cp" "-a" "$_file" "${_file}_${global_suffix}")
lib_utils::print_debug "${_backup[@]}"
"${_backup[@]}" || lib_utils::die_fatal
fi
lib_utils::print_custom " \e[32m│ │ └─\e[34m Overwrite existing file? [Y/n] \e[0m"
read -p "" _read
local _confirm="${_read:-y}"
if [[ "$_confirm" == [yY] ]]; then
# Overwrite existing file
lib_gen::__gen_shell_write
else
# Append subprofile source to existing file
lib_utils::print_custom " \e[32m│ │ └─\e[34m Append to existing file? [Y/n] \e[0m"
read -p "" _read
local _confirm="${_read:-y}"
if [[ "$_confirm" == [yY] ]]; then
grep "$_source" "$_file" >&/dev/null || sed -i "$(wc -l <$_file)i\\$_source\\" "$_file"
fi
fi
local _print_custom=" \e[32m│ │ └─\e[34m Edit (new) superscript now? [Y/n] \e[0m"
else
# Create new file
lib_gen::__gen_shell_write
local _print_custom=" \e[32m│ │ └─\e[34m Edit (new) superscript now? [Y/n] \e[0m"
fi
lib_utils::print_custom "$_print_custom"
read -p "" _read
local _confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] && $EDITOR "$_file"
}
function lib_gen::__gen_shell_write()
{
[ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal
[ -z "$DOCKER_FINANCE_VERSION" ] && lib_utils::die_fatal
lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}"
lib_utils::print_debug "DOCKER_FINANCE_VERSION=${DOCKER_FINANCE_VERSION}"
sed \
-e "s:@DOCKER_FINANCE_SUBPROFILE_SOURCE@:${_source}:g" \
-e "s:@DOCKER_FINANCE_VERSION@:${DOCKER_FINANCE_VERSION}:g" \
"${global_repo_conf_dir}/container/shell/superscript.bash.in" >"$_file"
}
#
# Generate hledger-flow
#
function lib_gen::__gen_flow()
{
local -r _gen_path="$1"
local -r _gen_conf_path="$2"
lib_utils::print_custom " \e[32m│ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's shell script? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] && lib_gen::__gen_flow_shell
lib_utils::print_custom " \e[32m│ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's fetch configuration? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] && lib_gen::__gen_flow_fetch
lib_utils::print_custom " \e[32m│ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's financial metadata? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] && lib_gen::__gen_flow_meta
lib_utils::print_custom " \e[32m│ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's hledger-flow accounts? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] && lib_gen::__gen_flow_accounts
lib_utils::print_custom " \e[32m│\e[0m\n"
# Placeholder connector between hledger-flow code and end-user's hledger-flow profiles
# NOTE: entrypoint will (MUST) update this on every container run (yes, looks different than entrypoint)
lib_utils::print_custom " \e[32m├─\e[34;1m Connecting flow sources \e[0m\n"
[ -z "$DOCKER_FINANCE_CONTAINER_REPO" ] && lib_utils::die_fatal
ln -f -s "${DOCKER_FINANCE_CONTAINER_REPO}/src/hledger-flow" "${DOCKER_FINANCE_CLIENT_FLOW}/src"
lib_utils::print_custom " \e[32m│\e[0m\n"
}
#
# Generate subprofile script
#
function lib_gen::__gen_flow_shell()
{
local _dir="${_gen_conf_path}/shell"
[ ! -d "$_dir" ] && mkdir -p "$_dir"
local _file="${_dir}/subprofile.bash"
if [ -f "$_file" ]; then
lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Subprofile script 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_flow_shell_write
local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile script now? [Y/n] \e[0m"
fi
else
lib_gen::__gen_flow_shell_write
local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile script 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_flow_shell_write()
{
[ -z "$DOCKER_FINANCE_CONTAINER_CMD" ] && lib_utils::die_fatal
sed \
-e "s:@DOCKER_FINANCE_CONTAINER_CMD@:${DOCKER_FINANCE_CONTAINER_CMD}:g" \
-e "s:@DOCKER_FINANCE_CONTAINER_REPO@:${DOCKER_FINANCE_CONTAINER_REPO}:g" \
-e "s:@DOCKER_FINANCE_VERSION@:${DOCKER_FINANCE_VERSION}:g" \
-e "s:@DOCKER_FINANCE_PROFILE@:${_profile}:g" \
-e "s:@DOCKER_FINANCE_SUBPROFILE@:${_subprofile}:g" \
"${global_repo_conf_dir}/container/shell/subprofile.bash.in" >"$_file"
}
#
# Generate client-side container fetch
#
function lib_gen::__gen_flow_fetch()
{
local _dir="${_gen_conf_path}/fetch"
[ ! -d "$_dir" ] && mkdir -p "$_dir"
local _file="${_dir}/fetch.yaml"
if [ -f "$_file" ]; then
lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Subprofile's fetch 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_flow_fetch_write
local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's fetch configuration now? [Y/n] \e[0m"
fi
else
lib_gen::__gen_flow_fetch_write
local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's fetch 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_flow_fetch_write()
{
[ -z "$DOCKER_FINANCE_VERSION" ] && lib_utils::die_fatal
[ -z "$_profile" ] && lib_utils::die_fatal
[ -z "$_subprofile" ] && lib_utils::die_fatal
[ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal
[ -z "$_file" ] && lib_utils::die_fatal
sed \
-e "s:@DOCKER_FINANCE_VERSION@:${DOCKER_FINANCE_VERSION}:g" \
-e "s:@DOCKER_FINANCE_PROFILE@:${_profile}:g" \
-e "s:@DOCKER_FINANCE_SUBPROFILE@:${_subprofile}:g" \
"${global_repo_conf_dir}/container/fetch/fetch.yaml.in" >"$_file"
}
#
# Generate financial metadata
#
function lib_gen::__gen_flow_meta()
{
local _dir="${_gen_conf_path}/meta"
[ ! -d "$_dir" ] && mkdir -p "$_dir"
local _file="${_dir}/meta.csv"
if [ -f "$_file" ]; then
lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Subprofile's financial metadata 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_flow_meta_write
local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's financial metadata now? [Y/n] \e[0m"
fi
else
lib_gen::__gen_flow_meta_write
local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's financial metadata 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_flow_meta_write()
{
# Deletes default comments or else ROOT meta sample won't work out-of-the-box
sed \
-e "/\/\/\\!/d" \
-e "s:@DOCKER_FINANCE_VERSION@:${DOCKER_FINANCE_VERSION}:g" \
"${global_repo_conf_dir}/container/meta/meta.csv.in" >"$_file"
}
#
# Generate accounts
#
function lib_gen::__gen_flow_accounts()
{
lib_utils::print_debug "Generating accounts"
local _subprofile_path="${_gen_path}/import/${_subprofile}"
lib_utils::print_debug "_subprofile_path=${_subprofile_path}"
if [ -d "$_subprofile_path" ]; then
lib_utils::print_custom " \e[32m│ │ │ ├─\e[34m Subprofile import path exists! Continue with account generation (files will be backed up)? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] || return 0
else
mkdir -p "$_subprofile_path"
fi
# docker-finance templates
local _templates_paths
mapfile -t _templates_paths < <(find ${DOCKER_FINANCE_CLIENT_REPO}/container/src/hledger-flow/accounts -name template | sort)
declare -r _templates_paths
lib_utils::print_debug "_templates_paths=${_templates_paths[*]}"
lib_utils::print_custom " \e[32m│ │ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ │ ├─\e[34;1m Generate individual subprofile accounts instead of generating them all at once? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
# Cycle through all available docker-finance templates and populate subprofile
if [[ $_confirm == [yY] ]]; then
for _template_path in "${_templates_paths[@]}"; do
lib_utils::print_debug "_template_path=${_template_path}"
lib_utils::print_custom " \e[32m│ │ │ │ │\e[0m\n"
lib_utils::print_custom " \e[32m│ │ │ │ ├─\e[34m\e[1m Generate $(echo $_template_path | rev | cut -d/ -f2 | rev) ? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] \
&& lib_gen::__gen_flow_accounts_populate "$_subprofile_path" "$_template_path" "$_is_testing"
done
else
for _template_path in "${_templates_paths[@]}"; do
lib_utils::print_debug "$_subprofile_path $_template_path $_is_testing"
lib_gen::__gen_flow_accounts_populate "$_subprofile_path" "$_template_path" "$_is_testing"
done
fi
}
function lib_gen::__gen_flow_accounts_populate()
{
[ -z "$1" ] && lib_utils::die_fatal
[ -z "$2" ] && lib_utils::die_fatal
[ -z "$3" ] && lib_utils::die_fatal
local _subprofile_path="$1"
local _template_path="$2"
local _is_testing="$3"
# Continue if hledger-flow account exists
local _continue
_continue=true
local _account_path
_account_path="${_subprofile_path}/$(echo $_template_path | rev | cut -d/ -f2 | rev)"
lib_utils::print_debug "_account_path=${_account_path}"
# TODO: doesn't check for blockchain explorer shared rules/bash
if [ -d "$_account_path" ]; then
lib_utils::print_custom " \e[32m│ │ │ │ │ └─\e[34m Account exists! Continue with backup and generation? [Y/n] \e[0m"
read -p "" _read
_confirm="${_read:-y}"
[[ "$_confirm" == [yY] ]] || _continue=false
fi
# Populate with template
if [[ $_continue == true ]]; then
# Populates account and blockchain explorer's shared rules/preprocess
# (at top-level, along with all the account's shared rules/preprocess)
lib_utils::print_debug "Copying '${_template_path}/*' to '${_subprofile_path}/'"
cp -a -R --backup --suffix="_${global_suffix}" \
"${_template_path}"/* "$_subprofile_path/" || lib_utils::die_fatal
# If not blockchain explorers (since those exist as subaccounts)
if [[ ! "$_template_path" =~ "blockchain-explorers" ]]; then
# Add year to 1-in
while read _in_path; do
local _year_path
_year_path="${_in_path}/$(date +%Y)"
lib_utils::print_debug "Making year path '${_year_path}'"
mkdir -p "$_year_path"
# Appropriate test data if testing
local _mockup="${_in_path}/mockup"
lib_utils::print_debug "Getting mockup '${_mockup}'"
if [[ $_is_testing == true ]]; then
lib_utils::print_debug "Copying mockup to '${_in_path}'"
cp -a -R --backup --suffix="_${global_suffix}" "${_mockup}"/* "${_in_path}/" || lib_utils::die_fatal
fi
# Always remove original test data
rm -fr "$_mockup"
done < <(find "$_account_path" -name 1-in)
fi
fi
}
# vim: sw=2 sts=2 si ai et