URI:
       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/\&/\&amp;/g
          458 s/>/\&gt;/g
          459 s/</\&lt;/g
          460 s/"/\&quot;/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