1
1
#! /usr/bin/env bash
2
2
3
- # espmerge .sh: read esphome yaml and output merged esphome yaml.
3
+ # idmerge .sh: read esphome yaml and output merged esphome yaml.
4
4
5
5
# Common blocks are merged backwards in the output by referencing tags
6
6
# specified in "id: <tag>" yaml lines. The first common block to
10
10
# as common blocks so it is possible to merge into an array element
11
11
# as long as it declares itself using "id: <tag>"
12
12
13
- # Usage: espmerge .sh <input.yaml >output.yaml
13
+ # Usage: idmerge .sh -id <idTag> <input.yaml >output.yaml
14
14
15
- # After running espmerge .sh, it is advised to postprocess the yaml with
15
+ # After running idmerge .sh, it is advised to postprocess the yaml with
16
16
# awk '/^[[:alnum:]_]/{print "---"} | yq '... comments=""' esphome.yaml
17
17
# before piping it into yq to merge sections using:
18
18
21
21
# Maarten's Law: "Everything is more complicated than it should be."
22
22
# For proof of Maarten's Law, uncomment the next line and run this script.
23
23
24
- # more espmerge .sh; exit
24
+ # more idmerge .sh; exit
25
25
26
26
# Require all variables to be declared - to catch variable typos early.
27
27
28
+ declare -r me=${0##*/ } # basename of this script
29
+ declare -r usage=" $me : read yaml and output merged yaml using an id tag.
30
+
31
+ Usage: $me : [-q] [-p] [-m] [-h] [-t tag] [-o outfile] <file.yaml>\n
32
+ -t|--tag\tItem tag that uniquely names what to merge. Defaults to " id" .
33
+ -o|--outfile\tFile to write to, else stdout.
34
+ -q|--quiet\tDo not output operational feedback to stdout.
35
+ -p|--parseinfo\tOutput input parser debug info to stderr
36
+ -m|--mergeinfo\tOutput merge debug info to stderr
37
+ -h|--help\tOutput this help.
38
+
39
+ <file.yaml>\tThe yaml file to merge, else stdin.
40
+
41
+ This script is from git repo github.com/maartenSXM/cpphash.
42
+ Note: This script does not vet arguments securely. Do not setuid or host it.
43
+ "
44
+
28
45
set -e
29
46
set -o nounset
30
47
set -o pipefail
31
48
32
49
if (( $BASH_VERSINFO < 4 )) ; then
33
- echo " $0 *** bash must be at version 4 or greater. Install it. ***" >&2
50
+ echo " $me : *** bash must be at version 4 or greater. Install it. ***" >&2
34
51
exit -1
35
52
fi
36
53
37
- declare -i chatty=1 # some operational feedback to stdout
38
- declare -i dbgParse=0 # input parser debug output to stderr
39
- declare -i dbgMerge=0 # merge engine debug output to stderr
54
+ declare outfile=/dev/stdout # output yaml file
55
+ declare idtoken=" id" # id tag for yaml item unique keys
56
+ declare -i chatinfo=1 # some operational feedback to stdout
57
+ declare -i parseinfo=0 # input parser debug output to stderr
58
+ declare -i mergeinfo=0 # merge engine debug output to stderr
59
+
60
+ while [[ $# > 0 ]]
61
+ do
62
+ case $1 in
63
+ -h|--help) printf " $usage " ; exit 0;;
64
+ -o|--out) outfile=" $2 " ; shift 2;;
65
+ -t|--tag) idtoken=" $2 " ; shift 2;;
66
+ -q|--quiet) chatinfo=0; shift 1;;
67
+ -p|--parseinfo) parseinfo=1; shift 1;;
68
+ -m|--mergeinfo) mergeinfo=1; shift 1;;
69
+ * ) break
70
+ esac
71
+ done
72
+
73
+ if [ -z " ${infile:- } " ]; then
74
+ declare -r infile=/dev/stdin
75
+ else
76
+ declare -r infile=" $1 "
77
+ fi
40
78
41
79
# Tips for debugging:
42
80
@@ -45,17 +83,16 @@ declare -i dbgMerge=0 # merge engine debug output to stderr
45
83
46
84
# 2. For large yaml test files, it is helpful to redirect stdout to
47
85
# /dev/null and then redirect stderr to more. That is done
48
- # like this: ./espmerge .sh bigtest.yaml 2>&1 >/dev/null | more
86
+ # like this: ./idmerge .sh bigtest.yaml 2>&1 >/dev/null | more
49
87
# and that works because the shell does redirections from right to left.
50
88
51
- # 3. dbgParse can be set to one to see how blocks are found.
52
- # dbgMerge can be set to one to see how blocks are stored and output.
89
+ # 3. parseinfo can be set to one to see how blocks are found.
90
+ # mergeinfo can be set to one to see how blocks are stored and output.
53
91
54
92
# FWIW, this script does not use any subprocesses and runs entirely in bash.
55
93
56
94
# Globals
57
95
58
- declare -r me=${0##*/ } # basename of this script
59
96
declare retval # optional return value of a function
60
97
declare -i nlines=0 # number of yaml lines input
61
98
declare -i _nlines=0 # number of digits in nlines variable (e.g 2 for 64)
@@ -91,7 +128,7 @@ declare -a -i block_idline # block "id:" line if it has one, else zero
91
128
92
129
declare -r newdoc=" ---"
93
130
declare -r is_array=' ^[[:blank:]]*- ([[:alnum:]: _]+)[[:blank:]]*$'
94
- declare -r is_id=' ^[[:blank:]]+id :[[:blank:]]+([[:alnum:]_]+)[[:blank:]]*$ '
131
+ declare -r is_id=" ^[[:blank:]]+$idtoken :[[:blank:]]+([[:alnum:]_]+)[[:blank:]]*\$ "
95
132
declare -r is_key=' ^[[:blank:]]*([[:alnum:]_]+):[[:blank:]]*$'
96
133
declare -r is_mapkey=' ^([[:alnum:]_]+:)[[:blank:]]*$|^---$'
97
134
declare -r is_comment=' ^[[:blank:]]*#.*$'
@@ -121,7 +158,7 @@ read_lines () {
121
158
skip[$n ]=1
122
159
fi
123
160
n=n+1
124
- done
161
+ done < $infile
125
162
126
163
nlines=$n # number of lines of yaml
127
164
_nlines=${# nlines} # number of digits in nlines
@@ -140,21 +177,31 @@ _write_lines () {
140
177
declare -i b # block index
141
178
declare -i p # column indentation alignment for merge comment
142
179
180
+ # if the line has been flagged to not be output, skip it.
181
+ # This can happen when it is a merge block and the leading lines
182
+ # are duplicates.ince the block being merged into has those lines also.
183
+ # It can also occur for lines in the merge block itself that have
184
+ # already been output since they moved earlier.
185
+
143
186
if (( skip[$n ] == 1 )) ; then
144
187
return ;
145
188
fi
146
189
147
190
# Output line $n.
148
191
192
+ # oldline is 0 when the line is being simply output and not moving.
193
+
149
194
if (( oldline== 0 )) ; then
150
- echo " ${lines[$n]} "
195
+ echo " ${lines[$n]} " > $outfile
151
196
else
152
197
153
198
# Round comment column to next highest x10 after yaml line, min 40.
154
199
155
200
p=(${# lines[$n]} /10+1)* 10
156
201
(( p<= 40 )) && p=40
157
- printf " %-*s # espmerge.sh: was line $(( oldline+ 1 )) \n" $p " ${lines[$n]} "
202
+ printf \
203
+ " %-*s # idmerge.sh: was line $(( oldline+ 1 )) \n" $p " ${lines[$n]} " \
204
+ > $outfile
158
205
fi
159
206
160
207
skip[$n ]=1 # a line can only be output once
@@ -209,7 +256,7 @@ _record_work () {
209
256
# If the key has never been seen, save it and return.
210
257
211
258
if [ -z " ${key_block[$key]:- } " ]; then
212
- (( dbgMerge )) && echo \
259
+ (( mergeinfo )) && echo \
213
260
" *** Saved block $(( block+ 1 )) $(( from+ 1 )) -$(( to+ 1 )) ($id )" >&2
214
261
key_block[$key ]=$block
215
262
return
@@ -220,22 +267,22 @@ _record_work () {
220
267
kb=${key_block[$key]}
221
268
dest=${block_to[$kb]}
222
269
223
- (( dbgMerge )) && echo \
270
+ (( mergeinfo )) && echo \
224
271
" *** Found block $(( kb+ 1 )) for block $(( block+ 1 )) $(( from+ 1 )) -$(( to+ 1 )) ($id )" >&2
225
272
226
- (( chatty )) && echo \
227
- " $me : Moving lines $(( from+ 1 )) -$(( to+ 1 )) to line $(( dest+ 1 )) (id : $id )" >&2
273
+ (( chatinfo )) && echo \
274
+ " $me : Moving lines $(( from+ 1 )) -$(( to+ 1 )) to line $(( dest+ 1 )) ($idtoken : $id )" >&2
228
275
229
276
# Skip the id: line since it is in the block being merged into.
230
277
231
278
skip[$idline ]=1
232
279
233
- (( dbgMerge )) && echo " *** Skipping id line $(( idline+ 1 )) (id : $id )" >&2
280
+ (( mergeinfo )) && echo " *** Skipping id line $(( idline+ 1 )) ($idtoken : $id )" >&2
234
281
235
282
# If we are merging an array elemeny, we will skip the from line too.
236
283
237
284
if [[ " ${lines[$from]} " =~ $is_array ]]; then
238
- (( dbgMerge )) && (( skip[from]== 0 )) && echo \
285
+ (( mergeinfo )) && (( skip[from]== 0 )) && echo \
239
286
" *** Skipping array line $(( from+ 1 )) " >&2
240
287
skip[$from ]=1
241
288
fi
@@ -250,7 +297,7 @@ _record_work () {
250
297
n=from-1
251
298
252
299
while (( n>= 0 )) ; do
253
- if (( dbgMerge == 1 && skip[n]== 0 )) ; then
300
+ if (( mergeinfo == 1 && skip[n]== 0 )) ; then
254
301
if [[ " ${lines[$n]} " =~ $is_key ]]; then
255
302
id=" ${BASH_REMATCH[1]} "
256
303
echo " *** Skipping key line $(( n+ 1 )) ($id )" >&2
@@ -266,7 +313,7 @@ _record_work () {
266
313
done
267
314
}
268
315
269
- # Debug function for when dbgParse is set to 1.
316
+ # Debug function for when parseinfo is set to 1.
270
317
271
318
dump_common_block () {
272
319
declare -r -i b=$1
@@ -403,7 +450,7 @@ _find_blocks() {
403
450
404
451
_get_indentation " $first " ; blockSpaces=" $retval "
405
452
406
- (( dbgParse )) && echo \
453
+ (( parseinfo )) && echo \
407
454
" *** Start _find_block $(( first+ 1 )) $(( last+ 1 )) \" $key \" \" $idTag \" " >&2
408
455
409
456
n=$first
@@ -422,7 +469,7 @@ _find_blocks() {
422
469
# If indentation goes left, end the current block.
423
470
424
471
if [[ " $lineSpaces " < " $blockSpaces " ]]; then
425
- (( dbgParse )) && echo " ***: Indentation went left"
472
+ (( parseinfo )) && echo " ***: Indentation went left" >&2
426
473
break
427
474
fi
428
475
@@ -432,15 +479,15 @@ _find_blocks() {
432
479
queue_first[$nqueue ]=$n
433
480
# if mapkey, use default tag "id: <null>". Else use the previous line
434
481
if [[ " $blockSpaces " == " " ]]; then
435
- queue_idTag[$nqueue ]=" id : <none>"
482
+ queue_idTag[$nqueue ]=" $idtoken : <none>"
436
483
else
437
484
_get_prev_non_skip $n ; prev=$retval
438
485
queue_idTag[$nqueue ]=" ${lines[$prev]} "
439
486
fi
440
487
_get_block_to $n 1; n=$retval
441
488
queue_last[$nqueue ]=$n
442
489
443
- (( dbgParse )) && echo \
490
+ (( parseinfo )) && echo \
444
491
" *** Indentation went right $(( queue_first[$nqueue ]+ 1 )) $(( n+ 1 )) " >&2
445
492
446
493
nqueue=nqueue+1
@@ -462,7 +509,7 @@ _find_blocks() {
462
509
_get_block_to $n 0; n=$retval
463
510
queue_last[$nqueue ]=$n
464
511
465
- (( dbgParse )) && echo \
512
+ (( parseinfo )) && echo \
466
513
" *** Queued block $(( queue_first[$nqueue ]+ 1 )) $(( n+ 1 )) " >&2
467
514
468
515
nqueue=nqueue+1
@@ -475,11 +522,11 @@ _find_blocks() {
475
522
476
523
if [[ " ${lines[$n]} " =~ $is_id ]]; then
477
524
idline=$n
478
- (( dbgParse )) && echo " *** Parsed ${BASH_REMATCH[1]} id at $(( n+ 1 )) " >&2
525
+ (( parseinfo )) && echo " *** Parsed ${BASH_REMATCH[1]} id at $(( n+ 1 )) " >&2
479
526
idTag=" ${BASH_REMATCH[1]} "
480
527
fi
481
528
482
- (( dbgParse )) && echo " *** Queued line $(( n+ 1 )) " >&2
529
+ (( parseinfo )) && echo " *** Queued line $(( n+ 1 )) " >&2
483
530
484
531
_get_next_non_skip $n ; n=$retval
485
532
done
@@ -495,7 +542,7 @@ _find_blocks() {
495
542
block_idline[$nblocks ]=idline
496
543
block_key[$nblocks ]=" $key "
497
544
498
- (( dbgParse )) && \
545
+ (( parseinfo )) && \
499
546
dump_common_block $nblocks
500
547
501
548
nblocks=nblocks+1
@@ -508,7 +555,7 @@ _find_blocks() {
508
555
" $key " " ${queue_idTag[$q]} "
509
556
done
510
557
511
- (( dbgParse )) && echo \
558
+ (( parseinfo )) && echo \
512
559
" *** End _find_block $(( first+ 1 )) $(( last+ 1 )) \" $key \" \" ${idTag} \" " >&2
513
560
514
561
retval=$n
@@ -530,7 +577,7 @@ find_blocks() {
530
577
_find_blocks $n $(( nlines- 1 )) " $key " " "
531
578
n=$retval
532
579
else
533
- echo " $me : Error: line $(( n+ 1 )) is not a map key: ${lines[$n]} "
580
+ echo " $me : Error: line $(( n+ 1 )) is not a map key: ${lines[$n]} " >&2
534
581
exit -1
535
582
fi
536
583
done
0 commit comments