diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dff37a0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,35 @@ +This software is provided under the BSD license. The text of this license +is provided below: + +-------------------------------------------------------------------------- + +Copyright (C) 2009 Kevin Porter / Advanced Web Construction Ltd +Copyright (C) 2010-2015 Ruediger Meier +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of any contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README b/README index 2b5b70f..9fed9dd 100644 --- a/README +++ b/README @@ -13,9 +13,11 @@ COPYRIGHT Copyright (c) 2009 Kevin Porter / Advanced Web Construction Ltd (http://coding.tinternet.info / http://webutils.co.uk) -Copyright (c) 2010-2012 Ruediger Meier +Copyright (c) 2010-2015 Ruediger Meier (https://github.com/rudimeier/) +License: BSD-3-Clause, see LICENSE file + @@ -44,6 +46,8 @@ SECTION and VARNAME are the section name and variable name respectively. Additionally you can get a list of all these variable names: PREFIX__ALL_VARS +and get a list of sections: +PREFIX__ALL_SECTIONS and the number of sections: PREFIX__NUMSECTIONS @@ -94,7 +98,9 @@ Whether to interpret special unquoted string values 'yes', 'no', 'true', 'false', 'on', 'off' as booleans. Default: 1 - +[--allow_no_value | -nv] [0|1] +Whether to allow keys without values in the ini file. +Default: 0 INI FILE FORMAT diff --git a/read_ini.sh b/read_ini.sh index 28740cf..a86d55c 100755 --- a/read_ini.sh +++ b/read_ini.sh @@ -1,9 +1,11 @@ # # Copyright (c) 2009 Kevin Porter / Advanced Web Construction Ltd # (http://coding.tinternet.info, http://webutils.co.uk) -# Copyright (c) 2010-2012 Ruediger Meier +# Copyright (c) 2010-2015 Ruediger Meier # (https://github.com/rudimeier/) # +# License: BSD-3-Clause, see LICENSE file +# # Simple INI file parser. # # See README for usage. @@ -60,19 +62,43 @@ function read_ini() # Set defaults local BOOLEANS=1 + local NV=0 local VARNAME_PREFIX=INI local CLEAN_ENV=0 + local INDENT_LEVEL=0 + local CURRENT_INDENT_LEVEL=0 + local LONGEST_LINE=0 + + # Regular expression so skip blank lines and commented out lines + local RE_SKIP='^(\s*$)|^\s*(#|;)' + + # Regular expression to fine leading spaces + RE_SPACES='^(\ +)' + + # Regular expression for tracking down section markers + RE_SEC='\[([^]]+)\]' + + # Regular expression for value lines + RE='' + RE_REG='^(.*?)\s*(=|:)\s*(.+)$' + + # Regular expression to use if optional values are allowed + RE_OPT='^(.*?)\s*(=|:)\s*(.*)$|(.*?)\s*$' + #RE_OPT='^(.*?)\s*(?:(=|:)\s*(.*))?$' # {{{ START Options # Available options: - # --boolean Whether to recognise special boolean values: ie for 'yes', 'true' - # and 'on' return 1; for 'no', 'false' and 'off' return 0. Quoted - # values will be left as strings - # Default: on + # --boolean Whether to recognise special boolean values: ie for 'yes', 'true' + # and 'on' return 1; for 'no', 'false' and 'off' return 0. Quoted + # values will be left as strings + # Default: on # - # --prefix=STRING String to begin all returned variables with (followed by '__'). - # Default: INI + # --allow_no_value Whether keys without values are allowed in the ini file: ie. 'key' or 'key=' + # Default: off + # + # --prefix=STRING String to begin all returned variables with (followed by '__'). + # Default: INI # # First non-option arg is filename, second is section name @@ -90,6 +116,10 @@ function read_ini() BOOLEANS=$1 ;; + --allow_no_value | -nv ) + NV=1 + ;; + --prefix | -p ) shift VARNAME_PREFIX=$1 @@ -113,7 +143,7 @@ function read_ini() done if [ -z "$INI_FILE" ] && [ "${CLEAN_ENV}" = 0 ] ;then - echo -e "Usage: read_ini [-c] [-b 0| -b 1]] [-p PREFIX] FILE"\ + echo -e "Usage: read_ini [-c] [-b 0| -b 1] [-nv] [-p PREFIX] FILE"\ "[SECTION]\n or read_ini -c [-p PREFIX]" >&2 cleanup_bash return 1 @@ -124,16 +154,21 @@ function read_ini() return 1 fi + local INI_ALL_VARNAME="${VARNAME_PREFIX}__ALL_VARS" + local INI_ALL_SECTION="${VARNAME_PREFIX}__ALL_SECTIONS" local INI_NUMSECTIONS_VARNAME="${VARNAME_PREFIX}__NUMSECTIONS" if [ "${CLEAN_ENV}" = 1 ] ;then # TODO How to clear the whole array without unset it for i in "${!INI[@]}" ;do unset ${VARNAME_PREFIX}['$i'] done + eval unset "\$${INI_ALL_VARNAME}" fi # TODO How to declare -A ${VARNAME_PREFIX} non local? Or we have to # check for a global declared one + unset ${INI_ALL_VARNAME} + unset ${INI_ALL_SECTION} unset ${INI_NUMSECTIONS_VARNAME} if [ -z "$INI_FILE" ] ;then @@ -152,6 +187,15 @@ function read_ini() BOOLEANS=1 fi + if [ "$NV" == "0" ] + then + RE=$RE_REG + else + RE=$RE_OPT + fi + + # Get longest line in the file - used to reset indentation level + LONGEST_LINE=$(wc -L < "$INI_FILE") # }}} END Options @@ -160,43 +204,48 @@ function read_ini() local LINE_NUM=0 local SECTIONS_NUM=0 local SECTION="" - - # IFS is used in "read" and we want to switch it within the loop - local IFS=$' \t\n' - local IFS_OLD="${IFS}" - + # we need some optional shell behavior (shopt) but want to restore # current settings before returning local SWITCH_SHOPT="" pollute_bash - while read -r line || [ -n "$line" ] + while IFS= read -r line || [ -n "$line" ] do #echo line = "$line" ((LINE_NUM++)) # Skip blank lines and comments - if [ -z "$line" -o "${line:0:1}" = ";" -o "${line:0:1}" = "#" ] + if [[ "${line}" =~ $RE_SKIP ]] then + # Empty line marks end of value + INDENT_LEVEL="$LONGEST_LINE" continue fi + # Check if multi-line value + if [[ "${line}" =~ $RE_SPACES ]] + then + LEADING_SPACES="${BASH_REMATCH[1]}" + CUR_INDENT_LEVEL=${#LEADING_SPACES} + else + CUR_INDENT_LEVEL=0 + fi + # Section marker? - if [[ "${line}" =~ ^\[.*\]$ ]] + if [[ "${line}" =~ $RE_SEC ]] then - # Set SECTION var to name of section (strip [ and ] from section marker) - SECTION="${line#[}" - SECTION="${SECTION%]}" - read SECTION <<<"${SECTION}" + # Set SECTION var to name of section + SECTION="${BASH_REMATCH[1]}" if ! [[ "${line}" =~ ^[[:print:]]+$ ]] ;then echo "Error: Invalid section:" >&2 echo " ${LINE_NUM}: '$line'" >&2 cleanup_bash return 1 fi + eval "${INI_ALL_SECTION}=\"\${${INI_ALL_SECTION}# } $SECTION\"" ((SECTIONS_NUM++)) - continue fi @@ -209,26 +258,46 @@ function read_ini() fi fi - # Valid var/value line? (check for variable name and then '=') - if ! [[ "${line}" =~ ^[[:print:]]+[[:space:]]*= ]] + # Valid var/value line? (check for variable name and then '=' or ':') + LINE_IS_VALID=1 + + if [[ "${line}" =~ $RE ]] + then + if [ $CUR_INDENT_LEVEL -gt $INDENT_LEVEL ] + then + VAL="$VAL${line}" + else + VAR="${BASH_REMATCH[1]}" + VAL="${BASH_REMATCH[3]}" + INDENT_LEVEL=$CUR_INDENT_LEVEL + fi + elif [ $CUR_INDENT_LEVEL -gt $INDENT_LEVEL ] + then + line="${line##+([[:space:]])}" + VAL="$VAL\\n${line}" + else + LINE_IS_VALID=0 + fi + + if [ "$VAL" = "" ] && [ "$NV" = 0 ] then - echo "Error: Invalid line:" >&2 + LINE_IS_VALID=0 + fi + + if [ "$LINE_IS_VALID" = 0 ] + then + echo "Error: Invalid line:" >&2 echo " ${LINE_NUM}: '$line'" >&2 cleanup_bash return 1 - fi - + fi - # split line at "=" sign - IFS="=" - read -r VAR VAL <<< "${line}" - IFS="${IFS_OLD}" - # delete spaces around the equal sign (using extglob) + VAR="${VAR##+([[:space:]])}" VAR="${VAR%%+([[:space:]])}" - VAL="${VAL##+([[:space:]])}" - VAR=$(echo $VAR) + VAL="${VAL##+([[:space:]])}" + VAL="${VAL%%+([[:space:]])}" # Construct variable name: # ${VARNAME_PREFIX}__$SECTION__$VAR @@ -241,17 +310,16 @@ function read_ini() else VARNAME=${SECTION}__${VAR//./_} fi + eval "${INI_ALL_VARNAME}=\"\${${INI_ALL_VARNAME}# } ${VARNAME}\"" - if [[ "${VAL}" =~ ^\".*\"$ ]] + if [[ "${VAL}" =~ (^\s*\")(.*)(\"\s*) ]] then # remove existing double quotes - VAL="${VAL##\"}" - VAL="${VAL%%\"}" - elif [[ "${VAL}" =~ ^\'.*\'$ ]] + VAL="${BASH_REMATCH[2]}" + elif [[ "${VAL}" =~ (^\s*\')(.*)(\'\s*) ]] then # remove existing single quotes - VAL="${VAL##\'}" - VAL="${VAL%%\'}" + VAL="${BASH_REMATCH[2]}" elif [ "$BOOLEANS" = 1 ] then # Value is not enclosed in quotes @@ -269,7 +337,7 @@ function read_ini() ;; esac fi - + # echo "pair: '${VARNAME}' = '${VAL}'" eval ${VARNAME_PREFIX}$'[${VARNAME}]=${VAL}' # eval $'echo "array: \'${VARNAME}\' = \'${'${VARNAME_PREFIX}$'[${VARNAME}]}\'"' diff --git a/test/test1.ini b/test/test1.ini index ff86fe2..3e74446 100644 --- a/test/test1.ini +++ b/test/test1.ini @@ -19,8 +19,8 @@ var4="VAR 4" ; not become part of the value var5 = " VAR 5 " -; var6 - value in double quotes; value's leading and trailing whitespace should -; be preserved; leading and trailing whitespace before/after double quotes should +; var6 - value in single quotes; value's leading and trailing whitespace should +; be preserved; leading and trailing whitespace before/after single quotes should ; not become part of the value var6 = ' VAR 6 ' diff --git a/test/test10.ini b/test/test10.ini new file mode 100644 index 0000000..8126c13 --- /dev/null +++ b/test/test10.ini @@ -0,0 +1,41 @@ + +; Testing Python configuration file format +; see https://docs.python.org/3/library/configparser.html + +[Simple Values] +key=value +spaces in keys=allowed +spaces in values=allowed as well +spaces around the delimiter = obviously +you can also use : to delimit keys from values + +[All Values Are Strings] +values like this: 1000000 +or this: 3.14159265359 +are they treated as numbers? : no +integers, floats and booleans are held as: strings +can use the API to get converted values directly: true + +[Multiline Values] +chorus: I'm a lumberjack, and I'm okay + I sleep all night and I work all day + +[You can use comments] +# like this +; or this + +# By default only in an empty line. +# Inline comments can be harmful because they prevent users +# from using the delimiting characters as parts of values. +# That being said, this can be customized. + + [Sections Can Be Indented] + can_values_be_as_well = True + does_that_mean_anything_special = False + purpose = formatting for readability + multiline_values = are + handled just fine as + long as they are indented + deeper than the first line + of a value + # Did I mention we can indent comments, too? diff --git a/test/test10.out.correct b/test/test10.out.correct new file mode 100644 index 0000000..393a8a8 --- /dev/null +++ b/test/test10.out.correct @@ -0,0 +1,16 @@ +Simple Values_key:value +Simple Values_spaces in keys:allowed +Simple Values_spaces in values:allowed as well +Simple Values_spaces around the delimiter:obviously +Simple Values_you can also use:to delimit keys from values +All Values Are Strings_values like this:1000000 +All Values Are Strings_or this:3.14159265359 +All Values Are Strings_are they treated as numbers?:no +All Values Are Strings_integers, floats and booleans are held as:strings +All Values Are Strings_can use the API to get converted values directly:true +Multiline Values_chorus:I'm a lumberjack, and I'm okay\nI sleep all night and I work all day +Sections Can Be Indented_can_values_be_as_well:True +Sections Can Be Indented_does_that_mean_anything_special:False +Sections Can Be Indented_purpose:formatting for readability +Sections Can Be Indented_multiline_values:are\nhandled just fine as\nlong as they are indented\ndeeper than the first line\nof a value +number sections:5 diff --git a/test/test10.sh b/test/test10.sh new file mode 100755 index 0000000..278def1 --- /dev/null +++ b/test/test10.sh @@ -0,0 +1,31 @@ + +# Test 10 +# +# Python configuration file format + +. ../read_ini.sh + +unset INI +declare -A INI +read_ini --booleans 0 test10.ini + +echo "Simple Values_key:${INI[Simple Values__key]}" +echo "Simple Values_spaces in keys:${INI[Simple Values__spaces in keys]}" +echo "Simple Values_spaces in values:${INI[Simple Values__spaces in values]}" +echo "Simple Values_spaces around the delimiter:${INI[Simple Values__spaces around the delimiter]}" +echo "Simple Values_you can also use:${INI[Simple Values__you can also use]}" + +echo "All Values Are Strings_values like this:${INI[All Values Are Strings__values like this]}" +echo "All Values Are Strings_or this:${INI[All Values Are Strings__or this]}" +echo "All Values Are Strings_are they treated as numbers?:${INI[All Values Are Strings__are they treated as numbers?]}" +echo "All Values Are Strings_integers, floats and booleans are held as:${INI[All Values Are Strings__integers, floats and booleans are held as]}" +echo "All Values Are Strings_can use the API to get converted values directly:${INI[All Values Are Strings__can use the API to get converted values directly]}" + +echo "Multiline Values_chorus:${INI[Multiline Values__chorus]}" + +echo "Sections Can Be Indented_can_values_be_as_well:${INI[Sections Can Be Indented__can_values_be_as_well]}" +echo "Sections Can Be Indented_does_that_mean_anything_special:${INI[Sections Can Be Indented__does_that_mean_anything_special]}" +echo "Sections Can Be Indented_purpose:${INI[Sections Can Be Indented__purpose]}" +echo "Sections Can Be Indented_multiline_values:${INI[Sections Can Be Indented__multiline_values]}" + +echo "number sections:$INI__NUMSECTIONS" diff --git a/test/test11.ini b/test/test11.ini new file mode 100644 index 0000000..fbee7a1 --- /dev/null +++ b/test/test11.ini @@ -0,0 +1,6 @@ + +; Testing Python configuration file format - empty values not allowed +; see https://docs.python.org/3/library/configparser.html + +[No Values] +empty string value here = diff --git a/test/test11.out.correct b/test/test11.out.correct new file mode 100644 index 0000000..49b8ad9 --- /dev/null +++ b/test/test11.out.correct @@ -0,0 +1,3 @@ +Error: Invalid line: + 6: 'empty string value here =' +No Values_empty string value here: diff --git a/test/test11.sh b/test/test11.sh new file mode 100755 index 0000000..9cc274f --- /dev/null +++ b/test/test11.sh @@ -0,0 +1,12 @@ + +# Test 11 +# +# Python configuration file format - empty values not allowed + +. ../read_ini.sh + +unset INI +declare -A INI +read_ini --booleans 0 test11.ini + +echo "No Values_empty string value here:${INI[No Values__empty string value here]}" diff --git a/test/test12.ini b/test/test12.ini new file mode 100644 index 0000000..30da61e --- /dev/null +++ b/test/test12.ini @@ -0,0 +1,7 @@ + +; Testing Python configuration file format - empty values allowed +; see https://docs.python.org/3/library/configparser.html + +[No Values] +key_without_value +empty string value here = diff --git a/test/test12.out.correct b/test/test12.out.correct new file mode 100644 index 0000000..777ea06 --- /dev/null +++ b/test/test12.out.correct @@ -0,0 +1,3 @@ +No Values_key_without_value: +No Values_empty string value here: +number sections:1 diff --git a/test/test12.sh b/test/test12.sh new file mode 100755 index 0000000..6f3e8a7 --- /dev/null +++ b/test/test12.sh @@ -0,0 +1,15 @@ + +# Test 12 +# +# Python configuration file format - empty values allowed + +. ../read_ini.sh + +unset INI +declare -A INI +read_ini --booleans 0 --allow_no_value test12.ini + +echo "No Values_key_without_value:${INI[No Values__key_without_value]}" +echo "No Values_empty string value here:${INI[No Values__empty string value here]}" + +echo "number sections:$INI__NUMSECTIONS" diff --git a/test/test3.ini b/test/test3.ini index 406947d..010e98a 100644 --- a/test/test3.ini +++ b/test/test3.ini @@ -21,3 +21,6 @@ var1="section 3 VAR 1" var2= "section 3 VAR 2" +[section-4] +var1="section 4 VAR 1" +var2= "section 4 VAR 2" diff --git a/test/test3.out.correct b/test/test3.out.correct index 69f72af..bac0432 100644 --- a/test/test3.out.correct +++ b/test/test3.out.correct @@ -1,13 +1,17 @@ var1:VAR 1 var2:VAR 2 -setion1_var1:section 1 VAR 1 -setion1_var2:section 1 VAR 2 -setion1_var3:section 1 VAR 3 -setion2_var1:section 2 VAR 1 -setion2_var2: section 2 VAR 2 -setion2_var3:section 2 VAR 3 -setion2_var4:section 2 VAR 4 -setion2_var5:section 2 VAR 5 -setion3_var1:section 3 VAR 1 -setion3_var2:section 3 VAR 2 -number sections:3 +section1_var1:section 1 VAR 1 +section1_var2:section 1 VAR 2 +section1_var3:section 1 VAR 3 +section2_var1:section 2 VAR 1 +section2_var2: section 2 VAR 2 +section2_var3:section 2 VAR 3 +section2_var4:section 2 VAR 4 +section2_var5:section 2 VAR 5 +section3_var1:section 3 VAR 1 +section3_var2:section 3 VAR 2 +section-4_var1:section 4 VAR 1 +section-4_var2:section 4 VAR 2 +all sections:section1 section2 section3 section-4 +all variables:var1 var2 section1__var1 section1__var2 section1__var3 section2__var1 section2__var2 section2__var3 section2__var4 section2__var5 section3__var1 section3__var2 section-4__var1 section-4__var2 +number sections:4 diff --git a/test/test3.sh b/test/test3.sh index 8f38c85..3fe9407 100755 --- a/test/test3.sh +++ b/test/test3.sh @@ -12,17 +12,22 @@ read_ini test3.ini echo "var1:${INI[var1]}" echo "var2:${INI[var2]}" -echo "setion1_var1:${INI[section1__var1]}" -echo "setion1_var2:${INI[section1__var2]}" -echo "setion1_var3:${INI[section1__var3]}" +echo "section1_var1:${INI[section1__var1]}" +echo "section1_var2:${INI[section1__var2]}" +echo "section1_var3:${INI[section1__var3]}" -echo "setion2_var1:${INI[section2__var1]}" -echo "setion2_var2:${INI[section2__var2]}" -echo "setion2_var3:${INI[section2__var3]}" -echo "setion2_var4:${INI[section2__var4]}" -echo "setion2_var5:${INI[section2__var5]}" +echo "section2_var1:${INI[section2__var1]}" +echo "section2_var2:${INI[section2__var2]}" +echo "section2_var3:${INI[section2__var3]}" +echo "section2_var4:${INI[section2__var4]}" +echo "section2_var5:${INI[section2__var5]}" -echo "setion3_var1:${INI[section3__var1]}" -echo "setion3_var2:${INI[section3__var2]}" +echo "section3_var1:${INI[section3__var1]}" +echo "section3_var2:${INI[section3__var2]}" +echo "section-4_var1:${INI[section-4__var1]}" +echo "section-4_var2:${INI[section-4__var2]}" + +echo "all sections:$INI__ALL_SECTIONS" +echo "all variables:$INI__ALL_VARS" echo "number sections:$INI__NUMSECTIONS" diff --git a/test/test9.ini b/test/test9.ini index 9a8c271..b4e5421 100644 --- a/test/test9.ini +++ b/test/test9.ini @@ -1,5 +1,5 @@ -; Testing quoted quotes (didnt't worked in 0.3) +; Testing quoted quotes (didn't work in 0.3) ; code injection var1="" ls "."