Added --repeat-sections and --duplicates-merge.

This commit is contained in:
Darren 'Tadgy' Austin 2019-07-19 13:53:45 +01:00
commit ec8ab16ef7
2 changed files with 110 additions and 13 deletions

63
SPEC
View file

@ -1,3 +1,39 @@
# http://en.wikipedia.org/wiki/INI_file:
# * Provides a good explanation of the ini format - use this for docs *
# * INI's have 'sections' and 'properties'. Properties have key = value format *
#
# Case insensitivity: Case is not changed, unless option used to covert to lower/upper case.
# Comments: Allow ; and # for comments. Must be on their own line.
# Blank lines: Blank lines are ignored.
# Escape chars: \ at the end of a line will continue it onto next (leading whitespace is removed per normal)
# Ordering: GLOBAL section must be at the top, sections continue until next section or EOF.
# Duplicate names: Duplicate property values overwrite previous values.
# Provide an option to abort/error is duplicate is found?
# Add option to merge duplicates separated by octal byte (\036 ??)
# Duplicate sections are merged. Option to error if dup.
# Global properties: Support. Add to a GLOBAL section?
# Hierarchy: No hierarchy support. Each section is own section.
# Name/value delim: Use = by default. Allow : via option?
# Quoted values: Allow values to be within " and ' to keep literal formatting.
# Whitespace: Whitespace around section labels and []s is removed.
# Whitespace within section labels is kept / translated.
# Whitespace around property names is removed.
# Whitespace within property names is kept as is (spaces squashed - option to override).
# Property values have whitespace between = and data removed.
# Property values are kept as is (no squashing)
# http://www.regular-expressions.info/posixbrackets.html
# http://ajdiaz.wordpress.com/2008/02/09/bash-ini-parser/
# https://github.com/rudimeier/bash_ini_parser/blob/ff9d46a5503bf41b3344af85447e28cbaf95350e/read_ini.sh
# http://tldp.org/LDP/abs/html/
# Specs:
# [section] Can be upper/lower/mixed case (set by options)
# Can only include: '-+_. [:alnum:]'
# # Any single or consecutive occurance of '-+_. ' are converted to a *single* _
# # eg: [foo -+_. bar] becomes [foo_bar] ??
# Any leading/trailing spaces/tabs between the []s and name will be removed.
General file format
-------------------
* Blank lines are ignored.
@ -26,3 +62,30 @@ Booleans
--------
* no_<option> sets it to 0/false, else 1/true.
* Later settings of the same key override previous ones - last one wins.
ToDo
----
--check
Check/validate the INI file by running it through the parser. Testing the
ini file will report any problems or syntax errors in the file, but will
not set up the environment variables as would happen in normal parsing.
Any parse errors are reported to stderr. When combined with the --debug
option, every detail of the parsing process is reported to stderr.
# -c, --check-only Only validate the ini file, don't parse it into the environment
# --check Parse the file, report any problems, but don't output the code.
--debug
Show full details of the ini file parsing process. Detail is written to
stderr. Unless --check is used with this option, the parser will still
set up the environment as would happen normally,
# --debug Show all details of the parsing process to stderr. If --check is used, no code is outputted.
# --?? Set comment characters. Each char can be used to indicate a comment.
# --?? Treat all problems as errors and stop processing at that point. Need to integrate into code.
What happens if:
"[ section ]"
[ "section" ]

View file

