| 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 | 
|---|