mu-wizard

shell script to easily configure mu4e accounts on Emacs
git clone git://git.ckyln.com/mu-wizard
Log | Files | Refs | LICENSE

commit 8d6cddb4a7f7ce7f97a7952467f001b376b7dd7e
parent 96c0e7ac86d9d0546fbe11f40ac261fc854107aa
Author: Cem Keylan <cem@ckyln.com>
Date:   Sat,  6 Feb 2021 20:37:52 +0300

muw: prepare for initial release

ADDED:
- getoptions option parser
- mu-init subcommand: Initiate mu database
- sync subcommand: Move 'mailsync' script functionality to muw

CHANGED:
- Adding accounts no longer require interaction (with the exception of
  password generation)
- Purging/deleting accounts no longer require interaction
- Renamed 'share' subcommand to 'data'

Diffstat:
Mbin/muw | 563+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 491 insertions(+), 72 deletions(-)

diff --git a/bin/muw b/bin/muw @@ -1,8 +1,342 @@ #!/bin/sh -e +parser_definition() { + setup REST help:usage -- "usage: ${0##*/} [cmd] [option...]" + msg -- '' 'mu-wizard, a helper script for configuring mu4e accounts' '' + msg -- 'Commands:' + cmd_a add -- "Add and autoconfigure an email address" + cmd_a delete -- "Pick an account to delete" + cmd_a list -- "List configured accounts" + cmd_a purge -- "Purge all configuration" + cmd_a sync -- "Sync mail for accounts" + cmd mu-init -- "Run 'mu init' with the configured accounts" + cmd data -- "Output system data directory and exit" + msg -- '' "Run '${0##*/} COMMAND --help' to see help information for the" + msg -- "given command." + global_options + msg -- 'Note:' + msg -- 'Once at least one account is added, you can run' + msg -- "'${0##*/} sync' to begin downloading your mail." +} + +cmd_a() { + # Grab the first character of the subcommand, and add it to the option + # parser as a subcommand. + cmd "${1%${1#?}}" hidden:1; cmd "$@" +} + +parser_definition_add() { + setup REST help:usage -- "usage: ${0##*/} add [option...]" + msg -- '' 'Add and autoconfigure an email address' '' + msg -- 'Options:' + flag nocheck -c --disable-checks -- "Disable checking domains.csv for acquiring domain information" + flag nooverride -o --disable-overrides -- "Disable checking override files for acquiring domain information" + msg -- '' 'Any information not provided below, will be asked interactively.' + msg -- 'Accont options:' + param fulladdr -a --address var:EMAIL -- "Email address for account generation" + option maxmes -m --max-messages var:COUNT on:0 -- \ + "Maximum message COUNT to be kept offline, leave" "empty for unlimited." + param realname -r --real-name var:NAME -- "Your full name to be identified with the account" + param title -t --title var:TITLE -- "Unique account title" + option login -l --login var:USERNAME -- "Use your full address if empty or USERNAME for" \ + "your login name" + msg -- '' "Domain options: (setting either of these implies '-c -o')" + param imap -i --imap var:SERVER -- "IMAP server address" + param iport -I --imap-port var:PORT -- "IMAP server port" + param smtp -s --smtp var:SERVER -- "SMTP server address" + param sport -S --smtp-port var:PORT -- "SMTP server port" + global_options +} + +parser_definition_delete() { + setup REST help:usage -- "usage: ${0##*/} delete [option...] [account...]" + msg -- '' 'Delete account' '' + flag noconfirm -y -- "Don't confirm account deletion" + global_options +} + +parser_definition_list() { + global_options "usage: ${0##*/} list" "List configured accounts" +} +parser_definition_data() { + global_options "usage: ${0##*/} data" "Output system data directory" +} +parser_definition_sync() { + global_options "usage: ${0##*/} sync [account...]" "Sync mail for accounts" +} +parser_definition_mu() { + setup REST help:usage -- "usage: ${0##*/} mu-init" + msg -- '' 'Initiate/reinitate mu database' '' + msg -- 'Options:' + flag index -i -- "Index the mail directory after initiating the database" + global_options +} + +parser_definition_purge() { + setup REST help:usage -- "usage: ${0##*/} purge [option...]" + msg -- '' 'Purge all configuration' '' + msg -- 'Options:' + flag noconfirm -y -- "Don't confirm configuration purge" + flag keep_isync -i -- "Keep isync configuration" + flag keep_msmtp -m -- "Keep msmtp configuration" + global_options +} + +global_options() { + [ "$1" ] && setup REST help:usage -- "$1" '' "$2" + msg -- '' 'Global options:' + disp :version -v --version -- "Display version information" + disp :usage -h --help -- "Print this help message" + msg -- '' +} + +# shellcheck shell=sh disable=SC2016 +# [getoptions] License: Creative Commons Zero v1.0 Universal +getoptions() { + _error='' _on=1 _off='' _export='' _plus='' _mode='' _alt='' _rest='' + _flags='' _nflags='' _opts='' _help='' _abbr='' _cmds='' _init=@empty IFS=' ' + + _0() { echo "$@"; } + for i in 1 2 3 4 5; do eval "_$i() { _$((${i-}-1)) \" \$@\"; }"; done + + quote() { + q="$2'" r='' + while [ "$q" ]; do r="$r${q%%\'*}'\''" && q=${q#*\'}; done + q="'${r%????}'" && q=${q#\'\'} && q=${q%\'\'} + eval "$1=\${q:-\"''\"}" + } + code() { + [ "${1#:}" = "$1" ] && c=3 || c=4 + eval "[ ! \${$c:+x} ] || $2 \"\$$c\"" + } + kv() { eval "${2-}${1%%:*}=\${1#*:}"; } + loop() { [ $# -gt 1 ] && [ "$2" != -- ]; } + + invoke() { eval '"_$@"'; } + prehook() { invoke "$@"; } + for i in setup flag param option disp msg; do + eval "$i() { prehook $i \"\$@\"; }" + done + + args() { + on=$_on off=$_off export=$_export init=$_init _hasarg=$1 && shift + while loop "$@" && shift; do + case $1 in + -?) [ "$_hasarg" ] && _opts="$_opts${1#-}" || _flags="$_flags${1#-}" ;; + +?) _plus=1 _nflags="$_nflags${1#+}" ;; + [!-+]*) kv "$1" + esac + done + } + defvar() { + case $init in + @none) : ;; + @export) code "$1" _0 "export $1" ;; + @empty) code "$1" _0 "${export:+export }$1=''" ;; + @unset) code "$1" _0 "unset $1 ||:" "unset OPTARG ||:; ${1#:}" ;; + *) + case $init in @*) eval "init=\"=\${${init#@}}\""; esac + case $init in [!=]*) _0 "$init"; return 0; esac + quote init "${init#=}" + code "$1" _0 "${export:+export }$1=$init" "OPTARG=$init; ${1#:}" + esac + } + _setup() { + [ "${1#-}" ] && _rest=$1 + while loop "$@" && shift; do kv "$1" _; done + } + _flag() { args '' "$@"; defvar "$@"; } + _param() { args 1 "$@"; defvar "$@"; } + _option() { args 1 "$@"; defvar "$@"; } + _disp() { args '' "$@"; } + _msg() { args '' _ "$@"; } + + cmd() { _mode=@ _cmds="$_cmds${_cmds:+|}'$1'"; } + "$@" + cmd() { :; } + _0 "${_rest:?}=''" + + _0 "$2() {" + _1 'OPTIND=$(($#+1))' + _1 'while OPTARG= && [ $# -gt 0 ]; do' + [ "$_abbr" ] && getoptions_abbr "$@" + + args() { + sw='' validate='' pattern='' counter='' on=$_on off=$_off export=$_export + while loop "$@" && shift; do + case $1 in + --\{no-\}*) i=${1#--?no-?}; sw="$sw${sw:+|}'--$i'|'--no-$i'" ;; + --with\{out\}-*) i=${1#--with?out?-}; sw="$sw${sw:+|}'--with-$i'|'--without-$i'" ;; + [-+]? | --*) sw="$sw${sw:+|}'$1'" ;; + *) kv "$1" + esac + done + quote on "$on" + quote off "$off" + } + setup() { :; } + _flag() { + args "$@" + [ "$counter" ] && on=1 off=-1 v="\$((\${$1:-0}+\$OPTARG))" || v='' + _3 "$sw)" + _4 '[ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break' + _4 "eval '[ \${OPTARG+x} ] &&:' && OPTARG=$on || OPTARG=$off" + valid "$1" "${v:-\$OPTARG}" + _4 ';;' + } + _param() { + args "$@" + _3 "$sw)" + _4 '[ $# -le 1 ] && set "required" "$1" && break' + _4 'OPTARG=$2' + valid "$1" '$OPTARG' + _4 'shift ;;' + } + _option() { + args "$@" + _3 "$sw)" + _4 'set -- "$1" "$@"' + _4 '[ ${OPTARG+x} ] && {' + _5 'case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac' + _5 '[ "${OPTARG:-}" ] && { shift; OPTARG=$2; } ||' "OPTARG=$on" + _4 "} || OPTARG=$off" + valid "$1" '$OPTARG' + _4 'shift ;;' + } + valid() { + set -- "$validate" "$pattern" "$1" "$2" + [ "$1" ] && _4 "$1 || { set -- ${1%% *}:\$? \"\$1\" $1; break; }" + [ "$2" ] && { + _4 "case \$OPTARG in $2) ;;" + _5 '*) set "pattern:'"$2"'" "$1"; break' + _4 "esac" + } + code "$3" _4 "${export:+export }$3=\"$4\"" "${3#:}" + } + _disp() { + args "$@" + _3 "$sw)" + code "$1" _4 "echo \"\${$1}\"" "${1#:}" + _4 'exit 0 ;;' + } + _msg() { :; } + + [ "$_alt" ] && _2 'case $1 in -[!-]?*) set -- "-$@"; esac' + _2 'case $1 in' + _wa() { _4 "eval 'set -- $1' \${1+'\"\$@\"'}"; } + _op() { + _3 "$1) OPTARG=\$1; shift" + _wa '"${OPTARG%"${OPTARG#??}"}" '"$2"'"${OPTARG#??}"' + _4 "$3" + } + _3 '--?*=*) OPTARG=$1; shift' + _wa '"${OPTARG%%\=*}" "${OPTARG#*\=}"' + _4 ';;' + _3 '--no-*|--without-*) unset OPTARG ;;' + [ "$_alt" ] || { + [ "$_opts" ] && _op "-[$_opts]?*" '' ';;' + [ ! "$_flags" ] || _op "-[$_flags]?*" - 'OPTARG= ;;' + } + [ "$_plus" ] && { + [ "$_nflags" ] && _op "+[$_nflags]?*" + 'unset OPTARG ;;' + _3 '+*) unset OPTARG ;;' + } + _2 'esac' + _2 'case $1 in' + "$@" + rest() { + _4 'while [ $# -gt 0 ]; do' + _5 "$_rest=\"\${$_rest}" '\"\${$(($OPTIND-$#))}\""' + _5 'shift' + _4 'done' + _4 'break ;;' + } + _3 '--)' + [ "$_mode" = @ ] || _4 'shift' + rest + _3 "[-${_plus:++}]?*)" + case $_mode in [=#]) rest ;; *) _4 'set "unknown" "$1"; break ;;'; esac + _3 '*)' + case $_mode in + @) + _4 "case \$1 in ${_cmds:-*}) ;;" + _5 '*) set "notcmd" "$1"; break' + _4 'esac' + rest ;; + [+#]) rest ;; + *) _4 "$_rest=\"\${$_rest}" '\"\${$(($OPTIND-$#))}\""' + esac + _2 'esac' + _2 'shift' + _1 'done' + _1 '[ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }' + _1 'case $1 in' + _2 'unknown) set "Unrecognized option: $2" "$@" ;;' + _2 'noarg) set "Does not allow an argument: $2" "$@" ;;' + _2 'required) set "Requires an argument: $2" "$@" ;;' + _2 'pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;' + _2 'notcmd) set "Not a command: $2" "$@" ;;' + _2 '*) set "Validation error ($1): $2" "$@"' + _1 'esac' + [ "$_error" ] && _1 "$_error" '"$@" >&2 || exit $?' + _1 'echo "$1" >&2' + _1 'exit 1' + _0 '}' + + [ ! "$_help" ] || eval "shift 2; getoptions_help $1 $_help" ${3+'"$@"'} +} +# [getoptions_help] License: Creative Commons Zero v1.0 Universal +getoptions_help() { + _width='30,12' _plus='' _leading=' ' + + pad() { p=$2; while [ ${#p} -lt "$3" ]; do p="$p "; done; eval "$1=\$p"; } + kv() { eval "${2-}${1%%:*}=\${1#*:}"; } + sw() { pad sw "$sw${sw:+, }" "$1"; sw="$sw$2"; } + + args() { + _type=$1 var=${2%% *} sw='' label='' hidden='' && shift 2 + while [ $# -gt 0 ] && i=$1 && shift && [ "$i" != -- ]; do + case $i in + --*) sw $((${_plus:+4}+4)) "$i" ;; + -?) sw 0 "$i" ;; + +?) [ ! "$_plus" ] || sw 4 "$i" ;; + *) [ "$_type" = setup ] && kv "$i" _; kv "$i" + esac + done + [ "$hidden" ] && return 0 || len=${_width%,*} + + [ "$label" ] || case $_type in + setup | msg) label='' len=0 ;; + flag | disp) label="$sw " ;; + param) label="$sw $var " ;; + option) label="${sw}[=$var] " + esac + [ "$_type" = cmd ] && label=${label:-$var } len=${_width#*,} + pad label "${label:+$_leading}$label" "$len" + [ ${#label} -le "$len" ] && [ $# -gt 0 ] && label="$label$1" && shift + echo "$label" + pad label '' "$len" + for i; do echo "$label$i"; done + } + + for i in setup flag param option disp 'msg -' cmd; do + eval "${i% *}() { args $i \"\$@\"; }" + done + + echo "$2() {" + echo "cat<<'GETOPTIONSHERE'" + "$@" + echo "GETOPTIONSHERE" + echo "}" +} + out() { printf '%s\n' "$@" >&2 ;} err() { printf 'err: %s\n' "$@" >&2 ;} die() { err "$@"; exit 1 ;} +version() { printf 'mu-wizard version: %s\n' unreleased ;} +notify() { + notify-send -i mail-unread -a "mu-wizard" "$@" +} prompt() { # The first argument is used as the prompt, and will print as 'prompt: '. # Any other arguments will be printed before prompt if they exist. @@ -53,33 +387,44 @@ yesno() { } delete() { - rm -f "$accountdir/$title.el" - sed_i "/^IMAPStore $title-remote\$/,/^# End profile\$/d" "$HOME/.mbsyncrc" - sed_i "/^account $title\$/,/^# End profile\$/d" "$config_home/msmtp/config" + rm -f "$accountdir/$1.el" + sed_i "$HOME/.mbsyncrc" "/^IMAPStore $1-remote\$/,/^# End profile\$/d" + sed_i "$config_home/msmtp/config" "/^account $1\$/,/^# End profile\$/d" rm -f /tmp/mbsync-boxes } -contains() { - # Check whether a list contains the given value - # Example: 'contains "a" "a b c d e f"' returns 0 - case " $2 " in *" $1 "*) return 0; esac; return 1 +in_profiles() { + # Check whether the title is already a profile. + while read -r profile; do + [ "$profile" = "$1" ] && return 0 + done <<EOF +$(get_profiles) +EOF + return 1 } sed_i() { - # 'sed -i' like function without actually running it. This can be only used - # for a single file, and the filename should always come last. - eval "[ -f \$$# ] || return 1" - sed "$@" > _; for file; do :; done; mv _ "$file" + # POSIX compliant 'sed -i' like function. This can be only used for a single + # file, and the filename should always come first. + file=$1; shift + [ -f "$file" ] || die "$file: No such file or directory" + sed "$@" "$file" > _; cat _ > "$file" + rm -f _ } get_profiles() { # Function to get all available profiles - unset profiles - for profile in "$accountdir"/*.el; do - [ -f "$profile" ] || continue + eval=$1 + set -- + for profile in "$accountdir/"*.el; do profile=${profile##*/} - profiles="${profile%.el} $profiles" + set -- "$@" "${profile%.el}" done + if [ "$eval" ]; then + printf "'%s' " "$@" + else + printf '%s\n' "$@" + fi } msmtp_header() { @@ -98,10 +443,10 @@ pm_ask() { case ${pass_prog##*/} in pass) pass insert "$pmt" password_command="pass show $pmt" ;; - pash) trap 'delete; rm -f _' EXIT INT + pash) trap 'delete $title; rm -f _' EXIT INT sed 's/yn "Gen/false "Gen/g' "$(command -v pash)" >_ sh _ add "$pmt" - rm -f _; trap delete EXIT INT + rm -f _; trap 'delete $title' EXIT INT password_command="pash show $pmt" ;; pm) pass=$(prompt_noecho "Enter your password") pass2=$(prompt_noecho "Enter your password again") @@ -126,7 +471,7 @@ pm_del() { } test_connection() { - mkdir -p "$maildir/$title" + mkdir -p "$MAILDIR/$title" # Since we are "Flattening" the inbox structure by replacing '/' with '.', # we need to also replace it in the 'mbsync -l' output by hand. See the @@ -136,7 +481,7 @@ test_connection() { "or there are requirements for your account out of the control of mu-wizard." } | sed 's|/|.|g' > /tmp/mbsync-boxes - while read -r dir; do mkdir -p "$maildir/$title/${dir#/}"; done < /tmp/mbsync-boxes + while read -r dir; do mkdir -p "$MAILDIR/$title/${dir#/}"; done < /tmp/mbsync-boxes drafts=$(grep -i drafts /tmp/mbsync-boxes | sed 1q) trash=$(grep -i trash /tmp/mbsync-boxes | sed 1q) @@ -233,12 +578,13 @@ get_information() { sslcert=$file; break done - fulladdr=$(prompt_required Email \ - "Insert the email address that you want to configure for mu4e") + : "${fulladdr:=$(prompt_required Email \ + "Insert the email address that you want to configure for mu4e")}" # Check the override directory for possible alterations of the # configuration. If the override is found on the configuration dirctory, it # will be used instead. + [ "$nooverride" ] || for file in "$confdir/overrides/"* "$sharedir/overrides/"*; do [ -f "$sharedir/overrides/${fulladdr##*@}" ] && { domain=${fulladdr##*@} @@ -248,7 +594,8 @@ get_information() { } done - [ "$domain" ] || while IFS=, read -r domain imap iport smtp sport; do + [ "$nocheck" ] || [ "$domain" ] || + while IFS=, read -r domain imap iport smtp sport; do case "$domain" in "${fulladdr##*@}") break; esac done < "$confdir/domains.csv" @@ -259,44 +606,44 @@ get_information() { "IMAP: $imap:$iport" \ "SMTP: $smtp:$sport" else - imap=$(prompt Server "Insert the IMAP server for your email provider" \ - "(excluding the port number)") + : "${imap:="$(prompt Server "Insert the IMAP server for your email provider" \ + '(excluding the port number)')"}" - iport=$(prompt Port \ - "What is your server's IMAP port number? (Usually 993)") + : "${iport:=$(prompt Port \ + "What is your server's IMAP port number? (Usually 993)")}" - smtp=$(prompt Server "Insert the SMTP server for your email provider" \ - "(excluding the port number)") + : "${smtp:=$(prompt Server "Insert the SMTP server for your email provider" \ + "(excluding the port number)")}" - sport=$(prompt Port \ - "What is your server's SMTP port number? (Usually 587)") + : "${sport:=$(prompt Port \ + "What is your server's SMTP port number? (Usually 587)")}" fi - realname=$(prompt "Real name" \ - "Enter your full name you want to be identified on this account.") + : "${realname:=$(prompt "Real name" \ + "Enter your full name you want to be identified on this account.")}" - title=$(prompt_required "Account name" \ + : "${title:=$(prompt_required "Account name" \ "Enter a short, one-word identifier for this email account that will" \ - "distinguish them from any other accounts you add") + "distinguish them from any other accounts you add")}" - while contains "$title" "$profiles"; do + while in_profiles "$title"; do out "The title '$title' is already used." title=$(prompt_required "Account name" \ "Enter a short, one-word identifier for this email account that will" \ "distinguish them from any other accounts you add") done - login=$(prompt "Login" \ + : "${login:=$(prompt "Login" \ "If your account has a special username different from your address," \ - "insert it now. Otherwise leave this blank.") + "insert it now. Otherwise leave this blank.")}" - # If login is empty use fulladdr - [ "$login" ] || login="$fulladdr" + # If login is unspecified, use the full address + [ "${login:-1}" = 1 ] && login="$fulladdr" - maxmes=$(prompt "Maximum messages" \ + : "${maxmes=$(prompt "Maximum messages" \ "If you want to limit the number of messages kept offline to a number," \ - "enter it below. Otherwise leave this blank.") + "enter it below. Otherwise leave this blank.")}" case "$sport" in 465) starttlsoff="tls_starttls off"; esac } @@ -312,19 +659,29 @@ main() { mkdir -p "${config_home:=${XDG_CONFIG_HOME:-$HOME/.config}}" \ "${confdir:=$config_home/mu4e}" \ + "${cac_dir:=${XDG_CACHE_HOME:-$HOME/.cache}}" \ "${accountdir:=$confdir/accounts}" \ - "${maildir:=$HOME/.local/share/mail}" + "${MAILDIR:=${XDG_DATA_HOME:-$HOME/.local/share}/mail}" - get_profiles + export MAILDIR - case "$1" in - l|list) for profile in $profiles; do out " $profile"; done ;; + eval "$(getoptions parser_definition parse)" + parse "$@"; eval set -- "$REST" + action=$1; shift + case $action in a|add) + eval "$(getoptions parser_definition_add parse)" + parse "$@"; eval set -- "$REST" + + # If any domain information is provided, disable checking domains.csv and + # overrides. + test -n "$imap" -o -n "$iport" -o -n "$smtp" -o -n "$sport" && + nocheck=1 nooverride=1 get_domains get_information - trap delete INT EXIT - pmt="mu-wizard-$title" + trap 'delete $title' INT EXIT + pmt="muw/$fulladdr" while :; do pm_del; pm_ask && break; done set_mbsync test_connection @@ -336,41 +693,103 @@ main() { # so even if the user doesn't want to share the information in a git # repository, they can have it themselves when they may need it. - printf '%s,%s,%s,%s,%s' \ - "${fulladdr##*@}" "$imap" "$iport" "$smtp" "$sport" >> "$confdir/domains.csv" + printf '%s,%s,%s,%s,%s' "${fulladdr##*@}" "$imap" "$iport" "$smtp" "$sport" | + sort -uo "$confdir/domains.csv" "$confdir/domains.csv" - trap - INT EXIT - out "All done. You can now run 'mbsync $title' in order to sync this account." + out "All done. You can now run '${0##*/} sync $title' in order to sync this account." + ;; + l|list) + eval "$(getoptions parser_definition_list parse)" + parse "$@"; eval set -- "$REST" + get_profiles ;; - d|delete) - out "Pick a profile to be deleted" - for profile in $profiles; do out " $profile"; done - read -r title - contains "$title" "$profiles" || die "Profile '$title' doesn't exist." - yesno "Are you sure you want to delete '$title'?" && delete + eval "$(getoptions parser_definition_delete parse)" + parse "$@"; eval set -- "$REST" + [ "$1" ] || { + get_profiles + set -- "$(prompt Profile "Pick a profile to be deleted")" + } + for title; do + in_profiles "$title" || die "Profile '$title' doesn't exist." + yesno "Are you sure you want to delete '$title'?" || exit 1 + delete "$title" + done ;; p|purge) + eval "$(getoptions parser_definition_purge parse)" + parse "$@"; eval set -- "$REST" yesno "Are you sure you want to delete all account data?" || exit 1 rm -rf "$HOME/.mbsyncrc" "$confdir" "$config_home/msmtp" ;; - s|share) - # Output the share directory and exit. - printf '%s\n' "$sharedir" ;; - ''|--help|-h) - out "usage: ${0##*/} [action]" \ - "mu-wizard, auto-configure email accounts for mu4e" \ - " Options:" \ - " [a]dd: Add and autoconfigure an email address" \ - " [d]elete: Pick an account to delete" \ - " [l]ist: List configured accounts" \ - " [p]urge: Purge all configuration" - " [s]hare: See your share directory" "" \ - "NOTE: Once at least one account is added, you can run" \ - "'mbsync -a' to begin downloading mail." + s|sync) + lastsync='' + syncfile=$cac_dir/muw-lastsync + mu_format="$(printf 'f\ts')" + eval "$(getoptions parser_definition_sync parse)" + parse "$@"; eval set -- "$REST" + pgrep -x mbsync >/dev/null && die "mbsync is already running." + [ "$1" ] || eval set -- "$(get_profiles eval)" + for acc; do in_profiles "$acc" || die "Invalid account: '$acc'"; done + for acc; do mbsync "${acc##*/}" & done; wait + for acc; do + newcount=$( + if [ -f "$syncfile" ]; then + find "$MAILDIR/$acc/"*/new -type f -newer "$syncfile" + else + find "$MAILDIR/$acc/"*/new -type f + fi | wc -l + ) + [ "$newcount" -gt 0 ] && + notify "mu-wizard" "$newcount new mail in '$acc' account." + done + + [ -f "$syncfile" ] && + lastsync=d:$(date -r "$syncfile" "+%Y-%m-%dT%H:%M..") + + touch "$syncfile" + + # Update mu index, first try using Emacs and fallback to using mu itself + emacsclient -e "(mu4e-update-index)" >/dev/null 2>&1 || mu index + + # Send notification for new unread mails. Here is an explanation of + # what this does: + # + # --skip-dups - Skip duplicates + # 'g:u' - Find unread email + # '$lastsync' - Received after the last synchronization + # -s date --reverse - Reverse the emails by date + # -f "$format" - Get 'from' and 'subject' fields, tab seperated + mu find --skip-dups g:u "$lastsync" -s d --reverse -f "$mu_format" 2>/dev/null | + while IFS=$(printf '\t') read -r from subject; do + + # Only display the name, not the email address. Remove + # trailing whitespace, and quotation marks if there are any. + from=${from%%\<*} from=${from%"${from##*[!\ ]}"} + from=${from#\"} from=${from%\"} + + notify "$from" "$subject" + done ;; - *) die "Unknown action '$1'" + mu-init) + eval "$(getoptions parser_definition_mu parse)" + parse "$@"; set -- + for account in "$accountdir/"*.el; do + [ -f "$account" ] || continue + set -- "$@" "--my-address=$(sed -n 's|"[^"]*$||;/user-mail-address/s|^.*"||p' "$account")" + done + [ "$1" ] || die "No address could be found, did you add any accounts?" + mu init -m "$MAILDIR" "$@" + + # The 'index' variable is declared and assigned by the parser + # shellcheck disable=2154 + [ -z "$index" ] || mu index + ;; + data) + eval "$(getoptions parser_definition_data parse)" + parse "$@"; eval set -- "$REST" + printf '%s\n' "$sharedir" esac - } main "$@"