@ -36,6 +36,10 @@ parser_getopts() {
fi
shift
;;
-duplicates-merge|--duplicates-merge)
shift
DUPLICATES_MERGE=1
;;
-e|-export|--export)
shift
DECLARE_SCOPE="-x"
@ -98,6 +102,10 @@ parser_getopts() {
fi
shift
;;
-repeat-sections|--repeat-sections)
shift
REPEAT_SECTIONS="1"
;;
-textual-booleans|--textual-booleans)
shift
TEXTUAL_BOOLEANS="1"
@ -169,6 +177,13 @@ parser_help() {
Show version and copyright information.
Lesser used options:
--duplicates-merge
If a duplicate key for a specific section is found, the normal behaviour
is to have the latter instance of the key overwrite the value of the
earlier. With this option, the keys are merged, and a new, concatinated,
value will result. The concatinated values are not separated by any
characters. Booleans are the exception to this behaviour, as the
latter bool will always override an earlier setting.
--global-name <name>
The name of the 'global' section used when defining the arrays. Only
alphanumerics and _ may be used with this option, which cannot be empty.
@ -187,6 +202,11 @@ parser_help() {
--no-squash
Do not squash multiple consecutive blanks (which are later translated to
a _) into a single space while reading section names and properties.
--repeat-sections
Usually, if a section is repeated in the INI file its properties are
ignored. Using this option allows sections to be repeated in the file,
but this does not affect the processing of the keys/values. Ssee the
'--duplicates-merge' option also.
--textual-booleans
When defining the arrays, boolean keys are given a value of "0" or "1"
(representing 'false' and 'true' respectivly). With this option the value
@ -216,8 +236,7 @@ parser_version() {
parse_ini() {
# Bash v4.4+ is required.
# if [[ -z "${BASH_VERSINFO[0]}" ]] || ((BASH_VERSINFO[0] < 4)); then
# Bash v4.0+ is required.
if [[ -z "${BASH_VERSINFO[0]}" ]] || ((BASH_VERSINFO[0] < 4)); then
echo "${0##*/}: minimum of bash v4 required" >&2
return 1
@ -230,7 +249,9 @@ parse_ini() {
local CONVERT_CHARS="[:blank:].+-" # Characters from ACCEPTABLE_CHARS in section and key names that should be converted to _. Must be a valid regex bracket expression.
local CURRENT_SECTION="global" # Name used for the 'global' section of the INI file.
local DECLARE_SCOPE="-g" # The scope given in the array definitions. "-g" = global scope, "-l" = local scope, "-x" = export values.
local DUPLICATES_MERGE="0" # Whether to merge latter duplicate key's values with earlier key's values. 0 = don't merge, 1 = do merge.
local KEYVALUE_DELIM="=" # Delimiter between key and value. Must be a single character.
local REPEAT_SECTIONS="0" # Whether to allow section names to repeat. 0 = no repeats, 1 = allow repeats.
local SQUASH_SPACES="1" # Whether to squash multiple consecutive blanks into a single space. 0 = don't squash, 1 = do squash.
local TEXTUAL_BOOLEANS="0" # Whether to use "false" and "true" for booleans. 0 = use "0" and "1", 1 = use "false" and "true".
local USE_BOOLEANS="1" # Whether to allow the use of boolean values in the INI file. 0 = don't allow, 1 = do allow.
@ -238,7 +259,7 @@ parse_ini() {
local VARIABLE_DELIM="_" # Delimiter between prefix and section name, unless VARIABLE_PREFIX is empty.
# Variables.
local BOOL_VALUE DELIM ERR IGNORE_SECTION=0 INIFD KEY LINE LINENUMBER=0 PREFIX REPLY VALUE
local BOOL_VALUE DELIM ERR IGNORE_SECTION=0 INIFD KEY LINE LINENUMBER=0 PREFIX REPLY SECTION SECTIONS_SEEN=() VALUE
declare INIFILE
# Parse options.
@ -320,8 +341,7 @@ parse_ini() {
done
# Ignore the line if it's a comment.
# FIXME: Is the printf required here?
[[ "$LINE" =~ ^[[:blank:]]*([$(printf "%q" "$COMMENT_CHARS")].*)*$ ]] && continue
[[ "$LINE" =~ ^[[:blank:]]*([$COMMENT_CHARS].*)*$ ]] && continue
# Process the line.
if [[ "${LINE:0:1}" == "[" ]]; then # Found the beginning of a section definition.
@ -362,15 +382,28 @@ parse_ini() {
LINE="${LINE^^}"
fi
# Output the associative array declaration.
# FIXME: If doing validation only, don't output declaration here.
printf "declare %s -A %s%s%s\\n" "$DECLARE_SCOPE" "$PREFIX" "$DELIM" "$LINE"
# Keep track of the current section name.
CURRENT_SECTION="$LINE"
# Should we process repeat sections?
if ((REPEAT_SECTIONS == 0)); then
for SECTION in "${SEEN_SECTIONS[@]}"; do
if [[ "$CURRENT_SECTION" == "$SECTION" ]]; then
# It's a section we've seen before - don't process it.
echo "${0##*/}: line $LINENUMBER: repeated section name - ignoring section" >&2
IGNORE_SECTION=1
continue 2
fi
done
SEEN_SECTIONS+=("$CURRENT_SECTION")
fi
# Reset the ignore flag.
IGNORE_SECTION=0
# Output the associative array declaration.
# FIXME: If doing validation only, don't output declaration here.
printf "declare %s -A %s%s%s\\n" "$DECLARE_SCOPE" "$PREFIX" "$DELIM" "$CURRENT_SECTION"
fi
elif ((IGNORE_SECTION == 0)) && [[ "$LINE" != *$KEYVALUE_DELIM* ]]; then # Process the property definition as if it's a boolean.
# If the value starts with a " or ' it must end with same.
@ -402,7 +435,6 @@ parse_ini() {
# Output the associative array element definition.
if ((USE_BOOLEANS == 1)); then
# printf "%s%s%s+=([\"%s\"]=\"%s\")\\n" "$PREFIX" "${PREFIX:+$DELIM}" "$CURRENT_SECTION" "$LINE" "$BOOL_VALUE"
printf "%s%s%s[\"%s\"]=\"%s\"\\n" "$PREFIX" "${PREFIX:+$DELIM}" "$CURRENT_SECTION" "$LINE" "$BOOL_VALUE"
else
echo "${0##*/}: line $LINENUMBER: key without a value - ignoring property" >&2
@ -435,12 +467,14 @@ parse_ini() {
# Output the associative array element definition.
# FIXME: If doing validation only, don't output declaration here.
# FIXME: Have an option to have repeat sections/properties over-write previous ones rather than append.
if ((DUPLICATES_MERGE == 0)); then
printf "%s%s%s[\"%s\"]=\"%s\"\\n" "$PREFIX" "${PREFIX:+$DELIM}" "$CURRENT_SECTION" "$KEY" "$VALUE"
else
printf "%s%s%s[\"%s\"]+=\"%s\"\\n" "$PREFIX" "${PREFIX:+$DELIM}" "$CURRENT_SECTION" "$KEY" "$VALUE"
fi
else
# FIXME: Make this debug output only.
echo "Skipping line $LINENUMBER" >&2
true
fi
done