bash: lib_fetch: prices: CLI: allow multiple APIs

- Fetch from all available price APIs within a single command
  * allows passing multiple CLI APIs to `api` argument
  * will now fetch from all available APIs if `api` is not given
  * will warn if API config block is missing or die of no APIs are found
- Related refactoring
This commit is contained in:
2024-06-18 14:05:04 -07:00
parent d5a1897e39
commit a9d4afd1df

View File

@@ -63,7 +63,7 @@ function lib_fetch::__parse_args()
Asset values (see documentation):
price${global_arg_delim_2}<asset1,asset2,...>
price${global_arg_delim_2}<asset1${global_arg_delim_3}asset2${global_arg_delim_3}...>
api${global_arg_delim_2}<mobula|coingecko>
@@ -277,12 +277,15 @@ function lib_fetch::__parse_args()
# Arg: api
if [ ! -z "$_arg_api" ]; then
if [[ ! "$_arg_api" =~ ^coingecko$|^mobula$ ]]; then
lib_utils::die_usage "$_usage"
fi
declare -gr global_arg_api="$_arg_api"
read -ra _read <<<"$_arg_api"
for _arg in "${_read[@]}"; do
if [[ ! "$_arg" =~ ^coingecko$|^mobula$ ]]; then
lib_utils::die_usage "$_usage"
fi
done
declare -gr global_arg_api=("${_read[@]}")
else
declare -gr global_arg_api="mobula"
declare -gr global_arg_api=("coingecko" "mobula")
fi
# Arg: account
@@ -539,88 +542,13 @@ function lib_fetch::__fetch_account()
fi
}
function lib_fetch::__fetch_price()
function lib_fetch::__parse_price()
{
#
# Test configuration for supported members
#
local _master_prices="$1"
lib_utils::print_debug "$_master_prices"
lib_fetch::__parse_yaml "get-value" "price.${global_arg_api}" &>/dev/null \
|| lib_utils::die_fatal "'${global_arg_api}' is unavailable"
local -r _members=("key" "asset")
for _member in "${_members[@]}"; do
if _value=$(lib_fetch::__parse_yaml "get-value" "price.${global_arg_api}.${_member}"); then
if [[ "$_member" == "key" ]]; then
local _api_key="$_value"
[ -z "$_api_key" ] \
&& lib_utils::print_warning "${global_arg_api} has empty ${_member}"
fi
if [[ "$_member" == "asset" ]]; then
[[ $(lib_fetch::__parse_yaml "get-type" "price.${global_arg_api}.${_member}") != "sequence" ]] \
&& lib_utils::die_fatal "Invalid asset sequence!"
fi
else
lib_utils::die_fatal "$_member is unavailable"
fi
done
#
# Prepare given assets and filesystem layout
#
# Get assets from configuration
if [ -z "${global_arg_price[*]}" ]; then
# Parse out each asset
while read _line; do
local _assets+=("$_line")
done < <(lib_fetch::__parse_yaml "get-values" "price.${global_arg_api}.asset")
else
# Get assets from CLI
local _assets=("${global_arg_price[*]}")
fi
# Set base dir for price files (subdirs will be by year)
local -r _prices_dir="${global_child_profile_flow}/prices"
[ ! -d "$_prices_dir" ] && mkdir -p "$_prices_dir"
# Prices master file
local -r _master_prices="${_prices_dir}/master.prices"
lib_utils::print_custom "\n"
lib_utils::print_info \
"Fetching prices in year '${global_arg_year}' for '${global_parent_profile}/${global_child_profile}' ..."
lib_utils::print_custom "\n"
#
# Execute fetch for master price file creation
#
declare -x API_FETCH_YEAR="$global_arg_year"
lib_utils::print_debug "$API_FETCH_YEAR"
declare -x API_PRICES_PATH="$_master_prices"
lib_utils::print_debug "$API_PRICES_PATH"
declare -x API_PRICES_API="$global_arg_api"
lib_utils::print_debug "$API_PRICES_API"
declare -x API_PRICES_KEY="$_api_key"
lib_utils::print_debug "$API_PRICES_KEY"
declare -x API_PRICES_ASSETS="${_assets[*]}"
lib_utils::print_debug "$API_PRICES_ASSETS"
lib_fetch::__fetch_exec "prices" "crypto"
if [ $? -eq 0 ]; then
lib_utils::print_custom "\n"
lib_utils::print_info "Fetching prices completed successfully!"
else
return $?
fi
# Parse master file into respective years
[ ! -f "$_master_prices" ] && lib_utils::die_fatal "Master prices not found!"
#
# Master prices line format:
@@ -628,13 +556,11 @@ function lib_fetch::__fetch_price()
# P 2023/01/01 USD 1
#
lib_utils::print_normal "\n ─ Parsing master prices file ..."
lib_utils::print_normal "\n ─ Parsing master prices file"
# Temporary storage
local -r _tmp_dir="$(mktemp -d -p /tmp docker-finance_XXX)"
# Parse master file into respective years
[ ! -f "$_master_prices" ] && lib_utils::die_fatal "Master prices not found!"
local _tmp_dir
_tmp_dir="$(mktemp -d -p /tmp docker-finance_XXX)"
lib_utils::print_custom " \e[32m│\e[0m\n"
echo "$(<$_master_prices)" \
@@ -648,7 +574,8 @@ function lib_fetch::__fetch_price()
if [[ "$global_arg_year" == "all" || "$_year" == "$global_arg_year" ]]; then
# Prices year location
local _sub_dir="${_prices_dir}/${_year}"
local _sub_dir
_sub_dir="$(dirname $_master_prices)/${_year}"
[ ! -d "$_sub_dir" ] && mkdir -p "$_sub_dir"
# Prices year journal
@@ -676,9 +603,119 @@ function lib_fetch::__fetch_price()
lib_utils::print_debug "$_journal"
sort -u -o "$_journal" "$_journal"
fi
done
local _return=$?
# Cleanup
[ -d "$_tmp_dir" ] && rm -fr "$_tmp_dir"
return $?
}
function lib_fetch::__fetch_price()
{
# Reconstruct global API arg into only available API(s)
local _arg_api=()
#
# Test configuration for supported members
#
for _api in "${global_arg_api[@]}"; do
if ! lib_fetch::__parse_yaml "get-value" "price.${_api}" &>/dev/null; then
lib_utils::print_warning "'${_api}' price API is not set"
continue
fi
local _members=("key" "asset")
for _member in "${_members[@]}"; do
if _value=$(lib_fetch::__parse_yaml "get-value" "price.${_api}.${_member}"); then
if [[ "$_member" == "key" ]]; then
[ -z "$_value" ] \
&& lib_utils::print_warning "${_api} has empty ${_member}"
fi
if [[ "$_member" == "asset" ]]; then
[[ $(lib_fetch::__parse_yaml "get-type" "price.${_api}.${_member}") != "sequence" ]] \
&& lib_utils::die_fatal "Invalid asset sequence!"
fi
else
lib_utils::die_fatal "'${_member}' is unavailable"
fi
done
# Available API(s)
_arg_api+=("$_api")
done
[ ${#_arg_api[@]} -eq 0 ] && lib_utils::die_fatal "No price API has been set"
#
# Prepare given assets and filesystem layout
#
# Prices master file
# NOTE: base dir of all price files (subdirs will be by year)
local _master_prices="${global_child_profile_flow}/prices/master.prices"
for _api in "${_arg_api[@]}"; do
# Get API key from configuration
local _key
_key="$(lib_fetch::__parse_yaml "get-value" "price.${_api}.key")"
# Get assets from configuration
if [ -z "${global_arg_price[*]}" ]; then
# Parse out each asset
while read _line; do
local _assets+=("$_line")
done < <(lib_fetch::__parse_yaml "get-values" "price.${_api}.asset")
else
# Get assets from CLI
local _assets=("${global_arg_price[*]}")
fi
lib_utils::print_custom "\n"
lib_utils::print_info \
"Fetching prices from '${_api}' since year '${global_arg_year}' for '${global_parent_profile}/${global_child_profile}' ..."
lib_utils::print_custom "\n"
#
# Execute fetch for master prices file creation
#
declare -x API_FETCH_YEAR="$global_arg_year"
lib_utils::print_debug "$API_FETCH_YEAR"
declare -x API_PRICES_PATH="$_master_prices"
lib_utils::print_debug "$API_PRICES_PATH"
declare -x API_PRICES_API="$_api"
lib_utils::print_debug "$API_PRICES_API"
unset _api
declare -x API_PRICES_KEY="$_key"
lib_utils::print_debug "$API_PRICES_KEY"
unset _key
declare -x API_PRICES_ASSETS="${_assets[*]}"
lib_utils::print_debug "$API_PRICES_ASSETS"
unset _assets
lib_fetch::__fetch_exec "prices" "crypto" || return $?
#
# Parse master prices file into final journal
#
lib_fetch::__parse_price "$API_PRICES_PATH" || return $?
done
local -r _return=$?
if [ $_return -eq 0 ]; then
@@ -688,8 +725,7 @@ function lib_fetch::__fetch_price()
fi
# Cleanup
[ -d "$_tmp_dir" ] && rm -fr "$_tmp_dir"
[ -f "$_master_prices" ] && rm -f "$_master_prices"
[ -f "$API_PRICES_PATH" ] && rm -f "$API_PRICES_PATH"
return $_return
}
@@ -714,12 +750,16 @@ function lib_fetch::__fetch_exec()
[ -z "$_subtype" ] && lib_utils::die_fatal
local _dir="${DOCKER_FINANCE_CONTAINER_REPO}/src/finance/lib/internal/fetch"
local _ifs="$IFS"
IFS=' '
# TODO: remove no-limit after internal fetching writes per-paginated instead of per-fetch
local _php=("/usr/bin/php" "-d" "memory_limit=\"-1\"" "-d" "include_path=\"/usr/local/lib:${_dir}\"")
lib_utils::print_debug "${_php[@]}"
IFS=' '
lib_utils::print_debug "${_php[*]}"
"${_php[@]}" "${_dir}/fetch.php" "$_type" "$_subtype"
IFS="$_ifs"
}
# vim: sw=2 sts=2 si ai et