arm-sdk

os build toolkit for various embedded devices
git clone https://git.parazyd.org/arm-sdk
Log | Files | Refs | Submodules | README | LICENSE

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