jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

commit b89665a20ce042d69e3834750e5b654eef7cbc90
parent 993044f8f78996aa70f0fb9087ec6aabe6045a30
Author: Jaromil <jaromil@dyne.org>
Date:   Sun, 22 Mar 2015 10:30:04 +0100

major cleanup and adoption of zuper extension

Diffstat:
MTODO.md | 8++++++++
Mbuild/build-gnu.sh | 7++-----
Mbuild/install-gnu.sh | 2+-
Msrc/jaro | 331+++++++++++++++++++++++++++++--------------------------------------------------
Msrc/zlibs/email | 16++++++++--------
Msrc/zlibs/filters | 6+++---
Msrc/zlibs/helpers | 95+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/zlibs/maildirs | 4++--
Msrc/zlibs/search | 2+-
Asrc/zlibs/zuper | 410+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/zlibs/zuper.init | 16++++++++++++++++
11 files changed, 623 insertions(+), 274 deletions(-)

diff --git a/TODO.md b/TODO.md @@ -3,6 +3,14 @@ Contribute code or donate to complete this TODO https://www.dyne.org/donate +## Save and check SSL/TLS server fingerprints + +use a k/v store to save and check the fingerprints of servers +using fingerprint() inside helpers +provide a command to save a new fingerprint overriding old one +bail out in error when known fingeprint doesn't match + + ## Notmuch web Fire up the web interface for notmuch searches diff --git a/build/build-gnu.sh b/build/build-gnu.sh @@ -6,8 +6,6 @@ builddir=`pwd` # cc="${builddir}/cc-static.zsh" cc="gcc -O3" -pushd .. - which apt-get > /dev/null && distro=debian which yum > /dev/null && distro=fedora @@ -82,8 +80,8 @@ debian_req() { print -n "Compiling the address parser (RFC2047) ... " ${=cc} -c helpers.c ${=cc} -c rfc2047.c - ${=cc} -c rfc822.c; - ${=cc} -c -DHAVE_ICONV fetchaddr.c; + ${=cc} -c rfc822.c + ${=cc} -c -DHAVE_ICONV fetchaddr.c ${=cc} -o fetchaddr fetchaddr.o helpers.o rfc2047.o rfc822.o popd cp src/fetchaddr build/gnu/ @@ -155,4 +153,3 @@ print "Now run 'make install' as root to install jaromail in /usr/local" print "use PREFIX=/home/private/jaromail to avoid system-wide installation." print -popd diff --git a/build/install-gnu.sh b/build/install-gnu.sh @@ -33,7 +33,7 @@ cp -ra $srcdir/src/stats/* $JARO_SHARE/.stats/ mkdir -p $JARO_LIBEXEC/{bin,zlibs} cp $srcdir/src/jaro $JARO_LIBEXEC/bin cp -ra $srcdir/build/gnu/* $JARO_LIBEXEC/bin -cp -ra $srcdir/src/zlibs/* $JARO_LIBEXEC/zlibs/ +cp -r $srcdir/src/zlibs/* $JARO_LIBEXEC/zlibs/ for l in `ls $JARO_LIBEXEC/zlibs/ | grep '.zwc$'`; do rm -f $l diff --git a/src/jaro b/src/jaro @@ -4,7 +4,9 @@ # # a tool to easily and privately handle your e-mail communication # -# Copyleft (C) 2010-2015 Denis Roio <jaromil@dyne.org> +# Copyright (C) 2010-2015 Dyne.org Foundation +# +# JaroMail is designed, written and maintained by Denis Roio <jaromil@dyne.org> # # This source code is free software; you can redistribute it and/or # modify it under the terms of the GNU Public License as published by @@ -20,37 +22,90 @@ # this source code; if not, write to: # Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -VERSION=3.2 -DATE=Jan/2015 +VERSION=3.3 +DATE=Mar/2015 JAROMAILEXEC=$0 -typeset -a OLDARGS -for arg in ${argv}; do OLDARGS+=($arg); done -########################## -# declare global variables +# default permission on files +umask 066 -QUIET=0 -DEBUG=0 -DRYRUN=0 -CALLMUTT=1 +# honor quiet and debug flags as early as possible +if [[ ${@} == *-q* ]]; then quiet=1; fi +if [[ ${@} == *-D* ]]; then debug=1; fi + + +# check if we are inside the directory +if [ -r jaro/bin/jaro ]; then + MAILDIRS=`pwd` +# check if we are on OSX +elif [ -r /Applications/JaroMail.app ]; then + MAILDIRS="$HOME/Library/Application Support/JaroMail" +# else use GNU/Linux default +else + MAILDIRS=$HOME/Mail +fi + +# end override +MAILDIRS=${JAROMAILDIR:-$MAILDIRS} + + +# check if we are testing from source +if [ -r ../src/jaro ]; then + WORKDIR="../src" +# check if we are on OSX +elif [ -r /Applications/JaroMail.app/Contents/Resources/jaro ]; then + WORKDIR="/Applications/JaroMail.app/Contents/Resources/jaro" +else # use GNU/Linux default + WORKDIR="/usr/local/share/jaromail" +fi + +# env override +WORKDIR=${JAROWORKDIR:-$WORKDIR} + +# load our zuper extension +source $WORKDIR/zlibs/zuper + + +# what operating system are we in? use os_detect() +# simplifying modes of operation: GNU or MAC +case $(uname -o) in + GNU/Linux) OS=GNU + notice "Jaro Mail v$VERSION running on GNU/Linux" ;; + + Darwin) OS=MAC + notice "Jaro Mail v$VERSION running on Mac/OSX" ;; + + Cygwin) OS=WIN + notice "Jaro Mail v$VERSION runing on MS/Win" ;; + + *) OS=GNU # default + error "Running on an unknown operating system, assuming GNU" ;; +esac + + + +# global variables +vars+=(DEBUG QUIET DRYRUN CALLMUTT cur_fun) +QUIET=${QUIET:-0} +DEBUG=${DEBUG:-0} +DRYRUN=${DRYRUN:-0} +CALLMUTT=${CALLMUTT:-1} # use gnome-keyring for passwords on GNU systems -GNOMTEKEY=0 +vars+=(GNOMEKEY) +GNOMEKEY=${GNOMEKEY:-0} -# default permission on files -umask 066 # global variables for binaries called -typeset -h rm mkdir mutt +vars+=(rm mkdir mutt SQL) -# load zsh regex module +# load zsh modules zmodload zsh/regex zmodload zsh/mapfile zmodload zsh/system zmodload -F zsh/stat b:zstat zmodload zsh/sched -# date stamp -datestamp=`date '+%d%b%y'` + ########################## # SQL command @@ -65,32 +120,32 @@ typeset -h global_quit global_quit=0 # global variable for account selection -typeset -h account account_type +vars+=(account account_type) # account=default -typeset -h list +vars+=(list) list=whitelist # global variables for accounts -typeset -h name login imap imap_port smtp smtp_port protocol password auth accountopt +vars+=(name login imap imap_port smtp smtp_port protocol password auth accountopt) typeset -ah folders exclude -typeset -h host port type +vars+=(host port type) # global variables for addressbook -typeset -h hostname addressbook addressbook_tmp +vars+=(hostname addressbook addressbook_tmp) # global variables for email parsers typeset -A e_addr -typeset -h e_parsed +vars+=(e_parsed) # global array for maildirs (filled by list_maildirs) typeset -al maildirs # global variable for mutt binary -typeset -h mutt pgpewrap dotlock +vars+=(mutt pgpewrap dotlock) # global variable for exit code -typeset -h exitcode +vars+=(exitcode) # exitcode=0 # global variable for infos on imap folder @@ -99,123 +154,10 @@ typeset -h exitcode typeset -alU imap_info # global variable for mutt options -typeset -h muttflags +vars+=(muttflags) autoload colors; colors -# temporary directory -typeset TMPPREFIX=${TMPPREFIX:-/tmp} -typeset -H JAROTMP # Filename of secure temp just created (see _tmp_create()) -typeset -aH JAROTMPFILES # Keep track of temporary files - -# Provide a random filename in shared memory -_tmp_create() { - [[ -d "$TMPPREFIX" ]] || { - # we create the tempdir with the sticky bit on - mkdir -m 1777 "$TMPPREFIX" - [[ $? == 0 ]] || _failure "Fatal error creating the temporary directory: ::1 temp dir::" "$TMPPREFIX" - } - - # We're going to add one more $RANDOM for each time someone complain - # about this being too weak of a random. - tfile="${TMPPREFIX}/$RANDOM$RANDOM$RANDOM$RANDOM" # Temporary file - umask 066 - [[ $? == 0 ]] || { - error "Fatal error setting the permission umask for temporary files" - return 1 - } - - [[ -r "$tfile" ]] && { - error "Someone is messing up with us trying to hijack temporary files." - return 1 - } - - touch "$tfile" - [[ $? == 0 ]] || { - error "Fatal error creating a temporary file: ::1 temp file::" "$tfile" - return 1 - } - - [[ $? == 0 ]] || { - error "Fatal error setting ownership on temporary file: ::1 temp file::" "$tfile" - return 1 - } - - func "Created tempfile: $tfile" - JAROTMP="$tfile" - JAROTMPFILES+=("$tfile") - - return 0 -} - -# standard output message routines -# it's always useful to wrap them, in case we change behaviour later -notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[default] $1" >&2; fi } -error() { if [[ $QUIET == 0 ]]; then print "$fg[red][!]$fg[default] $1" >&2; fi } -func() { if [[ $DEBUG == 1 ]]; then print "$fg[blue][D]$fg[default] $1" >&2; fi } -act() { - if [[ $QUIET == 0 ]]; then - if [ "$1" = "-n" ]; then - print -n "$fg_bold[white] . $fg_no_bold[default] $2" >&2; - else - print "$fg_bold[white] . $fg_no_bold[default] $1" >&2; - fi - fi -} - -# honor quiet and debug flags as early as possible -if [[ ${@} == *-q* ]]; then QUIET=1; fi -if [[ ${@} == *-D* ]]; then DEBUG=1; fi - - -# what operating system are we in? use os_detect() -# simplifying modes of operation: GNU or MAC -case $(uname -o) in - GNU/Linux) OS=GNU - notice "Jaro Mail v$VERSION running on GNU/Linux" ;; - - Darwin) OS=MAC - notice "Jaro Mail v$VERSION running on Mac/OSX" ;; - - Cygwin) OS=WIN - notice "Jaro Mail v$VERSION runing on MS/Win" ;; - - *) OS=GNU # default - error "Running on an unknown operating system, assuming GNU" ;; -esac - -# check if we are inside the directory -if [ -r jaro/bin/jaro ]; then - MAILDIRS=`pwd` - -# check if we are on OSX -elif [ -r /Applications/JaroMail.app ]; then - MAILDIRS="$HOME/Library/Application Support/JaroMail" - -# else use GNU/Linux default -else - MAILDIRS=$HOME/Mail -fi - -# end override -{ test "$JAROMAILDIR" = "" } || { MAILDIRS="$JAROMAILDIR" } - -# default working dir - - -# check if we are testing from source -if [ -r ../src/jaro ]; then - WORKDIR="../src" -# check if we are on OSX -elif [ -r /Applications/JaroMail.app/Contents/Resources/jaro ]; then - WORKDIR="/Applications/JaroMail.app/Contents/Resources/jaro" -else # use GNU/Linux default - WORKDIR="/usr/local/share/jaromail" -fi - -# env override -[[ "$JAROWORKDIR" = "" ]] || { WORKDIR="${JAROWORKDIR}" } - # default addressbook ADDRESSBOOK="$MAILDIRS/whitelist.abook" @@ -260,52 +202,29 @@ else exit 1 fi -ACCOUNTS="$MAILDIRS/Accounts" -KEYRING="$MAILDIRS/Keyring" -# Cleanup anything sensitive before exiting. -endgame() { - func "endgame() $1" - [[ "$1" = "NOERRORS" ]] || { - error "$1 signal sent to processes, waiting to quit..." - global_quit=1 - sleep 2 - } - # Clear temporary files - for f in $JAROTMPFILES; do - func "endgame() cleaning tempfile $f" - rm -f "$f" - done - JAROTMPFILES=() +########## +# complete +source $WORKDIR/zlibs/zuper.init - func "endgame() exit code: $exitcode" - return $exitcode -} -# Trap functions for the endgame event -TRAPINT() { endgame INT; return $? } -# TRAPEXIT() { endgame EXIT; return $? } -TRAPHUP() { endgame HUP; return $? } -TRAPQUIT() { endgame QUIT; return $? } -TRAPABRT() { endgame ABORT; return $? } -TRAPKILL() { endgame KILL; return $? } -# TRAPPIPE() { endgame PIPE; return $? } -TRAPTERM() { endgame TERM; return $? } -TRAPSTOP() { endgame STOP; return $? } -# TRAPZERR() { func "function returns non-zero." } + + +ACCOUNTS="$MAILDIRS/Accounts" +KEYRING="$MAILDIRS/Keyring" case $OS in - GNU) + GNU) # backward compatibility tests for old paths in JaroMail <1.3 { test -d $WORKDIR/Accounts } && { test ! -d $ACCOUNTS } && { act "Updating accounts location: $ACCOUNTS" cp -ra $WORKDIR/Accounts $ACCOUNTS } - + { test -r "$WORKDIR/keyring" } && { test ! -r "$KEYRING" } && { act "Updating keyring location: $KEYRING" cp $WORKDIR/keyring "$KEYRING" - } + } ;; MAC) ;; @@ -313,36 +232,35 @@ esac hostname=$(hostname) # gather the current hostname - [[ "$1" = "source" ]] || { # skip checks if just sourcing -# make sure we have a directory for account configurations -{ test -d "$ACCOUNTS" } || { ${=mkdir} "$ACCOUNTS" } + # make sure we have a directory for account configurations + { test -d "$ACCOUNTS" } || { ${=mkdir} "$ACCOUNTS" } -# make sure we have a local keyring in case system-wide not found -{ test -r "$KEYRING" } || { create_keyring "$KEYRING" } + # make sure we have a local keyring in case system-wide not found + { test -r "$KEYRING" } || { create_keyring "$KEYRING" } -# make sure we have an addressbook -[[ -r "$ADDRESSBOOK" ]] || { create_addressbook "$ADDRESSBOOK" } + # make sure we have an addressbook + [[ -r "$ADDRESSBOOK" ]] || { create_addressbook "$ADDRESSBOOK" } -${=mkdir} "$MAILDIRS/logs" -# ${=mkdir} "$MAILDIRS/certs" + ${=mkdir} "$MAILDIRS/logs" + # ${=mkdir} "$MAILDIRS/certs" -MUTTDIR="$MAILDIRS/.mutt" -{ test -d "$MUTTDIR" } || { ${=mkdir} "$MUTTDIR" } + MUTTDIR="$MAILDIRS/.mutt" + { test -d "$MUTTDIR" } || { ${=mkdir} "$MUTTDIR" } -# make sure we have Filters.txt Applications.txt Mutt.txt -{ test -r "$MAILDIRS/Filters.txt" } || { - cp "$WORKDIR/Filters.txt" "$MAILDIRS/Filters.txt" - notice "Default filters created" } + # make sure we have Filters.txt Applications.txt Mutt.txt + { test -r "$MAILDIRS/Filters.txt" } || { + cp "$WORKDIR/Filters.txt" "$MAILDIRS/Filters.txt" + notice "Default filters created" } -{ test -r "$MAILDIRS/Applications.txt" } || { - cp "$WORKDIR/Applications.txt" "$MAILDIRS/Applications.txt" - notice "Default helper applications settings created" } + { test -r "$MAILDIRS/Applications.txt" } || { + cp "$WORKDIR/Applications.txt" "$MAILDIRS/Applications.txt" + notice "Default helper applications settings created" } -{ test -r "$MAILDIRS/Mutt.txt" } || { - cp "$WORKDIR/Mutt.txt" "$MAILDIRS/Mutt.txt" - notice "Default Mutt configuration template created" } + { test -r "$MAILDIRS/Mutt.txt" } || { + cp "$WORKDIR/Mutt.txt" "$MAILDIRS/Mutt.txt" + notice "Default Mutt configuration template created" } } # if not sourcing @@ -415,7 +333,7 @@ check_bin() { act "using gnome-keyring to store secrets" GNOMEKEY=1 } - + return 0 } @@ -494,7 +412,7 @@ a pipe | in front indicate they take an email body from stdin |smtp read a mail from stdin and send it via [accounts]'s smtp -== Storage commands +== Storage commands open open a maildir folder (can use -R for read-only) @@ -575,7 +493,6 @@ main() { subcommands_opts[passwd]="" subcommands_opts[cert]="" - subcommands_opts[ramdisk]="" subcommands_opts[source]="" subcommands_opts[isonline]="" @@ -589,7 +506,7 @@ main() { subcommands_opts[extract]="" subcommands_opts[smtp]="" - + subcommands_opts[crypt]="" subcommands_opts[cryptsign]="" @@ -611,7 +528,7 @@ main() { if [[ -z $subcommand ]]; then subcommand="__default" fi - + if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then # unknown command, pass it to autostart func "unknown command, autostart: $@" @@ -623,10 +540,10 @@ main() { } return $exitcode fi - + argv=(${oldstar}) unset oldstar - + ### Parsing global + command-specific options # zsh magic: ${=string} will split to multiple arguments when spaces occur set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]} @@ -666,7 +583,7 @@ main() { fi { option_is_set -a } && { account=`option_value -a` } { option_is_set -l } && { - if [[ "`option_value -l`" =~ "black" ]]; then + if [[ "`option_value -l`" =~ "black" ]]; then list=blacklist elif [[ "`option_value -l`" =~ "white" ]]; then list=whitelist @@ -725,7 +642,7 @@ main() { update_filters update_mutt update_sieve - command -v notmuch > /dev/null && { + command -v notmuch > /dev/null && { nm_setup nm new 2>&1 | grep -v '^Note: Ignoring' } @@ -814,11 +731,9 @@ main() { cert) cert ${PARAM} ;; # was checking is_online - ramdisk) ramdisk ${PARAM} ;; - isonline) is_online ${=PARAM}; exitcode=$? ;; - + publish) md="$1" { maildircheck "$md" 2>/dev/null } || { md="${MAILDIRS}/${md}" } @@ -883,7 +798,7 @@ main() { isml) ismailinglist [[ $? = 0 ]] && \ - notice "Email read from stdin is a mailinglist" + notice "Email read from stdin is a mailinglist" exitcode=$? ;; @@ -912,4 +827,4 @@ main() { check_bin main $@ endgame NOERRORS -return $exitcode +#return $exitcode diff --git a/src/zlibs/email b/src/zlibs/email @@ -38,8 +38,8 @@ queue() { # set it ready for saving in outbux queue_body="$base" - _tmp_create - tmpqueue="$JAROTMP" + ztmp + tmpqueue=$ztmpfile # pre-processing of the email headers awk ' @@ -285,8 +285,8 @@ smtp_send() { unset password return 1 } - _tmp_create - msmtpcfg="$JAROTMP" + ztmp + msmtpcfg=$ztmpfile sysread -o 1 <<EOF > $msmtpcfg account default from ${email} @@ -336,8 +336,8 @@ send() { hdr "$qbody" | grep -i '^from: anon' > /dev/null if [[ $? = 0 ]]; then - _tmp_create - anoncfg="$JAROTMP" + ztmp + anoncfg=$ztmpfile sysread -o 1 <<EOF > "$anoncfg" REMAIL n @@ -462,8 +462,8 @@ peek() { unset password all; return 1 } - _tmp_create - _pass_tmp="$JAROTMP" + ztmp + _pass_tmp=$ztmpfile sysread -o 1 <<EOF >> "$_pass_tmp" set imap_pass = "${password}" # set imap_peek = yes diff --git a/src/zlibs/filters b/src/zlibs/filters @@ -587,8 +587,8 @@ EOF print " \\" >> $MUTTDIR/mboxes print " +unsorted.ml +unsorted" >> $MUTTDIR/mboxes - _tmp_create - ttmp="$JAROTMP" + ztmp + ttmp=$ztmpfile uniq $MUTTDIR/mboxes > $ttmp mv $ttmp $MUTTDIR/mboxes @@ -649,7 +649,7 @@ update_sieve() { ####### # SIEVE act "generating sieve filter rules" - id=$datestamp.$RANDOM + id=`datestamp`.$RANDOM newlock "$MAILDIRS/Filters.sieve" rm -f "$MAILDIRS/Filters.sieve" touch "$MAILDIRS/Filters.sieve" diff --git a/src/zlibs/helpers b/src/zlibs/helpers @@ -27,6 +27,7 @@ # which mutt binary to use mutt="mutt" +datestamp() { date '+%d%b%y' } # remote leading and trailing spaces in a string taken from stdin trim() { @@ -329,52 +330,6 @@ is_online() { } -# opens and closes a ramdisk for temporary files -# users can do this explicitly between session to speed up operations -ramdisk() { - case $OS in - GNU) - # TODO - # not so urgent, since usually /dev/shm is mounted and writable - ;; - MAC) - case ${PARAM[1]} in - open) - mount | grep 'JaroTmp' > /dev/null - { test $? = 0 } && { - error "A Jaro Mail ramdisk is already open" - return 1 } - { test -z ${PARAM[2]} } && { size=10 } || { size=${PARAM[2]} } - act "Creating ramdisk of ${size}MB" - - # 2048 is a megabyte here - devsize=$((1024*2*$size)) - devname=`hdid -nomount ram://${devsize}` - act "Mounting ramdisk on $devname" - diskutil eraseVolume HFS+ JaroTmp `basename $devname` > /dev/null - { test $? != 0 } && { - error "Error initializing ramdisk" - hdiutil detach `basename $devname` - return 1 } - notice "Operation successful, ramdisk ready on /Volume/JaroTmp" - TMPRAM=1 - ;; - close) - devname=`mount | awk '/JaroTmp/ {print $1}'` - { test "$devname" = "" } && { - error "No ramdisk seems to be open" - return 1 } - act "Unmounting ramdisk: $devname" - diskutil unmount /Volumes/JaroTmp > /dev/null - hdiutil detach `basename $devname` > /dev/null - notice "Ramdisk succesfully detached" - TMPRAM=0 - ;; - esac - ;; - esac -} - human_size() { { test $1 -gt 0 } || { error "human_size() called with zero argument" @@ -467,3 +422,51 @@ cert() { } ###################### +# obsoleted + + +# opens and closes a ramdisk for temporary files +# users can do this explicitly between session to speed up operations +ramdisk() { + case $OS in + GNU) + # TODO + # not so urgent, since usually /dev/shm is mounted and writable + ;; + MAC) + case ${PARAM[1]} in + open) + mount | grep 'JaroTmp' > /dev/null + { test $? = 0 } && { + error "A Jaro Mail ramdisk is already open" + return 1 } + { test -z ${PARAM[2]} } && { size=10 } || { size=${PARAM[2]} } + act "Creating ramdisk of ${size}MB" + + # 2048 is a megabyte here + devsize=$((1024*2*$size)) + devname=`hdid -nomount ram://${devsize}` + act "Mounting ramdisk on $devname" + diskutil eraseVolume HFS+ JaroTmp `basename $devname` > /dev/null + { test $? != 0 } && { + error "Error initializing ramdisk" + hdiutil detach `basename $devname` + return 1 } + notice "Operation successful, ramdisk ready on /Volume/JaroTmp" + TMPRAM=1 + ;; + close) + devname=`mount | awk '/JaroTmp/ {print $1}'` + { test "$devname" = "" } && { + error "No ramdisk seems to be open" + return 1 } + act "Unmounting ramdisk: $devname" + diskutil unmount /Volumes/JaroTmp > /dev/null + hdiutil detach `basename $devname` > /dev/null + notice "Ramdisk succesfully detached" + TMPRAM=0 + ;; + esac + ;; + esac +} diff --git a/src/zlibs/maildirs b/src/zlibs/maildirs @@ -107,8 +107,8 @@ rmdupes() { tot=0 typeset -al msgs - _tmp_create - formail_cache="$JAROTMP" + ztmp + formail_cache=$ztmpfile for folder in ${=@}; do { test -r "$folder" } || { folder="$MAILDIRS/$folder" } diff --git a/src/zlibs/search b/src/zlibs/search @@ -236,7 +236,7 @@ search() { } backup() { - id=$datestamp.$RANDOM + id=`datestamp`.$RANDOM c=0 dst=${PARAM[1]} diff --git a/src/zlibs/zuper b/src/zlibs/zuper @@ -0,0 +1,410 @@ +#!/usr/bin/env zsh +# +# Zuper - Zsh Ultimate Programmer's Extensions Refurbished +# +# Copyright (C) 2015 Dyne.org Foundation +# +# Zuper is designed, written and maintained by Denis Roio <jaromil@dyne.org> +# +# This source code is free software; you can redistribute it and/or +# modify it under the terms of the GNU Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This source code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# Please refer to the GNU Public License for more details. +# +# You should have received a copy of the GNU Public License along with +# this source code; if not, write to: +# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +########################## +typeset -aU vars +typeset -aU arrs +vars=(debug quiet ztmpfile) +arrs=() + +debug=${debug:-0} +quiet=${quiet:-0} + +vars+=(zuper_version) +zuper_version=0.1 + +# Messaging function with pretty coloring +autoload colors +colors + + +vars+=(last_act last_func last_notice) + +function _msg() { + local msg="$2" + command -v gettext 1>/dev/null 2>/dev/null && msg="$(gettext -s "$2")" + for i in $(seq 3 ${#}); + do + msg=${(S)msg//::$(($i - 2))*::/$*[$i]} + done + + local command="print -P" + local progname="$fg[magenta]${TOMBEXEC##*/}$reset_color" + local message="$fg_bold[normal]$fg_no_bold[normal]$msg$reset_color" + local -i returncode + + case "$1" in + inline) + command+=" -n"; pchars=" > "; pcolor="yellow" + ;; + message) + last_act="$msg" + pchars=" . "; pcolor="white"; message="$fg_no_bold[$pcolor]$msg$reset_color" + ;; + verbose) + last_func="$msg" + pchars="[D]"; pcolor="blue" + ;; + success) + last_notice="$msg" + pchars="(*)"; pcolor="green"; message="$fg_no_bold[$pcolor]$msg$reset_color" + ;; + warning) + pchars="[W]"; pcolor="yellow"; message="$fg_no_bold[$pcolor]$msg$reset_color" + ;; + failure) + pchars="[E]"; pcolor="red"; message="$fg_no_bold[$pcolor]$msg$reset_color" + returncode=1 + ;; + print) + progname="" + ;; + *) + pchars="[F]"; pcolor="red" + message="Developer oops! Usage: _msg MESSAGE_TYPE \"MESSAGE_CONTENT\"" + returncode=127 + zerr + ;; + esac + ${=command} "${progname} $fg_bold[$pcolor]$pchars$reset_color ${message}$color[reset_color]" >&2 + return $returncode +} + +function _message say act() { + local notice="message" + [[ "$1" = "-n" ]] && shift && notice="inline" + [[ $quiet = 1 ]] || _msg "$notice" $@ + return 0 +} + +function _verbose xxx func() { + [[ $debug = 1 ]] && _msg verbose $@ + return 0 +} + +function _success yes notice() { + [[ $quiet = 1 ]] || _msg success $@ + return 0 +} + +function _warning no warn warning() { + [[ $quiet = 1 ]] || _msg warning $@ + return 0 +} + +function _failure fatal die error() { + # typeset -i exitcode=${exitv:-1} + [[ $quiet = 1 ]] || _msg failure $@ + return 1 +} + +function _print() { + [[ $quiet = 1 ]] || _msg print $@ + return 0 +} + + + +fn() { + local msg="$1" + command -v gettext 1>/dev/null 2>/dev/null \ + && msg="$(gettext -s "$1")" + for i in $(seq 2 ${#}); do + msg=${(S)msg//::$(($i - 1))*::/$*[$i]} + done + + fun="$msg" + func "$fun" + req=() + freq=() +} + +ckreq reqck() { + err=0 + for v in $req; do + [[ "${(P)v}" = "" ]] && { + warn "required setting is blank: $v" + err=1 + } + done + + [[ $err = 1 ]] && return $err + + for f in $freq; do + # exists and has size greater than zero + [[ -s $f ]] || { + warn "required file empty: $f" + err=1 + } + done + return $err +} + +zerr() { + error "error in: ${fun:-$last_notice}" + + [[ "$last_func" = "" ]] || warn "called in: $last_func" + [[ "$last_act" = "" ]] || warn "called in: $last_act" + [[ "$last_notice" = "" ]] || warn "called in: $last_notice" + [[ "$fun" = "" ]] || warn "called in: $fun" + error "error reported, operation aborted." + return 1 +} + +zdump() { + fn zdump + for v in $vars; do + print "$v \t ${(P)v}" + done + for a in $arrs; do + print "$a \t ${(P)a}" + done +} + + + +########################## +# Endgame handling + +arrs+=(destruens) + +# Trap functions for the endgame event +TRAPINT() { endgame INT; return $? } +# TRAPEXIT() { endgame EXIT; return $? } +TRAPHUP() { endgame HUP; return $? } +TRAPQUIT() { endgame QUIT; return $? } +TRAPABRT() { endgame ABORT; return $? } +TRAPKILL() { endgame KILL; return $? } +# TRAPPIPE() { endgame PIPE; return $? } +TRAPTERM() { endgame TERM; return $? } +TRAPSTOP() { endgame STOP; return $? } +# TRAPZERR() { func "function returns non-zero." } + + +endgame() { + fn "endgame $*" + + # execute all no matter what + TRAPZERR() { } + + # process registered destructors + for d in $destruens; do + fn "destructor: $d" + $d + done + return 0 +} + + +########################## +# Temp file handling + +# ztmp() fills in $ztmpfile global. Caller must copy that variable as +# it will be overwritten at every call. +ztmp() { + fn ztmp + + ztmpfile=`mktemp` + tmpfiles+=($ztmpfile) +} + +# All tempfiles are freed in endgame() +_ztmp_destructor() { + fn _ztmp_destructor + + for f in $tmpfiles; do + rm -f "$f" + done + tmpfiles=() +} + +arrs+=(tmpfiles) +destruens+=(_ztmp_destructor) + + +# optional: define zkv=1 on source + +[[ "$zkv" = "" ]] || { + + ########################## + # Key/Value file storage using ZSh associative maps + + zmodload zsh/system + + # load a map from a file + # map must be already instantiated with typeset -A by called + # name of map is defined inside the file + zkv.load() { + fn "zkv-load $*" + + file=$1 + [[ "$file" = "" ]] && { + error "zkv-open() missing argument: file-path" + zerr + return 1 } + [[ -r "$file" ]] || { + error "zkv-open() file not found $file" + zerr + return 1 } + [[ -s "$file" ]] || { + error "zkv-open() file is empty" + zerr + return 1 } + + source $file + } + + # save a map in a file + # $1 = name of the map associative array + # $2 = full path to the file + zkv.save() { + fn "zkv.save $*" + + _map=$1 + _path=$2 + [[ "$_path" = "" ]] && { + error "zkv.save() missing argument: map-name path-to-file" + zerr + return 1 + } + [[ -r $_path ]] && { + func "zkv.close() overwriting $_path" + func "backup turd left behind: ${_path}~" + mv $_path $_path~ + } + touch $_path + + # wondering about http://www.zsh.org/mla/users/2015/msg00286.html + # meanwhile solved using a double array, wasting a full map memcpy + _karr=(${(Pk)_map}) + _varr=(${(Pv)_map}) + _num="${#_karr}" + for c in {1..$_num}; do + # can also be cat here, however for speed we use builtins + # switch to cat if compatibility is an issue + sysread -o 1 <<EOF >> $_path +$_map+=("${_karr[$c]}" "${(v)_varr[$c]}") +EOF + done + func "$_num key/values stored in $_path" + } + +} + +# optional: define zconsul=1 on source + +[[ "$zconsul" = "" ]] || { + + ######## + # Consul + # there is a clear zsh optimization here in get/set kv + # using zsh/tcp instead of spawning curl + # and perhaps querying with one call using ?recursive + + zmodload zsh/net/tcp + + zconsul.set() { + fn "zconsul.set $*" + + # checks if consul running up to the caller + + _host=$1 # ip address + _port=${host[(ws@:@)2]:-8500} + _k=$2 # key name + _v=$3 # value + + req=(_host _port _k _v) + ckreq || return $? + + ztcp $_host $_port || { + zerr + return 1 + } + + _fd=$REPLY + # func "tcp open on fd $fd" + cat <<EOF >& $_fd +PUT /v1/kv/$_k HTTP/1.1 +User-Agent: Zuper/$zuper_version +Host: $_host:$_port +Accept: */* +Content-Length: ${#v} +Content-Type: application/x-www-form-urlencoded + +EOF + + print -n "$v" >& $_fd + + sysread -i $_fd _res + + # close connection + ztcp -c $_fd + + [[ "$_res" =~ "true" ]] || { + warn "cannot set key/value in consul: $_k = $_v" + zerr + return 1 + } + + return 0 + + } + + zconsul.get() { + fn "zconsul.get $*" + + _host=$1 # ip address + _port=${host[(ws@:@)2]:-8500} + _k=$2 # key name + _v=$3 # value + + req=(_host _port _k _v) + ckreq || return $? + + _k=$1 + + ztcp $_host $_port || { + zerr + return 1 + } + + _fd=$REPLY + + cat <<EOF >& $_fd +GET /v1/kv/$k HTTP/1.1 +User-Agent: Zuper/$zuper_version +Host: $_host:$_port +Accept: */* + +EOF + sysread -i $_fd -o 1 | awk -F: ' +/"Value":/ { gsub(/"|}]/,"",$7) ; print $7 }' | base64 -d + + # close connection + ztcp -c $_fd + + return 0 + + } + +} diff --git a/src/zlibs/zuper.init b/src/zlibs/zuper.init @@ -0,0 +1,16 @@ +########################## +# Zuper Init + +# initialize globals only after sourcing everything +# since zlibs may contain more variable declarations +for v in $vars; do + typeset -h $v +done +for a in $arrs; do + typeset -a $a +done +func "Zuper $zuper_version initialized" +func "${#vars} global variables registered" +func "${#arrs} global arrays registered" + +