tzuper - arm-sdk - os build toolkit for various embedded devices
HTML git clone https://git.parazyd.org/arm-sdk
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
tzuper (25989B)
---
1 #!/usr/bin/env zsh
2 ## -*- origami-fold-style: triple-braces -*-
3 #
4 # Zuper - Zsh Ultimate Programmer's Extensions Refurbished
5 #
6 # Copyright (C) 2015 Dyne.org Foundation
7 #
8 # Zuper is designed, written and maintained by Denis Roio <jaromil@dyne.org>
9 #
10 # This source code is free software; you can redistribute it and/or
11 # modify it under the terms of the GNU Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This source code is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 # Please refer to the GNU Public License for more details.
19 #
20 # You should have received a copy of the GNU Public License along with
21 # this source code; if not, write to:
22 # Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23
24
25 if [[ ! -z ${zuper_version} ]]; then
26 warning "zuper version ::1 version:: was already loaded -- doing nothing" ${zuper_version}
27 return;
28 fi
29
30
31 ##########################
32 typeset -aU vars
33 typeset -aU arrs
34 typeset -aU maps
35
36 typeset -aU funs
37
38 vars=(DEBUG QUIET LOG)
39 arrs=(req freq)
40
41 vars+=(zuper_version)
42 zuper_version=0.4
43
44 # load necessary zsh extensions
45 zmodload zsh/regex
46 zmodload zsh/system
47 zmodload zsh/net/tcp
48 zmodload zsh/mapfile
49
50 # {{{ Messaging
51
52 # Messaging function with pretty coloring
53 autoload colors
54 colors
55
56 vars+=(last_act last_func last_notice)
57
58 function _msg() {
59 local msg="$2"
60 local i
61 command -v gettext 1>/dev/null 2>/dev/null && msg="$(gettext -s "$2")"
62 for i in {3..${#}}; do
63 msg=${(S)msg//::$(($i - 2))*::/$*[$i]}
64 done
65
66 local command="print -P"
67 local progname="$fg[magenta]${PROGRAM##*/}$reset_color"
68 local message="$fg_bold[normal]$fg_no_bold[normal]$msg$reset_color"
69 local -i returncode
70
71 case "$1" in
72 inline)
73 command+=" -n"; pchars=" > "; pcolor="yellow"
74 ;;
75 message)
76 last_act="$msg"
77 pchars=" . "; pcolor="white"; message="$fg_no_bold[$pcolor]$msg$reset_color"
78 ;;
79 verbose)
80 last_func="$msg"
81 pchars="[D]"; pcolor="blue"
82 ;;
83 success)
84 last_notice="$msg"
85 pchars="(*)"; pcolor="green"; message="$fg_no_bold[$pcolor]$msg$reset_color"
86 ;;
87 warning)
88 pchars="[W]"; pcolor="yellow"; message="$fg_no_bold[$pcolor]$msg$reset_color"
89 ;;
90 failure)
91 pchars="[E]"; pcolor="red"; message="$fg_no_bold[$pcolor]$msg$reset_color"
92 returncode=1
93 ;;
94 print)
95 progname=""
96 ;;
97 *)
98 pchars="[F]"; pcolor="red"
99 message="Developer oops! Usage: _msg MESSAGE_TYPE \"MESSAGE_CONTENT\""
100 returncode=127
101 zerr
102 ;;
103 esac
104 ${=command} "${progname} $fg_bold[$pcolor]$pchars$reset_color ${message}$color[reset_color]" >&2
105
106 # write the log if its configured
107 [[ "$LOG" = "" ]] || {
108 touch $LOG || return $?
109 ${=command} "${progname} $fg_bold[$pcolor]$pchars$reset_color ${message}$color[reset_color]" >> $LOG
110 }
111
112 return $returncode
113 }
114
115 function _message say act() {
116 local notice="message"
117 [[ "$1" = "-n" ]] && shift && notice="inline"
118 [[ $QUIET = 1 ]] || _msg "$notice" $@
119 return 0
120 }
121
122 function _verbose xxx func() {
123 [[ $DEBUG = 1 ]] && _msg verbose $@
124 return 0
125 }
126
127 function _success yes notice() {
128 [[ $QUIET = 1 ]] || _msg success $@
129 return 0
130 }
131
132 function _warning no warn warning() {
133 [[ $QUIET = 1 ]] || _msg warning $@
134 return 0
135 }
136
137 function _failure fatal die error() {
138 # typeset -i exitcode=${exitv:-1}
139 [[ $QUIET = 1 ]] || _msg failure $@
140 return 1
141 }
142
143 function _print() {
144 [[ $QUIET = 1 ]] || _msg print $@
145 return 0
146 }
147
148 # }}} Messaging
149
150 # {{{ Debugging
151
152 fn() {
153 fun="$@"
154 req=()
155 freq=()
156 func "$fun"
157 }
158
159 zerr() {
160 error "error in: ${fun:-$last_notice}"
161 [[ "$last_func" = "" ]] || warn "called in: $last_func"
162 [[ "$last_act" = "" ]] || warn "called in: $last_act"
163 [[ "$last_notice" = "" ]] || warn "called in: $last_notice"
164 # [[ "$fun" = "" ]] || warn "called in: $fun"
165 TRAPEXIT() {
166 error "error reported, operation aborted."
167 }
168 return 1
169 }
170
171
172 function ckreq reqck() {
173 err=0
174 for v in $req; do
175 [[ "${(P)v}" = "" ]] && {
176 warn "${fun[(ws: :)1]}(): required setting is blank: $v"
177 err=1
178 }
179 done
180
181 [[ $err = 1 ]] && return $err
182
183 for f in $freq; do
184 # exists and has size greater than zero
185 [[ -s $f ]] || {
186 warn "required file empty: $f"
187 err=1
188 }
189 done
190 [[ $err == 1 ]] && zerr
191 return $err
192 }
193
194 # dump all variables, arrays and maps declared as global in zuper
195 # do not print out what is empty
196 zdump() {
197 fn zdump
198 [[ ${#vars} -gt 0 ]] && {
199 print "Global variables:"
200 for _v in $vars; do
201 _c=${(P)_v}
202 [[ "$_c" = "" ]] ||
203 print " $_v = \t $_c"
204 done
205 }
206 [[ ${#arrs} -gt 0 ]] && {
207 print "Global arrays:"
208 for _a in $arrs; do
209 _c=${(P)_a}
210 [[ "$_c" = "" ]] ||
211 print " $_a \t ( ${(P)_a} )"
212 done
213 }
214 [[ ${#maps} -gt 0 ]] && {
215 print "Global maps:"
216 for _m in $maps; do
217 [[ "${(Pv)_m}" = "" ]] || {
218 print " $_m [key] \t ( ${(Pk)_m} )"
219 print " $_m [val] \t ( ${(Pv)_m} )"
220 }
221 done
222 }
223 }
224
225 # handy wrappers for throw/catch execution of blocks where we need the
226 # program to exit on any error (non-zero) returned by any function
227 throw() { function TRAPZERR() { zerr; return 1 } }
228 catch() { function TRAPZERR() { } }
229
230 ##########################
231 # Endgame handling
232
233 arrs+=(destruens)
234
235 # Trap functions for the endgame event
236 # TRAPINT() { endgame INT; return $? }
237 # TRAPEXIT() { endgame EXIT; return $? }
238 TRAPHUP() { endgame HUP; return $? }
239 TRAPQUIT() { endgame QUIT; return $? }
240 TRAPABRT() { endgame ABORT; return $? }
241 TRAPKILL() { endgame KILL; return $? }
242 # TRAPPIPE() { endgame PIPE; return $? }
243 TRAPTERM() { endgame TERM; return $? }
244 TRAPSTOP() { endgame STOP; return $? }
245 # TRAPZERR() { func "function returns non-zero." }
246
247
248 funs+=(__test_fn)
249
250 __test_fn(){
251 echo "foo"
252 }
253
254 function zuper_end endgame() {
255 fn "endgame $*"
256
257 # execute all no matter what
258 TRAPZERR() { }
259
260 # process registered destructors
261 for d in $destruens; do
262 fn "destructor: $d"
263 $d
264 done
265
266 # unset all the variables included in "vars"
267 for v in $vars; do
268 unset $v
269 done
270
271 # unset all the assoc-arrays included in "arrs"
272 for a in $arrs; do
273 unset $a
274 done
275
276 # unset all the maps included in "maps"
277 for m in $maps; do
278 unset $m
279 done
280
281 ## We should also undefine the core zuper functions to make it
282 ## really idempotent. I have added an array "funs" which contains
283 ## the names of the functions to be undefined by endgame/zuper_end
284 ## FIXME!!!! The only "registered" function so far is __test_fn,
285 ## but if we like this we should register all the core zuper
286 ## functions as soon as they are declared
287 for f in $funs; do
288 unfunction $f
289 done
290 unset maps
291 unset arrs
292 unset vars
293 unset funs
294
295 return 0
296 }
297
298 ## This function should reinitialise zuper and all the variables
299 # zuper_restart(){
300 # endgame
301 # source zuper
302 # }
303
304
305 # Use this to make sure endgame() is called at exit.
306 # unlike TRAPEXIT, the zshexit() hook is not called when functions exit.
307 function zuper.exit zshexit() { endgame EXIT; return $? }
308
309 # }}} Debugging
310
311 # {{{ Tempfiles
312
313 ##########################
314 # Temp file handling
315
316 vars+=(ztmpfile)
317 # ztmp() fills in $ztmpfile global. Caller must copy that variable as
318 # it will be overwritten at every call.
319 ztmp() {
320 fn ztmp
321
322 ztmpfile=`mktemp`
323 tmpfiles+=($ztmpfile)
324 }
325
326 vars+=(ztmpdir)
327 # ztmpd() fills in $ztmpdir global. Caller must copy that variable as
328 # it will be overwritten at every call.
329
330 ztmpd() {
331 fn ztmpd
332
333 ztmpdir=`mktemp -d`
334 tmpdirs+=($ztmpdir)
335 }
336
337 # All tempfiles are freed in endgame()
338 _ztmp_destructor() {
339 fn _ztmp_destructor
340
341 for f in $tmpfiles; do
342 rm -f "$f"
343 done
344 for d in $tmpdirs; do
345 [[ $d == "" || ! -d $d ]] && continue
346 pushd $d
347 [[ `pwd` == "/" ]] && {popd; continue}
348 popd
349 rm -rf "$d"
350 done
351
352 tmpfiles=()
353 tmpdirs=()
354 }
355
356 arrs+=(tmpfiles)
357 arrs+=(tmpdirs)
358 destruens+=(_ztmp_destructor)
359
360 # }}} Tempfiles
361
362 # {{{ Strings
363
364 # tokenizer, works only with one char length delimiters
365 # saves everything in global array tok=()
366 arrs+=(tok)
367 function string.strtok strtok() {
368 fn "strtok $*"
369 _string="$1"
370 _delim="$2"
371 req=(_string _delim)
372 ckreq || return $?
373
374 tok=()
375 f=0
376 c=0
377 for c in {1..${#_string}}; do
378 if [[ "${_string[(e)$c]}" == "$_delim" ]]; then
379 # check if not empty
380 t="${_string[(e)$(($f + 1)),$(($c - 1))]}"
381 if [[ "$t" == "" ]]; then
382 tok+=("null")
383 else
384 tok+=("$t")
385 fi
386 # save last found
387 f=$c
388 fi
389 done
390 # add last token
391 t=${_string[(e)$(($f + 1)),$c]}
392 if [[ "$t" == "" ]]; then
393 tok+=("null")
394 else
395 tok+=("$t")
396 fi
397 }
398
399 # remote leading and trailing spaces in a string taken from stdin
400 function string.trim trim() {
401 sed -e 's/^[[:space:]]*//g ; s/[[:space:]]*\$//g'
402 }
403
404 # extract all emails found in a text from stdin
405 # outputs them one per line
406 function string.extract_emails extract_emails() {
407 awk '{ for (i=1;i<=NF;i++)
408 if ( $i ~ /[[:alnum:]]@[[:alnum:]]/ ) {
409 gsub(/<|>|,/ , "" , $i); print $i } }'
410 }
411
412 # takes a string as argument, returns success if is an email
413 function string.isemail isemail() {
414 [[ "$1" =~ "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" ]] && return 0
415 # print "$1" | grep -q -E '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}' && return 0
416 return 1
417 }
418
419 # takes a numeric argument and prints out a human readable size
420 function string.human_size human_size() {
421 [[ $1 -gt 0 ]] || {
422 error "human_size() called with invalid argument"
423 return 1
424 }
425
426 # we use the binary operation for speed
427 # shift right 10 is divide by 1024
428
429 # gigabytes
430 [[ $1 -gt 1073741824 ]] && {
431 print -n "$(( $1 >> 30 )) GB"
432 return 0
433 }
434
435 # megabytes
436 [[ $1 -gt 1048576 ]] && {
437 print -n "$(( $1 >> 20 )) MB"
438 return 0
439 }
440 # kilobytes
441 [[ $1 -gt 1024 ]] && {
442 print -n "$(( $1 >> 10 )) KB"
443 return 0
444 }
445 # bytes
446 print -n "$1 Bytes"
447 return 0
448 }
449
450
451 # strips out all html/xml tags (everything between < >)
452 function string.html_strip xml_strip html_strip() { sed 's/<[^>]\+>//g' }
453
454 # changes stdin string special chars to be shown in html
455 function string.escape_html escape_html() {
456 sed -e '
457 s/\&/\&/g
458 s/>/\>/g
459 s/</\</g
460 s/"/\"/g
461 '
462 }
463
464 # escapes special chars in urls
465 function string.decode_url decode_url urldecode() {
466 sed -e '
467 s/%25/%/gi
468 s/%20/ /gi
469 s/%09/ /gi
470 s/%21/!/gi
471 s/%22/"/gi
472 s/%23/#/gi
473 s/%24/\$/gi
474 s/%26/\&/gi
475 s/%27/'\''/gi
476 s/%28/(/gi
477 s/%29/)/gi
478 s/%2a/\*/gi
479 s/%2b/+/gi
480 s/%2c/,/gi
481 s/%2d/-/gi
482 s/%2e/\./gi
483 s/%2f/\//gi
484 s/%3a/:/gi
485 s/%3b/;/gi
486 s/%3d/=/gi
487 s/%3e//gi
488 s/%3f/?/gi
489 s/%40/@/gi
490 s/%5b/\[/gi
491 s/%5c/\\/gi
492 s/%5d/\]/gi
493 s/%5e/\^/gi
494 s/%5f/_/gi
495 s/%60/`/gi
496 s/%7b/{/gi
497 s/%7c/|/gi
498 s/%7d/}/gi
499 s/%7e/~/gi
500 s/%09/ /gi
501 '
502 }
503
504 function helper.encode-url encode_url urlencode() {
505 sed -e '
506 s/%/%25/g
507 s/ /%20/g
508 s/ /%09/g
509 s/!/%21/g
510 s/"/%22/g
511 s/#/%23/g
512 s/\$/%24/g
513 s/\&/%26/g
514 s/'\''/%27/g
515 s/(/%28/g
516 s/)/%29/g
517 s/\*/%2a/g
518 s/+/%2b/g
519 s/,/%2c/g
520 s/-/%2d/g
521 s/\./%2e/g
522 s/\//%2f/g
523 s/:/%3a/g
524 s/;/%3b/g
525 s//%3e/g
526 s/?/%3f/g
527 s/@/%40/g
528 s/\[/%5b/g
529 s/\\/%5c/g
530 s/\]/%5d/g
531 s/\^/%5e/g
532 s/_/%5f/g
533 s/`/%60/g
534 s/{/%7b/g
535 s/|/%7c/g
536 s/}/%7d/g
537 s/~/%7e/g
538 s/ /%09/g
539 '
540 }
541
542 # Check if a version number (semantic format) is greater than the other
543 # returns 0 if the first argument is greater or equal than the second.
544 function string.version_greatoreq version_greatoreq() {
545 [[ "$(printf '%s\n' "$@" | sort -rV | head -n 1)" = "$1" ]] && return 0
546 return 1
547 }
548
549 # }}} Strings
550
551 # {{{ Networking
552
553 # This is only tested on GNU/Linux and makes use of sysfs
554
555 # index of all network devices
556 arrs+=(net_devices)
557
558 # map of ipv4 assigned addresses: [dev addr]
559 maps+=(net_ip4_addr)
560 # map of ipv6 assigned addresses: [dev addr]
561 maps+=(net_ip6_addr)
562
563 # map of dhcp served ipv4
564 maps+=(ip4dhcps)
565 # map of dhcp served ipv6
566 maps+=(ip6dhcps)
567
568 # map of external ipv4 addresses
569 maps+=(net_ip4_exit)
570 # map of internal ipv6 addresses
571 # maps+=(ip6exits)
572
573 net.scan_devices() {
574 for i in ${(f)"$(find /sys/devices/ -name net)"}; do
575 for dev in ${(f)"$(ls --indicator-style=none $i)"}; do
576 # skip the loopback device
577 [[ "$dev" =~ "^lo" ]] && continue
578 func "found network device: $dev"
579 net_devices+=($dev)
580 done
581 done
582
583 # return error if no device found
584 if [[ ${#net_devices} = 0 ]]; then return 1
585 else
586 act "${#net_devices} devices found: ${net_devices}"
587 return 0
588 fi
589 }
590
591 net.scan_addresses() {
592 [[ ${#net_devices} = 0 ]] && {
593 error "No network device found."
594 func "Have you ran net.scan_devices() first?"
595 return 1
596 }
597
598 for dev in ${net_devices}; do
599 # check ipv4 connections
600 conn=`ip addr show $dev | awk '/inet / {print $2}'`
601 [[ "$conn" = "" ]] || {
602 net_ip4_addr+=($dev $conn) }
603 # check ipv6 connections
604 conn=`ip addr show $dev | awk '/inet6/ {print $2}'`
605 [[ "$conn" = "" ]] || {
606 net_ip6_addr+=($dev $conn) }
607 done
608
609 # list ipv4
610 act "${#net_ip4_addr} ipv4 connected devices found"
611 for c in ${(k)net_ip4_addr}; do
612 act " $c ${net_ip4_addr[$c]}"
613 done
614
615 # list ipv6
616 act "${#net_ip6_addr} ipv6 connected devices found"
617 for c in ${(k)net_ip6_addr}; do
618 act " $c ${net_ip6_addr[$c]}"
619 done
620 # find out network addresses
621
622 return 0
623 }
624
625 net.scan_exits() {
626 # just ipv4 for now, also we use curl to drive the call over the
627 # specific interface, but if that wouldn't matter then rest.get is
628 # better to avoid this dependency
629
630 for dev in ${(k)net_ip4_addr}; do
631 addr=`curl --silent --interface $dev https://api.ipify.org`
632 if [[ "$?" != "0" ]]; then
633 error "curl returns $?: $addr"
634 else
635 [[ "$addr" = "" ]] || {
636 notice "$dev external ip: $addr"
637 net_ip4_exit+=($dev $addr)
638 }
639 fi
640 done
641
642 for dev in ${(k)net_ip6_addr}; do
643 addr=`curl --silent --ipv6 --interface $dev https://api.ipify.org`
644 if [[ $? != 0 ]]; then
645 error "curl returns $?: $addr"
646 else
647 [[ "$addr" = "" ]] || {
648 notice "$dev external ip: $addr"
649 net_ip4_exit+=($dev $addr)
650 }
651 fi
652 done
653
654 }
655
656 # }}} Networking
657
658 # {{{ Key/Value filesave
659
660 # optional: define zkv=1 on source
661
662 ##########################
663 # Key/Value file storage using ZSh associative maps
664
665
666 # load a map from a file
667 # map must be already instantiated with typeset -A by called
668 # name of map is defined inside the file
669 function zkv.load() {
670 fn "zkv-load $*"
671
672 file=$1
673 [[ "$file" = "" ]] && {
674 error "zkv-open() missing argument: file-path"
675 zerr
676 return 1 }
677 [[ -r "$file" ]] || {
678 error "zkv-open() file not found $file"
679 zerr
680 return 1 }
681 [[ -s "$file" ]] || {
682 error "zkv-open() file is empty"
683 zerr
684 return 1 }
685
686 source $file
687 }
688
689 # save a map in a file
690 # $1 = name of the map associative array
691 # $2 = full path to the file
692 function zkv.save() {
693 fn "zkv.save $*"
694
695 _map=$1
696 _path=$2
697 [[ "$_path" = "" ]] && {
698 error "zkv.save() missing argument: map-name path-to-file"
699 zerr
700 return 1
701 }
702 [[ -r $_path ]] && {
703 func "zkv.close() overwriting $_path"
704 func "backup turd left behind: ${_path}~"
705 mv $_path $_path~
706 }
707 touch $_path
708
709 # wondering about http://www.zsh.org/mla/users/2015/msg00286.html
710 # meanwhile solved using a double array, wasting a full map memcpy
711 _karr=(${(Pk)_map})
712 _varr=(${(Pv)_map})
713 _num="${#_karr}"
714 for c in {1..$_num}; do
715 # can also be cat here, however for speed we use builtins
716 # switch to cat if compatibility is an issue
717 sysread -o 1 <<EOF >> $_path
718 $_map+=("${_karr[$c]}" "${(v)_varr[$c]}")
719 EOF
720 done
721 func "$_num key/values stored in $_path"
722 }
723
724
725 # }}} Key/Value filesave
726
727 # {{{ Get/Set REST API
728
729 ########
730 # Restful API client (WIP, needs more testing)
731 # there is a clear zsh optimization here in get/set kv
732 # using zsh/tcp instead of spawning curl
733 # and perhaps querying with one call using ?recursive
734
735 vars+=(rest_reply_body rest_reply_header)
736 maps+=(rest_header)
737
738 function rest.put() {
739 fn "rest.put $*"
740
741 # $1 = hostname
742 # $2 = port
743 # $3 = path
744 # value from stdin |
745
746 # to check if the http service is running is up to the caller
747
748 _host=${1} # ip address
749 _port=${2}
750 _path=${3}
751 sysread _v
752
753 req=(_host)
754 ckreq || return $?
755
756 if ztcp $_host $_port; then
757
758 # TODO: work out various parsers, this one works with consul.io
759
760 _fd=$REPLY
761 # func "tcp open on fd $fd"
762 cat <<EOF >& $_fd
763 PUT ${_path} HTTP/1.1
764 User-Agent: Zuper/$zuper_version
765 Host: ${_host}:${_port}
766 Accept: */*
767 Content-Length: ${#_v}
768 Content-Type: application/x-www-form-urlencoded
769
770 EOF
771
772 print -n "$_v" >& $_fd
773
774 sysread -i $_fd _res
775
776 # close connection
777 ztcp -c $_fd
778
779 [[ "$_res" =~ "true" ]] || {
780 warn "failed PUT on restful key/value"
781 warn "host: ${_host}"
782 warn "port: ${_port}"
783 warn "path: ${_path}"
784 warn "value: $_v"
785 print - "$_res"
786 zerr
787 return 1
788 }
789
790 else
791 error "cannot connect to restful service: $_host:$_port"
792 zerr
793 return 1
794 fi
795
796 return 0
797
798 }
799
800 function rest.get() {
801 fn "rest.get $*"
802
803 _host=${1}
804 _port=${2}
805 _path=${3}
806
807 req=(_host _port)
808 ckreq || return $?
809
810 ztcp $_host $_port || {
811 zerr
812 return 1
813 }
814
815 _fd=$REPLY
816
817 # TODO: work out various parsers, this one works with consul.io
818
819 cat <<EOF >& $_fd
820 GET ${_path} HTTP/1.1
821 User-Agent: Zuper/$zuper_version
822 Host: $_host:$_port
823 Accept: */*
824
825 EOF
826
827 # read header response
828 rest_reply=`sysread -i $_fd -o 1`
829
830 for i in "${(f)rest_reply}"; do
831 print $i | hexdump -C
832 # first line is the response code
833
834 [[ "$i" -regex-match "\x0d\x0a$" ]] && {
835 func BLANK
836 break }
837
838 # # save other lines in map for fast retrieval
839 # _field=${i[(ws@:@)1]}
840 # func "$_field - header field parsed"
841 # rest_header[$_field]="${i[(ws@:@)2]}"
842
843 # c=$(( $c + 1 ))
844 done
845 # rest_reply_header="${(f)$(cat <&$_fd)}"
846
847 func "${#rest_reply_header} bytes response header stored in rest_reply_header"
848 # | awk -F: '
849 #/"Value":/ { gsub(/"|}]/,"",$7) ; print $7 }' | base64 -d
850
851 # TODO: read content-length and use it here
852
853 rest_reply_body="${(f)$(sysread -i $_fd -o 1)}"
854 func "${#rest_reply_body} bytes response body stored in rest_reply_body"
855
856 # close connection
857 ztcp -c $_fd
858
859 return 0
860
861 }
862
863
864 # }}} Get/Set REST API
865
866 # {{{ Parse commandline options
867
868 # for example usage, see Tomb http://tomb.dyne.org
869 vars+=(subcommand)
870 arrs+=(option_main option_params)
871 maps+=(option option_subcommands)
872
873 # Hi, dear developer! Are you trying to add a new subcommand, or
874 # to add some options? Well, keep in mind that option names are
875 # global: they cannot bear a different meaning or behaviour across
876 # subcommands. The only exception is "-o" which means: "options
877 # passed to the local subcommand", and thus can bear a different
878 # meaning for different subcommands.
879 #
880 # For example, "-s" means "size" and accepts one argument. If you
881 # are tempted to add an alternate option "-s" (e.g., to mean
882 # "silent", and that doesn't accept any argument) DON'T DO IT!
883 #
884 # There are two reasons for that:
885 # I. Usability; users expect that "-s" is "size"
886 # II. Option parsing WILL EXPLODE if you do this kind of bad
887 # things (it will complain: "option defined more than once")
888 #
889 # If you want to use the same option in multiple commands then you
890 # can only use the non-abbreviated long-option version like:
891 # -force and NOT -f
892
893 option.is_set() {
894
895 # Check whether a commandline option is set.
896 #
897 # Synopsis: option_is_set -flag [out]
898 #
899 # First argument is the commandline flag (e.g., "-s").
900 # If the second argument is present and set to 'out', print out the
901 # result: either 'set' or 'unset' (useful for if conditions).
902 #
903 # Return 0 if is set, 1 otherwise
904 local -i r # the return code (0 = set, 1 = unset)
905
906 [[ -n ${(k)option[$1]} ]];
907 r=$?
908
909 [[ $2 == "out" ]] && {
910 [[ $r == 0 ]] && { print 'set' } || { print 'unset' }
911 }
912
913 return $r;
914 }
915 # Print the option value matching the given flag
916 # Unique argument is the commandline flag (e.g., "-s").
917 option.value() {
918 print -n - "${option[$1]}"
919 }
920 option.parse() {
921
922 ### Detect subcommand
923 local -aU every_opts #every_opts behave like a set; that is, an array with unique elements
924 for optspec in ${option_subcommands}${option_main}; do
925 for opt in ${=optspec}; do
926 every_opts+=${opt}
927 done
928 done
929 local -a oldstar
930 oldstar=("${(@)argv}")
931 #### detect early: useful for --option-parsing
932 zparseopts -M -D -Adiscardme ${every_opts}
933 if [[ -n ${(k)discardme[--option-parsing]} ]]; then
934 print $1
935 if [[ -n "$1" ]]; then
936 return 1
937 fi
938 return 0
939 fi
940 unset discardme
941 if ! zparseopts -M -E -D -Adiscardme ${every_opts}; then
942 _failure "Command parses error."
943 return 1
944 fi
945 unset discardme
946 subcommand=${1}
947 if [[ -z $subcommand ]]; then
948 subcommand="__empty"
949 fi
950
951 if [[ -z ${(k)option_subcommands[$subcommand]} ]]; then
952 subcommand="__unknown:$subcommand"
953 # _warning "There's no such command \"::1 subcommand::\"." $subcommand
954 # _failure "Please try -h for help."
955 fi
956 argv=("${(@)oldstar}")
957 unset oldstar
958
959 ### Parsing global + command-specific options
960 # zsh magic: ${=string} will split to multiple arguments when spaces occur
961 set -A cmd_opts ${option_main} ${=option_subcommands[$subcommand]}
962 # if there is no option, we don't need parsing
963 if [[ -n $cmd_opts ]]; then
964 zparseopts -M -E -D -Aoption ${cmd_opts}
965 if [[ $? != 0 ]]; then
966 _warning "Some error occurred during option processing."
967 _failure "See zuper option.parse for more info."
968 return 1
969 fi
970 fi
971 #build option_params (array of arguments) and check if there are unrecognized options
972 ok=0
973 option_params=()
974 for arg in $*; do
975 if [[ $arg == '--' || $arg == '-' ]]; then
976 ok=1
977 continue #it shouldn't be appended to option_params
978 elif [[ $arg[1] == '-' ]]; then
979 if [[ $ok == 0 ]]; then
980 _failure "Unrecognized option ::1 arg:: for subcommand ::2 subcommand::" $arg $subcommand
981 return 1
982 fi
983 fi
984 option_params+=$arg
985 done
986 # First parameter actually is the subcommand: delete it and shift
987 [[ $subcommand != '__empty' ]] && { option_params[1]=(); shift }
988
989 ### End parsing command-specific options
990
991 [[ "$option_params" == "" ]] && {
992 func "arg command: ::1 subcommand::" $subcommand
993 } || {
994 func "arg command: ::1 subcommand:: ::2 param::" $subcommand $option_params
995 }
996
997 }
998
999 # Later: process subcommand
1000 # case "$subcommand" in
1001 # help)
1002 # print "TODO: help"
1003 # ;;
1004 # __empty)
1005 # zdump
1006 # ;;
1007
1008 # # Reject unknown command and suggest help
1009 # *)
1010 # _warning "Command \"::1 subcommand::\" not recognized." $subcommand
1011 # _message "Try -h for help."
1012 # return 1
1013 # ;;
1014 # esac
1015
1016 # }}}
1017
1018 # {{{ Helpers
1019
1020 function helper.isfound isfound() {
1021 command -v $1 1>/dev/null 2>/dev/null
1022 return $?
1023 }
1024
1025 # faster substitute for cat
1026 function helper.printfile printfile() {
1027 print ${mapfile[$1]}
1028 }
1029
1030 # }}} Helpers
1031
1032 # {{{ Config
1033
1034 # This is not a full config parser, but its a mechanism to read single
1035 # sections of configuration files that are separated using various
1036 # syntax methods. The only method supported is now org-mode whose
1037 # sections start with #+ . It fills in the global array
1038 # $config_section which can be read out to a file or interpreted in
1039 # memory, whatever syntax it may contain.
1040
1041 vars+=(config_section_type)
1042 arrs+=(config_section)
1043 config_section_type=org-mode
1044
1045 config.section_type() {
1046 fn config.section.type
1047 _type=$1
1048 req=(_type)
1049 ckreq || return $?
1050
1051 case $_type in
1052 org-mode)
1053 config_section_type=org-mode
1054 ;;
1055 *)
1056 error "Unknown config type:$_type"
1057 return 1
1058 ;;
1059 esac
1060
1061 act "$_type config section parser initialized"
1062 return 0
1063
1064 }
1065
1066 # fills in contents of section in array config_section
1067 config.section_read() {
1068 fn config.section.read
1069 _file=$1
1070 _section=$2
1071 req=(_file _section)
1072 freq=($_file)
1073 ckreq || return $?
1074
1075 case $config_section_type in
1076 org-mode)
1077 _contents=`awk '
1078 BEGIN { found=0 }
1079 /^#\+ '"$_section"'$/ { found=1; next }
1080 /^#\+/ { if(found==1) exit 0 }
1081 /^$/ { next }
1082 { if(found==1) print $0 }
1083 ' $_file`
1084
1085 ;;
1086 *)
1087 error "Unknown config type:$_type"
1088 ;;
1089 esac
1090
1091 config_section=()
1092 for c in ${(f)_contents}; do
1093 config_section+=("$c")
1094 done
1095 return 0
1096
1097 }
1098
1099 # }}} Config