commit:     3868d097a576275641d55c9fb32bfe4976385e02
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Thu Jul 24 21:22:08 2025 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Sat Aug  2 16:31:28 2025 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=3868d097

isolated-functions.sh: add and integrate the configparser() function

Presently, the __repo_attr() function acts as a crude parser for the
value of the 'PORTAGE_REPOSITORIES' environment variable, which contains
the contents of files beneath the "/etc/portage/repos.conf" directory.
Its implementation is substandard, to say the least. Firstly, it suffers
from several amateurish bugs. Secondly, it is unable to detect errors in
syntax. Thirdly, it is an incomplete, incorrect implementation of a
parser for the file format that it is supposed to handle.

This commit incorporates a new function by the name of configparser().
It consumes the standard input and - as its name suggests - attempts to
parse it as the "configparser" configuration file format that is native
to python. It has been tested against the sample file provided by the
python documentation and fully supports multiline values and key/value
pairs that are separated by the <colon> character. Each key/value entry
that is encountered shall be printed in the following format:

  "%s\0%s\0%s\0", <section>, <key>, <value>

It is capable of detecting syntax errors. Upon encountering any such
error, a diagnostic message shall be printed to standard error and the
function shall immediately return a non-zero status.

This commit also revises the __repo_attr() function so as to call the
configparser() function and interpret its output.

Fixes: e72b8b90542d1ed2bf53c2b9a0491795c12e63c2
Link: https://docs.python.org/3/library/configparser.html
Signed-off-by: Kerin Millar <kfm <AT> plushkava.net>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 bin/isolated-functions.sh | 118 +++++++++++++++++++++++++++++++++++++++++-----
 bin/save-ebuild-env.sh    |  14 +++---
 2 files changed, 112 insertions(+), 20 deletions(-)

diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh
index 546efa0190..a1c431694e 100644
--- a/bin/isolated-functions.sh
+++ b/bin/isolated-functions.sh
@@ -539,23 +539,22 @@ has() {
 }
 
 __repo_attr() {
-       local in_section exit_status=1 line saved_extglob_shopt=$(shopt -p 
extglob)
-       shopt -s extglob
+       local section key val i
+       local -a records
+
+       mapfile -td '' records < <(configparser <<<"${PORTAGE_REPOSITORIES}")
 
-       while read -r line; do
-               if (( ! in_section )) && [[ ${line} == "[$1]" ]]; then
-                       in_section=1
-               elif (( in_section )) && [[ ${line} == "["*"]" ]]; then
-                       in_section=0
-               elif (( in_section )) && [[ ${line} =~ ^${2}[[:space:]]*= ]]; 
then
-                       echo "${line##$2*( )=*( )}"
-                       exit_status=0
+       for (( i = 0; i < ${#records[@]}; i += 3 )); do
+               section=${records[i]}
+               key=${records[i + 1]}
+               if [[ ${section} == "$1" && ${key} == "$2" ]]; then
+                       val=${records[i + 2]}
                        break
                fi
-       done <<< "${PORTAGE_REPOSITORIES}"
+       done
 
-       eval "${saved_extglob_shopt}"
-       return ${exit_status}
+       # Only print the found value in the case that configparser succeeded.
+       wait "$!" && [[ -v val ]] && printf '%s\n' "${val}"
 }
 
 # eqaquote <string>
@@ -703,4 +702,97 @@ else
        }
 fi
 
+# Consumes the standard input and attempts to parse it as the "configparser"
+# configuration file format that is native to python. Each key/value entry
+# shall be printed in the format of "%s\0%s\0%s\0", <section>, <key>, <value>.
+# If the entirety of the input can be successfully parsed, the return value
+# shall be 0. Otherwise, a diagnostic message shall be printed to standard
+# error and the return value shall be greater than 0. Upon encountering the
+# first error of syntax, no further key/value entries shall be printed.
+configparser() {
+       local IFS next_{section,key} reset_extglob scalar_val section flush 
key_i line key nr i
+       local -a next_val lines val
+       local -A seen
+
+       reset_extglob=$(shopt -p extglob)
+       shopt -s extglob
+       IFS=$'\n'
+
+       # 
https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+       mapfile -t lines
+       for nr in "${!lines[@]}"; do
+               # Trim trailing whitespace characters.
+               line=${lines[nr++]%%+([[:space:]])}
+
+               # Count and trim leading whitespace characters.
+               [[ ${line} =~ ^[[:space:]]* ]]
+               if (( i = ${#BASH_REMATCH} )); then
+                       line=${line:i}
+               fi
+
+               if [[ ${line} == [#\;]* ]]; then
+                       # Encountered a comment.
+                       false
+               elif (( ${#val[@]} && (${#line} == 0 || i > key_i) )); then
+                       # Encountered the continuation of a multiline value.
+                       val+=( "${line}" )
+                       false
+               elif (( ${#line} == 0 )); then
+                       # Encountered an empty line that is not a continuation.
+                       true
+               elif [[ ${line} =~ ^\[(.+)\]$ ]]; then
+                       # Encountered a new section.
+                       next_section=${BASH_REMATCH[1]}
+                       seen=()
+               elif [[ ${line} =~ ^([^:=]+)[:=][[:space:]]*(.*) ]]; then
+                       # Encountered the beginning of a key/value entry.
+                       next_key=${BASH_REMATCH[1]%%*([[:space:]])}
+                       if let 'seen[$next_key]++'; then
+                               printf >&2 \
+                                       'configparser: duplicate key in section 
%s at STDIN[%d]: %s\n' \
+                                       "${section@Q}" "${nr}" "${line@Q}"
+                               eval "${reset_extglob}"
+                               return 1
+                       elif [[ ! ${section} ]]; then
+                               printf >&2 \
+                                       'configparser: key declared before 
section at STDIN[%d]: %s\n' \
+                                       "${nr}" "${line@Q}"
+                               eval "${reset_extglob}"
+                               return 1
+                       fi
+                       next_val=( "${BASH_REMATCH[2]}" )
+                       key_i=$i
+               else
+                       printf >&2 \
+                               'configparser: syntax error at STDIN[%d]: %s\n' 
\
+                               "${nr}" "${line@Q}"
+                       eval "${reset_extglob}"
+                       return 1
+               fi && flush=1
+
+               if (( nr == ${#lines[@]} - 1 )); then
+                       # Last line encountered. Flush any pending value.
+                       flush=1
+               fi
+
+               if (( flush && ${#val[@]} )); then
+                       # Print section, key and value, null-terminated.
+                       scalar_val=${val[*]}
+                       scalar_val=${scalar_val%%+($'\n')}
+                       printf '%s\0' "${section}" "${key}" "${scalar_val}"
+                       val=()
+               fi
+
+               section=${next_section}
+               key=${next_key}
+               if (( ${#next_val[@]} )); then
+                       val=( "${next_val[@]}" )
+                       next_val=()
+               fi
+               flush=0
+       done
+
+       eval "${reset_extglob}"
+}
+
 true

diff --git a/bin/save-ebuild-env.sh b/bin/save-ebuild-env.sh
index 3197e3c297..32837e8950 100644
--- a/bin/save-ebuild-env.sh
+++ b/bin/save-ebuild-env.sh
@@ -104,13 +104,13 @@ __save_ebuild_env() (
                __unpack_tar __vecho
 
                addpredict addwrite adddeny addread assert best_version
-               contains_word debug-print-function debug-print-section
-               debug-print docompress default diropts docinto dostrip die
-               einstall eqawarn exeinto exeopts ebegin eerror einfon econf
-               einfo ewarn eend elog find0 get_KV has_version hasq hasv has
-               inherit insinto insopts into libopts nonfatal portageq
-               register_success_hook register_die_hook use_enable use_with
-               unpack useq usev use
+               contains_word configparser debug-print-function
+               debug-print-section debug-print docompress default diropts
+               docinto dostrip die einstall eqawarn exeinto exeopts ebegin
+               eerror einfon econf einfo ewarn eend elog find0 get_KV
+               has_version hasq hasv has inherit insinto insopts into libopts
+               nonfatal portageq register_success_hook register_die_hook
+               use_enable use_with unpack useq usev use
 
                # Defined by the "ebuild.sh" utility.
                ${QA_INTERCEPTORS}

Reply via email to