From 60cc1357db02a4e7101672173c0fe184e67d249f Mon Sep 17 00:00:00 2001 From: DrMaxNix Date: Fri, 31 May 2024 22:30:56 +0200 Subject: [PATCH] :heavy_plus_sign: switch to bash-ini for config parsing --- lib/ini | 175 +++++++++++++++++++++++++++++++++++++++++++++--------- workplace | 36 +++++------ 2 files changed, 165 insertions(+), 46 deletions(-) diff --git a/lib/ini b/lib/ini index 39052d5..7badc14 100644 --- a/lib/ini +++ b/lib/ini @@ -1,34 +1,153 @@ #!/bin/bash +set -euo pipefail +# ===================================================== +# bash-ini v1.0.0 | (c) 2024 DrMaxNix | www.drmaxnix.de +# ===================================================== + +# +# Parse a ini config file. +# +# @param $1 Path to ini file. +# @param $2 (Optional) prefix for output variable names. +# ini_parse(){ - local filename="$1" + ## VALIDATE PATH ## + # get from param + local path="$1" - sections="$(gawk '{ if ($1 ~ /^\[/) section=tolower(gensub(/\[(.+)\]/,"\\1",1,$1)); configuration[section]=1 } END {for (key in configuration) { print key} }' $filename)" - for section in $sections; do - array_name="ini_${section}" - declare -g -A ${array_name} - done + # make sure it is readable + if [[ ! -r "$path" ]]; then + ini_log_error "Unable to read config file '$path'" + fi - eval $(gawk -F= '{ - if($1 ~ /^\[/) - section=tolower(gensub(/\[(.+)\]/,"\\1",1,$1)) - else if($1 !~ /^$/ && $1 !~ /^;/ && $1 !~ /^#/){ - gsub(/^[ \t]+|[ \t]+$/, "", $1); - gsub(/[\[\]]/, "", $1); - gsub(/^[ \t]+|[ \t]+$/, "", $2); - if(configuration[section][$1] == "") - configuration[section][$1]=$2 - else - configuration[section][$1]=configuration[section][$1]" "$2 - } - } - END { - for(section in configuration) - for(key in configuration[section]){ - section_name = section - gsub("-", "_", section_name) - print "ini_" section_name "[\""key"\"]=\""configuration[section][key]"\";" - } - }' ${filename} - ) + + ## VAR PREFIX ## + # get from param or fallback + local var_prefix="${2:-ini}" + + # validate format + if [[ ! "$var_prefix" =~ ^[a-z0-9]+(_[a-z0-9]+)*$ ]]; then + ini_log_error "Invalid variable prefix '$var_prefix'" + fi + + # build global var names + local -n out_section_list="${var_prefix}" + + + local line + local section_name + while IFS= read -r line; do + ## TRIM WHITESPACES ## + line=$(echo "$line" | sed -E "s/^\s*//g" | sed -E "s/\s*$//g") + + + ## IGNORE LINES ## + # empty lines + if [[ "$line" == "" ]]; then + # ignore + continue; fi + + # comments + if [[ "$line" =~ ^# || "$line" =~ ^\; ]]; then + # ignore + continue; fi + + + ## MATCH SECTION HEADER ## + if [[ "$line" =~ ^\[.*\]$ ]]; then + # get section name + section_name=$(echo "$line" | sed -E "s/^\[(.*)\]$/\1/g") + + # trim whitespaces + section_name=$(echo "$section_name" | sed -E "s/^\s*//g" | sed -E "s/\s*$//g") + + # validate format + if [[ ! "$section_name" =~ ^[A-Za-z0-9]+([\._-][A-Za-z0-9]+)*$ ]]; then + ini_log_error "Invalid section name '$section_name'" + fi + + # rewrite section name + section_name=$(echo "$section_name" | tr 'A-Z' 'a-z' | tr '-' '_' | tr '.' '_') + + # maybe add to section list + local found="no" + for s in ${out_section_list[*]:-}; do + if [[ "$s" == "$section_name" ]]; then found="yes"; break; fi + done + if [[ "$found" != "yes" ]]; then + out_section_list=(${out_section_list[@]:-""} "$section_name") + fi + + # declare section map + section_map_varname="${var_prefix}__${section_name}" + declare -g -A ${section_map_varname} + continue; fi + + + ## MATCH ASSIGNMENT ## + if [[ "$line" =~ ^([A-Za-z0-9]+([\._-][A-Za-z0-9]+)*)[[:space:]\t]*=[[:space:]\t]*(.*)$ ]]; then + # maybe enter root section + if [[ ! "${section_name:+x}" ]]; then + section_name="_root" + out_section_list=(${out_section_list[@]:-""} "$section_name") + section_map_varname="${var_prefix}__${section_name}" + declare -g -A ${section_map_varname} + fi + + # get key and value + local key=$(echo "$line" | sed -E "s/^([A-Za-z0-9]+([\._-][A-Za-z0-9]+)*)[ \t]*=[ \t]*(.*)$/\1/g") + local value=$(echo "$line" | sed -E "s/^([A-Za-z0-9]+([\._-][A-Za-z0-9]+)*)[ \t]*=[ \t]*(.*)$/\3/g") + + # maybe trim quote marks + if [[ "$value" =~ ^\".*\"$ ]]; then + value=$(echo "$value" | sed -E "s/^\"(.*)\"$/\1/g") + elif [[ "$value" =~ ^\'.*\'$ ]]; then + value=$(echo "$value" | sed -E "s/^'(.*)'$/\1/g") + fi + + # maybe add key to key list + local -n out_section_key_list="${var_prefix}__${section_name}__key_list" + local found="no" + for k in ${out_section_key_list[*]:-}; do + if [[ "$k" == "$key" ]]; then found="yes"; break; fi + done + if [[ "$found" != "yes" ]]; then + out_section_key_list=(${out_section_key_list[@]:-""} "$key") + fi + + # set value in section map + local -n out_section_map="${var_prefix}__${section_name}" + out_section_map[$key]="$value" + continue; fi + + + ## UNKNOWN ## + ini_log_error "Unknown config string '$line'" + done < "$path" +} + + + +# +# HELPER: try calling external logger, else use fallback. +# +# @param $1 Message to print. +# +ini_log_debug(){ + if [[ "$(type -t log_debug)" == "function" ]]; then log_debug "$1"; return; fi + echo "[DEBUG] $1" 1>&2 +} +ini_log_info(){ + if [[ "$(type -t log_info)" == "function" ]]; then log_info "$1"; return; fi + echo "[INFO ] $1" 1>&2 +} +ini_log_warn(){ + if [[ "$(type -t log_warn)" == "function" ]]; then log_warn "$1"; return; fi + echo "[WARN ] $1" 1>&2 +} +ini_log_error(){ + if [[ "$(type -t log_error)" == "function" ]]; then log_error "$1"; return; fi + echo "[ERROR] $1" 1>&2 + exit 1 } diff --git a/workplace b/workplace index bcac3eb..4fc9f17 100755 --- a/workplace +++ b/workplace @@ -76,19 +76,14 @@ workplace_set(){ ## RUN SET COMMANDS ## - local -n workplace_section_name="ini_$workplace_name" - for k in "${!workplace_section_name[@]}"; do - # make sure this is a `set` command - if [[ ! "$k" =~ ^set\. ]]; then - continue - fi - - # extract command and parameter - command=$(echo "$k" | grep -oPe '(?<=^set.).*') - param="${workplace_section_name[$k]}" - - # run command - workplace_setcommand_run "$command" "$param" + local -n workplace_section="ini__$workplace_name" + for k in "${!workplace_section[@]}"; do + # `set` command + if [[ "$k" =~ ^set\. ]]; then + command=$(echo "$k" | grep -oPe '(?<=^set.).*') + param="${workplace_section[$k]}" + workplace_setcommand_run "$command" "$param" + continue; fi done } @@ -107,16 +102,16 @@ workplace_update(){ workplace_list_prio_0="" workplace_list_prio_1="" for n in $(workplace_available_name_list); do - local -n workplace_section_name="ini_$n" + local -n workplace_section="ini__$n" # default - trigger_default="${workplace_section_name[trigger.default]:-""}" + trigger_default="${workplace_section[trigger.default]:-""}" if [[ "$trigger_default" == "true" ]]; then workplace_list_prio_0="${workplace_list_prio_0} $n" fi # usb-id - trigger_usbid="${workplace_section_name[trigger.usb-id]:-""}" + trigger_usbid="${workplace_section[trigger.usb-id]:-""}" if [[ "$trigger_usbid" ]]; then for i in $usb_id_list; do if [[ "$i" == "$trigger_usbid" ]]; then @@ -195,7 +190,8 @@ workplace_updatedaemon(){ # workplace_config_load(){ # clear used variables - for v in $(compgen -A variable | grep -Pe '^ini_'); do + unset ini + for v in $(compgen -A variable | grep -Pe '^ini__'); do unset $v done @@ -209,7 +205,11 @@ workplace_config_load(){ # HELPER: Get defined workplace names. # workplace_available_name_list(){ - for v in $(compgen -A variable | grep -oPe '(?<=^ini_).*'); do + for v in ${ini[*]}; do + # ignore root section + [[ "$v" == "_root" ]] && continue + + # include this section's name echo $v done }