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}
