From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by finch.gentoo.org (Postfix) with ESMTPS id 36EE5158091 for ; Sun, 5 Jun 2022 19:43:07 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 74768E0893; Sun, 5 Jun 2022 19:43:00 +0000 (UTC) Received: from mail-vs1-xe2d.google.com (mail-vs1-xe2d.google.com [IPv6:2607:f8b0:4864:20::e2d]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id 23889E0855 for ; Sun, 5 Jun 2022 19:42:59 +0000 (UTC) Received: by mail-vs1-xe2d.google.com with SMTP id w10so12070962vsa.4 for ; Sun, 05 Jun 2022 12:42:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=date:from:to:subject:message-id:mail-followup-to:references :mime-version:content-disposition:in-reply-to; bh=pHgOO96tBYnaLgn/Y781dvu4nSboJS/gyneHu0ndTeI=; b=YEby6KOmpa+nHW0eW5xEm84GHTqVHzh/CmmG6ee1T+pty8qljhXkrgONZlDkoXBV4D prvUmHKEzu9O0mV6IANAFbfaWNKlRLPs1ds74sW1pYywxiNWg7h18A0dYN7BpRRQf35u b2PfEjq7606cX9YoQYVaaDU3Kt8Jg3JxqmGaJ4Tyhec6fm7Gi5Ulo5m5831eo7XkNDaV l26ZVRhREZRgpLCzDoD/LuXCQWz01UkRFHJX203Q3jpwb/biZaxaC8CtpChiWToigfg8 6EQ2gwbBquCwTldxJ61gdDeoPnh15UryPW6gM2zuPTEWgVqT1mKEJS58rZlgzFdIOcbO kQGA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:subject:message-id:mail-followup-to :references:mime-version:content-disposition:in-reply-to; bh=pHgOO96tBYnaLgn/Y781dvu4nSboJS/gyneHu0ndTeI=; b=7lwMOHJq1pbj0ajazw3PjHmKGSE7VUrbwKKehryZ/G3H7Ho8hjapYdv3c+zRHxIjjA N40ePKBxot+Tt+IDxtzaI9tl1J2ZWeTOJR6B0AZ1QWaAI7wpbsMhw1gswfZJ/ElcIvhA 8PvyDuWZkVegs9CkhzvutGMaIGlVaqLaXKOwkId9HOpp2zgdMbUmhaQb8D3No1KYa93c iBfad5LybINSkhn+Euj5dzpcvpiNpE8A/pmtgYsEACMfQbt4OI+Cw1UGydQ52QIZpU7+ swMSFagcIwMuzKXReCsdTjAZ6J9P5YUlZm4T6t++gAJG81GSftESGmpzWyDlEcsk81Rp CGCw== X-Gm-Message-State: AOAM530gm9BwRm6qsf6R5CEA8Hw8F61saVZIKNeR9pSDsOWfFDCnae9n wizPO6TtyDWrfQ+0vbgzlNwZg+g/5IE= X-Google-Smtp-Source: ABdhPJyiXDFY+ZB9yfUGJc/uCN7aS1v7ECakd27jQ0OjcsGYF1CgcdQnMe8c+3b54Z1bN+FleyJWEA== X-Received: by 2002:a67:1a47:0:b0:332:9c08:8bfa with SMTP id a68-20020a671a47000000b003329c088bfamr8149814vsa.3.1654458178721; Sun, 05 Jun 2022 12:42:58 -0700 (PDT) Received: from dj3ntoo (88.sub-72-109-207.myvzw.com. [72.109.207.88]) by smtp.gmail.com with ESMTPSA id y43-20020a9f326e000000b003693e4797f6sm1800843uad.15.2022.06.05.12.42.57 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 05 Jun 2022 12:42:57 -0700 (PDT) Date: Sun, 5 Jun 2022 14:42:55 -0500 From: Oskari Pirhonen To: gentoo-dev@lists.gentoo.org Subject: Re: [gentoo-dev] [PATCH v3 1/2] esed.eclass: new eclass Message-ID: Mail-Followup-To: gentoo-dev@lists.gentoo.org References: <20220604204634.27567-1-ionen@gentoo.org> <20220604204634.27567-2-ionen@gentoo.org> Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-dev@lists.gentoo.org Reply-to: gentoo-dev@lists.gentoo.org X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="HDCAa5PQRNJEDlPG" Content-Disposition: inline In-Reply-To: <20220604204634.27567-2-ionen@gentoo.org> X-Archives-Salt: 65cec382-419a-4dca-b6a8-571907d8e47a X-Archives-Hash: 2cd22ed4bcc7fb76f40d412874abebe6 --HDCAa5PQRNJEDlPG Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Sat, Jun 04, 2022 at 16:46:33 -0400, Ionen Wolkens wrote: > Signed-off-by: Ionen Wolkens > --- > eclass/esed.eclass | 265 +++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 265 insertions(+) > create mode 100644 eclass/esed.eclass >=20 > diff --git a/eclass/esed.eclass b/eclass/esed.eclass > new file mode 100644 > index 00000000000..414daceaf8b > --- /dev/null > +++ b/eclass/esed.eclass > @@ -0,0 +1,265 @@ > +# Copyright 2022 Gentoo Authors > +# Distributed under the terms of the GNU General Public License v2 > + > +# @ECLASS: esed.eclass > +# @MAINTAINER: > +# Ionen Wolkens > +# @AUTHOR: > +# Ionen Wolkens > +# @SUPPORTED_EAPIS: 8 > +# @BLURB: sed(1) and alike wrappers that die if did not modify any files > +# @EXAMPLE: > +# > +# @CODE > +# # sed(1) wrappers, die if no changes > +# esed s/a/b/ file.c # -i is default > +# enewsed s/a/b/ project.pc.in "${T}"/project.pc > +# > +# # bash-only simple fixed string alternatives, also die if no changes > +# erepl string replace file.c > +# ereplp ^match string replace file.c # like /^match/s:string:replace:g > +# erepld ^match file.c # deletes matching lines, like /^match/d > +# use prefix && enewreplp ^prefix=3D /usr "${EPREFIX}"/usr pn.pc.in pn.pc > +# > +# # find(1) wrapper that sees shell functions, dies if no files found > +# efind . -name '*.c' -erun esed s/a/b/ # dies if no files changed > +# efind . -name '*.c' -erun sed s/a/b/ # only dies if no files found > +# @CODE > +# > +# Migration notes: be wary of non-deterministic cases involving variable= s, > +# e.g. s|lib|$(get_libdir)|, s|-O3|${CFLAGS}|, or s|/usr|${EPREFIX}/usr|. > +# erepl/esed() die if these do nothing, like libdir being 'lib' on x86. > +# Either verify, keep sed(1), or ensure a change (extra space, @libdir@). > +# > +# Where possible, it is also good to consider if using patches is more > +# suitable to ensure adequate changes. These functions are also unsafe > +# for binary files containing null bytes (erepl() will remove them). Some way to test for NULL in the file before reading might be useful. Possibly die if found? Although right now I can't think of a super elegant and/or efficient way to do so without writing a simple external helper. > + > +case ${EAPI} in > + 8) ;; > + *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; > +esac > + > +if [[ ! ${_ESED_ECLASS} ]]; then > +_ESED_ECLASS=3D1 > + > +# @ECLASS_VARIABLE: ESED_VERBOSE > +# @DEFAULT_UNSET > +# @USER_VARIABLE > +# @DESCRIPTION: > +# If set to a non-empty value, erepl/esed() and wrappers will use diff(1) > +# to display file differences. Recommended for maintainers to easily > +# confirm the changes being made. > + > +# @FUNCTION: esed > +# @USAGE: [-E|-r|-n] [-e ]... [--] ... > +# @DESCRIPTION: > +# sed(1) wrapper that dies if any of the expressions did not modify any = files. > +# sed's -i/--in-place is forced, -e can be omitted if only one expressio= n, and > +# arguments must be passed in the listed order with files last. Each -e= will > +# be a separate sed(1) call to evaluate changes of each. > +esed() { > + (( ${#} >=3D 2 )) || die "too few arguments for ${_esed_cmd[0]:-${FUNCN= AME[0]}}" > + > + local endopts=3Dfalse args=3D() contents=3D() exps=3D() files=3D() > + local -i i > + for ((i=3D1; i<=3D${#}; i++)); do > + if [[ ${!i} =3D~ ^- ]] && ! ${endopts}; then > + case ${!i} in > + --) endopts=3Dtrue ;; > + -E|-n|-r) args+=3D( ${!i} ) ;; > + -e) > + i+=3D1 > + [[ ${!i} ]] || die "missing argument to -e" > + exps+=3D( "${!i}" ) > + ;; > + *) die "unrecognized option for ${FUNCNAME[0]}" ;; > + esac > + elif (( ! ${#exps[@]} )); then > + exps+=3D( "${!i}" ) # like sed, if no -e, first non-option is exp > + else > + [[ -f ${!i} ]] || die "not a file: ${!i}" Somewhere here might be a good place for the hypothetical: null_free ${!i} || die "file ${!i} contains NULL bytes" > + files+=3D( "${!i}" ) > + contents+=3D( "$(<"${!i}")" ) || die "failed reading: ${!i}" > + fi > + done > + (( ${#files[@]} )) || die "no files in ${FUNCNAME[0]} arguments" > + > + if [[ ${_esed_output} ]]; then > + (( ${#files[@]} =3D=3D 1 )) || die "${_esed_cmd[0]} needs exactly one = input file" > + > + # swap file for output to simplify sequential sed'ing > + cp -- "${files[0]}" "${_esed_output}" || die > + files[0]=3D${_esed_output} > + fi > + > + local changed exp newcontents sed > + for exp in "${exps[@]}"; do > + sed=3D( sed -i "${args[@]}" -e "${exp}" -- "${files[@]}" ) > + [[ ${ESED_VERBOSE} ]] && einfo "${sed[*]}" > + > + "${sed[@]}" + > + changed=3Dfalse > + for ((i=3D0; i<${#files[@]}; i++)); do > + newcontents=3D$(<"${files[i]}") || die "failed reading: ${files[i]}" > + > + if [[ ${contents[i]} !=3D "${newcontents}" ]]; then > + changed=3Dtrue > + > + [[ ${ESED_VERBOSE} ]] || break > + > + diff -u --color --label=3D"${files[i]}"{,} \ > + <(echo "${contents[i]}") <(echo "${newcontents}") > + fi > + done > + > + ${changed} \ > + || die "no-op: ${FUNCNAME[0]} ${*}${_esed_cmd[0]:+ (from: ${_esed_cmd= [*]})}" > + done > +} > + > +# @FUNCTION: enewsed > +# @USAGE: ... > +# @DESCRIPTION: > +# esed() wrapper to save the result to . Intended to repla= ce > +# ``sed ... input > output`` given esed() does not support stdin/out. > +enewsed() { > + local _esed_cmd=3D( ${FUNCNAME[0]} "${@}" ) > + local _esed_output=3D${*: -1:1} > + esed "${@:1:${#}-1}" > +} > + > +# @FUNCTION: erepl > +# @USAGE: ... > +# @DESCRIPTION: > +# Do basic bash-only ``${//""/}`` per-line > +# replacement in files(s). Dies if no changes were made. Suggested over > +# sed(1) where possible for simplicity and avoiding issues with delimite= rs. > +# Warning: erepl-based functions strip null bytes, use for text only. > +erepl() { > + local _esed_cmd=3D( ${FUNCNAME[0]} "${@}" ) > + ereplp '.*' "${@}" > +} > + > +# @FUNCTION: enewrepl > +# @USAGE: ... > +# @DESCRIPTION: > +# erepl() wrapper to save the result to . > +enewrepl() { > + local _esed_cmd=3D( ${FUNCNAME[0]} "${@}" ) > + local _esed_output=3D${*: -1:1} > + ereplp '.*' "${@:1:${#}-1}" > +} > + > +# @FUNCTION: erepld > +# @USAGE: ... > +# @DESCRIPTION: > +# Deletes lines in file(s) matching ``[[ ${line} =3D~ ]]``. > +erepld() { > + local _esed_cmd=3D( ${FUNCNAME[0]} "${@}" ) > + local _esed_repld=3D1 > + ereplp "${@}" > +} > + > +# @FUNCTION: enewrepld > +# @USAGE: ... > +# @DESCRIPTION: > +# erepl() wrapper to save the result to . > +enewrepld() { > + local _esed_cmd=3D( ${FUNCNAME[0]} "${@}" ) > + local _esed_output=3D${*: -1:1} > + erepld "${@:1:${#}-1}" > +} > + > +# @FUNCTION: ereplp > +# @USAGE: ... > +# @DESCRIPTION: > +# Like erepl() but replaces only on ``[[ ${line} =3D~ ]]``. > +ereplp() { > + local -i argsmin=3D$(( ${_esed_repld:-0}=3D=3D1?2:4 )) > + (( ${#} >=3D argsmin )) \ > + || die "too few arguments for ${_esed_cmd[0]:-${FUNCNAME[0]}}" > + > + [[ ! ${_esed_output} || ${#} -le ${argsmin} ]] \ > + || die "${_esed_cmd[0]} needs exactly one input file" > + > + local contents changed=3Dfalse file line newcontents > + for file in "${@:argsmin}"; do A good place to put the test might be here: null_free ${file} || die "file ${file} contains NULL bytes" > + mapfile contents < "${file}" || die > + newcontents=3D() > + > + for line in "${contents[@]}"; do > + if [[ ${line} =3D~ ${1} ]]; then > + if [[ ${_esed_repld} =3D=3D 1 ]]; then > + changed=3Dtrue > + else > + newcontents+=3D( "${line//"${2}"/${3}}" ) > + [[ ${line} !=3D "${newcontents[-1]}" ]] && changed=3Dtrue > + fi > + else > + newcontents+=3D( "${line}" ) > + fi > + done > + printf %s "${newcontents[@]}" > "${_esed_output:-${file}}" || die > + > + if [[ ${ESED_VERBOSE} ]]; then > + einfo "${FUNCNAME[0]} ${*:1:argsmin-1} ${file} ${_esed_output:+(to ${= _esed_output})}" > + diff -u --color --label=3D"${file}" --label=3D"${_esed_output:-${file= }}" \ > + <(printf %s "${contents[@]}") <(printf %s "${newcontents[@]}") > + fi > + done > + > + ${changed} || die "no-op: ${_esed_cmd[*]:-${FUNCNAME[0]} ${*}}" > +} > + > +# @FUNCTION: enewreplp > +# @USAGE: ... > +# @DESCRIPTION: > +# ereplp() wrapper to save the result to . > +enewreplp() { > + local _esed_cmd=3D( ${FUNCNAME[0]} "${@}" ) > + local _esed_output=3D${*: -1:1} > + ereplp "${@:1:${#}-1}" > +} > + > +# @FUNCTION: efind > +# @USAGE: ... -erun ... > +# @DESCRIPTION: > +# find(1) wrapper that dies if no files were found. can be a = shell > +# function, e.g. ``efind ... -erun erepl /usr /opt``. -print0 is added = to > +# find arguments, and found files to end of arguments (``{} +`` is unuse= d). > +# Found files must not exceed args limits. Use is discouraged if files = add > +# up to a large total size (50+MB), notably with slower erepl/esed(). S= hell > +# functions called this way are expected to ``|| die`` themselves on err= or. > +efind() { > + (( ${#} >=3D 3 )) || die "too few arguments for ${FUNCNAME[0]}" > + > + local _esed_cmd=3D( ${FUNCNAME[0]} "${@}" ) > + > + local find=3D( find ) > + while (( ${#} )); do > + if [[ ${1} =3D~ -erun ]]; then > + shift > + break > + fi > + find+=3D( "${1}" ) > + shift > + done > + find+=3D( -print0 ) > + > + local files > + mapfile -d '' -t files < <("${find[@]}" || die "failed: ${find[*]}") > + > + (( ${#files[@]} )) || die "no files from: ${find[*]}" > + (( ${#} )) || die "missing -erun arguments for ${FUNCNAME[0]}" > + > + # skip `|| die` for shell functions (should be handled internally) > + if declare -f "${1}" >/dev/null; then > + "${@}" "${files[@]}" > + else > + "${@}" "${files[@]}" || die "failed: ${*} ${files[*]}" > + fi > +} > + > +fi > --=20 > 2.35.1 >=20 >=20 - Oskari --HDCAa5PQRNJEDlPG Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQQfOU+JeXjo4uxN6vCp8he9GGIfEQUCYp0HOgAKCRCp8he9GGIf EUeJAP9alfb2GzKDv47f2ae8JEZeo/J5fdcFTAur02YXXkL3egD/UDeCbD+hJ72t uwDa0kZ2C7xnfDkrT8/zsqCd31Hd3w8= =l3Tb -----END PGP SIGNATURE----- --HDCAa5PQRNJEDlPG--