diff --git a/README.md b/README.md index 5d0d92c..74bc075 100644 --- a/README.md +++ b/README.md @@ -329,9 +329,10 @@ Supported blockchains (independent of wallet type): The following will generate your Docker-related client/container environment for the default image (see [Environment Generation](#environment-generation) for details): ```bash - dfi archlinux/${USER}:default gen + dfi archlinux/${USER}:default gen all=all ``` - > If you would like to use the `ubuntu` image, replace `archlinux` with `ubuntu` here and for all remaining steps + > To regenerate any step in this process, now or in the future, use `gen help` for available options + > To use the `ubuntu` image instead, replace `archlinux` with `ubuntu` here and for all remaining steps 7. **Build default `docker-finance` image**: ```bash @@ -349,7 +350,7 @@ Supported blockchains (independent of wallet type): 10. (Optional) **Developers: on your client (host), build and setup the `dev-tools` platform**: ```bash - dfi dev-tools/${USER}:default build type=default && dfi dev-tools/${USER}:default gen + dfi dev-tools/${USER}:default build type=default && dfi dev-tools/${USER}:default gen all=all ``` ### Environment Generation @@ -371,7 +372,7 @@ You'll create these files (and more) when running client (host) command `gen`, a When running `gen`, you'll see the following: -> *Client-side environment found, backup then generate new one?* +> *Client environment file found, backup then generate new one? [Y/n]* Generates the client (host) configuration file (see the [Client (Host) Configuration File](#client-host-configuration) for details). @@ -384,22 +385,21 @@ Generates custom Dockerfile. Do as you wish; install your own container packages - To easily edit this configuration file after `gen` is complete, run client (host) command `edit type=build` +> *Generate joint client/container superscript? [Y/n]* + +Select 'y' if this is your first-run for the given platform and tag, or if you need to regenerate the file (see [Superscript](#clientcontainer-superscript) for details). + --- #### Container Generation After the previous client environment is generated, the following will prepare the container environment (everything you'll need while inside `docker-finance`). -> *Generate (or update) container flow (profiles, etc.)?* +> *Generate container finance flow (layout and profiles)? [Y/n]* -Although the container environment is a minimum requirement, here you'll have the option to continue generating or to backup a previous install. +Not limited to `hledger-flow` data, this option leads to generating the layout and files needed for processing *all* `docker-finance` end-user generated data (journals, configurations, etc.). -> *Will this profile be used for development and/or demonstration?* - -If you're a developer or wish to see the mockup test profile, select 'y' here. - -It should be noted that: - - mockup data can be found in the `mockup` directories within this repository +Although the container environment is a minimum requirement, here you'll have the option to continue generation. > *Enter profile name (e.g., family in 'family/alice')* > *Enter subprofile name (e.g., alice in 'family/alice')* @@ -410,42 +410,30 @@ It should be noted that: - all subsequent questions and container generation will relate to this `profile/subprofile` pairing - all output will be sent to the `${DOCKER_FINANCE_CONTAINER_FLOW}/profiles/profile/subprofile` path -> *Generate (or update) joint client/container shell script (superscript)? [Y/n]* - -Select 'y' if this a first-run. If this is not a first-run but you need to regenerate the file, then select 'y' (see [Superscript](#clientcontainer-superscript) for details). - -> *Generate (or update) hledger configuration file?* - -If the container's version of hledger supports it, use this customizable configuration file. - -> *Generate (or update) container flow configs and/or accounts?* - -Not limited to `hledger-flow` data, this option leads to generating the layout and files needed for processing *all* `docker-finance` end-user generated data (journals, configurations, etc.). - -> *Generate (or update) subprofile's shell script?* +> *Generate subprofile's subscript file? [Y/n]* The container's subprofile's shell script is where all subprofile commands and aliases exist. -This file is generated on a per-subprofile basis and all custom code *on a per-subprofile basis* should go here (see [Subprofile](#subprofile) for details). +This file is generated on a per-subprofile basis and all custom code *on a per-subprofile basis* should go here (see [Subscript](#subscript) for details). -> *Generate (or update) subprofile's fetch configuration?* +> *Generate subprofile's fetch configuration file? [Y/n]* The container's fetch configuration is what all remote fetching relies on: prices, exchanges, blockchain explorers; all are configured here (see [Fetch](#fetch-1) for details). -> *Generate (or update) subprofile's financial metadata?* +> *Generate subprofile's financial metadata file? [Y/n]* The container's *per-subprofile* metadata file. This file contains all your custom metadata and can edited with the `edit` and analyzed with the `meta` or `root` command (see [Meta](#meta) for details). -> *Generate (or update) subprofile's hledger-flow accounts?* +> *Generate subprofile's hledger configuration file? [Y/n]* + +This configuration file is specific to `hledger`. See `hledger` documentation for options. + +> *Generate subprofile's hledger-flow accounts? [Y/n]* The container's `hledger-flow` accounts to be installed. These are the accounts described in [What is supported?](#what-is-supported). -> *Generate individual subprofile accounts instead of generating them all at once?* - -If you intend to only use a few accounts, you can do so here. Otherwise, generate all accounts (recommended). - **WARNING**: if you plan to use blockchain-based wallets (coinbase-wallet, pera-wallet, ledger, metamask, etc.), you **MUST** generate their respective chains, as seen during generation (`algorand`, `ethereum-based`, `tezos`, etc). ### Configuration Files @@ -639,7 +627,7 @@ After `gen` is complete, you can edit this file with the client (host) command: The client/container shell script (Superscript) is a bind-mounted (by directory) script that: - is the intermediary between client and container - is unique to each client (host) user (/home/alice, /home/bob, etc.) - - is the glue that ties together **all** container [Subprofile](#subprofile) scripts + - is the glue that ties together **all** container [Subscript](#subscript) - is generated on a per-client basis: all custom code on a **per-client basis** should go here See the in-file comments for further documentation: @@ -654,15 +642,15 @@ After `gen` is complete, you can edit this file with the client (host) command: These configurations are confined solely to the container. -##### *Subprofile* +##### *Subscript* -The Subprofile script is unique to each subprofile for each `profile/subprofile` within the `profiles` parent directory. +The Subprofile's subscript is unique to each subprofile, for each `profile/subprofile` within the `profiles` parent directory. By default, this file will contain user aliases for all container commands. These aliases are mostly useful for small setups or setups with uniquely named subprofiles among all profiles. See the in-file comments for further documentation: - - [subprofile.bash.in](client/docker-finance.d/container/shell/subprofile.bash.in) + - [subscript.bash.in](client/docker-finance.d/container/shell/subscript.bash.in) After `gen` is complete, from within the container, you can edit this file with: `dfi profile/subprofile edit type=shell` (see [Container Command Format](#container-command-format)). @@ -771,7 +759,7 @@ Assuming `` is `testprofile/testuser`: dfi testprofile/testuser fetch help ``` -Or, use a subprofile alias, as described in [Subprofile](#subprofile): +Or, use a subprofile alias, as described in [Subscript](#subscript): ```bash testuser_fetch help @@ -909,8 +897,11 @@ For other dependencies, please see their individual contributing guidelines. You'll greatly benefit from building the [`dev-tools` image](#image-dev-tools), as seen in `docker-finance help`. -Additionally, when developing with the [`docker-finance` image](#image-docker-finance), please test your work with mockups as described in [Environment Generation](#environment-generation) and [Flow Layout](#flow-layout). -> Note: mockup CSVs will *intentionally* have multiple years within in a `1-in/year` directory in order to test for year parsing. +Additionally, when developing with the [`docker-finance` image](#image-docker-finance) create a development profile with the `gen` command argument `dev=on`. From there, you'll have access to mockup CSVs as described in [Flow Layout](#flow-layout). + +> - In addition to `dev=on`, run the `confirm=off` and `profile=` arguments to quickly spin-up a new development profile +> - Developer mockup CSVs can be found in the `mockup` directories within the hledger-flow section of this repository +> - Developer mockup CSVs will *intentionally* have multiple years within in a `1-in/year` directory in order to test for year parsing #### Plugins diff --git a/client/docker-finance.d/container/shell/subprofile.bash.in b/client/docker-finance.d/container/shell/subscript.bash.in similarity index 98% rename from client/docker-finance.d/container/shell/subprofile.bash.in rename to client/docker-finance.d/container/shell/subscript.bash.in index c9a9768..09abc80 100644 --- a/client/docker-finance.d/container/shell/subprofile.bash.in +++ b/client/docker-finance.d/container/shell/subscript.bash.in @@ -20,7 +20,7 @@ # docker-finance @DOCKER_FINANCE_VERSION@ # -# Subprofile script +# Subscript (subprofile script) called by superscript # [ -z "$DOCKER_FINANCE_CONTAINER_CMD" ] && echo "DOCKER_FINANCE_CONTAINER_CMD not set, check installation" >&2 diff --git a/client/docker-finance.d/container/shell/superscript.bash.in b/client/docker-finance.d/container/shell/superscript.bash.in index 734e3c3..22832ef 100644 --- a/client/docker-finance.d/container/shell/superscript.bash.in +++ b/client/docker-finance.d/container/shell/superscript.bash.in @@ -40,7 +40,6 @@ alias c='clear' # or ctrl+l alias e='exit' alias grep='grep --color=auto' -# All subprofiles' scripts -@DOCKER_FINANCE_SUBPROFILE_SOURCE@ +# All subprofiles' subscripts are appended below # vim: syn=bash sw=2 sts=2 si ai et diff --git a/client/src/docker/completion.bash b/client/src/docker/completion.bash index a217574..1ee4cce 100644 --- a/client/src/docker/completion.bash +++ b/client/src/docker/completion.bash @@ -73,7 +73,7 @@ function docker-finance::completion() case "$_prev" in gen) - # TODO: _currently no-op + mapfile -t _reply < <(compgen -W "help all${global_arg_delim_2} type${global_arg_delim_2} profile${global_arg_delim_2} config${global_arg_delim_2} account${global_arg_delim_2} dev${global_arg_delim_2} confirm${global_arg_delim_2}" -- "$_cur") ;; edit) mapfile -t _reply < <(compgen -W "help type${global_arg_delim_2}" -- "$_cur") diff --git a/client/src/docker/lib/internal/lib_docker.bash b/client/src/docker/lib/internal/lib_docker.bash index d32914f..15cde64 100644 --- a/client/src/docker/lib/internal/lib_docker.bash +++ b/client/src/docker/lib/internal/lib_docker.bash @@ -58,7 +58,7 @@ function lib_docker::__docker() [ -z "$global_platform" ] && lib_utils::die_fatal [ -z "$global_tag" ] && lib_utils::die_fatal - # Inherited from caller (via lib_gen) + # Inherited from caller (via `lib_env`) [ -z "$global_client_version" ] && lib_utils::die_fatal [ -z "$global_repo_dockerfiles" ] && lib_utils::die_fatal @@ -561,11 +561,11 @@ function lib_docker::__edit() [ -z "$global_env_file" ] && lib_utils::die_fatal [ ! -f "$global_env_file" ] \ - && lib_utils::die_fatal "Environment file now found" + && lib_utils::die_fatal "Environment file not found" - [ -z "$global_shell_file" ] && lib_utils::die_fatal - [ ! -f "$global_shell_file" ] \ - && lib_utils::die_fatal "Shell (superscript) file now found" + [ -z "$global_superscript" ] && lib_utils::die_fatal + [ ! -f "$global_superscript" ] \ + && lib_utils::die_fatal "Shell (superscript) file not found" # Run all files through one editor instance local _paths=() @@ -581,7 +581,7 @@ function lib_docker::__edit() [[ "$global_platform" == "dev-tools" ]] \ && lib_utils::die_fatal "Invalid platform, use finance image" - _paths+=("$global_shell_file") + _paths+=("$global_superscript") ;; build | dockerfile) [ -z "$global_custom_dockerfile" ] && lib_utils::die_fatal diff --git a/client/src/docker/lib/internal/lib_env.bash b/client/src/docker/lib/internal/lib_env.bash new file mode 100644 index 0000000..e957cf7 --- /dev/null +++ b/client/src/docker/lib/internal/lib_env.bash @@ -0,0 +1,311 @@ +#!/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 . + +[ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && exit 1 + +# +# "Libraries" +# + +# Utilities (a container library (but container is never exposed to client code)) +source "${DOCKER_FINANCE_CLIENT_REPO}/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 + +# Dependencies +deps=("sed") +lib_utils::deps_check "${deps[@]}" + +# IMPORTANT: keep umask for security +umask o-rwx + +# +# "Constructor" for environment generation +# +# 1. Sets client-side environment with defaults or use existing environment +# 2. If configured, resets to alternative environment configuration after bootstrap +# +# NOTE: some bootstrapped defaults are ignored by environment file (as seen below) +# + +function lib_env::env() +{ + [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal + + # NOTE: global_* *MUST* be reset after sourcing new env file + + # + # Generate `docker-finance` version + # + + global_repo_manifest="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.yaml" + declare -g global_repo_manifest + + global_client_version="$(grep '^version: ' $global_repo_manifest | sed -e 's/version: "//' -e 's/"//g')" + # shellcheck disable=SC2034 # used during env gen + declare -g global_client_version + + # + # If empty environment: + # + # 1. Poke at possible (default) location of end-user environment file + # a. If found, point to new location and read/reset from there + # + # 2. If file not found, bootstrap with defaults + # + + # Environment (end-user) + global_conf_filename="${USER}@$(uname -n)" + declare -g global_conf_filename + + # Environment (end-user) tag dir (not full path) + [ -z "$global_tag" ] && lib_utils::die_fatal + global_tag_dir="client/$(uname -s)-$(uname -m)/${global_platform}/${global_tag}" + declare -g global_tag_dir + + local _env_dir="/home/${USER}/.config/docker-finance.d/${global_tag_dir}/env" # NOTE: keep aligned with gen.bash + local _env_file="${_env_dir}/${global_conf_filename}" + + # shellcheck source=/dev/null + [ -f "$_env_file" ] && source "$_env_file" + + if [ -z "$DOCKER_FINANCE_CLIENT_CONF" ]; then + # shellcheck source=/dev/null + source "${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.d/client/env/gen.bash" + fi + + [ -z "$DOCKER_FINANCE_CLIENT_CONF" ] \ + && lib_utils::die_fatal "Defaults not generated! (${global_repo_env_file})" + + lib_env::__set_client_globals + + # + # Reset environment with user-provided (user-defined) existing file (if available) + # + + _env_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/env" + _env_file="${_env_dir}/${global_conf_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_env::__read "$_env_file" + lib_env::__set_client_globals + else + lib_utils::print_warning \ + "Client environment '${_env_file}' is empty! Writing defaults" + lib_env::__write "$_env_file" + fi + else + [ -z "$global_command" ] && lib_utils::die_fatal + [ -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_env::__write "$_env_file" + fi +} + +# +# Set client globals from environment +# + +function lib_env::__set_client_globals() +{ + lib_utils::print_debug "Setting (or resetting) client globals" + + # + # Repository env + # + + [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal + + # Generate `docker-finance` version + global_repo_manifest="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.yaml" + declare -g global_repo_manifest + lib_utils::print_debug "global_repo_manifest=${global_repo_manifest}" + + global_client_version="$(grep '^version: ' $global_repo_manifest | sed -e 's/version: "//' -e 's/"//g')" + # shellcheck disable=SC2034 # used during env gen + declare -g global_client_version + lib_utils::print_debug "global_client_version=${global_client_version}" + + # This does not reset when reading env; export again + export DOCKER_FINANCE_VERSION="$global_client_version" + lib_utils::print_debug "DOCKER_FINANCE_VERSION=${DOCKER_FINANCE_VERSION}" + + # Repository-provided (not user-defined) default environment + global_repo_conf_dir="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.d" + declare -g global_repo_conf_dir + lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" + + # Environment (repo) + declare -g 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)" + lib_utils::print_debug "global_repo_env_file=${global_repo_env_file}" + + # + # Repository env (Dockerfiles) + # + + # Set image type + [ -z "$global_platform" ] && lib_utils::die_fatal + case "$global_platform" in + archlinux | ubuntu) + global_platform_image="finance" + ;; + dev-tools) + global_platform_image="dev-tools" + ;; + *) + lib_utils::die_fatal "unsupported platform" + ;; + esac + declare -g global_platform_image + lib_utils::print_debug "global_platform_image=${global_platform_image}" + + # Base location of Docker-related files (.in and final out files) + global_repo_dockerfiles="${DOCKER_FINANCE_CLIENT_REPO}/client/Dockerfiles/local/${global_platform_image}" + # shellcheck disable=SC2034 # used in lib_docker + declare -g global_repo_dockerfiles + lib_utils::print_debug "global_repo_dockerfiles=${global_repo_dockerfiles}" + + # Base custom end-user .in Dockerfile (to be appended to final Dockerfile after installation) + declare -g global_repo_custom_dockerfile="${global_repo_conf_dir}/client/Dockerfiles/${global_platform_image}/Dockerfile.${global_platform}.in" + [ ! -f "$global_repo_custom_dockerfile" ] \ + && lib_utils::die_fatal "Missing default custom Dockerfile '${global_repo_custom_dockerfile}'" + lib_utils::print_debug "global_repo_custom_dockerfile=${global_repo_custom_dockerfile}" + + # + # Client-side env + # + + [ -z "$global_tag_dir" ] && lib_utils::die_fatal + + # Environment (end-user) format + [ -z "$DOCKER_FINANCE_USER" ] && lib_utils::die_fatal + global_conf_filename="${DOCKER_FINANCE_USER}@$(uname -n)" + declare -g global_conf_filename + lib_utils::print_debug "global_conf_filename=${global_conf_filename}" + + # Environment file (if available) + local _client_env_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/env" + [ ! -d "$_client_env_dir" ] && mkdir -p "$_client_env_dir" + global_env_file="${_client_env_dir}/${global_conf_filename}" + lib_utils::print_debug "global_env_file=${global_env_file}" + + # Custom Dockerfile (if available) + local _client_dockerfile_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/Dockerfiles" + [ ! -d "$_client_dockerfile_dir" ] && mkdir -p "$_client_dockerfile_dir" + global_custom_dockerfile="${_client_dockerfile_dir}/${global_conf_filename}" + lib_utils::print_debug "global_custom_dockerfile=${global_custom_dockerfile}" + + # 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_superscript="${_client_shell_dir}/superscript.bash" + lib_utils::print_debug "global_superscript=${global_superscript}" + + # Client view of client portion of repository + 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_env::__read() +{ + local _file="$1" + + # Get environment + local _env=() + while read _line; do + _env+=("$_line") + done < <(printenv | grep -Eo "^DOCKER_FINANCE[^=]+") + + # Reset environment + for _line in "${_env[@]}"; do + lib_utils::print_debug "Unsetting $_line" + unset "$_line" + done + + # Set, if a script that generates env + if [[ "$(head -n1 $_file)" =~ (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 + # Ignore comments + if [[ ! "$_line" =~ ^# ]]; then + # Don't allow manipulating version via file + if [[ "$_line" =~ ^DOCKER_FINANCE_VERSION ]]; then + continue + fi + # Export valid line + export "${_line?}" # SC2163 + lib_utils::print_debug "$_line" + fi + done <"$_file" +} + +# +# Write client-side environment to given file +# + +function lib_env::__write() +{ + lib_utils::print_debug "Writing environment" + + unset DOCKER_FINANCE_VERSION # Must be generated internally + printenv | grep -E "DOCKER_FINANCE" | sort >"$1" +} + +# vim: sw=2 sts=2 si ai et diff --git a/client/src/docker/lib/internal/lib_gen.bash b/client/src/docker/lib/internal/lib_gen.bash index 4502581..d8ddb29 100644 --- a/client/src/docker/lib/internal/lib_gen.bash +++ b/client/src/docker/lib/internal/lib_gen.bash @@ -23,399 +23,475 @@ # "Libraries" # +# Runtime environment handler +source "${DOCKER_FINANCE_CLIENT_REPO}/client/src/docker/lib/internal/lib_env.bash" || exit 1 + +# Utilities (a container library (but container is never exposed to client code)) source "${DOCKER_FINANCE_CLIENT_REPO}/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 +function lib_gen::__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 -# IMPORTANT: keep umask for security -umask o-rwx + # All available hledger-flow accounts + local _accounts + mapfile -t _accounts < <(find ${DOCKER_FINANCE_CLIENT_REPO}/container/src/hledger-flow/accounts -name template | sort | rev | cut -d/ -f2 | rev) + declare -r _accounts -if ! docker compose version 1>/dev/null; then - lib_utils::die_fatal "Docker compose plugin not installed" -fi + local -r _usage=" +\e[32mDescription:\e[0m -if [ -z "$EDITOR" ]; then - editors=("vim" "vi" "emacs" "nano") - for editor in "${editors[@]}"; do - hash "$editor" &>/dev/null && export EDITOR="$editor" && break + Generate environment and configurations + +\e[32mUsage:\e[0m + + $ $global_usage <<[all${global_arg_delim_2}] [type${global_arg_delim_3}]> [<[profile${global_arg_delim_2}]> [config${global_arg_delim_2}] [account${global_arg_delim_2}]] [confirm${global_arg_delim_2}<{on|yes|true} | {off|no|false}>] [dev${global_arg_delim_2}<{on|yes|true} | {off|no|false}>] + +\e[32mArguments:\e[0m + + All categories and data: + + all${global_arg_delim_2} + + Note: 'type' is currently the same as 'all' + + Category type: + + type${global_arg_delim_2} + + Flow (only): + + Full profile (w/ subprofile): + + profile${global_arg_delim_2} + + Configuration type: + + config${global_arg_delim_2} + + Accounts: + + account${global_arg_delim_2}<$(echo "${_accounts[@]}" | sed 's: : | :g')> + + Enable developer profile w/ mockups: + + dev${global_arg_delim_2}<{on|yes|true} | {off|no|false}> # (default disabled) + + Enable confirmations: + + confirm${global_arg_delim_2}<{on|yes|true} | {off|no|false}> # (default enabled) + + +\e[32mExamples:\e[0m + + \e[37;2m#\e[0m + \e[37;2m# All (client, container)\e[0m + \e[37;2m#\e[0m + + \e[37;2m# Generate all client and container data\e[0m + $ $global_usage all${global_arg_delim_2}all + + \e[37;2m# Generate all client and container data, skipping confirmations (using a default profile/subprofile name)\e[0m + $ $global_usage all${global_arg_delim_2}all confirm${global_arg_delim_2}no + + \e[37;2m# Generate all client and container data for profile called 'parent/child', skipping confirmations\e[0m + $ $global_usage all${global_arg_delim_2}all profile${global_arg_delim_2}parent${global_arg_delim_1}child confirm${global_arg_delim_2}off + + \e[37;2m#\e[0m + \e[37;2m# Client (joint container)\e[0m + \e[37;2m#\e[0m + + \e[37;2m# Generate only the Docker environment\e[0m + $ $global_usage type${global_arg_delim_2}env + + \e[37;2m# Generate custom Dockerfile and joint client/container superscript\e[0m + $ $global_usage type${global_arg_delim_2}build${global_arg_delim_3}superscript + + \e[37;2m# Generate all client related data\e[0m + $ $global_usage type${global_arg_delim_2}env${global_arg_delim_3}build${global_arg_delim_3}superscript + + \e[37;2m#\e[0m + \e[37;2m# Flow: Profile -> Configs / Accounts\e[0m + \e[37;2m#\e[0m + + \e[37;2m# Generate all client related and all flow related for profile/subprofile called 'parent/child'\e[0m + $ $global_usage type${global_arg_delim_2}env${global_arg_delim_3}build${global_arg_delim_3}superscript${global_arg_delim_3}flow profile${global_arg_delim_2}parent${global_arg_delim_1}child + + \e[37;2m# Same command as above but without confirmations and with developer mockups\e[0m + $ $global_usage type${global_arg_delim_2}env${global_arg_delim_3}build${global_arg_delim_3}superscript${global_arg_delim_3}flow profile${global_arg_delim_2}parent${global_arg_delim_1}child confirm${global_arg_delim_2}false dev${global_arg_delim_2}true + + \e[37;2m# Generate only the given configurations for 'parent/child'\e[0m + $ $global_usage type${global_arg_delim_2}flow profile${global_arg_delim_2}parent${global_arg_delim_1}child config${global_arg_delim_2}fetch${global_arg_delim_3}hledger${global_arg_delim_3}meta${global_arg_delim_3}subscript + + \e[37;2m# Generate only the given accounts for 'parent/child'\e[0m + $ $global_usage type${global_arg_delim_2}flow profile${global_arg_delim_2}parent${global_arg_delim_1}child account${global_arg_delim_2}capital-one${global_arg_delim_3}chase${global_arg_delim_3}coinbase + + \e[37;2m# Generate the given configs and accounts for given 'parent/child'\e[0m + $ $global_usage type${global_arg_delim_2}flow profile${global_arg_delim_2}parent${global_arg_delim_1}child config${global_arg_delim_2}meta${global_arg_delim_3}subscript account${global_arg_delim_2}ethereum-based${global_arg_delim_3}metamask + +\e[32mNotes:\e[0m + + - The 'profile' argument is limited to 'type' flow + - The 'config' and 'account' arguments require 'profile' +" + + # + # Ensure supported arguments + # + + [ $# -eq 0 ] && lib_utils::die_usage "$_usage" + + for _arg in "$@"; do + [[ ! "$_arg" =~ ^all${global_arg_delim_2} ]] \ + && [[ ! "$_arg" =~ ^type${global_arg_delim_2} ]] \ + && [[ ! "$_arg" =~ ^profile${global_arg_delim_2} ]] \ + && [[ ! "$_arg" =~ ^config[s]?${global_arg_delim_2} ]] \ + && [[ ! "$_arg" =~ ^account[s]?${global_arg_delim_2} ]] \ + && [[ ! "$_arg" =~ ^confirm${global_arg_delim_2} ]] \ + && [[ ! "$_arg" =~ ^dev${global_arg_delim_2} ]] \ + && lib_utils::die_usage "$_usage" done - if [ $? -ne 0 ]; then - lib_utils::die_fatal "Shell EDITOR is not set, export EDITOR in your shell" + + # + # 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$ ]]; then + local _arg_type="${_arg:${_len}}" + [ -z "$_arg_type" ] && lib_utils::die_usage "$_usage" + fi + + if [[ "$_key" =~ ^profile$ ]]; then + local _arg_profile="${_arg:${_len}}" + [ -z "$_arg_profile" ] && lib_utils::die_usage "$_usage" + fi + + if [[ "$_key" =~ ^config[s]?$ ]]; then + local _arg_config="${_arg:${_len}}" + [ -z "$_arg_config" ] && 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" =~ ^confirm$ ]]; then + local _arg_confirm="${_arg:${_len}}" + [ -z "$_arg_confirm" ] && lib_utils::die_usage "$_usage" + fi + + if [[ "$_key" =~ ^dev$ ]]; then + local _arg_dev="${_arg:${_len}}" + [ -z "$_arg_dev" ] && lib_utils::die_usage "$_usage" + fi + + done + + # + # Test for valid ordering/functionality of argument values + # + + [[ -z "$_arg_all" && -z "$_arg_type" ]] && lib_utils::die_usage "$_usage" + + # Arg: all + if [ ! -z "$_arg_all" ]; then + # If all= then no need for type= or config= or account= + if [[ ! -z "$_arg_type" || ! -z "$_arg_config" || ! -z "$_arg_account" ]]; then + lib_utils::die_usage "$_usage" + fi fi -fi + + # Arg: type + # Note: check not needed + + # Arg: profile + if [ ! -z "$_arg_profile" ]; then + # Requires type to be set + if [[ ! "${_arg_all[*]}" =~ all|type && -z "$_arg_type" ]]; then + lib_utils::die_usage "$_usage" + fi + fi + + # Arg: config + if [ ! -z "$_arg_config" ]; then + # Requires profile to be set + if [ -z "$_arg_profile" ]; then + lib_utils::die_usage "$_usage" + fi + fi + + # Arg: account + if [ ! -z "$_arg_account" ]; then + # Requires profile to be set + if [ -z "$_arg_profile" ]; then + lib_utils::die_usage "$_usage" + fi + fi + + # Arg: confirm + # Note: optional argument, check not needed + + # Arg: dev + # Note: optional argument, check not needed + + # + # Test argument values, set globals + # + + IFS="$global_arg_delim_3" + + # Arg: all + if [ ! -z "$_arg_all" ]; then + read -ra _read <<<"$_arg_all" + for _arg in "${_read[@]}"; do + # Supported values + [[ ! "$_arg" =~ ^all$|^type[s]?$ ]] \ + && lib_utils::die_usage "$_usage" + + # If all=all then no need for all={type} + [[ "$_arg" == "all" && "${#_read[@]}" -gt 1 ]] \ + && lib_utils::die_usage "$_usage" + done + # TODO: currently, 'all' will be equivalent 'type' + declare -gr global_arg_all=("${_read[@]}") + fi + + # Arg: type + if [ ! -z "$_arg_type" ]; then + read -ra _read <<<"$_arg_type" + for _arg in "${_read[@]}"; do + if [[ ! "$_arg" =~ ^env$|^build$|^flow$|^superscript$ ]]; then + lib_utils::die_usage "$_usage" + fi + done + declare -gr global_arg_type=("${_read[@]}") + fi + + # Arg: profile + if [ ! -z "$_arg_profile" ]; then + # Requires flow type + if [[ ! "${global_arg_all[*]}" =~ all|type && ! "${global_arg_type[*]}" =~ flow ]]; then + lib_utils::die_usage "$_usage" + fi + if [[ ! "$_arg_profile" =~ $global_arg_delim_1 ]]; then + lib_utils::die_usage "$_usage" + fi + declare -gr global_arg_profile="${_arg_profile%${global_arg_delim_1}*}" + declare -gr global_arg_subprofile="${_arg_profile##*${global_arg_delim_1}}" + fi + + # Arg: config + if [ ! -z "$_arg_config" ]; then + # Requires profile type + if [ -z "$global_arg_profile" ]; then + lib_utils::die_usage "$_usage" + fi + read -ra _read <<<"$_arg_config" + for _arg in "${_read[@]}"; do + if [[ ! "$_arg" =~ ^fetch$|^hledger$|^meta$|^subscript$ ]]; then + lib_utils::die_usage "$_usage" + fi + done + declare -gr global_arg_config=("${_read[@]}") + fi + + # Arg: account + if [ ! -z "$_arg_account" ]; then + # Requires profile type + if [ -z "$global_arg_profile" ]; then + lib_utils::die_usage "$_usage" + fi + read -ra _read <<<"$_arg_account" + # Test if given account is supported + for _arg in "${_read[@]}"; do + _found=false + for _account in "${_accounts[@]}"; do + [[ "$_arg" == "$_account" ]] && _found=true + done + [ $_found == true ] && continue || lib_utils::die_usage "$_usage" + done + declare -gr global_arg_account=("${_read[@]}") + fi + + # Arg: confirm + if [ ! -z "$_arg_confirm" ]; then + if [[ ! "$_arg_confirm" =~ (^on$|^yes$|^true$)|(^off$|^no$|^false$) ]]; then + lib_utils::die_usage "$_usage" + fi + if [[ "$_arg_confirm" =~ ^off$|^no$|^false$ ]]; then + declare -gr global_arg_confirm="" + else + declare -gr global_arg_confirm="true" + fi + else + declare -gr global_arg_confirm="true" + fi + + # Arg: dev + if [ ! -z "$_arg_dev" ]; then + if [[ ! "$_arg_dev" =~ (^on$|^yes$|^true$)|(^off$|^no$|^false$) ]]; then + lib_utils::die_usage "$_usage" + fi + if [[ "$_arg_dev" =~ ^on$|^yes$|^true$ ]]; then + declare -gr global_arg_dev="true" + else + declare -gr global_arg_dev="" + fi + else + declare -gr global_arg_dev="" + fi +} # -# "Constructor" for environment generation -# -# 1. Sets client-side environment with defaults or use existing environment -# 2. If configured, resets to alternative environment configuration after bootstrap -# -# NOTE: some bootstrapped defaults are ignored by environment file (as seen below) +# Generate new configurations or layout # function lib_gen::gen() { - [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal + lib_gen::__parse_args "$@" - # NOTE: global_* *MUST* be reset after sourcing new env file - - # - # Generate `docker-finance` version - # - - global_repo_manifest="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.yaml" - declare -g global_repo_manifest - - global_client_version="$(grep '^version: ' $global_repo_manifest | sed -e 's/version: "//' -e 's/"//g')" - # shellcheck disable=SC2034 # used during env gen - declare -g global_client_version - - # - # If empty environment: - # - # 1. Poke at possible (default) location of end-user environment file - # a. If found, point to new location and read/reset from there - # - # 2. If file not found, bootstrap with defaults - # - - # Environment (end-user) - global_conf_filename="${USER}@$(uname -n)" - declare -g global_conf_filename - - # Environment (end-user) tag dir (not full path) - [ -z "$global_tag" ] && lib_utils::die_fatal - global_tag_dir="client/$(uname -s)-$(uname -m)/${global_platform}/${global_tag}" - declare -g global_tag_dir - - local _env_dir="/home/${USER}/.config/docker-finance.d/${global_tag_dir}/env" # NOTE: keep aligned with gen.bash - local _env_file="${_env_dir}/${global_conf_filename}" - - # shellcheck source=/dev/null - [ -f "$_env_file" ] && source "$_env_file" - - if [ -z "$DOCKER_FINANCE_CLIENT_CONF" ]; then - # shellcheck source=/dev/null - source "${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.d/client/env/gen.bash" - fi - - [ -z "$DOCKER_FINANCE_CLIENT_CONF" ] \ - && lib_utils::die_fatal "Defaults not generated! (${global_repo_env_file})" - - lib_gen::__set_client_globals - - # - # Reset environment with user-provided (user-defined) existing file (if available) - # - - _env_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/env" - _env_file="${_env_dir}/${global_conf_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_command" ] && lib_utils::die_fatal - [ -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 (or resetting) client globals" - - # - # Repository env - # - - [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal - - # Generate `docker-finance` version - global_repo_manifest="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.yaml" - declare -g global_repo_manifest - lib_utils::print_debug "global_repo_manifest=${global_repo_manifest}" - - global_client_version="$(grep '^version: ' $global_repo_manifest | sed -e 's/version: "//' -e 's/"//g')" - # shellcheck disable=SC2034 # used during env gen - declare -g global_client_version - lib_utils::print_debug "global_client_version=${global_client_version}" - - # This does not reset when reading env; export again - export DOCKER_FINANCE_VERSION="$global_client_version" - lib_utils::print_debug "DOCKER_FINANCE_VERSION=${DOCKER_FINANCE_VERSION}" - - # Repository-provided (not user-defined) default environment - global_repo_conf_dir="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.d" - declare -g global_repo_conf_dir - lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" - - # Environment (repo) - declare -g 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)" - lib_utils::print_debug "global_repo_env_file=${global_repo_env_file}" - - # - # Repository env (Dockerfiles) - # - - # Set image type - [ -z "$global_platform" ] && lib_utils::die_fatal - case "$global_platform" in - archlinux | ubuntu) - global_platform_image="finance" - ;; - dev-tools) - global_platform_image="dev-tools" - ;; - *) - lib_utils::die_fatal "unsupported platform" - ;; - esac - declare -g global_platform_image - lib_utils::print_debug "global_platform_image=${global_platform_image}" - - # Base location of Docker-related files (.in and final out files) - global_repo_dockerfiles="${DOCKER_FINANCE_CLIENT_REPO}/client/Dockerfiles/local/${global_platform_image}" - # shellcheck disable=SC2034 # used in lib_docker - declare -g global_repo_dockerfiles - lib_utils::print_debug "global_repo_dockerfiles=${global_repo_dockerfiles}" - - # Base custom end-user .in Dockerfile (to be appended to final Dockerfile after installation) - declare -g global_repo_custom_dockerfile="${global_repo_conf_dir}/client/Dockerfiles/${global_platform_image}/Dockerfile.${global_platform}.in" - [ ! -f "$global_repo_custom_dockerfile" ] \ - && lib_utils::die_fatal "Missing default custom Dockerfile '${global_repo_custom_dockerfile}'" - lib_utils::print_debug "global_repo_custom_dockerfile=${global_repo_custom_dockerfile}" - - # - # Client-side env - # - - [ -z "$global_tag_dir" ] && lib_utils::die_fatal - - # Environment (end-user) format - [ -z "$DOCKER_FINANCE_USER" ] && lib_utils::die_fatal - global_conf_filename="${DOCKER_FINANCE_USER}@$(uname -n)" - declare -g global_conf_filename - lib_utils::print_debug "global_conf_filename=${global_conf_filename}" - - # Environment file (if available) - local _client_env_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/env" - [ ! -d "$_client_env_dir" ] && mkdir -p "$_client_env_dir" - global_env_file="${_client_env_dir}/${global_conf_filename}" - lib_utils::print_debug "global_env_file=${global_env_file}" - - # Custom Dockerfile (if available) - local _client_dockerfile_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/Dockerfiles" - [ ! -d "$_client_dockerfile_dir" ] && mkdir -p "$_client_dockerfile_dir" - global_custom_dockerfile="${_client_dockerfile_dir}/${global_conf_filename}" - lib_utils::print_debug "global_custom_dockerfile=${global_custom_dockerfile}" - - # 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 - 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() -{ - local _file="$1" - - # Get environment - local _env=() - while read _line; do - _env+=("$_line") - done < <(printenv | grep -Eo "^DOCKER_FINANCE[^=]+") - - # Reset environment - for _line in "${_env[@]}"; do - lib_utils::print_debug "Unsetting $_line" - unset "$_line" - done - - # Set, if a script that generates env - if [[ "$(head -n1 $_file)" =~ (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 - # Ignore comments - if [[ ! "$_line" =~ ^# ]]; then - # Don't allow manipulating version via file - if [[ "$_line" =~ ^DOCKER_FINANCE_VERSION ]]; then - continue - fi - # Export valid line - export "${_line?}" # SC2163 - lib_utils::print_debug "$_line" - fi - done <"$_file" -} - -# -# Write client-side environment to given file -# - -function lib_gen::__write_env() -{ - lib_utils::print_debug "Writing environment" - - unset DOCKER_FINANCE_VERSION # Must be generated internally - printenv | grep -E "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 + [ -z "$global_platform" ] && lib_utils::die_fatal if [ "$global_platform" != "dev-tools" ]; then lib_gen::__gen_container fi fi if [ $? -eq 0 ]; then + lib_utils::print_custom "\n" lib_utils::print_info "Congratulations, environment sucessfully generated!" lib_utils::print_custom "\n" else + lib_utils::print_custom "\n" lib_utils::die_fatal "Environment not fully generated! Try again" + lib_utils::print_custom "\n" fi } function lib_gen::__gen_client() { + [ -z "$global_suffix" ] && lib_utils::die_fatal + lib_utils::print_debug "Generating client" # # Client-side environment file # - # Backup existing 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" + if [[ -z "${global_arg_type[*]}" || "${global_arg_type[*]}" =~ env ]]; then - read -p "" _read - local _confirm="${_read:-y}" + [ -z "$global_env_file" ] && lib_utils::die_fatal + [ -z "$global_repo_env_file" ] && lib_utils::die_fatal - if [[ "$_confirm" == [yY] ]]; then - cp -a "$global_env_file" "${global_env_file}_${global_suffix}" \ - || lib_utils::die_fatal + # Backup existing 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 environment file found, backup then generate new one? [Y/n] \e[0m" - # Get/Set with repository defaults - lib_utils::print_debug "Reading $global_repo_env_file" - lib_gen::__read_env "$global_repo_env_file" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + local _confirm="${_read:-y}" - lib_utils::print_debug "Writing $global_env_file" - lib_gen::__write_env "$global_env_file" + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then + cp -a "$global_env_file" "${global_env_file}_${global_suffix}" \ + || lib_utils::die_fatal + + # Get/Set with repository defaults + lib_utils::print_debug "Reading $global_repo_env_file" + lib_env::__read "$global_repo_env_file" + + lib_utils::print_debug "Writing $global_env_file" + lib_env::__write "$global_env_file" + fi fi + + lib_utils::print_custom " \e[32m│ └─\e[34m Edit file now? [Y/n] \e[0m" + lib_gen::__gen_edit "$global_env_file" + + # Get/Set new (edited) environment variables + lib_env::__read "$global_env_file" + lib_env::__set_client_globals + 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 - # # Custom (optional) Dockerfile # - # Backup existing custom Dockerfile - if [ -f "$global_custom_dockerfile" ]; then - lib_utils::print_custom " \e[32m│\e[0m\n" - lib_utils::print_custom " \e[32m├─\e[34;1m Custom (optional) Dockerfile found, backup then generate new one? [Y/n] \e[0m" + if [[ -z "${global_arg_type[*]}" || "${global_arg_type[*]}" =~ build ]]; then - read -p "" _read - local _confirm="${_read:-y}" - if [[ "$_confirm" == [yY] ]]; then - cp -a "$global_custom_dockerfile" "${global_custom_dockerfile}_${global_suffix}" || lib_utils::die_fatal - cp -fa "$global_repo_custom_dockerfile" "$global_custom_dockerfile" || lib_utils::die_fatal + [ -z "$global_custom_dockerfile" ] && lib_utils::die_fatal + [ -z "$global_repo_custom_dockerfile" ] && lib_utils::die_fatal + + # Backup existing custom Dockerfile + if [ -f "$global_custom_dockerfile" ]; then + lib_utils::print_custom " \e[32m│\e[0m\n" + lib_utils::print_custom " \e[32m├─\e[34;1m Custom (optional) Dockerfile found, backup then generate new one? [Y/n] \e[0m" + + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + local _confirm="${_read:-y}" + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then + cp -a "$global_custom_dockerfile" "${global_custom_dockerfile}_${global_suffix}" || lib_utils::die_fatal + cp -fa "$global_repo_custom_dockerfile" "$global_custom_dockerfile" || lib_utils::die_fatal + fi + else + lib_utils::print_custom " \e[32m│\e[0m\n" + lib_utils::print_custom " \e[32m├─\e[34;1m Generating new custom (optional) Dockerfile\e[0m\n" + + lib_utils::print_debug "$global_repo_custom_dockerfile" + lib_utils::print_debug "$global_custom_dockerfile" + + cp -a "$global_repo_custom_dockerfile" "$global_custom_dockerfile" fi - else - lib_utils::print_custom " \e[32m│\e[0m\n" - lib_utils::print_custom " \e[32m├─\e[34;1m Generating new custom (optional) Dockerfile\e[0m\n" - lib_utils::print_debug "$global_repo_custom_dockerfile" - lib_utils::print_debug "$global_custom_dockerfile" - - cp -a "$global_repo_custom_dockerfile" "$global_custom_dockerfile" + lib_utils::print_custom " \e[32m│ └─\e[34m Edit file now? [Y/n] \e[0m" + lib_gen::__gen_edit "$global_custom_dockerfile" fi - - lib_utils::print_custom " \e[32m│ └─\e[34m Edit (new) custom Dockerfile now? [Y/n] \e[0m" - read -p "" _read - local _confirm="${_read:-y}" - [[ "$_confirm" == [yY] ]] && $EDITOR "$global_custom_dockerfile" || return 0 } function lib_gen::__gen_container() { lib_utils::print_debug "Generating container" + # + # Generate joint client/container superscript + # + + if [[ -z "${global_arg_type[*]}" || "${global_arg_type[*]}" =~ superscript ]]; then + + lib_utils::print_custom " \e[32m│\e[0m\n" + lib_utils::print_custom " \e[32m├─\e[34;1m Generate joint client/container superscript? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then + lib_gen::__gen_superscript + fi + + fi + # # Generate plugins (custom) # @@ -431,23 +507,82 @@ function lib_gen::__gen_container() # Generate flow # - lib_utils::print_custom " \e[32m│\e[0m\n" - lib_utils::print_custom " \e[32m├─\e[34;1m Generate (or update) container flow (profiles, etc.)? [Y/n] \e[0m" - read -p "" _read - local _confirm="${_read:-y}" - if [[ "$_confirm" != [yY] ]]; then + if [[ "${global_arg_all[*]}" =~ all|type || "${global_arg_type[*]}" =~ flow ]]; then + lib_utils::print_custom " \e[32m│\e[0m\n" - lib_utils::print_normal "" - return 0 + lib_utils::print_custom " \e[32m├─\e[34;1m Generate container finance flow (layout and profiles)? [Y/n] \e[0m" + # Prompt if type not given + if [[ ! "${global_arg_all[*]}" =~ all|type || -z "${global_arg_type[*]}" ]]; then + if [ -z "$global_arg_confirm" ]; then + lib_utils::print_custom "\n" + else + read -p "" _read + local _confirm="${_read:-y}" + if [[ "$_confirm" != [yY] ]]; then + lib_utils::print_custom " \e[32m│\e[0m\n" + return 0 + fi + fi + fi + + [ -z "$DOCKER_FINANCE_CLIENT_FLOW" ] && lib_utils::die_fatal + if [ ! -d "$DOCKER_FINANCE_CLIENT_FLOW" ]; then + mkdir -p "$DOCKER_FINANCE_CLIENT_FLOW" || lib_utils::die_fatal + fi + + lib_gen::__gen_times + lib_gen::__gen_profile + + fi +} + +# +# Generate joint client/container superscript +# + +function lib_gen::__gen_superscript() +{ + [ -z "$global_superscript" ] && lib_utils::die_fatal + + if [ -f "$global_superscript" ]; then + lib_utils::print_custom " \e[32m│ └─\e[34m Backup existing file and generate a new one? [N/y] \e[0m" + + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + local _confirm="${_read:-n}" + + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then + # Backup + local -r _backup=("cp" "-a" "$global_superscript" "${global_superscript}_${global_suffix}") + lib_utils::print_debug "${_backup[@]}" + "${_backup[@]}" || lib_utils::die_fatal + # Write + lib_gen::__gen_superscript_write + fi + local _print_custom=" \e[32m│ └─\e[34m Edit file now? [Y/n] \e[0m" + else + # Generate new default file + lib_gen::__gen_superscript_write + local _print_custom=" \e[32m│ │ └─\e[34m Edit file now? [Y/n] \e[0m" fi - [ -z "$DOCKER_FINANCE_CLIENT_FLOW" ] && lib_utils::die_fatal - if [ ! -d "$DOCKER_FINANCE_CLIENT_FLOW" ]; then - mkdir -p "$DOCKER_FINANCE_CLIENT_FLOW" || lib_utils::die_fatal - fi + lib_utils::print_custom "$_print_custom" + lib_gen::__gen_edit "$global_superscript" +} - lib_gen::__gen_times - lib_gen::__gen_profile +function lib_gen::__gen_superscript_write() +{ + [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal + lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" + + [ -z "$global_client_version" ] && lib_utils::die_fatal + lib_utils::print_debug "global_client_version=${global_client_version}" + + [ -z "$global_superscript" ] && lib_utils::die_fatal + lib_utils::print_debug "global_superscript=${global_superscript}" + + sed \ + -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ + "${global_repo_conf_dir}/container/shell/superscript.bash.in" >"$global_superscript" } # @@ -460,6 +595,8 @@ function lib_gen::__gen_container() function lib_gen::__gen_plugins() { + [ -z "$DOCKER_FINANCE_CLIENT_PLUGINS" ] && lib_utils::die_fatal + lib_utils::print_debug "Generating custom plugins layout" local -r _client="${DOCKER_FINANCE_CLIENT_PLUGINS}/client" @@ -509,39 +646,56 @@ function lib_gen::__gen_profile() mkdir -p "$_profiles" || lib_utils::die_fatal fi - # - # Profile: 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}" - # # Profile: construct full path to subprofile # + [ -z "$global_user" ] && lib_utils::die_fatal + + # Profile 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 + if [ ! -z "$global_arg_profile" ]; then + lib_utils::print_custom "\n" + _read="$global_arg_profile" + else + if [ -z "$global_arg_confirm" ]; then + lib_utils::print_custom "\n" + _read="" + else + read -p "" _read + fi + fi local -r _profile="${_read:-default}" lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Using profile:\e[0m ${_profile}\n" + # Subprofile 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 + lib_utils::print_custom " \e[32m│ │ ├─\e[34m Enter subprofile name (e.g., alice in 'family/alice'): \e[0m" + if [ ! -z "$global_arg_subprofile" ]; then + lib_utils::print_custom "\n" + _read="$global_arg_subprofile" + else + if [ -z "$global_arg_confirm" ]; then + lib_utils::print_custom "\n" + _read="" + else + read -p "" _read + fi + fi local -r _subprofile="${_read:-${global_user}}" - lib_utils::print_custom " \e[32m│ │ └─\e[34m Using subprofile:\e[0m ${_subprofile}\n" + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Using subprofile:\e[0m ${_subprofile}\n" + # Check if full profile exists if [ -d "${_profiles}/${_profile}/${_subprofile}" ]; then - lib_utils::print_custom " \e[32m│ │ └─\e[34m Subprofile exists! Continue with backup and generation? [Y/n] \e[0m" + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Subprofile exists! Continue with backup and generation? [Y/n] \e[0m" - read -p "" _read + if [ -z "$global_arg_confirm" ]; then + lib_utils::print_custom "\n" + _read="" + else + read -p "" _read + fi _confirm="${_read:-y}" if [[ "$_confirm" != [yY] ]]; then lib_utils::print_custom " \e[32m│\e[0m\n" @@ -551,158 +705,16 @@ function lib_gen::__gen_profile() fi # - # Profile: execute profile-specific + # Profile: generate profile-specific # - 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}" + local -r _gen_path="${_profiles}/${_profile}/${_subprofile}" + lib_utils::print_debug "_gen_path=${_gen_path}" - lib_gen::__gen_subprofile_shell "$_gen_path" "$_gen_conf_path" - fi + local -r _gen_conf_path="${_gen_path}/docker-finance.d" + lib_utils::print_debug "_gen_conf_path=${_gen_conf_path}" - lib_utils::print_custom " \e[32m│ │\e[0m\n" - 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 - local _gen_path="${_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_subprofile_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 flow configs and/or accounts? [Y/n] \e[0m" - read -p "" _read - _confirm="${_read:-y}" - if [[ "$_confirm" == [yY] ]]; then - local _gen_path="${_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_subprofile_flow "$_gen_path" "$_gen_conf_path" - fi -} - -# -# Subprofile: generate client-side container superscript -# TODO: separate superscript gen from subprofile append -# - -function lib_gen::__gen_subprofile_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 - # Append subprofile source to existing file - lib_utils::print_custom " \e[32m│ │ └─\e[34m Shell superscript found, append new subprofile 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 - # Or generate new file - lib_utils::print_custom " \e[32m│ │ └─\e[34m Backup existing superscript and generate a new one? [N/y] \e[0m" - read -p "" _read - local _confirm="${_read:-n}" - if [[ "$_confirm" == [yY] ]]; then - # Backup - local -r _backup=("cp" "-a" "$_file" "${_file}_${global_suffix}") - lib_utils::print_debug "${_backup[@]}" - "${_backup[@]}" || lib_utils::die_fatal - # Write - lib_gen::__gen_subprofile_shell_write - fi - local _print_custom=" \e[32m│ │ └─\e[34m Edit (new) superscript now? [Y/n] \e[0m" - else - # Generate new default file - lib_gen::__gen_subprofile_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_subprofile_shell_write() -{ - [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal - lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" - - [ -z "$global_client_version" ] && lib_utils::die_fatal - lib_utils::print_debug "global_client_version=${global_client_version}" - - sed \ - -e "s:@DOCKER_FINANCE_SUBPROFILE_SOURCE@:${_source}:g" \ - -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ - "${global_repo_conf_dir}/container/shell/superscript.bash.in" >"$_file" -} - -# -# Subprofile: generate hledger configuration file -# - -function lib_gen::__gen_subprofile_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_subprofile_hledger_write "$_file" - local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) hledger configuration now? [Y/n] \e[0m" - fi - else - lib_gen::__gen_subprofile_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_subprofile_hledger_write() -{ - local _file="$1" - [ -z "$_file" ] && lib_utils::die_fatal - - [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal - lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" - - [ -z "$global_client_version" ] && lib_utils::die_fatal - lib_utils::print_debug "global_client_version=${global_client_version}" - - sed \ - -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ - "${global_repo_conf_dir}/container/hledger/hledger.conf.in" >"$_file" + lib_gen::__gen_subprofile_flow } # @@ -711,32 +723,58 @@ function lib_gen::__gen_subprofile_hledger_write() function lib_gen::__gen_subprofile_flow() { - local -r _gen_path="$1" - local -r _gen_conf_path="$2" + [[ -z "$_gen_path" || -z "$_gen_conf_path" ]] && lib_utils::die_fatal - 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_subprofile_flow_shell + if lib_gen::__gen_subprofile_flow_args_config "subscript"; then + lib_utils::print_custom " \e[32m│ │ │\e[0m\n" + lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate subprofile's subscript file? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then + # Subprofile's shell script + lib_gen::__gen_subprofile_flow_subscript + # Append subprofile source to superscript + lib_gen::__gen_subprofile_flow_superscript + fi + fi - 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_subprofile_flow_fetch + if lib_gen::__gen_subprofile_flow_args_config "fetch"; then + # Prompt for default generation + lib_utils::print_custom " \e[32m│ │ │\e[0m\n" + lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate subprofile's fetch configuration file? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]] && lib_gen::__gen_subprofile_flow_fetch + fi - 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_subprofile_flow_meta + if lib_gen::__gen_subprofile_flow_args_config "meta"; then + lib_utils::print_custom " \e[32m│ │ │\e[0m\n" + lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate subprofile's financial metadata file? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]] && lib_gen::__gen_subprofile_flow_meta + fi - 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_subprofile_flow_accounts + if lib_gen::__gen_subprofile_flow_args_config "hledger"; then + lib_utils::print_custom " \e[32m│ │ │\e[0m\n" + lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate subprofile's hledger configuration file? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]] && lib_gen::__gen_subprofile_flow_hledger + fi + + if lib_gen::__gen_subprofile_flow_args_account; then + lib_utils::print_custom " \e[32m│ │ │\e[0m\n" + lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate subprofile's hledger-flow accounts? [Y/n] \e[0m" + if [ ! -z "${global_arg_account[*]}" ]; then + lib_utils::print_custom "\n" + lib_gen::__gen_subprofile_flow_accounts + else + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]] && lib_gen::__gen_subprofile_flow_accounts + fi + fi lib_utils::print_custom " \e[32m│\e[0m\n" @@ -750,48 +788,94 @@ function lib_gen::__gen_subprofile_flow() lib_utils::print_custom " \e[32m│\e[0m\n" } +function lib_gen::__gen_subprofile_flow_args_config() +{ + [ -z "$1" ] && lib_utils::die_fatal + if [[ -z "${global_arg_type[*]}" || ("${global_arg_type[*]}" =~ flow && -z "$global_arg_profile") || ("${global_arg_type[*]}" =~ flow && ! -z "$global_arg_profile" && -z "${global_arg_account[*]}" && -z "${global_arg_config[*]}") || "${global_arg_config[*]}" =~ $1 ]]; then + return 0 + fi + return 1 +} + +function lib_gen::__gen_subprofile_flow_args_account() +{ + if [[ -z "${global_arg_type[*]}" || ("${global_arg_type[*]}" =~ flow && -z "$global_arg_profile") || ("${global_arg_type[*]}" =~ flow && ! -z "$global_arg_profile" && -z "${global_arg_account[*]}" && -z "${global_arg_config[*]}") || ("${global_arg_type[*]}" =~ flow && ! -z "$global_arg_profile" && ! -z "${global_arg_account[*]}") ]]; then + return 0 + fi + return 1 +} + # -# Subprofile: flow: generate subprofile script +# Subprofile: flow: generate subprofile subscript # -function lib_gen::__gen_subprofile_flow_shell() +function lib_gen::__gen_subprofile_flow_subscript() { + [[ -z "$_gen_path" || -z "$_gen_conf_path" ]] && lib_utils::die_fatal + local _dir="${_gen_conf_path}/shell" [ ! -d "$_dir" ] && mkdir -p "$_dir" - local _file="${_dir}/subprofile.bash" + local _file="${_dir}/subscript.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 + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m File found, backup then generate new one? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read _confirm="${_read:-y}" - if [[ "$_confirm" == [yY] ]]; then + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal - lib_gen::__gen_subprofile_flow_shell_write - local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile script now? [Y/n] \e[0m" + lib_gen::__gen_subprofile_flow_subscript_write + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" fi else - lib_gen::__gen_subprofile_flow_shell_write - local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile script now? [Y/n] \e[0m" + lib_gen::__gen_subprofile_flow_subscript_write + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" - read -p "" _read - _confirm="${_read:-y}" - [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" + lib_gen::__gen_edit "$_file" } -function lib_gen::__gen_subprofile_flow_shell_write() +function lib_gen::__gen_subprofile_flow_subscript_write() { - [ -z "$DOCKER_FINANCE_CONTAINER_CMD" ] && lib_utils::die_fatal + [[ -z "$DOCKER_FINANCE_CONTAINER_REPO" || -z "$DOCKER_FINANCE_CONTAINER_CMD" ]] && lib_utils::die_fatal + + [[ -z "$_profile" || -z "$_subprofile" || -z "$_file" ]] && lib_utils::die_fatal + + [ -z "$global_client_version" ] && lib_utils::die_fatal + [ -z "$global_repo_conf_dir" ] && 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@:${global_client_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" + "${global_repo_conf_dir}/container/shell/subscript.bash.in" >"$_file" +} + +# +# Subprofile: flow: append subscript to superscript +# + +function lib_gen::__gen_subprofile_flow_superscript() +{ + [[ -z "$_profile" || -z "$_subprofile" ]] && lib_utils::die_fatal + + [ -z "$global_superscript" ] && lib_utils::die_fatal + [ ! -f "$global_superscript" ] && lib_utils::die_fatal "Superscript does not exist!" + + # Append subprofile source to superscript + local -r _source="source \"\${DOCKER_FINANCE_CONTAINER_FLOW}/profiles/${_profile}/${_subprofile}/docker-finance.d/shell/subscript.bash\"" + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Appending subprofile to superscript\e[0m\n" + + # If source subprofile does not exist, append + grep "$_source" "$global_superscript" >&/dev/null \ + || sed -i "$(wc -l <$global_superscript)i\\$_source\\" "$global_superscript" + + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Edit superscript now? [Y/n] \e[0m" + lib_gen::__gen_edit "$global_superscript" } # @@ -800,42 +884,38 @@ function lib_gen::__gen_subprofile_flow_shell_write() function lib_gen::__gen_subprofile_flow_fetch() { - local _dir="${_gen_conf_path}/fetch" + [[ -z "$_gen_path" || -z "$_gen_conf_path" ]] && lib_utils::die_fatal + + local -r _dir="${_gen_conf_path}/fetch" [ ! -d "$_dir" ] && mkdir -p "$_dir" - local _file="${_dir}/fetch.yaml" + local -r _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 + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m File found, backup then generate new one? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read _confirm="${_read:-y}" - if [[ "$_confirm" == [yY] ]]; then + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal lib_gen::__gen_subprofile_flow_fetch_write - local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's fetch configuration now? [Y/n] \e[0m" + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" fi else lib_gen::__gen_subprofile_flow_fetch_write - local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's fetch configuration now? [Y/n] \e[0m" + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" - read -p "" _read - _confirm="${_read:-y}" - [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" + lib_gen::__gen_edit "$_file" } function lib_gen::__gen_subprofile_flow_fetch_write() { - [ -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 + [[ -z "$_profile" || -z "$_subprofile" || -z "$_file" ]] && lib_utils::die_fatal [ -z "$global_client_version" ] && lib_utils::die_fatal + [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal sed \ -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ @@ -850,35 +930,40 @@ function lib_gen::__gen_subprofile_flow_fetch_write() function lib_gen::__gen_subprofile_flow_meta() { + [[ -z "$_gen_path" || -z "$_gen_conf_path" ]] && lib_utils::die_fatal + 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 + lib_utils::print_custom " \e[32m│ │ │ └─\e[34m File found, backup then generate new one? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read _confirm="${_read:-y}" - if [[ "$_confirm" == [yY] ]]; then + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal lib_gen::__gen_subprofile_flow_meta_write - local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's financial metadata now? [Y/n] \e[0m" + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" fi else lib_gen::__gen_subprofile_flow_meta_write - local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's financial metadata now? [Y/n] \e[0m" + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" - read -p "" _read - _confirm="${_read:-y}" - [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" + lib_gen::__gen_edit "$_file" } function lib_gen::__gen_subprofile_flow_meta_write() { + [ -z "$_file" ] && lib_utils::die_fatal + + [ -z "$global_client_version" ] && lib_utils::die_fatal + [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal + # Deletes default comments or else ROOT meta sample won't work out-of-the-box sed \ -e "/\/\/\\!/d" \ @@ -886,12 +971,61 @@ function lib_gen::__gen_subprofile_flow_meta_write() "${global_repo_conf_dir}/container/meta/meta.csv.in" >"$_file" } +# +# Subprofile: flow: generate hledger configuration file +# + +function lib_gen::__gen_subprofile_flow_hledger() +{ + [[ -z "$_gen_path" || -z "$_gen_conf_path" ]] && lib_utils::die_fatal + + 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 File found, backup then generate new one? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + + if [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]]; then + cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal + + lib_gen::__gen_subprofile_flow_hledger_write + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" + fi + else + lib_gen::__gen_subprofile_flow_hledger_write + local _print_custom=" \e[32m│ │ │ └─\e[34m Edit file now? [Y/n] \e[0m" + fi + + lib_utils::print_custom "$_print_custom" + lib_gen::__gen_edit "$_file" +} + +function lib_gen::__gen_subprofile_flow_hledger_write() +{ + [ -z "$_file" ] && lib_utils::die_fatal + + [ -z "$global_client_version" ] && lib_utils::die_fatal + lib_utils::print_debug "global_client_version=${global_client_version}" + + [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal + lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" + + sed \ + -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ + "${global_repo_conf_dir}/container/hledger/hledger.conf.in" >"$_file" +} + # # Subprofile: flow: generate accounts # function lib_gen::__gen_subprofile_flow_accounts() { + [[ -z "$_gen_path" || -z "$_gen_conf_path" || -z "$_subprofile" ]] && lib_utils::die_fatal + lib_utils::print_debug "Generating accounts" local _subprofile_path="${_gen_path}/import/${_subprofile}" @@ -899,51 +1033,49 @@ function lib_gen::__gen_subprofile_flow_accounts() 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 + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read _confirm="${_read:-y}" - [[ "$_confirm" == [yY] ]] || return 0 + [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]] || return 0 else mkdir -p "$_subprofile_path" fi - # docker-finance templates + # All available hledger-flow account paths local _templates_paths - mapfile -t _templates_paths < <(find ${DOCKER_FINANCE_CLIENT_REPO}/container/src/hledger-flow/accounts -name template | sort) + 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_subprofile_flow_accounts_populate "$_subprofile_path" "$_template_path" "$_is_testing" + # All available hledger-flow account names + local _accounts + if [ -z "${global_arg_account[*]}" ]; then + for _template in "${_templates_paths[@]}"; do + _accounts+=("$(echo "$_template" | rev | cut -d/ -f2 | rev)") done else - for _template_path in "${_templates_paths[@]}"; do - lib_utils::print_debug "$_subprofile_path $_template_path $_is_testing" - lib_gen::__gen_subprofile_flow_accounts_populate "$_subprofile_path" "$_template_path" "$_is_testing" - done + declare -r _accounts=("${global_arg_account[@]}") fi + + # Cycle through all available docker-finance templates and populate subprofile + for _account in "${_accounts[@]}"; do + for _template_path in "${_templates_paths[@]}"; do + if [[ "$_template_path" =~ \/$_account\/ ]]; then + 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 $_account ? [Y/n] \e[0m" + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read + _confirm="${_read:-y}" + [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]] \ + && lib_gen::__gen_subprofile_flow_accounts_populate "$_subprofile_path" "$_template_path" + fi + done + done } function lib_gen::__gen_subprofile_flow_accounts_populate() { - [ -z "$1" ] && lib_utils::die_fatal - [ -z "$2" ] && lib_utils::die_fatal - [ -z "$3" ] && lib_utils::die_fatal + [[ -z "$1" || -z "$2" ]] && lib_utils::die_fatal local _subprofile_path="$1" local _template_path="$2" - local _is_testing="$3" # Continue if hledger-flow account exists local _continue @@ -956,9 +1088,9 @@ function lib_gen::__gen_subprofile_flow_accounts_populate() # 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 + [ -z "$global_arg_confirm" ] && lib_utils::print_custom "\n" || read -p "" _read _confirm="${_read:-y}" - [[ "$_confirm" == [yY] ]] || _continue=false + [[ "$_confirm" == [yY] || -z "$global_arg_confirm" ]] || _continue=false fi # Populate with template @@ -984,7 +1116,7 @@ function lib_gen::__gen_subprofile_flow_accounts_populate() local _mockup="${_in_path}/mockup" lib_utils::print_debug "Getting mockup '${_mockup}'" - if [[ $_is_testing == true ]]; then + if [ ! -z "$global_arg_dev" ]; 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 @@ -998,4 +1130,21 @@ function lib_gen::__gen_subprofile_flow_accounts_populate() fi } +function lib_gen::__gen_edit() +{ + local _file="$1" + [[ -z "$_file" || ! -f "$_file" ]] && lib_utils::die_fatal + + if [ -z "$global_arg_confirm" ]; then + lib_utils::print_custom "\n" + _read="n" + else + read -p "" _read + fi + local _confirm="${_read:-y}" + + [ -z "$EDITOR" ] && lib_utils::die_fatal + [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" || return 0 +} + # vim: sw=2 sts=2 si ai et diff --git a/client/src/docker/lib/lib_docker.bash b/client/src/docker/lib/lib_docker.bash index 0212788..5b7a05f 100644 --- a/client/src/docker/lib/lib_docker.bash +++ b/client/src/docker/lib/lib_docker.bash @@ -26,7 +26,10 @@ # Docker impl source "${DOCKER_FINANCE_CLIENT_REPO}/client/src/docker/lib/internal/lib_docker.bash" || exit 1 -# Environment generation +# Runtime environment handler +source "${DOCKER_FINANCE_CLIENT_REPO}/client/src/docker/lib/internal/lib_env.bash" || exit 1 + +# Environment layout generator source "${DOCKER_FINANCE_CLIENT_REPO}/client/src/docker/lib/internal/lib_gen.bash" || exit 1 # Plugins support @@ -44,14 +47,6 @@ source "${DOCKER_FINANCE_CLIENT_REPO}/container/src/finance/lib/internal/lib_uti # Implementation # -if [ $UID -lt 1000 ]; then - lib_utils::die_fatal "Do not run as root or system user!" -fi - -# Dependencies -deps=("sed") -lib_utils::deps_check "${deps[@]}" - # Top-level caller global_basename="$(basename -- $0)" declare -rx global_basename @@ -114,8 +109,8 @@ function lib_docker::docker() declare -gxr global_usage="$global_basename ${global_platform}${global_arg_delim_1}${global_user}:${global_tag} $global_command" lib_utils::print_debug "global_usage=${global_usage}" - # Setup remaining client/container globals - lib_gen::gen || return $? + # Setup remaining environment globals + lib_env::env || return $? # Remaining "constructor" implementation lib_docker::__docker || return $? @@ -129,7 +124,7 @@ function lib_docker::docker() function lib_docker::gen() { - lib_gen::generate + lib_gen::gen "$@" lib_utils::catch $? } diff --git a/container/plugins/root/example.cc b/container/plugins/root/example.cc index a6cfa08..23b0248 100644 --- a/container/plugins/root/example.cc +++ b/container/plugins/root/example.cc @@ -96,7 +96,7 @@ void example2() print_env("global_conf_fetch"); print_env("global_conf_hledger"); print_env("global_conf_meta"); - print_env("global_conf_shell"); + print_env("global_conf_subscript"); // TODO(unassigned): read array from shell? ROOT impl simply calls stdlib getenv() // print_env("global_hledger_cmd"); print_env("global_parent_profile"); diff --git a/container/src/finance/lib/internal/lib_edit.bash b/container/src/finance/lib/internal/lib_edit.bash index 36e02f4..5a3b87a 100644 --- a/container/src/finance/lib/internal/lib_edit.bash +++ b/container/src/finance/lib/internal/lib_edit.bash @@ -324,14 +324,14 @@ function lib_edit::__edit() $EDITOR "${_paths[@]}" || lib_utils::die_fatal ;; shell) - [ -z "$global_conf_shell" ] && lib_utils::die_fatal + [ -z "$global_conf_subscript" ] && lib_utils::die_fatal local _dir - _dir="$(dirname $global_conf_shell)" + _dir="$(dirname $global_conf_subscript)" [ ! -d "$_dir" ] && mkdir -p "$_dir" local _file - _file="$(basename $global_conf_shell)" + _file="$(basename $global_conf_subscript)" local _path _path="${_dir}/${_file}" diff --git a/container/src/finance/lib/lib_finance.bash b/container/src/finance/lib/lib_finance.bash index aa95a68..cabbc68 100644 --- a/container/src/finance/lib/lib_finance.bash +++ b/container/src/finance/lib/lib_finance.bash @@ -80,7 +80,7 @@ function lib_finance::finance() 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" + declare -grx global_conf_subscript="${global_child_profile_flow}/docker-finance.d/shell/subscript.bash" # Implementation "libraries" (requires previously set globals) source "${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/lib_edit.bash" || exit 1