Added --repeat-sections and --duplicates-merge.
This commit is contained in:
parent
67028c4fae
commit
ec8ab16ef7
2 changed files with 110 additions and 13 deletions
63
SPEC
63
SPEC
|
|
@ -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
|
General file format
|
||||||
-------------------
|
-------------------
|
||||||
* Blank lines are ignored.
|
* Blank lines are ignored.
|
||||||
|
|
@ -26,3 +62,30 @@ Booleans
|
||||||
--------
|
--------
|
||||||
* no_<option> sets it to 0/false, else 1/true.
|
* no_<option> sets it to 0/false, else 1/true.
|
||||||
* Later settings of the same key override previous ones - last one wins.
|
* 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" ]
|
||||||
|
|
|
||||||
58
parse_ini
58
parse_ini
|
|
@ -36,6 +36,10 @@ parser_getopts() {
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
-duplicates-merge|--duplicates-merge)
|
||||||
|
shift
|
||||||
|
DUPLICATES_MERGE=1
|
||||||
|
;;
|
||||||
-e|-export|--export)
|
-e|-export|--export)
|
||||||
shift
|
shift
|
||||||
DECLARE_SCOPE="-x"
|
DECLARE_SCOPE="-x"
|
||||||
|
|
@ -98,6 +102,10 @@ parser_getopts() {
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
-repeat-sections|--repeat-sections)
|
||||||
|
shift
|
||||||
|
REPEAT_SECTIONS="1"
|
||||||
|
;;
|
||||||
-textual-booleans|--textual-booleans)
|
-textual-booleans|--textual-booleans)
|
||||||
shift
|
shift
|
||||||
TEXTUAL_BOOLEANS="1"
|
TEXTUAL_BOOLEANS="1"
|
||||||
|
|
@ -169,6 +177,13 @@ parser_help() {
|
||||||
Show version and copyright information.
|
Show version and copyright information.
|
||||||
|
|
||||||
Lesser used options:
|
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>
|
--global-name <name>
|
||||||
The name of the 'global' section used when defining the arrays. Only
|
The name of the 'global' section used when defining the arrays. Only
|
||||||
alphanumerics and _ may be used with this option, which cannot be empty.
|
alphanumerics and _ may be used with this option, which cannot be empty.
|
||||||
|
|
@ -187,6 +202,11 @@ parser_help() {
|
||||||
--no-squash
|
--no-squash
|
||||||
Do not squash multiple consecutive blanks (which are later translated to
|
Do not squash multiple consecutive blanks (which are later translated to
|
||||||
a _) into a single space while reading section names and properties.
|
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
|
--textual-booleans
|
||||||
When defining the arrays, boolean keys are given a value of "0" or "1"
|
When defining the arrays, boolean keys are given a value of "0" or "1"
|
||||||
(representing 'false' and 'true' respectivly). With this option the value
|
(representing 'false' and 'true' respectivly). With this option the value
|
||||||
|
|
@ -216,8 +236,7 @@ parser_version() {
|
||||||
|
|
||||||
|
|
||||||
parse_ini() {
|
parse_ini() {
|
||||||
# Bash v4.4+ is required.
|
# Bash v4.0+ is required.
|
||||||
# if [[ -z "${BASH_VERSINFO[0]}" ]] || ((BASH_VERSINFO[0] < 4)); then
|
|
||||||
if [[ -z "${BASH_VERSINFO[0]}" ]] || ((BASH_VERSINFO[0] < 4)); then
|
if [[ -z "${BASH_VERSINFO[0]}" ]] || ((BASH_VERSINFO[0] < 4)); then
|
||||||
echo "${0##*/}: minimum of bash v4 required" >&2
|
echo "${0##*/}: minimum of bash v4 required" >&2
|
||||||
return 1
|
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 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 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 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 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 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 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.
|
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.
|
local VARIABLE_DELIM="_" # Delimiter between prefix and section name, unless VARIABLE_PREFIX is empty.
|
||||||
|
|
||||||
# Variables.
|
# 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
|
declare INIFILE
|
||||||
|
|
||||||
# Parse options.
|
# Parse options.
|
||||||
|
|
@ -320,8 +341,7 @@ parse_ini() {
|
||||||
done
|
done
|
||||||
|
|
||||||
# Ignore the line if it's a comment.
|
# Ignore the line if it's a comment.
|
||||||
# FIXME: Is the printf required here?
|
[[ "$LINE" =~ ^[[:blank:]]*([$COMMENT_CHARS].*)*$ ]] && continue
|
||||||
[[ "$LINE" =~ ^[[:blank:]]*([$(printf "%q" "$COMMENT_CHARS")].*)*$ ]] && continue
|
|
||||||
|
|
||||||
# Process the line.
|
# Process the line.
|
||||||
if [[ "${LINE:0:1}" == "[" ]]; then # Found the beginning of a section definition.
|
if [[ "${LINE:0:1}" == "[" ]]; then # Found the beginning of a section definition.
|
||||||
|
|
@ -362,15 +382,28 @@ parse_ini() {
|
||||||
LINE="${LINE^^}"
|
LINE="${LINE^^}"
|
||||||
fi
|
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.
|
# Keep track of the current section name.
|
||||||
CURRENT_SECTION="$LINE"
|
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.
|
# Reset the ignore flag.
|
||||||
IGNORE_SECTION=0
|
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
|
fi
|
||||||
elif ((IGNORE_SECTION == 0)) && [[ "$LINE" != *$KEYVALUE_DELIM* ]]; then # Process the property definition as if it's a boolean.
|
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.
|
# If the value starts with a " or ' it must end with same.
|
||||||
|
|
@ -402,7 +435,6 @@ parse_ini() {
|
||||||
|
|
||||||
# Output the associative array element definition.
|
# Output the associative array element definition.
|
||||||
if ((USE_BOOLEANS == 1)); then
|
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"
|
printf "%s%s%s[\"%s\"]=\"%s\"\\n" "$PREFIX" "${PREFIX:+$DELIM}" "$CURRENT_SECTION" "$LINE" "$BOOL_VALUE"
|
||||||
else
|
else
|
||||||
echo "${0##*/}: line $LINENUMBER: key without a value - ignoring property" >&2
|
echo "${0##*/}: line $LINENUMBER: key without a value - ignoring property" >&2
|
||||||
|
|
@ -435,12 +467,14 @@ parse_ini() {
|
||||||
|
|
||||||
# Output the associative array element definition.
|
# Output the associative array element definition.
|
||||||
# FIXME: If doing validation only, don't output declaration here.
|
# 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"
|
printf "%s%s%s[\"%s\"]+=\"%s\"\\n" "$PREFIX" "${PREFIX:+$DELIM}" "$CURRENT_SECTION" "$KEY" "$VALUE"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# FIXME: Make this debug output only.
|
# FIXME: Make this debug output only.
|
||||||
echo "Skipping line $LINENUMBER" >&2
|
echo "Skipping line $LINENUMBER" >&2
|
||||||
true
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue