1 |
Signed-off-by: Ionen Wolkens <ionen@g.o> |
2 |
--- |
3 |
eclass/esed.eclass | 201 +++++++++++++++++++++++++++++++++++++++++++++ |
4 |
1 file changed, 201 insertions(+) |
5 |
create mode 100644 eclass/esed.eclass |
6 |
|
7 |
diff --git a/eclass/esed.eclass b/eclass/esed.eclass |
8 |
new file mode 100644 |
9 |
index 00000000000..f327c3bbdf4 |
10 |
--- /dev/null |
11 |
+++ b/eclass/esed.eclass |
12 |
@@ -0,0 +1,201 @@ |
13 |
+# Copyright 2022 Gentoo Authors |
14 |
+# Distributed under the terms of the GNU General Public License v2 |
15 |
+ |
16 |
+# @ECLASS: esed.eclass |
17 |
+# @MAINTAINER: |
18 |
+# Ionen Wolkens <ionen@g.o> |
19 |
+# @AUTHOR: |
20 |
+# Ionen Wolkens <ionen@g.o> |
21 |
+# @SUPPORTED_EAPIS: 8 |
22 |
+# @BLURB: sed(1) wrappers that die if expressions did not modify any files |
23 |
+# @EXAMPLE: |
24 |
+# |
25 |
+# @CODE |
26 |
+# esed 's/a/b/' src/file.c # -i is default, dies if 'a' does not become 'b' |
27 |
+# |
28 |
+# enewsed 's/a/b/' project.pc.in "${T}"/project.pc # stdin/out not supported |
29 |
+# |
30 |
+# esedfind . -type f -name '*.c' -esed 's/a/b/' # dies if zero files changed |
31 |
+# |
32 |
+# local esedexps=( |
33 |
+# # dies if /any/ of these did nothing, -e 's/a/b/' -e 's/c/d/' would not |
34 |
+# 's/a/b/' |
35 |
+# 's/c/d/' # bug 000000 |
36 |
+# # use quotes around "$(use..)" to avoid word splitting/globs, won't run |
37 |
+# # sed(1) for empty elements (i.e. if USE is disabled) |
38 |
+# "$(usev fnord "s/foo bar/${baz}/")" |
39 |
+# ) |
40 |
+# esed Makefile lib/Makefile # unsets esedexps so it's not re-used |
41 |
+# |
42 |
+# use prefix && esed "s|^prefix=|&${EPREFIX}|" project.pc # deterministic |
43 |
+# @CODE |
44 |
+# |
45 |
+# Migration note: be wary of non-deterministic esed() involving variables, |
46 |
+# e.g. s|lib|$(get_libdir)|, s|-O3|${CFLAGS}|, and the above ${EPREFIX} one. |
47 |
+# esed() dies if these do nothing, like libdir being 'lib' on x86. Either |
48 |
+# verify, keep sed(1), or ensure a change (extra space, @placeholders@). |
49 |
+ |
50 |
+case ${EAPI} in |
51 |
+ 8) ;; |
52 |
+ *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; |
53 |
+esac |
54 |
+ |
55 |
+if [[ ! -v _ESED_ECLASS ]]; then |
56 |
+_ESED_ECLASS=1 |
57 |
+ |
58 |
+# @ECLASS_VARIABLE: ESED_VERBOSE |
59 |
+# @DEFAULT_UNSET |
60 |
+# @USER_VARIABLE |
61 |
+# @DESCRIPTION: |
62 |
+# If set to a non-empty value, esed() and its wrappers will use diff(1) |
63 |
+# if available to display file differences. |
64 |
+ |
65 |
+# @VARIABLE: esedexps |
66 |
+# @DEFAULT_UNSET |
67 |
+# @DESCRIPTION: |
68 |
+# Bash array that can optionally contain sed expressions to use sequencially |
69 |
+# on separate sed calls when using esed() and its wrappers. Allows inspection |
70 |
+# of modifications per-expressions. Unset after use so it's not used in |
71 |
+# subsequent calls. Will not run sed(1) for empty array elements. |
72 |
+ |
73 |
+# @FUNCTION: esed |
74 |
+# @USAGE: <sed-argument>... |
75 |
+# @DESCRIPTION: |
76 |
+# sed(1) wrapper that dies if the expression(s) did not modify any files. |
77 |
+# sed's -i/--in-place is forced, and so stdin/out cannot be used. |
78 |
+esed() { |
79 |
+ local -i i |
80 |
+ |
81 |
+ if [[ ${esedexps@a} =~ a ]]; then |
82 |
+ # expression must be before -- but after the rest for e.g. -E to work |
83 |
+ local -i pos |
84 |
+ for ((pos=1; pos<=${#}; pos++)); do |
85 |
+ [[ ${!pos} == -- ]] && break |
86 |
+ done |
87 |
+ |
88 |
+ for ((i=0; i<${#esedexps[@]}; i++)); do |
89 |
+ [[ ${esedexps[i]} ]] && |
90 |
+ esedexps= esed "${@:1:pos-1}" -e "${esedexps[i]}" "${@:pos}" |
91 |
+ done |
92 |
+ |
93 |
+ unset esedexps |
94 |
+ return 0 |
95 |
+ fi |
96 |
+ |
97 |
+ # Roughly attempt to find files in arguments by checking if it's a |
98 |
+ # readable file (aka s/// is not a file) and does not start with - |
99 |
+ # (unless after --), then store contents for comparing after sed. |
100 |
+ local contents=() endopts files=() |
101 |
+ for ((i=1; i<=${#}; i++)); do |
102 |
+ if [[ ${!i} == -- && ! -v endopts ]]; then |
103 |
+ endopts=1 |
104 |
+ elif [[ ${!i} =~ ^(-i|--in-place)$ && ! -v endopts ]]; then |
105 |
+ # detect rushed sed -i -> esed -i, -i also silently breaks enewsed |
106 |
+ die "passing ${!i} to ${FUNCNAME[0]} is invalid" |
107 |
+ elif [[ ${!i} =~ ^(-f|--file)$ && ! -v endopts ]]; then |
108 |
+ i+=1 # ignore script files |
109 |
+ elif [[ ( ${!i} != -* || -v endopts ) && -f ${!i} && -r ${!i} ]]; then |
110 |
+ files+=( "${!i}" ) |
111 |
+ |
112 |
+ # 2>/dev/null to silence null byte warnings if sed binary files |
113 |
+ { contents+=( "$(<"${!i}")" ); } 2>/dev/null \ |
114 |
+ || die "failed to read: ${!i}" |
115 |
+ fi |
116 |
+ done |
117 |
+ (( ${#files[@]} )) || die "no readable files found from '${*}' arguments" |
118 |
+ |
119 |
+ local verbose |
120 |
+ [[ ${ESED_VERBOSE} ]] && type diff &>/dev/null && verbose=1 |
121 |
+ |
122 |
+ local changed newcontents |
123 |
+ if [[ -v _esed_output ]]; then |
124 |
+ [[ -v verbose ]] && |
125 |
+ einfo "${FUNCNAME[0]}: sed ${*} > ${_esed_output} ..." |
126 |
+ |
127 |
+ sed "${@}" > "${_esed_output}" \ |
128 |
+ || die "failed to run: sed ${*} > ${_esed_output}" |
129 |
+ |
130 |
+ { newcontents=$(<"${_esed_output}"); } 2>/dev/null \ |
131 |
+ || die "failed to read: ${_esed_output}" |
132 |
+ |
133 |
+ local IFS=$'\n' # sed concats with newline even if none at EOF |
134 |
+ contents=${contents[*]} |
135 |
+ unset IFS |
136 |
+ |
137 |
+ if [[ ${contents} != "${newcontents}" ]]; then |
138 |
+ changed=1 |
139 |
+ |
140 |
+ [[ -v verbose ]] && |
141 |
+ diff -u --color --label="${files[*]}" --label="${_esed_output}" \ |
142 |
+ <(echo "${contents}") <(echo "${newcontents}") |
143 |
+ fi |
144 |
+ else |
145 |
+ [[ -v verbose ]] && einfo "${FUNCNAME[0]}: sed -i ${*} ..." |
146 |
+ |
147 |
+ sed -i "${@}" || die "failed to run: sed -i ${*}" |
148 |
+ |
149 |
+ for ((i=0; i<${#files[@]}; i++)); do |
150 |
+ { newcontents=$(<"${files[i]}"); } 2>/dev/null \ |
151 |
+ || die "failed to read: ${files[i]}" |
152 |
+ |
153 |
+ if [[ ${contents[i]} != "${newcontents}" ]]; then |
154 |
+ changed=1 |
155 |
+ |
156 |
+ [[ -v verbose ]] || break |
157 |
+ |
158 |
+ diff -u --color --label="${files[i]}"{,} \ |
159 |
+ <(echo "${contents[i]}") <(echo "${newcontents}") |
160 |
+ fi |
161 |
+ done |
162 |
+ fi |
163 |
+ |
164 |
+ [[ -v changed ]] \ |
165 |
+ || die "no-op: ${FUNCNAME[0]} ${*}${_esed_command:+ (from: ${_esed_command})}" |
166 |
+} |
167 |
+ |
168 |
+# @FUNCTION: enewsed |
169 |
+# @USAGE: <esed-argument>... <output-file> |
170 |
+# @DESCRIPTION: |
171 |
+# esed() wrapper to save the result to <output-file>. Same as using |
172 |
+# `sed ... input > output` given esed() does not support stdin/out. |
173 |
+enewsed() { |
174 |
+ local _esed_command="${FUNCNAME[0]} ${*}" |
175 |
+ local _esed_output=${*: -1:1} |
176 |
+ esed "${@:1:${#}-1}" |
177 |
+} |
178 |
+ |
179 |
+# @FUNCTION: esedfind |
180 |
+# @USAGE: <find-argument>... [-esed [<esed-argument>...]] |
181 |
+# @DESCRIPTION: |
182 |
+# esed() wrapper to ease use with find(1) given -exec wouldn't see a shell |
183 |
+# function. Will die if find(1) found no files, or if not a single file |
184 |
+# was changed. -esed is optional with the esedexps=( .. ) array. -print0 |
185 |
+# will be appended to <find-arguments>. |
186 |
+# |
187 |
+# Requires that the found list not exceed args limit for file changes to be |
188 |
+# evaluated together in a single esed() call. Use is discouraged if modifying |
189 |
+# files with a large total size (50+MB), as they will be loaded in memory |
190 |
+# and compared ineffectively by the shell. |
191 |
+esedfind() { |
192 |
+ local _esed_command="${FUNCNAME[0]} ${*}" |
193 |
+ |
194 |
+ local find=( find ) |
195 |
+ while (( ${#} )); do |
196 |
+ if [[ ${1} == -esed ]]; then |
197 |
+ shift |
198 |
+ break |
199 |
+ fi |
200 |
+ find+=( "${1}" ) |
201 |
+ shift |
202 |
+ done |
203 |
+ find+=( -print0 ) |
204 |
+ |
205 |
+ local files |
206 |
+ mapfile -d '' -t files < <("${find[@]}" || die "failed to run: ${find[*]}") |
207 |
+ |
208 |
+ (( ${#files[@]} )) || die "no files found from: ${find[*]}" |
209 |
+ |
210 |
+ esed "${@}" -- "${files[@]}" |
211 |
+} |
212 |
+ |
213 |
+fi |
214 |
-- |
215 |
2.35.1 |