-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbashx
More file actions
418 lines (397 loc) · 14 KB
/
bashx
File metadata and controls
418 lines (397 loc) · 14 KB
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
#!/bin/bash
# bashx compiler
# (C) Tirito6626 2025
[[ ! -v _red ]] && {
_green="$(tput setaf 2)"
_red="$(tput setaf 1)"
_white="$(tput setaf 255)"
_bold="$(tput bold)"
_nc="$(tput sgr 0)"
}
function _conditionalRender() {
# set -x
while read line; do
if [[ "$line" == '$(' ]]; then # match start of subshell capture
continue
elif [[ "${line::2}" =~ \<[a-Z]|\</|\"\<|\'\< ]]; then # match html element
line="${line%% }"
if [[ ${line: -2} == "&&" ]]; then # skip && for now
line="${line%%\&\&}"
@unquote line
local appendand=true
else
@unquote line
fi
output+=" printf '%s' \""
while read line; do
[[ "$nextline" ]] && local buf="$line" && line="$nextline" && nextline="$buf" # move line to buffer
if echo "${line## }" | grep -qE '^[[:space:]]*</?([A-Z][a-zA-Z0-9]*)\b'; then # match custom element
[[ "$line" =~ /\>.* ]] && nextline=$(sed 's|^.*/>\(.*\)|\1|' <<< "$line") && line="${line//$nextline}"
line="${line%%\/\>}"
line="${line%%\>}"
line="${line##\<}"
output+="\`$line\`" $'\n'
else
output+="${line//\"/\\\"}"
fi
done < <(echo "$line" | sed 's|\([a-zA-Z0-9|"|\@|\/]\)>|\1>\n|g; s|\(</[^>]*>\)|\n\1|g') # html parser basically
[[ $appendand == true ]] && output+=$'" &&' && unset appendadd || output+=$'"\n' # add && back
else
output+=" $line"
fi
done
output="${output//$'\n' ||/ ||}"
output="${output%%)}"$'\n'
# set +x
}
function @error() {
[[ ${#FUNCNAME[@]} > 1 ]] && echo -n "(${FUNCNAME[1]}) " >&2
echo "${_red}Error:${_nc} ${@}" >&2
}
function _render() {
#set -x
local dom=false level=0 slevel=0 inscript=false output='' i=0 tag=''
local -a parent=() extra=()
while read -r line; do
line="${line%% }"
line="${line## }"
if [[ "$readchunk" ]]; then
if [[ "${line: -1}" != ')' ]]; then
chunk+="${line}"$'\n' && continue
else
chunk+="${line%%)}"$'\n'
line=")"
unset readchunk
fi
fi
[[ "$line" == '' || "${line::1}" == '#' ]] && continue # skip empty lines or comments
[[ "$line" =~ @render= ]] && @render_type "${line##@render=}" && continue
if [[ "${line::1}" == "<" ]]; then
# splits <html>string</html> into
# <html>
# string
# </html>
# and parses each part separately
while read -r line; do
[[ "$nextline" ]] && local buf="$line" && line="$nextline" && nextline="$buf"
[[ "$line" == '' || "${line::1}" == '#' ]] && continue
if grep -qE '^[[:space:]]*</?([A-Z][a-zA-Z0-9]*)\b' <<< "${line## }"; then # regex if it's a custom element or native html
[[ "$line" =~ /\>.* ]] && nextline=$(sed 's|^.*/>\(.*\)|\1|' <<< "$line") && line="${line//$nextline}"
line="${line%%\/\>}"
line="${line%%\>}"
line="${line##\<}"
output+="$line"$'\n' # only keep the function and it's arguments
else
read tag attr <<< "$line"
# any lines related to execute() and x-bash-client are made for bash-wasm module which isn't working for now
case "$tag" in
*'<script'*)
slevel=0
case "$attr" in
*'bash-client'*)
[[ "$type" =~ silent || ! -v bash_wasm_path ]] && inscript=bash-client-silent || inscript=bash-client
output+=$'cat <<- "_EOF_"\n'
[[ ! -v bash_wasm_path ]] && output+="<script>addServerAction(\`" || output+="<script>execute(\`"
;;
*'bash'*)
# match "<script type=text/x-bash>" blocks
if [[ $__render_type == client ]]; then
inscript=bash-client
output+=$'cat <<- "EOF"\n<script>execute(`'
else
inscript=bash
fi
;;
*)
inscript=js
output+=$'cat <<- "_EOF_"\n'
output+="$line"$'\n'
esac
;;
*'</script'*)
slevel=0
case "$inscript" in
js) output+=$'</script>\n_EOF_\n' ;;
bash-client-silent) output+=$'`)</script>\n_EOF_\n' ;;
bash-client)
local uid=$(cat /proc/sys/kernel/random/uuid)
output+="\`,'$uid')</script><div id='$uid'></div>"
unset uid
output+=$'\n_EOF_\n'
esac
inscript=false
;;
'<body'*|'<main'*|'<div'*)
[[ "$inscript" != false ]] && ((++slevel)) #&& echo "i $slevel $tag $line" && set -x
output+="printf '%s\n' '${line}'"$'\n'
;;
'</head>')
[[ -v ismeta ]] && unset ismeta
;;
*'</'*)
((--slevel)) #&& echo "d $slevel $tag"
output+="printf '%s\n' '${line}'"$'\n'
;;
'<head')
local ismeta=true
;;
*)
# check if there are special callbacks
case "$line" in
on:*)
line="${line%%\>}";
line="${line##\<}";
local arg attr key events;
read el args <<< "$line";
if [[ "$args" =~ on:?* ]]; then
while read arg; do
IFS='=' read key value <<< "${arg}";
@unquote value
[[ -z "$key" ]] && continue
case "$key" in
js-on:*) key="${key//js-on:}"; events+="on${key,,}=\"$($value)\" " ;;
server-on:*) key="${key//on:}"; _events+="on${key,,}=\"\$(addServerAction "$value")\" " ;;
on:*) key="${key//on:}"; _events+="on${key,,}=\"execute(\`$value\`)\" " ;;
on*) events+="${key,,}=\"$value\" " ;;
*) attr+="$key=\"$value\" "
esac
done <<< "$args"
output+="printf '%s' \"<$el $attr $events>'\""$'\n'
unset el args key events arg
fi
;;
*)
[[ "$inscript" != false && "${line::1}" == '<' ]] && ((++slevel)) #&& echo "i $slevel $tag" && set -x
[[ "$ismeta" ]] && output+="fiction.addMeta \"${line//\"/\\\"}\""$'\n' || output+="printf '%s' \"${line//\"/\\\"}\""$'\n' # automatically add stuff to <head>
esac
esac
unset tag _
#output+="printf '%s' \"${line//\"/\\\"}\""$'\n'
fi
done < <(sed 's|\([a-zA-Z0-9|"|\@|\/]\)>|\1>\n|g; s|\(</[^>]*>\)|\n\1|g' <<< "$line")
# add line without any wrapping in case it's bash/inside js block
elif [[ "$line" =~ '{@children}'|'{cache}'|'{/cache}'|'@return' || "$inscript" != false ]]; then
[[ $slevel > 0 ]] && output+="printf '%s' \"${line//\"/\\\"}\""$'\n' || output+="$line"$'\n'
elif [[ "${line::2}" =~ '$(' ]]; then # capture subshell execution
if [[ "${line: -1}" != ')' ]]; then
readchunk=true
line="${line//\$(/\$($'\n' }"
chunk="${line%%)}"$'\n'
continue
else
line="${line//\$(/\$($'\n' }"
line="${line%%)}"$'\n)'
# pass subshell chunk to conditional renderer
if [[ "$__render_type" == client ]]; then
output+=$'cat <<- "_EOF_"\n<script>execute(`'
line="${line//&&/&&$'\n'}"
_conditionalRender <<<"${line//||/$'\n'||$'\n'}"
local uid=$(cat /proc/sys/kernel/random/uuid)
output+="\`,'$uid')</script><div id='$uid'></div>" # extra div to redirect output from bash-client block
output+=$'\n_EOF_\n'
unset uid
else
line="${line//&&/&$'\n'}"
_conditionalRender <<<"${line//||/$'\n'||$'\n'}"
fi
fi
elif [[ "${line}" == ')' && "$chunk" ]]; then # append chunk in case subshell is multi-line
_conditionalRender <<<"${chunk//||/$'\n'||$'\n'})"
else
[[ "$line" =~ '{' && "$line" =~ '}' ]] && line=$(echo "$line" | sed 's|{\([^[:space:]]\+\)}|<a data-bind="\1"></a>|g') # "dynamic" variables manageable by setState
output+="printf '%s' \"${line//\"/\\\"}\""$'\n'
fi
done
render_output="$output"
unset input output level flevel i inscript parent line nextline
}
bashx() {
if [ -f "$1" ]; then
# regex for capturing all @return blocks
input=$(sed -E 's#(.*)\@return[[:space:]](json|html)[[:space:]]*\{[[[:space:]]*]?(.*)[^^]\}(.*)$#\1\n\@return \2 \{\n\3\n}\n\4#g' "$1" | sed 's/^source.*bashx//g')
else
return 1
fi
[[ "$BASHX_VERBOSE" ]] && local time=$(date +%s%3N)
# #NEWLIN#: literal newline ($'\n')
# #NL#: backslashed newline (\n)
# for some reason bash doesn't like \n during substitution so we replace it beforehand
local out1="${input//$'\n'/#NEWLIN#}"
out1="${out1//\\n/#NL#}"
# split @return blocks with $'\r' for further parsing per block
local out2=$(awk 'function count(s, c) { n=0; for (i=1; i<=length(s); i++) if (substr(s, i, 1) == c) n++; return n } /^[[:space:]]*@return[[:space:]](html|json)[[:space:]]*{[[:space:]]*$/ { in_block=1; depth=1; print; next } in_block { o=count($0,"{"); c=count($0,"}"); depth+=o-c; print; if (depth==0) { in_block=0; print "\r" } }' <<< "$input")
#echo "$out2"
#return
while read -rd $'\r' block; do
#echo "${block}"
#return
block1="$block"
if [[ "${block:8:4}" == 'html' ]]; then
block1="${block1%%\}}"
_render "" <<< "${block1/@return html \{}"
local newblock="{"$'\n'"${render_output}"$'\n'"}"
newblock="${newblock//\&/\\\&}"
elif [[ "${block:8:4}" == 'html' ]]; then
# remove semicolon, newlines and escape double quotes
local newblock="${block/@return json}"
newblock="${newblock//\};/\}}"
newblock="${newblock//$'\n'/ }"
newblock=$(cat <<-eof
echo "${newblock//\"/\\\"}"
eof
)
newblock="${newblock// }"
fi
newblock="${newblock##$'\n'}"
block="${block//$'\n'/#NEWLIN#}"
block="${block//\[/\\\[}"
block="${block//\]/\\\]}"
#newblock="${newblock//\\n/#NL#}"
#out1="${out1//\\n}"
out1="${out1//${block//\\n/#NL#}/${newblock% }}"
echo "$block"
echo "$newblock"
done <<< "$out2"
[[ "$BASHX_VERBOSE" ]] && printf "%s\n" "[$_green✓$_nc] Compiled $1 ($(($(date +%s%3N)-time))ms)"
unset input out out2 block time block1 render_output
#echo "${out1//#NEWLIN#/$'\n'}"
#return
if [[ "" ]]; then
echo "$1:"
echo "${out1//#NEWLIN#/$'\n'}"
printf "\n"
else
BASHX_FILENAME="$(readlink -f "$1")"
out1="${out1//\%AND\%/\&}"
eval "${out1//#NEWLIN#/$'\n'}" || return 1
fi
unset out out1 out2 block block1
#set -x
}
function @parse {
_events=''
for arg in "$@"; do
IFS='=' read key value <<< "${arg}";
if [[ -z "$key" ]]; then
continue
fi
case "$key" in
js-on:*)
key="${key//js-on:}"
value="${value##\'}"
value="${value//\'}"
_events+="on${key,,}=\"$($value)\" "
;;
on:*)
key="${key//on:}"
value="${value##\'}"
value="${value//\'}"
_events+="on${key,,}='execute('$value')"
;;
on*)
_events+="${key,,}=\"$value\" "
;;
*)
[[ "$BASH_BROWSER" ]] && export $key="$value" || printf -v "$key" "%s" "$value"
esac
done
unset arg key value
}
function @exclude {
local list="${1}" result=()
for arg in "${@:2}"; do
IFS='=' read key value <<< "${arg}";
[[ -z "$key" ]] && continue
printf -v "$key" "%s" "$value"
[[ "$key" =~ $list ]] && continue
result+=("$key='$value'")
done
set -- ${result[@]}
}
function @require {
for arg in $@; do
declare -F $arg || echo "Error: required $arg is not found"
done
}
function @unquote {
for key in "$@"; do
local -n key1=$key
key1="${key1%%\'}"; key1="${key1##\'}"; key1="${key1##\"}"; printf -v "$key" "${key1%%\"}";
done
}
function @wrapper() {
for key in $@; do
[ -z "$(declare -F "$key")" ] && return
eval "$(declare -f "$key" | sed "s+{@children};+}; /${key}() {+g")"
done
}
function @render_type() {
case "${1,,}" in
client) __render_type=client ;;
server) __render_type=server ;;
*) @error "Invalid render type provided" && return 1
esac
}
function @import() {
# Syntax: import <filename> || import <filename> as <ElementAlias>
# import <function> from <filename> || import <function> from <filename> as <ElementAlias>
local name filename
[ -z "$name" ] && name="$1"
shift
if [[ "$1" == "as" ]]; then
if [ -d "$name" ]; then
name="$name/index.sh"
elif [ ! -f "$name" ]; then
echo "$name not found to import."
exit 1
fi
shift
if [ -z "$1" ]; then
fn_name=""${name%.*}""
else
fn_name="$1"
fi
eval "${fn_name}() { $(cat "$name") }"
elif [[ "$1" == "from" ]]; then
shift
filename="$1"
if [ ! -f "$filename" ]; then
error "$name not found to import."
exit 1
fi
[[ "$filename" =~ .shx|.bashx ]] && local funcout="$(bashx "$filename" && declare -f "$name" && declare -f "/$name")" || local funcout="$(. "$filename" && declare -f "$name")"
[ -z "$funcout" ] && error "$name is not found in $filename" && return 1
shift
if [[ "$1" == "as" ]]; then
shift
if [ -z "$1" ]; then
fn_name="${name%.*}"
else
fn_name="$1"
fi
eval "${funcout/$name/$fn_name}"
else
[[ "$funcout" ]] && eval "$funcout"
fi
else
[[ -n "$name" ]] && { [[ "$name" =~ .shx|.bashx ]] && bashx "$name" || source "$name"; }
fi
}
declare -F exportFunction >/dev/null && exportFunction @require @unquote @parse @exclude
if ! (return 0 2>/dev/null); then
# [[ -z "$BASHX_NESTED" ]] && BASHX_NESTED=true && bashx "${BASH_SOURCE[-1]}" && exit
#elif [[ -z "$BASHX_NESTED" ]]; then
BASHX_NESTED=true
if [ -z "$1" ]; then
@error "Invalid usage. Use $0 [filename]"
exit 1
elif [ -f "$1" ]; then
filename="$1"
[[ "$filename" != "${filename##*/}" ]] && cd "${filename%/*}" && filename="${filename##*/}"
bashx "$filename"
exit
else
@error "$1 is not found"
exit 1
fi
fi