This repository was archived by the owner on Jun 6, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmo
executable file
·726 lines (596 loc) · 21.5 KB
/
mo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
#!/bin/bash
#
# Mo is a mustache template rendering software written in bash. It inserts
# environment variables into templates.
#
# Learn more about mustache templates at https://mustache.github.io/
#
# Mo is under a MIT style licence with an additional non-advertising clause.
# See LICENSE.md for the full text.
#
# This is open source! Please feel free to contribute.
#
# https://github.com/tests-always-included/mo
# Scan content until the right end tag is found. Returns an array with the
# following members:
# [0] = Content before end tag
# [1] = End tag (complete tag)
# [2] = Content after end tag
#
# Everything using this function uses the "standalone tags" logic.
#
# Parameters:
# $1: Where to store the array
# $2: Content
# $3: Name of end tag
# $4: If -z, do standalone tag processing before finishing
mustache-find-end-tag() {
local CONTENT SCANNED
# Find open tags
SCANNED=""
mustache-split CONTENT "$2" '{{' '}}'
while [[ "${#CONTENT[@]}" -gt 1 ]]; do
mustache-trim-whitespace TAG "${CONTENT[1]}"
# Restore CONTENT[1] before we start using it
CONTENT[1]='{{'"${CONTENT[1]}"'}}'
case $TAG in
'#'* | '^'*)
# Start another block
SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
mustache-trim-whitespace TAG "${TAG:1}"
mustache-find-end-tag CONTENT "${CONTENT[2]}" "$TAG" "loop"
SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
CONTENT=${CONTENT[2]}
;;
'/'*)
# End a block - could be ours
mustache-trim-whitespace TAG "${TAG:1}"
SCANNED="$SCANNED${CONTENT[0]}"
if [[ "$TAG" == "$3" ]]; then
# Found our end tag
if [[ -z "$4" ]] && mustache-is-standalone STANDALONE_BYTES "$SCANNED" "${CONTENT[2]}" true; then
# This is also a standalone tag - clean up whitespace
# and move those whitespace bytes to the "tag" element
STANDALONE_BYTES=( $STANDALONE_BYTES )
CONTENT[1]="${SCANNED:${STANDALONE_BYTES[0]}}${CONTENT[1]}${CONTENT[2]:0:${STANDALONE_BYTES[1]}}"
SCANNED="${SCANNED:0:${STANDALONE_BYTES[0]}}"
CONTENT[2]="${CONTENT[2]:${STANDALONE_BYTES[1]}}"
fi
local "$1" && mustache-indirect-array "$1" "$SCANNED" "${CONTENT[1]}" "${CONTENT[2]}"
return 0
fi
SCANNED="$SCANNED${CONTENT[1]}"
CONTENT=${CONTENT[2]}
;;
*)
# Ignore all other tags
SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
CONTENT=${CONTENT[2]}
;;
esac
mustache-split CONTENT "$CONTENT" '{{' '}}'
done
# Did not find our closing tag
SCANNED="$SCANNED${CONTENT[0]}"
local "$1" && mustache-indirect-array "$1" "${SCANNED}" "" ""
}
# Find the first index of a substring
#
# Parameters:
# $1: Destination variable
# $2: Haystack
# $3: Needle
mustache-find-string() {
local POS STRING
STRING=${2%%$3*}
[[ "$STRING" == "$2" ]] && POS=-1 || POS=${#STRING}
local "$1" && mustache-indirect "$1" $POS
}
# Return a dotted name based on current context and target name
#
# Parameters:
# $1: Target variable to store results
# $2: Context name
# $3: Desired variable name
mustache-full-tag-name() {
if [[ -z "$2" ]]; then
local "$1" && mustache-indirect "$1" "$3"
else
local "$1" && mustache-indirect "$1" "${2}.${3}"
fi
}
# Return the content to parse. Can be a list of partials for files or
# the content from stdin.
#
# Parameters:
# $1: Variable name to assign this content back as
# $2-*: File names (optional)
mustache-get-content() {
local CONTENT FILENAME TARGET
TARGET=$1
shift
if [[ "${#@}" -gt 0 ]]; then
CONTENT=""
for FILENAME in "$@"; do
# This is so relative paths work from inside template files
CONTENT="$CONTENT"'{{>'"$FILENAME"'}}'
done
else
mustache-load-file CONTENT /dev/stdin
fi
local "$TARGET" && mustache-indirect "$TARGET" "$CONTENT"
}
# Indent a string, placing the indent at the beginning of every
# line that has any content.
#
# Parameters:
# $1: Name of destination variable to get an array of lines
# $2: The indent string
# $3: The string to reindent
mustache-indent-lines() {
local CONTENT FRAGMENT LEN POS_N POS_R RESULT TRIMMED
RESULT=""
LEN=$((${#3} - 1))
CONTENT="${3:0:$LEN}" # Remove newline and dot from workaround - in mustache-partial
if [ -z "$2" ]; then
local "$1" && mustache-indirect "$1" "$CONTENT"
return 0
fi
mustache-find-string POS_N "$CONTENT" $'\n'
mustache-find-string POS_R "$CONTENT" $'\r'
while [[ "$POS_N" -gt -1 ]] || [[ "$POS_R" -gt -1 ]]; do
if [[ "$POS_N" -gt -1 ]]; then
FRAGMENT="${CONTENT:0:$POS_N + 1}"
CONTENT=${CONTENT:$POS_N + 1}
else
FRAGMENT="${CONTENT:0:$POS_R + 1}"
CONTENT=${CONTENT:$POS_R + 1}
fi
mustache-trim-chars TRIMMED "$FRAGMENT" false true " " $'\t' $'\n' $'\r'
if [ ! -z "$TRIMMED" ]; then
FRAGMENT="$2$FRAGMENT"
fi
RESULT="$RESULT$FRAGMENT"
mustache-find-string POS_N "$CONTENT" $'\n'
mustache-find-string POS_R "$CONTENT" $'\r'
done
mustache-trim-chars TRIMMED "$CONTENT" false true " " $'\t'
if [ ! -z "$TRIMMED" ]; then
CONTENT="$2$CONTENT"
fi
RESULT="$RESULT$CONTENT"
local "$1" && mustache-indirect "$1" "$RESULT"
}
# Send a variable up to caller of a function
#
# Parameters:
# $1: Variable name
# $2: Value
mustache-indirect() {
unset -v "$1"
printf -v "$1" '%s' "$2"
}
# Send an array up to caller of a function
#
# Parameters:
# $1: Variable name
# $2-*: Array elements
mustache-indirect-array() {
unset -v "$1"
eval $1=\(\"\${@:2}\"\)
}
# Determine if a given environment variable exists and if it is an array.
#
# Parameters:
# $1: Name of environment variable
#
# Return code:
# 0 if the name is not empty, 1 otherwise
mustache-is-array() {
local MUSTACHE_TEST
MUSTACHE_TEST=$(declare -p "$1" 2>/dev/null) || return 1
[[ "${MUSTACHE_TEST:0:10}" == "declare -a" ]] && return 0
[[ "${MUSTACHE_TEST:0:10}" == "declare -A" ]] && return 0
return 1
}
# Return 0 if the passed name is a function.
#
# Parameters:
# $1: Name to check if it's a function
#
# Return code:
# 0 if the name is a function, 1 otherwise
mustache-is-function() {
local FUNCTIONS NAME
FUNCTIONS=$(declare -F)
FUNCTIONS=( ${FUNCTIONS//declare -f /} )
for NAME in ${FUNCTIONS[@]}; do
if [[ "$NAME" == "$1" ]]; then
return 0
fi
done
return 1
}
# Determine if the tag is a standalone tag based on whitespace before and
# after the tag.
#
# Passes back a string containing two numbers in the format "BEFORE AFTER"
# like "27 10". It indicates the number of bytes remaining in the "before"
# string (27) and the number of bytes to trim in the "after" string (10).
# Useful for string manipulation:
#
# mustache-is-standalone RESULT "$before" "$after" false || return 0
# RESULT_ARRAY=( $RESULT )
# echo "${before:0:${RESULT_ARRAY[0]}}...${after:${RESULT_ARRAY[1]}}"
#
# Parameters:
# $1: Variable to pass data back
# $2: Content before the tag
# $3: Content after the tag
# $4: true/false: is this the beginning of the content?
mustache-is-standalone() {
local AFTER_TRIMMED BEFORE_TRIMMED CHAR
mustache-trim-chars BEFORE_TRIMMED "$2" false true " " $'\t'
mustache-trim-chars AFTER_TRIMMED "$3" true false " " $'\t'
CHAR=$((${#BEFORE_TRIMMED} - 1))
CHAR=${BEFORE_TRIMMED:$CHAR}
if [[ "$CHAR" != $'\n' ]] && [[ "$CHAR" != $'\r' ]]; then
if [[ ! -z "$CHAR" ]] || ! $4; then
return 1;
fi
fi
CHAR=${AFTER_TRIMMED:0:1}
if [[ "$CHAR" != $'\n' ]] && [[ "$CHAR" != $'\r' ]] && [[ ! -z "$CHAR" ]]; then
return 2;
fi
if [[ "$CHAR" == $'\r' ]] && [[ "${AFTER_TRIMMED:1:1}" == $'\n' ]]; then
CHAR="$CHAR"$'\n'
fi
local "$1" && mustache-indirect "$1" "$((${#BEFORE_TRIMMED})) $((${#3} + ${#CHAR} - ${#AFTER_TRIMMED}))"
}
# Join / implode an array
#
# Parameters:
# $1: Variable name to receive the joined content
# $2: Joiner
# $3-$*: Elements to join
mustache-join() {
local JOINER PART RESULT TARGET
TARGET=$1
JOINER=$2
RESULT=$3
shift 3
for PART in "$@"; do
RESULT="$RESULT$JOINER$PART"
done
local "$TARGET" && mustache-indirect "$TARGET" "$RESULT"
}
# Read a file
#
# Parameters:
# $1: Variable name to receive the file's content
# $2: Filename to load
mustache-load-file() {
local CONTENT LEN
# The subshell removes any trailing newlines. We forcibly add
# a dot to the content to preserve all newlines.
# TODO: remove cat and replace with read loop?
CONTENT=$(cat $2; echo '.')
LEN=$((${#CONTENT} - 1))
CONTENT=${CONTENT:0:$LEN} # Remove last dot
local "$1" && mustache-indirect "$1" "$CONTENT"
}
# Process a chunk of content some number of times.
#
# Parameters:
# $1: Content to parse and reparse and reparse
# $2: Tag prefix (context name)
# $3-*: Names to insert into the parsed content
mustache-loop() {
local CONTENT CONTEXT CONTEXT_BASE IGNORE
CONTENT=$1
CONTEXT_BASE=$2
shift 2
while [[ "${#@}" -gt 0 ]]; do
mustache-full-tag-name CONTEXT "$CONTEXT_BASE" "$1"
mustache-parse "$CONTENT" "$CONTEXT" false
shift
done
}
# Parse a block of text
#
# Parameters:
# $1: Block of text to change
# $2: Current name (the variable NAME for what {{.}} means)
# $3: true when no content before this, false otherwise
mustache-parse() {
# Keep naming variables MUSTACHE_* here to not overwrite needed variables
# used in the string replacements
local MUSTACHE_BLOCK MUSTACHE_CONTENT MUSTACHE_CURRENT MUSTACHE_IS_BEGINNING MUSTACHE_TAG
MUSTACHE_CURRENT=$2
MUSTACHE_IS_BEGINNING=$3
# Find open tags
mustache-split MUSTACHE_CONTENT "$1" '{{' '}}'
while [[ "${#MUSTACHE_CONTENT[@]}" -gt 1 ]]; do
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[1]}"
case $MUSTACHE_TAG in
'#'*)
# Loop, if/then, or pass content through function
# Sets context
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
mustache-find-end-tag MUSTACHE_BLOCK "$MUSTACHE_CONTENT" "$MUSTACHE_TAG"
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
if mustache-test "$MUSTACHE_TAG"; then
# Show / loop / pass through function
if mustache-is-function "$MUSTACHE_TAG"; then
# TODO: Consider piping the output to
# mustache-get-content so the lambda does not
# execute in a subshell?
MUSTACHE_CONTENT=$($MUSTACHE_TAG "${MUSTACHE_BLOCK[0]}")
mustache-parse "$MUSTACHE_CONTENT" "$MUSTACHE_CURRENT" false
MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
elif mustache-is-array "$MUSTACHE_TAG"; then
eval 'mustache-loop "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_TAG" "${!'"$MUSTACHE_TAG"'[@]}"'
else
mustache-parse "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_CURRENT" false
fi
fi
MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
;;
'>'*)
# Load partial - get name of file relative to cwd
mustache-partial MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING "$MUSTACHE_CURRENT"
;;
'/'*)
# Closing tag - If hit in this loop, we simply ignore
# Matching tags are found in mustache-find-end-tag
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
;;
'^'*)
# Display section if named thing does not exist
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
mustache-find-end-tag MUSTACHE_BLOCK "$MUSTACHE_CONTENT" "$MUSTACHE_TAG"
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
if ! mustache-test "$MUSTACHE_TAG"; then
mustache-parse "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_CURRENT" false "$MUSTACHE_CURRENT"
fi
MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
;;
'!'*)
# Comment - ignore the tag content entirely
# Trim spaces/tabs before the comment
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
;;
.)
# Current content (environment variable or function)
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
mustache-show "$MUSTACHE_CURRENT" "$MUSTACHE_CURRENT"
;;
'=')
# Change delimiters
# Any two non-whitespace sequences separated by whitespace.
# TODO
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
;;
'{'*)
# Unescaped - split on }}} not }}
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
MUSTACHE_CONTENT="${MUSTACHE_TAG:1}"'}}'"$MUSTACHE_CONTENT"
mustache-split MUSTACHE_CONTENT "$MUSTACHE_CONTENT" '}}}'
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[0]}"
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
MUSTACHE_CONTENT=${MUSTACHE_CONTENT[1]}
# Now show the value
mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
;;
'&'*)
# Unescaped
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
;;
*)
# Normal environment variable or function call
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
;;
esac
MUSTACHE_IS_BEGINNING=false
mustache-split MUSTACHE_CONTENT "$MUSTACHE_CONTENT" '{{' '}}'
done
echo -n "${MUSTACHE_CONTENT[0]}"
}
# Process a partial
#
# Indentation should be applied to the entire partial
#
# Prefix all variables
#
# Parameters:
# $1: Name of destination "content" variable.
# $2: Content before the tag that was not yet written
# $3: Tag content
# $4: Content after the tag
# $5: true/false: is this the beginning of the content?
# $6: Current context name
mustache-partial() {
local MUSTACHE_CONTENT MUSTACHE_FILENAME MUSTACHE_INDENT MUSTACHE_LINE MUSTACHE_PARTIAL MUSTACHE_STANDALONE
if mustache-is-standalone MUSTACHE_STANDALONE "$2" "$4" $5; then
MUSTACHE_STANDALONE=( $MUSTACHE_STANDALONE )
echo -n "${2:0:${MUSTACHE_STANDALONE[0]}}"
MUSTACHE_INDENT=${2:${MUSTACHE_STANDALONE[0]}}
MUSTACHE_CONTENT=${4:${MUSTACHE_STANDALONE[1]}}
else
MUSTACHE_INDENT=""
echo -n "$2"
MUSTACHE_CONTENT=$4
fi
mustache-trim-whitespace MUSTACHE_FILENAME "${3:1}"
# Execute in subshell to preserve current cwd and environment
(
# TODO: Remove dirname and use a function instead
cd "$(dirname "$MUSTACHE_FILENAME")"
mustache-indent-lines MUSTACHE_PARTIAL "$MUSTACHE_INDENT" "$(
mustache-load-file MUSTACHE_PARTIAL "${MUSTACHE_FILENAME##*/}"
# Fix bash handling of subshells
# The extra dot is removed in mustache-indent-lines
echo -n "${MUSTACHE_PARTIAL}."
)"
mustache-parse "$MUSTACHE_PARTIAL" "$6" true
)
local "$1" && mustache-indirect "$1" "$MUSTACHE_CONTENT"
}
# Show an environment variable or the output of a function.
#
# Limit/prefix any variables used
#
# Parameters:
# $1: Name of environment variable or function
# $2: Current context
mustache-show() {
local JOINED MUSTACHE_NAME_PARTS
if mustache-is-function "$1"; then
CONTENT=$($1 "")
mustache-parse "$CONTENT" "$2" false
return 0
fi
mustache-split MUSTACHE_NAME_PARTS "$1" "."
if [[ -z "${MUSTACHE_NAME_PARTS[1]}" ]]; then
if mustache-is-array "$1"; then
eval mustache-join JOINED "," "\${$1[@]}"
echo -n "$JOINED"
else
echo -n "${!1}"
fi
else
# Further subindexes are disallowed
eval 'echo -n "${'"${MUSTACHE_NAME_PARTS[0]}"'['"${MUSTACHE_NAME_PARTS[1]%%.*}"']}"'
fi
}
# Split a larger string into an array
#
# Parameters:
# $1: Destination variable
# $2: String to split
# $3: Starting delimiter
# $4: Ending delimiter (optional)
mustache-split() {
local POS RESULT
RESULT=( "$2" )
mustache-find-string POS "${RESULT[0]}" "$3"
if [[ "$POS" -ne -1 ]]; then
# The first delimiter was found
RESULT[1]=${RESULT[0]:$POS + ${#3}}
RESULT[0]=${RESULT[0]:0:$POS}
if [[ ! -z "$4" ]]; then
mustache-find-string POS "${RESULT[1]}" "$4"
if [[ "$POS" -ne -1 ]]; then
# The second delimiter was found
RESULT[2]="${RESULT[1]:$POS + ${#4}}"
RESULT[1]="${RESULT[1]:0:$POS}"
fi
fi
fi
local "$1" && mustache-indirect-array "$1" "${RESULT[@]}"
}
# Handle the content for a standalone tag. This means removing whitespace
# (not newlines) before a tag and whitespace and a newline after a tag.
# That is, assuming, that the line is otherwise empty.
#
# Parameters:
# $1: Name of destination "content" variable.
# $2: Content before the tag that was not yet written
# $3: Tag content (not used)
# $4: Content after the tag
# $5: true/false: is this the beginning of the content?
mustache-standalone-allowed() {
local STANDALONE_BYTES
if mustache-is-standalone STANDALONE_BYTES "$2" "$4" $5; then
STANDALONE_BYTES=( $STANDALONE_BYTES )
echo -n "${2:0:${STANDALONE_BYTES[0]}}"
local "$1" && mustache-indirect "$1" "${4:${STANDALONE_BYTES[1]}}"
else
echo -n "$2"
local "$1" && mustache-indirect "$1" "$4"
fi
}
# Handle the content for a tag that is never "standalone". No adjustments
# are made for newlines and whitespace.
#
# Parameters:
# $1: Name of destination "content" variable.
# $2: Content before the tag that was not yet written
# $3: Tag content (not used)
# $4: Content after the tag
mustache-standalone-denied() {
echo -n "$2"
local "$1" && mustache-indirect "$1" "$4"
}
# Returns 0 (success) if the named thing is a function or if it is a non-empty
# environment variable.
#
# Do not use unprefixed variables here if possible as this needs to check
# if any name exists in the environment
#
# Parameters:
# $1: Name of environment variable or function
# $2: Current value (our context)
#
# Return code:
# 0 if the name is not empty, 1 otherwise
mustache-test() {
# Test for functions
mustache-is-function "$1" && return 0
if mustache-is-array "$1"; then
# Arrays must have at least 1 element
eval '[[ "${#'"$1"'[@]}" -gt 0 ]]' && return 0
else
# Environment variables must not be empty
[[ ! -z "${!1}" ]] && return 0
fi
return 1
}
# Trim the leading whitespace only
#
# Parameters:
# $1: Name of destination variable
# $2: The string
# $3: true/false - trim front?
# $4: true/false - trim end?
# $5-*: Characters to trim
mustache-trim-chars() {
local BACK CURRENT FRONT LAST TARGET VAR
TARGET=$1
CURRENT=$2
FRONT=$3
BACK=$4
LAST=""
shift # Remove target
shift # Remove string
shift # Remove trim front flag
shift # Remove trim end flag
while [[ "$CURRENT" != "$LAST" ]]; do
LAST=$CURRENT
for VAR in "$@"; do
$FRONT && CURRENT="${CURRENT/#$VAR}"
$BACK && CURRENT="${CURRENT/%$VAR}"
done
done
local "$TARGET" && mustache-indirect "$TARGET" "$CURRENT"
}
# Trim leading and trailing whitespace from a string
#
# Parameters:
# $1: Name of variable to store trimmed string
# $2: The string
mustache-trim-whitespace() {
local RESULT
mustache-trim-chars RESULT "$2" true true $'\r' $'\n' $'\t' " "
local "$1" && mustache-indirect "$1" "$RESULT"
}
mustache-get-content MUSTACHE_CONTENT "$@"
mustache-parse "$MUSTACHE_CONTENT" "" true