Skip to content
Snippets Groups Projects
Select Git revision
  • a39b5cfad9d90106db03b3f733fb5447618ded56
  • main default protected
2 results

functions.js

Blame
  • ini.class.sh 15.60 KiB
    #!/bin/bash
    # ======================================================================
    #
    # READ INI FILE with Bash
    # https://axel-hahn.de/blog/2018/06/08/bash-ini-dateien-parsen-lesen/
    #
    #  Author:  Axel hahn
    #  License: GNU GPL 3.0
    #  Source:  https://github.com/axelhahn/bash_iniparser
    #  Docs:    https://www.axel-hahn.de/docs/bash_iniparser/
    #
    # ----------------------------------------------------------------------
    # TODO
    # - ini.validate: 
    #   - handle non existing validation rules
    #   - define all vars as local
    #   - detect invalid lines (all except sections, values, comments, empty lines)
    # - ini.export:
    #   - detect unsafe entries $() and backticks
    # - ini.value:
    #   - detect comment after value
    # ----------------------------------------------------------------------
    # 2024-02-04  v0.1  Initial version
    # 2024-02-08  v0.2  add ini.varexport; improve replacements of quotes
    # 2024-02-10  v0.3  handle spaces and tabs around vars and values
    # 2024-02-12  v0.4  rename local varables
    # 2024-02-20  v0.5  handle special chars in keys; add ini.dump + ini.help
    # 2024-02-21  v0.6  harden ini.value for keys with special chars; fix fetching last value
    # 2024-03-17  v0.7  add ini.validate
    # 2024-03-17  v0.8  errors are written to STDERR; update help; use [[:space:]] in regex; update help
    # 2024-03-18  v0.9  validator improvements (but ini validate is not rock solid yet)
    # ======================================================================
    
    INI_FILE=
    INI_SECTION=
    
    # ----------------------------------------------------------------------
    # SETTER
    # ----------------------------------------------------------------------
    
    # Set the INI file - and optional section - for short calls.
    # param  string  filename
    # param  string  optional: section
    function ini.set(){
        INI_FILE=
        INI_SECTION=
        if [ ! -f "$1" ]; then
            echo "ERROR: file does not exist: $1" >&2
            exit 1
        fi
        INI_FILE="$1"
    
        test -n "$2" && ini.setsection "$2"
        
    }
    
    # Set the INI section for short calls.
    # param  string  section
    function ini.setsection(){
        if [ -z "$INI_FILE" ]; then
            echo "ERROR: ini file needs to be set first. Use ini.set <INIFILE> [<SECTION>]." >&2
            exit 1
        fi
        if [ -n "$1" ]; then
            if ini.sections "$INI_FILE" | grep "^${1}$" >/dev/null; then
                INI_SECTION=$1
            else
                echo "ERROR: Section [$1] does not exist in [$INI_FILE]." >&2
                exit 1
            fi
        fi
    }
    
    # ----------------------------------------------------------------------
    # GETTER
    # ----------------------------------------------------------------------
    
    # Get all sections
    # param1 - name of the ini file
    function ini.sections(){
            local myinifile=${1:-$INI_FILE}
            grep "^\[" "$myinifile" | sed 's,^\[,,' | sed 's,\].*,,'
    }
    
    # Get all content inside a section
    # param1 - name of the ini file
    # param2 - name of the section in ini file
    function ini.section(){
            local myinifile=${1:-$INI_FILE}
            local myinisection=${2:-$INI_SECTION}
            sed -e "0,/^\[${myinisection}\]/ d" -e '/^\[/,$ d' "$myinifile" \
                | grep -v "^[#;]" \
                | sed -e "s/^[ \t]*//g" -e "s/[ \t]*=[ \t]*/=/g"
    }
    
    # Get all keys inside a section
    # param1 - name of the ini file
    # param2 - name of the section in ini file
    function ini.keys(){
            local myinifile=${1:-$INI_FILE}
            local myinisection=${2:-$INI_SECTION}
            ini.section "${myinifile}" "${myinisection}" \
                | grep "^[\ \t]*[^=]" \
                | cut -f 1 -d "=" \
                | sort -u
    }
    
    
    # Get a value of a variable in a given section
    # param1 - name of the ini file
    # param2 - name of the section in ini file
    # param3 - name of the variable to read
    function ini.value(){
    
            if [ -n "$2" ] && [ -z "$3" ]; then
                ini.value "$INI_FILE" "$1" "$2"
            elif [ -z "$2" ]; then
                ini.value "$INI_FILE" "$INI_SECTION" "$1"
            else
                local myinifile=$1
                local myinisection=$2
                local myvarname=$3
                local out
                regex="${myvarname//[^a-zA-Z0-9:()]/.}"
                out=$(ini.section "${myinifile}" "${myinisection}" \
                    | sed -e "s,^[[:space:]]*,,g" -e "s,[[:space:]]*=,=,g"  \
                    | grep -F "${myvarname}=" \
                    | grep "^${regex}=" \
                    | cut -f 2- -d "=" \
                    | sed -e 's,^[[:space:]]*,,' -e 's,[[:space:]]*$,,' 
                    )
                grep "\[\]$" <<< "$myvarname" >/dev/null || out="$( echo "$out" | tail -1 )"
    
                # delete quote chars on start and end
                grep '^".*"$' <<< "$out" >/dev/null && out=$(echo "$out" | sed -e's,^"\(.*\)"$,\1,g')
                grep "^'.*'$" <<< "$out" >/dev/null && out=$(echo "$out" | sed -e"s,^'\(.*\)'$,\1,g")
                echo "$out"
            fi
    }
    
    # dump the ini file for visuall check of the parsing functions
    # param  string  filename
    ini.dump() {
        local myinifile=${1:-$INI_FILE}
        echo -en "\e[1;33m"
        echo "+----------------------------------------"
        echo "|"
        echo "| $myinifile"
        echo "|"
        echo -e "+----------------------------------------\e[0m"
        echo -e "\e[34m"
        sed "s,^,    ,g" "${myinifile}"
        echo -e "\e[0m"
    
        echo "    Parsed data:"
        echo
        ini.sections "$myinifile" | while read -r myinisection; do
            if ! ini.keys "$myinifile" "$myinisection" | grep -q "."; then
                echo -e "    ----- section \e[35m[$myinisection]\e[0m"
            else
                echo -e "    --+-- section \e[35m[$myinisection]\e[0m"
                echo    "      |"
                ini.keys "$myinifile" "$myinisection" | while read -r mykey; do
                    value="$(ini.value "$myinifile" "$myinisection" "$mykey")"
                    # printf "        %-15s => %s\n" "$mykey" "$value"
                    printf "      \`---- %-20s => " "$mykey"
                    echo -e "\e[1;36m$value\e[0m"
                done
            fi
            echo
        done
        echo
    }
    
    function ini.help(){
    
        # local _self
        # if _is_sourced; then
        #     _self="ini."
        # else
        #     _self="$( basename "$0" ) "
        # fi
    
        cat <<EOH
    
        INI.CLASS.SH
    
        A bash implementation to read ini files.
    
        Author:  Axel hahn
        License: GNU GPL 3.0
        Source:  https://github.com/axelhahn/bash_iniparser
        Docs:    https://www.axel-hahn.de/docs/bash_iniparser/
    
        Usage:
    
        (1)
        source the file ini.class.sh
    
        (2)
        ini.help
        to show this help with all available functions.
    
    
        BASIC ACCESS:
    
        ini.value <INIFILE> <SECTION> <KEY>
            Get a avlue of a variable in a given section.
    
            Tho shorten ini.value with 3 parameters:
    
            ini.set <INIFILE> [<SECTION>]
    
            or
    
            ini.set <INIFILE>
            ini.setsection <SECTION>
    
            This sets the ini file and/ or section as default.
            Afterwards you can use:
    
            ini.value <KEY>
            and
            ini.value <SECTION> <KEY>
    
        OTHER GETTERS:
    
        ini.sections <INIFILE>
            Get all sections in the ini file.
            The <INIFILE> is not needed if ini.set <INIFILE> was used before.
    
        ini.keys <INIFILE> <SECTION>
            Get all keys in the given section.
            The <INIFILE> is not needed if ini.set <INIFILE> was used before.
            The <SECTION> is not needed if ini.setsection <SECTION> was used 
            before.
    
        ini.dump <INIFILE>
            Get a pretty overview of the ini file.
            You get a colored view of the content and a parsed view of the
            sections and keys + values.
    
        VALIDATION:
    
        ini.validate <INIFILE> <VALIDATIONINI> <FLAG>
            Validate your ini file with the rules of a given validation ini file.
            The ini for validation contains rules for 
            * writing sections and keys
            * sections and keys thet must exist or can exist
            * describe values of keys to ensure to get vald data (eg a regex)
            see https://www.axel-hahn.de/docs/bash_iniparser/Validation.html
            The <FLAG> is optional. By default it is 0 and shows error information
            only on STDOUT. Set it to 1 to see more output about the validation 
            process.
    
    EOH
    }
    
    # Create bash code to export all variables as hash.
    # Example: eval "$( ini.varexport "cfg_" "$inifile" )"
    #
    # param  string  prefix for the variables
    # param  string  ini file to read
    function ini.varexport(){
        local myprefix="$1"
        local myinifile="$2"
        local var=
    
        for myinisection in $(ini.sections "$myinifile"); do
            var="${myprefix}${myinisection}"
            echo "declare -A ${var}; "
            echo "export ${var}; "
            
            for mykey in $(ini.keys "$myinifile" "$myinisection"); do
                value="$(ini.value "$myinifile" "$myinisection" "$mykey")"
                echo ${var}[$mykey]="\"$value\""
            done
        done
        
    }
    
    # validate the ini file
    # param  string  path of ini file to validate
    # param  string  path of ini file with validation rules
    # param  bool    optional: show more output; default: 0
    function ini.validate(){
    
        function _vd(){
            test "$bShowAll" -ne "0" && echo "$*"
        }
    
        local myinifile="$1"
        local myvalidationfile="$2"
        local bShowAll="${3:-0}"
    
        local ERROR="\e[1;31mERROR\e[0m"
        local iErr; typeset -i iErr=0
    
        # TODO: make all used vars local
    
        _vd "START: Validate ini '${myinifile}'"
        _vd "       with '${myvalidationfile}'"
        if [ ! -f "${myinifile}" ]; then
            echo -e "$ERROR: Ini file in first param '${myinifile}' does not exist." >&2
            return 1
        fi
    
        if [ ! -f "${myvalidationfile}" ]; then
            echo -e "$ERROR: Validation file in 2nd param '${myvalidationfile}' does not exist." >&2
            return 1
        fi
    
        # declare all needed variables in case that those sections are not defined
        # in vilidation ini
        declare -A validate_style
        declare -A validate_sections
        declare -A validate_varsMust
        declare -A validate_varsCan
     
        eval "$( ini.varexport "validate_" "$myvalidationfile" )"
        
        if [ -z "${validate_style[*]}${validate_sections[*]}${validate_varsMust[*]}${validate_varsCan[*]}" ]; then
            echo -e "$ERROR: Validation file in 2nd param doesn't seem to be a validation.ini." >&2
            echo "       Hint: Maybe it is no validation file (yet) or you flipped the parameters." >&2
            return 1
        fi
    
    
        # ----- Check if all MUST sections are present
        if [ -n "${validate_sections['must']}" ]; then
            _vd "--- Sections that MUST exist:"
            for section in $( tr "," " " <<< "${validate_sections['must']}");
            do
                if ini.sections "$myinifile" | grep -q "^$section$" ; then
                    _vd "OK: Section is present [$section]."
                else
                    echo -e "$ERROR: Section [$section] is not present." >&2
                    iErr+=1
                fi
            done
        fi
    
        # ----- Loop over sections
        _vd "--- Validate section names"
        for section in $( ini.sections "$myinifile" )
        do
            # ----- Check if our section name has the allowed syntax
            if [ -n "${validate_style['section']}" ]; then
                if ! grep -qE "${validate_style['section']}" <<< "$section" ; then
                    echo -e "$ERROR: Section [$section] violates style rule '${validate_style['section']}'" >&2
                    iErr+=1
                else
                    _vd "OK: Section name [$section] matches style rule '${validate_style['section']}'"
                fi
            fi
    
            # ----- Check if our sections are in MUST or CAN
    
            if ! grep -Fq ",${section}," <<< ",${validate_sections['must']},${validate_sections['must']},"; then
                echo -e "$ERROR: Unknown section name: [$section] - ist is not listed as MUST nor CAN." >&2
                iErr+=1
            else
                _vd "OK: section [$section] is valid"
                _vd "  Check keys of section [$section]"
    
                # ----- Check MUST keys in the current section
                for myKeyEntry in "${!validate_varsMust[@]}"; do
                    if ! grep -q "^${section}\." <<< "${myKeyEntry}"; then
                        continue
                    fi
                    mustkey="$( echo "$myKeyEntry" | cut -f2 -d '.')"
                    # TODO
                    keyregex="$( echo "$mustkey" | sed -e's,\[,\\[,g' )"
                    if ini.keys "$myinifile" "$section" | grep -q "^${keyregex}$"; then
                        _vd "  OK: [$section] -> $mustkey is a MUST"
                    else
                        echo -e "  $ERROR: [$section] -> $mustkey is a MUST key but was not found im section [$section]." >&2
                        iErr+=1
                    fi
    
                done
    
                # ----- Check if our keys are MUST or CAN keys
                for mykey in $( ini.keys "$myinifile" "$section"); do
    
                    if [ -n "${validate_style['key']}" ]; then
                        if ! grep -qE "${validate_style['key']}" <<< "$mykey" ; then
                            echo -e "$ERROR: Key [$section] -> $mykey violates style rule '${validate_style['key']}'" >&2
                            iErr+=1
                        else
                            _vd "  OK: [$section] -> $mykey matches style rule '${validate_style['key']}'"
                        fi
                    fi
    
                    keyregex="$( echo "${mykey}" | sed -e's,\[,\\[,g' | sed -e's,\],\\],g' )"
    
                    local mustKeys
                    mustKeys="$( echo "${!validate_varsMust[@]}" | tr ' ' ',')"
                    local canKeys
                    canKeys="$( echo "${!validate_varsCan[@]}" | tr ' ' ',')"
    
                    if ! grep -Fq ",${section}.$mykey," <<< ",${canKeys},${mustKeys},"; then
                        echo -e "  $ERROR: [$section] -> $mykey is invalid." >&2
                        iErr+=1
                    else
    
                        local valKey
                        valKey="${section}.${mykey}"
                        if [ -n "${validate_varsCan[$valKey]}" ] && [ -n "${validate_varsMust[$valKey]}" ]; then
                            echo -e "  $ERROR: '$valKey' is defined twice - in varsMust and varsCan as well. Check validation file '$myvalidationfile'." >&2
                        fi
    
                        sValidate="${validate_varsCan[${section}.${mykey}]}"
                        if [ -z "$sValidate" ]; then
                            sValidate="${validate_varsMust[${section}.${mykey}]}"
                        fi
                        if [ -z "$sValidate" ]; then
                            _vd "  OK: [$section] -> $mykey exists (no check)"
                        else
                            local checkType
                            local checkValue
                            local value
    
                            checkType=$( cut -f 1 -d ':' <<< "$sValidate" )
                            checkValue=$( cut -f 2- -d ':' <<< "$sValidate" )
                            value="$(ini.value "$myinifile" "$section" "$mykey")"
                            local regex
                            case $checkType in
                                'INTEGER') regex="^[1-9][0-9]*$"          ;;
                                'ONEOF')   regex="^(${checkValue//,/|})$" ;;
                                'REGEX')   regex="$checkValue"            ;;
                                *)
                                    echo -e "  $ERROR: ceck type '$checkType' is not supported." >&2
                            esac
                            if [ -n "$regex" ]; then
                                if ! grep -Eq "${regex}" <<< "$value" ; then
                                    echo -e "  $ERROR: [$section] -> $mykey is valid but value '$value' does NOT match '$regex'" >&2
                                else
                                    _vd "  OK: [$section] -> $mykey is valid and value matches '$regex'"
                                fi
                            fi
    
                        fi
                    fi
                done
            fi
        done
    
        if [ $iErr -gt 0 ]; then
            echo "RESULT: Errors were found for $myinifile" >&2
        else
            _vd "RESULT: OK, Ini file $myinifile looks fine."
        fi
        return $iErr
    
    }
    
    # ----------------------------------------------------------------------