| 1 | #!/usr/bin/env bash
|
|---|
| 2 |
|
|---|
| 3 | ARGV=$@
|
|---|
| 4 |
|
|---|
| 5 | __tick_error() {
|
|---|
| 6 | echo "TICKTICK PARSING ERROR: "$1
|
|---|
| 7 | }
|
|---|
| 8 |
|
|---|
| 9 | # This is from https://github.com/dominictarr/JSON.sh
|
|---|
| 10 | # See LICENSE for more info. {{{
|
|---|
| 11 | __tick_json_tokenize() {
|
|---|
| 12 | local ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
|
|---|
| 13 | local CHAR='[^[:cntrl:]"\\]'
|
|---|
| 14 | local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
|
|---|
| 15 | local VARIABLE="\\\$[A-Za-z0-9_]*"
|
|---|
| 16 | local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
|
|---|
| 17 | local KEYWORD='null|false|true'
|
|---|
| 18 | local SPACE='[[:space:]]+'
|
|---|
| 19 | egrep -ao "$STRING|$VARIABLE|$NUMBER|$KEYWORD|$SPACE|." --color=never | egrep -v "^$SPACE$" # eat whitespace
|
|---|
| 20 | }
|
|---|
| 21 |
|
|---|
| 22 | __tick_json_parse_array() {
|
|---|
| 23 | local index=0
|
|---|
| 24 | local ary=''
|
|---|
| 25 |
|
|---|
| 26 | read -r Token
|
|---|
| 27 |
|
|---|
| 28 | case "$Token" in
|
|---|
| 29 | ']') ;;
|
|---|
| 30 | *)
|
|---|
| 31 | while :
|
|---|
| 32 | do
|
|---|
| 33 | __tick_json_parse_value "$1" "`printf "%012d" $index`"
|
|---|
| 34 |
|
|---|
| 35 | (( index++ ))
|
|---|
| 36 | ary+="$Value"
|
|---|
| 37 |
|
|---|
| 38 | read -r Token
|
|---|
| 39 | case "$Token" in
|
|---|
| 40 | ']') break ;;
|
|---|
| 41 | ',') ary+=_ ;;
|
|---|
| 42 | *)
|
|---|
| 43 | __tick_error "Array syntax malformed"
|
|---|
| 44 | break ;;
|
|---|
| 45 | esac
|
|---|
| 46 | read -r Token
|
|---|
| 47 | done
|
|---|
| 48 | ;;
|
|---|
| 49 | esac
|
|---|
| 50 | }
|
|---|
| 51 |
|
|---|
| 52 | __tick_json_parse_object() {
|
|---|
| 53 | local key
|
|---|
| 54 | local obj=''
|
|---|
| 55 | read -r Token
|
|---|
| 56 |
|
|---|
| 57 | case "$Token" in
|
|---|
| 58 | '}') ;;
|
|---|
| 59 | *)
|
|---|
| 60 | while :
|
|---|
| 61 | do
|
|---|
| 62 | # The key, it should be valid
|
|---|
| 63 | case "$Token" in
|
|---|
| 64 | '"'*'"'|\$[A-Za-z0-9_]*) key=$Token ;;
|
|---|
| 65 | # If we get here then we aren't on a valid key
|
|---|
| 66 | *)
|
|---|
| 67 | __tick_error "Object without a Key"
|
|---|
| 68 | break
|
|---|
| 69 | ;;
|
|---|
| 70 | esac
|
|---|
| 71 |
|
|---|
| 72 | # A colon
|
|---|
| 73 | read -r Token
|
|---|
| 74 |
|
|---|
| 75 | # The value
|
|---|
| 76 | read -r Token
|
|---|
| 77 | __tick_json_parse_value "$1" "$key"
|
|---|
| 78 | obj+="$key:$Value"
|
|---|
| 79 |
|
|---|
| 80 | read -r Token
|
|---|
| 81 | case "$Token" in
|
|---|
| 82 | '}') break ;;
|
|---|
| 83 | ',') obj+=_ ;;
|
|---|
| 84 | esac
|
|---|
| 85 | read -r Token
|
|---|
| 86 | done
|
|---|
| 87 | ;;
|
|---|
| 88 | esac
|
|---|
| 89 | }
|
|---|
| 90 |
|
|---|
| 91 | __tick_json_parse_value() {
|
|---|
| 92 | local jpath="${1:+$1_}$2"
|
|---|
| 93 | local prej=${jpath//\"/}
|
|---|
| 94 |
|
|---|
| 95 | [ "$prej" ] && prej="_$prej"
|
|---|
| 96 | [ "$prej" ] && prej=${prej/-/__hyphen__}
|
|---|
| 97 |
|
|---|
| 98 | case "$Token" in
|
|---|
| 99 | '{') __tick_json_parse_object "$jpath" ;;
|
|---|
| 100 | '[') __tick_json_parse_array "$jpath" ;;
|
|---|
| 101 |
|
|---|
| 102 | *)
|
|---|
| 103 | Value=$Token
|
|---|
| 104 | Path="$Prefix$prej"
|
|---|
| 105 | Path=${Path/#_/}
|
|---|
| 106 | echo __tick_data_${Path// /}=$Value
|
|---|
| 107 | ;;
|
|---|
| 108 | esac
|
|---|
| 109 | }
|
|---|
| 110 |
|
|---|
| 111 | __tick_json_parse() {
|
|---|
| 112 | read -r Token
|
|---|
| 113 | __tick_json_parse_value
|
|---|
| 114 | read -r Token
|
|---|
| 115 | }
|
|---|
| 116 | # }}} End of code from github
|
|---|
| 117 |
|
|---|
| 118 | # Since the JSON parser is just json parser, and we have a runtime
|
|---|
| 119 | # and assignments built on to this, along with javascript like referencing
|
|---|
| 120 | # there is a two-pass system, just because it was easier to code.
|
|---|
| 121 | #
|
|---|
| 122 | # This one separates out the valid JSON from the runtime library support
|
|---|
| 123 | # and little fo' language that this is coded in.
|
|---|
| 124 | __tick_fun_tokenize_expression() {
|
|---|
| 125 | CHAR='[0-9]*[A-Za-z_$\\][0-9]*'
|
|---|
| 126 | FUNCTION="(push|pop|shift|items|delete|length)[[:space:]]*\("
|
|---|
| 127 | NUMBER='[0-9]*'
|
|---|
| 128 | STRING="$CHAR*($CHAR*)*"
|
|---|
| 129 | PAREN="[()]"
|
|---|
| 130 | QUOTE="[\"\']"
|
|---|
| 131 | SPACE='[[:space:]]+'
|
|---|
| 132 | egrep -ao "$FUNCTION|$STRING|$QUOTE|$PAREN|$NUMBER|$SPACE|." --color=never |\
|
|---|
| 133 | sed "s/^/S/g;s/$/E/g" # Make sure spaces are respected
|
|---|
| 134 | }
|
|---|
| 135 |
|
|---|
| 136 | __tick_fun_parse_expression() {
|
|---|
| 137 | while read -r token; do
|
|---|
| 138 | token=${token/#S/}
|
|---|
| 139 | token=${token/%E/}
|
|---|
| 140 |
|
|---|
| 141 | if [ $done ]; then
|
|---|
| 142 | suffix+="$token"
|
|---|
| 143 | else
|
|---|
| 144 | case "$token" in
|
|---|
| 145 | #
|
|---|
| 146 | # The ( makes sure that you can do key.push = 1, not that you would, but
|
|---|
| 147 | # avoiding having reserved words lowers the barrier to entry. Try doing
|
|---|
| 148 | # say function debugger() {} in javascript and then run it in firefox. That's
|
|---|
| 149 | # a fun one.
|
|---|
| 150 | #
|
|---|
| 151 | # So, it's probably better to be as lenient as possible when dealing with
|
|---|
| 152 | # syntax like this.
|
|---|
| 153 | #
|
|---|
| 154 | 'push('|'pop('|'shift('|'items('|'delete('|'length(') function=$token ;;
|
|---|
| 155 | ')')
|
|---|
| 156 | function=${function/%(/}
|
|---|
| 157 |
|
|---|
| 158 | #
|
|---|
| 159 | # Since bash only returns integers, we have to have a significant hack in order
|
|---|
| 160 | # to return a string and then do something to the object. Basically, everything
|
|---|
| 161 | # gets slammed inline.
|
|---|
| 162 | #
|
|---|
| 163 | # Q: Why don't you just reserve a global and then have the subfunction assign to it?
|
|---|
| 164 | #
|
|---|
| 165 | # A: Because the assignment has to happen prior to the function running. There's a number
|
|---|
| 166 | # of syntax tricks where you can basically emulate "pointers", but then the coder would
|
|---|
| 167 | # have to know about this "pointer" idea and then deal with their variables a different
|
|---|
| 168 | # way.
|
|---|
| 169 | #
|
|---|
| 170 | # ---------
|
|---|
| 171 | #
|
|---|
| 172 | # Q: Why don't you just do stuff in a sub-shell and then make sure you emit things in
|
|---|
| 173 | # something like a ( ) or a ` ` block?
|
|---|
| 174 | #
|
|---|
| 175 | # A: Because environments get copied into the subshell and then you'd be modifying the
|
|---|
| 176 | # copy, not the original data. After the subshell ended, the original environment
|
|---|
| 177 | # would stay, unmodified.
|
|---|
| 178 | #
|
|---|
| 179 | # ---------
|
|---|
| 180 | #
|
|---|
| 181 | # Q: Why don't you use the file system and do some magic with subthreads or something?
|
|---|
| 182 | #
|
|---|
| 183 | # A: Really? This should have side-effects? In programming there is something called
|
|---|
| 184 | # the principle of least astonishment. In a way, the implementation below somewhat
|
|---|
| 185 | # breaks that principle. However, using a file system or doing something really
|
|---|
| 186 | # funky like that, would violate that principle far more.
|
|---|
| 187 | #
|
|---|
| 188 | # ---------
|
|---|
| 189 | #
|
|---|
| 190 | # But really, I sincerely hate the current solution. If you have a better idea, please
|
|---|
| 191 | # please, open a dialog with me.
|
|---|
| 192 | #
|
|---|
| 193 | case $function in
|
|---|
| 194 | items) echo '${!__tick_data_'"$Prefix"'*}' ;;
|
|---|
| 195 | delete) echo 'unset __tick_data_'${Prefix/%_/} ;;
|
|---|
| 196 | pop) echo '"$( __tick_runtime_last ${!__tick_data_'"$Prefix"'*} )"; __tick_runtime_pop ${!__tick_data_'"$Prefix"'*}' ;;
|
|---|
| 197 | shift) echo '`__tick_runtime_first ${!__tick_data_'"$Prefix"'*}`; __tick_runtime_shift ${!__tick_data_'"$Prefix"'*}' ;;
|
|---|
| 198 | length) echo '`__tick_runtime_length ${!__tick_data_'"$Prefix"'*}`' ;;
|
|---|
| 199 | *) echo "__tick_runtime_$function \"$arguments\" __tick_data_$Prefix "'${!__tick_data_'"$Prefix"'*}'
|
|---|
| 200 | esac
|
|---|
| 201 | unset function
|
|---|
| 202 |
|
|---|
| 203 | return
|
|---|
| 204 | ;;
|
|---|
| 205 |
|
|---|
| 206 | [0-9]*[A-Za-z]*[0-9]*) [ -n "$function" ] && arguments+="$token" || Prefix+="$token" ;;
|
|---|
| 207 |
|
|---|
| 208 | [0-9]*) Prefix+=`printf "%012d" $token` ;;
|
|---|
| 209 | '['|.) Prefix+=_ ;;
|
|---|
| 210 | '"'|"'"|']') ;;
|
|---|
| 211 | =) done=1 ;;
|
|---|
| 212 | # Only respect a space if its in the args.
|
|---|
| 213 | ' ') [ -n "$function" ] && arguments+="$token" ;;
|
|---|
| 214 | *) [ -n "$function" ] && arguments+="$token" || Prefix+="$token" ;;
|
|---|
| 215 | esac
|
|---|
| 216 | fi
|
|---|
| 217 | done
|
|---|
| 218 |
|
|---|
| 219 | if [ "$suffix" ]; then
|
|---|
| 220 | echo "$suffix" | __tick_json_tokenize | __tick_json_parse
|
|---|
| 221 | else
|
|---|
| 222 | Prefix=${Prefix/-/__hyphen__}
|
|---|
| 223 | echo '${__tick_data_'$Prefix'}'
|
|---|
| 224 | fi
|
|---|
| 225 | }
|
|---|
| 226 |
|
|---|
| 227 | __tick_fun_parse_tickcount_reset() {
|
|---|
| 228 | # If the tick count is 1 then the backtick we encountered was a
|
|---|
| 229 | # shell code escape. These ticks need to be preserved for the script.
|
|---|
| 230 | if (( ticks == 1 )); then
|
|---|
| 231 | code+='`'
|
|---|
| 232 | fi
|
|---|
| 233 |
|
|---|
| 234 | # This resets the backtick counter so that `some shell code` doesn't
|
|---|
| 235 | # trip up the tokenizer
|
|---|
| 236 | ticks=0
|
|---|
| 237 | }
|
|---|
| 238 |
|
|---|
| 239 | # The purpose of this function is to separate out the Bash code from the
|
|---|
| 240 | # special "tick tick" code. We do this by hijacking the IFS and reading
|
|---|
| 241 | # in a single character at a time
|
|---|
| 242 | __tick_fun_parse() {
|
|---|
| 243 | IFS=
|
|---|
| 244 |
|
|---|
| 245 | # code oscillates between being bash or tick tick blocks.
|
|---|
| 246 | code=''
|
|---|
| 247 |
|
|---|
| 248 | # By using -n, we are given that a newline will be an empty token. We
|
|---|
| 249 | # can certainly test for that.
|
|---|
| 250 | while read -r -n 1 token; do
|
|---|
| 251 | case "$token" in
|
|---|
| 252 | '`')
|
|---|
| 253 |
|
|---|
| 254 | # To make sure that we find two sequential backticks, we reset the counter
|
|---|
| 255 | # if it's not a backtick.
|
|---|
| 256 | if (( ++ticks == 2 )); then
|
|---|
| 257 |
|
|---|
| 258 | # Whether we are in the stanza or not, is controlled by a different
|
|---|
| 259 | # variable
|
|---|
| 260 | if (( tickFlag == 1 )); then
|
|---|
| 261 | tickFlag=0
|
|---|
| 262 | [ "$code" ] && echo -n "`echo $code | __tick_fun_tokenize_expression | __tick_fun_parse_expression`"
|
|---|
| 263 | else
|
|---|
| 264 | tickFlag=1
|
|---|
| 265 | echo -n "$code"
|
|---|
| 266 | fi
|
|---|
| 267 |
|
|---|
| 268 | # If we have gotten this deep, then we are toggling between backtick
|
|---|
| 269 | # and bash. So se should unset the code.
|
|---|
| 270 | unset code
|
|---|
| 271 | fi
|
|---|
| 272 | ;;
|
|---|
| 273 |
|
|---|
| 274 | '')
|
|---|
| 275 | __tick_fun_parse_tickcount_reset
|
|---|
| 276 |
|
|---|
| 277 | # this is a newline. If we are in ticktick, then we want to consume
|
|---|
| 278 | # them for the parser later on. If we are in bash, then we want to
|
|---|
| 279 | # preserve them. We do this by emitting our buffer and then clearing
|
|---|
| 280 | # it
|
|---|
| 281 | if (( tickFlag == 0 )); then
|
|---|
| 282 | echo "$code"
|
|---|
| 283 | unset code
|
|---|
| 284 | fi
|
|---|
| 285 |
|
|---|
| 286 | ;;
|
|---|
| 287 |
|
|---|
| 288 | *)
|
|---|
| 289 | __tick_fun_parse_tickcount_reset
|
|---|
| 290 |
|
|---|
| 291 | # This is a buffer of the current code, either bash or backtick
|
|---|
| 292 | code+="$token"
|
|---|
| 293 | ;;
|
|---|
| 294 | esac
|
|---|
| 295 | done
|
|---|
| 296 | }
|
|---|
| 297 |
|
|---|
| 298 | __tick_fun_tokenize() {
|
|---|
| 299 | # This makes sure that when we rerun the code that we are
|
|---|
| 300 | # interpreting, we don't try to interpret it again.
|
|---|
| 301 | export __tick_var_tokenized=1
|
|---|
| 302 |
|
|---|
| 303 | # Using bash's caller function, which is for debugging, we
|
|---|
| 304 | # can find out the name of the program that called us. We
|
|---|
| 305 | # then cat the calling program and push it through our parser
|
|---|
| 306 | local code=$(cat `caller 1 | cut -d ' ' -f 3` | __tick_fun_parse)
|
|---|
| 307 |
|
|---|
| 308 | # Before the execution we search to see if we emitted any parsing errors
|
|---|
| 309 | hasError=`echo "$code" | grep "TICKTICK PARSING ERROR" | wc -l`
|
|---|
| 310 |
|
|---|
| 311 | if [ $__tick_var_debug ]; then
|
|---|
| 312 | printf "%s\n" "$code"
|
|---|
| 313 | exit 0
|
|---|
| 314 | fi
|
|---|
| 315 |
|
|---|
| 316 | # If there are no errors, then we go ahead
|
|---|
| 317 | if (( hasError == 0 )); then
|
|---|
| 318 | # Take the output and then execute it
|
|---|
| 319 |
|
|---|
| 320 | bash -c "$code" -- $ARGV
|
|---|
| 321 | else
|
|---|
| 322 | echo "Parsing Error Detected, see below"
|
|---|
| 323 |
|
|---|
| 324 | # printf observes the new lines
|
|---|
| 325 | printf "%s\n" "$code"
|
|---|
| 326 | echo "Parsing stopped here."
|
|---|
| 327 | fi
|
|---|
| 328 |
|
|---|
| 329 | exit
|
|---|
| 330 | }
|
|---|
| 331 |
|
|---|
| 332 | ## Runtime {
|
|---|
| 333 | __tick_runtime_length() { echo $#; }
|
|---|
| 334 | __tick_runtime_first() { echo ${!1}; }
|
|---|
| 335 | __tick_runtime_last() { eval 'echo $'${!#}; }
|
|---|
| 336 | __tick_runtime_pop() { eval unset ${!#}; }
|
|---|
| 337 |
|
|---|
| 338 | __tick_runtime_shift() {
|
|---|
| 339 | local left=
|
|---|
| 340 | local right=
|
|---|
| 341 |
|
|---|
| 342 | for (( i = 1; i <= $# + 1; i++ )) ; do
|
|---|
| 343 | if [ "$left" ]; then
|
|---|
| 344 | eval "$left=\$$right"
|
|---|
| 345 | fi
|
|---|
| 346 | left=$right
|
|---|
| 347 | right=${!i}
|
|---|
| 348 | done
|
|---|
| 349 | eval unset $left
|
|---|
| 350 | }
|
|---|
| 351 | __tick_runtime_push() {
|
|---|
| 352 | local value="${1/\'/\\\'}"
|
|---|
| 353 | local base=$2
|
|---|
| 354 | local lastarg=${!#}
|
|---|
| 355 |
|
|---|
| 356 | let nextval=${lastarg/$base/}+1
|
|---|
| 357 | nextval=`printf "%012d" $nextval`
|
|---|
| 358 |
|
|---|
| 359 | eval $base$nextval=\'$value\'
|
|---|
| 360 | }
|
|---|
| 361 |
|
|---|
| 362 | tickParse() {
|
|---|
| 363 | eval `echo "$1" | __tick_json_tokenize | __tick_json_parse | tr '\n' ';'`
|
|---|
| 364 | }
|
|---|
| 365 | ## } End of Runtime
|
|---|
| 366 |
|
|---|
| 367 |
|
|---|
| 368 | [ $__tick_var_tokenized ] || __tick_fun_tokenize
|
|---|