#!/bin/bash trap "exit 1" TERM export MYPID=$$ # ANSI stuff BOLD="\033[1m" ITALIC="\033[3m" STRIKE="\033[9m" PLAIN="\033[0m" UNDERLINE="\033[4m" RED="\033[31m" MAGENTA="\033[35m" GREEN="\033[32m" YELLOW="\033[33m" BLUE="\033[34m" CYAN="\033[36m" GREY="\033[2;37m" LINK="$BLUE$UNDERLINE" brack="$CYAN" brackfold="$CYAN" HILITE="\033[43;1m" GREYBG="\033[48;2;30;30;30m" TOPTITLE="\033[44;1m" LEFTTITLE="\033[31;1m" #FOLDBG="\033[100m" FOLDBG="$BOLDD" function die() { kill -s TERM $MYPID } function getalts() { local verb alt var x verb=$1 alt="" var="words_$verb" IFS='|' for x in ${!var}; do if [[ $x != $verb ]]; then [[ -z $alt ]] && alt=$x || alt="$alt,$x" fi done IFS=' ' echo "$alt" } function usage() { local titleformat format local usage_var desc_var format="%22s %-40s %s\n" echo "usage: $0 COMMAND [OPTIONS] [commandopts]" echo echo " -c Select calendar to use (use '$0 showcals' to list)" echo " -f Assume 'yes' to all questions." echo " -l xx Specify name of local vdirsyncer storage name (default: $DEFAULT_VDS_LOCAL)" echo " -r xx Specify name of remote vdirsyncer storage name (default: $DEFAULT_VDS_REMOTE)" echo " -s Auto-sync with server when complete." echo " -S Sort tasks and subtasks alphabetically" echo printf "$format" "COMMAND" "DESCRIPTION" "SYNONYMS" for x in $valid_modes; do usage_var="usage_$x" desc_var="desc_$x" printf "$format" "${!usage_var}" "${!desc_var}" "$(getalts $x)" done } function inc() { indent=$((indent + $indentamt)) } function dec() { indent=$((indent - $indentamt)) } function getuid_witherror() { # [noun] local x rv noun if [[ $$ -ge 2 ]]; then noun=$2 else noun="task" fi x=$(getuid $1) rv=$? if [[ $rv -ne 0 ]]; then error "Specified $noun #$1 does not exist." die fi echo "$x" return $rv } function getid() { local res id uid uid=$1 res="" for id in ${!idmap[@]}; do if [[ ${idmap[$id]} == $uid ]]; then res=${id} break fi done echo $res if [[ -z $res ]]; then return 1 fi return 0 } function getuid() { local res id id=$1 res=${idmap[$id]} if [[ -z $res ]]; then return 1 fi echo $res return 0 } function loadids() { local LINE n uid id max count count=0 if [[ ! -e $idfile ]]; then error "No ID file found (looking here: $idfile)" return 1 fi max=0 while read LINE; do uid=${LINE%:*} id=${LINE#*:} idmap[$id]=$uid if [[ -z $uid || -z $id ]]; then error "invalid idmapping line: '$LINE'" return 1 fi [[ $id -gt $max ]] && max=$id count=$((count + 1)) done < "$idfile" nextid=$((max + 1)) dblog "got $count IDs from $idfile" return 0 } function saveids() { local id verbose=0 local ess nids=0 [[ $1 == "-v" ]] && verbose=1 if [[ ${#idmap[@]} -gt 0 ]]; then cp /dev/null "$idfile" for id in ${!idmap[@]}; do #dblog "save to $idfile: uid ${idmap[$id]} id $id sum ${tasksum[$id]}" >> $idfile echo "${idmap[$id]}:$id" >> "$idfile" nids=$((nids + 1)) done [[ $nids -eq 1 ]] && ess="" || ess="s" [[ $verbose -eq 1 ]] && action "Saved $nids ID mapping$ess to $idfile." else [[ $verbose -eq 1 ]] && warn "No ID mappings found to save" return 1 fi return 0 } function dumpids() { local id for id in ${!idmap[@]}; do echo "$id --> ${idmap[$id]}" done } function loadtasks() { local f ess x thislev par parid show thissum thisdesc local verbose=0 maxid=-1 maxindent=-1 maxtaskwidth=-1 ntasks=0 while [[ $# -ge 1 ]]; do [[ $1 == "-v" ]] && verbose=1 && shift 1 done dblog "Loading tasks..." for f in "$dir"/*.vcf; do dblog "--> loading from $f" loadtask "$f" || return 1 ntasks=$((ntasks + 1)) done numwid=${#maxid} dblog "Calculating max indent level" # figure out max indent level for x in ${!taskuid[@]}; do show=1 par=${taskparent[$x]} if [[ ! -z $par ]]; then parid=$(getid $par) if [[ ${taskfolded[$parid]} -eq 1 ]]; then show=0 fi fi dblog " uid $x id [${taskid[$x]}] parent [$par] parent [$parid] show [$show]" if [[ $show -eq 1 ]]; then dblog " ->showing $x [${tasksum[$x]}]" thislev=$(getindentlevel $x) dblog " ->lv: $thislev" [[ $thislev -gt $maxindent ]] && maxindent=$thislev thissum=${tasksum[$x]} thisdesc=${taskdesc[$x]} [[ ${#thissum} -gt $maxtaskwidth ]] && maxtaskwidth=${#thissum} [[ ${#thisdesc} -gt $maxtaskwidth ]] && maxtaskwidth=${#thisdesc} dblog " ->sum: $thissum" fi done [[ $ntasks -eq 1 ]] && ess="" || ess="s" [[ $verbose -eq 1 ]] && action "Finished loading $ntasks task$ess." dblog "Finished loading $ntasks task$ess." return 0 } function getindentlevel() { ## $0 id local id thisid parentuid parentid level id=$1 thisid=$id #dblog "gil() id $id thisid $thisid" level=0 while [ ! -z $parentuid ] ; do parentuid=${taskparent[$thisid]} if [[ ! -z $parentuid ]]; then thisid=$(getid $parentuid) dblog " parent uid: [$parentuid] id: [$thisid]" level=$((level + 1)) fi done echo $level } function dblog() { [[ $DEBUG -eq 1 ]] && echo "$(date) ${FUNCNAME[1]}() $*" >/dev/stderr } function action() { echo -e "$BOLD$GREEN* $PLAIN$GREEN$*$PLAIN" } function warn() { echo -e "$BOLD${YELLOW}WARNING: $PLAIN$YELLOW$*$PLAIN" >/dev/stderr } function error() { echo -e "$BOLD${RED}ERROR: $PLAIN$RED$*$PLAIN" >/dev/stderr } function info() { [[ $AUTOYES -eq 1 ]] && return echo -e "$BOLD${CYAN}>> $PLAIN$CYAN$*$PLAIN" } function confirm() { local yn if [[ $AUTOYES -eq 1 ]]; then return 0 fi echo -en "$BOLD${MAGENTA}CONFIRM: $PLAIN$MAGENTA$* (y/N)? $PLAIN" read yn if [[ $yn == "y" ]]; then return 0 fi return 1 } function loadtask() { local uid sum checked folded desc file id parent res i cats dblog "open $1" file="$1" res=$(cat "$file" | awk -F: 'BEGIN { checked=0; folded=0; } /^UID:/ { printf("uid©%s©",$2); } /^SUMMARY:/ { sub("SUMMARY:",""); printf("sum©%s©",$0); } /^CATEGORIES:/ { sub("^CATEGORIES:",""); printf("cats©%s©",$0); } /^DESCRIPTION:/ { sub("DESCRIPTION:",""); printf("desc©%s©",$0); } /^RELATED-TO/ { sub("^RELATED-TO.*:",""); printf("parent©%s©",$0); } /^X-OC-HIDESUBTASKS:1/ { folded=1; } /^STATUS:COMPLETED/ { checked=1; } END { printf("checked©%d©folded©%d\n",checked,folded); }') IFS='©' read -ra tok <<< "$res" parent="" i=0 #dblog "res: '$res'" #for x in ${!tok[@]}; do #dblog "forloop tok $x is '${tok[$x]}'" #done # while [[ ! -z ${tok[$i]} ]]; do #dblog "tok $i is '${tok[$i]}'" # i=$((i + 1)) # done while [[ ! -z ${tok[$i]} ]]; do dblog "process token '${tok[$i]}'" if [[ ${tok[$i]} == "uid" ]]; then uid=${tok[$((i + 1))]} #nexti=$((i + 1)) #dblog "i=$i tok[i]=${tok[$i]} i+1=$((i + 1)) tok[i+1]=${tok[$((i + 1))]} uid $uid id $id" #dblog "i=$i tok[i]=${tok[$i]} i+1=$((i + 1)) tok[nexti]=${tok[$nexti]} uid $uid id $id" id=$(getid $uid) if [[ -z $id ]]; then id=$nextid idmap[$nextid]=$uid nextid=$((nextid + 1)) needsave=1 fi #dblog "i=$i tok[i]=${tok[$i]} uid $uid id $id" elif [[ ${tok[$i]} == "sum" ]]; then sum="${tok[$((i + 1))]}" elif [[ ${tok[$i]} == "cats" ]]; then cats="${tok[$((i + 1))]}" elif [[ ${tok[$i]} == "desc" ]]; then desc="${tok[$((i + 1))]}" elif [[ ${tok[$i]} == "parent" ]]; then parent="${tok[$((i + 1))]}" elif [[ ${tok[$i]} == "checked" ]]; then checked="${tok[$((i + 1))]}" elif [[ ${tok[$i]} == "folded" ]]; then folded="${tok[$((i + 1))]}" fi i=$((i + 2)) done IFS=' ' taskuid[$id]=$uid taskid[$id]=$id tasksum[$id]=$sum taskcats[$id]=$cats taskdesc[$id]=$desc taskchecked[$id]=$checked taskfolded[$id]=$folded taskparent[$id]=$parent if [[ $DEBUG -eq 1 ]]; then echo "taskuid[$id]=$uid" echo "taskid[$id]=$id" echo "tasksum[$id]=$sum" echo "taskcats[$id]=$cats" echo "taskdesc[$id]=$desc" echo "taskchecked[$id]=$checked" echo "taskfolded[$id]=$folded" echo "taskparent[$id]=$parent" fi [[ $id -gt $maxid ]] && maxid=$id [[ ${#sum} -gt $taskwid ]] && taskwid=${#sum} [[ ${#desc} -gt $taskwid ]] && taskwid=${#desc} if [[ -z $uid ]]; then error "task has no uid:" echo "$res" return 1 fi return 0 } function marknotdone() { local id uid rv file sedrv local children childuid ischild noun uid=$1 id=$(getid $uid) [[ $# -gt 1 ]] && ischild=1 || ischild=0 [[ $ischild -eq 1 ]] && noun=subtask || noun=task # already not done? if [[ ${taskchecked[$id]} -eq 0 ]]; then error "$noun #${id} is already unfinished" return 1 fi file="$dir/$uid.vcf" sed -i '/^COMPLETED:/d;/^STATUS:COMPLETED/d;/PERCENT-COMPLETE:100/d' "$file" sedrv=$((sedrv + $?)) set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then action "Marked $noun #${id} as unfinished." processed="$processed ${uid} " loadtask "$file" rv=0 children=$(getchildren $uid) for childuid in ${children}; do marknotdone $childuid noprint rv=$((rv + $?)) done else failed="$failed ${uid} " error "failed to mark $noun #${id} as unfinished" rv=1 fi return $rv } function delq_clear() { deluids="" } function delq_add() { local x if [[ ! $deluids == *\ $1\ * ]]; then deluids="$deluids $1" for x in $(getchildren $1); do delq_add $x done fi } function delq_process() { local uidlist uid rv yn file count ess wantshow local od rv=0 # Remove duplicates deluids=$(echo $deluids | tr ' ' '\n' | sort -u | tr '\n' ' ') info "The following tasks will be removed:" needtitle=0 for uid in $deluids; do wantshow=0 id=$(getid $uid) # don't show if it's a subtask of one already in the list if [[ -z ${taskparent[$id]} ]]; then wantshow=1 elif [[ $deluids == *${taskparent[$id]}\ * ]]; then wantshow=0 else wantshow=1 fi if [[ $wantshow -eq 1 ]]; then showit $uid | sed -e 's/^/ /' fi done confirm "Really remove these tasks" if [[ $? -eq 0 ]]; then count=0 for uid in $deluids; do file="$dir/$uid.vcf" rm -f "$file" id=$(getid $uid) unset idmap[$id] count=$((count + 1)) done saveids [[ $count -eq 1 ]] && ess="" || ess="s" action "$count task$ess removed." else action "Aborted." rv=1 fi return $rv } function set_lastmodified() { local uid file now uid=$1 file="$dir/$uid.vcf" now=$(date -u +'%Y%m%dT%H%M%SZ') sed -i "s/^LAST-MODIFIED:.*/LAST-MODIFIED:$now/" "$file" return $? } function move_left() { # move_left uid local uid rv parent parentid parent2 sedrv local id uid=$1 id=$(getid $uid) rv=0 parent=${taskparent[$id]} if [[ -z $parent ]]; then error "Can't shift task #${id} any further left." exit 1 fi parentid=$(getid ${taskparent[$id]}) parent2=${taskparent[$parentid]} taskparent[$id]="$parent2" file="$dir/$uid.vcf" sed -i "s/^RELATED-TO.*/RELATED-TO;RELTYPE=PARENT:${taskparent[$id]}/" "$file" sedrv=$? set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then action "Shifted task #${id} upwards/left." loadtask "$file" showit ${taskparent[$id]} $uid rv=0 else error "Failed to shift task #${id} left." rv=1 fi return $rv } function move_right() { # move_right uid new_parent local uid rv newparent sedrv local id newparentid str toadd uid=$1 id=$(getid $uid) rv=0 newparent=$2 if [[ $newparent == "-" ]]; then newparent="" newparentid="" else newparentid=$(getid $newparent) fi rv=0 taskparent[$id]="$newparent" file="$dir/$uid.vcf" if [[ -z $newparent ]]; then sed -i "/^RELATED-TO.*/d" "$file" sedrv=$? else if grep -q ^RELATED-TO "$file"; then sed -i "s/^RELATED-TO.*/RELATED-TO;RELTYPE=PARENT:${taskparent[$id]}/" "$file" sedrv=$? else str="END:VTODO" toadd="RELATED-TO;RELTYPE=PARENT:${taskparent[$id]}" sed -i "/$str/i $toadd\n" "$file" sedrv=$? fi fi set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then if [[ -z $newparent ]]; then str="to toplevel." else str="underneath #${newparentid} ('${tasksum[$newparentid]}')." fi action "Shifted task #${id} $str" loadtask "$file" showit ${taskparent[$id]} $uid rv=0 else if [[ -z $newparent ]]; then str="to toplevel." else str="under #${newparentid}." fi error "Failed to shift task #${id} $str" rv=1 fi return $rv } function setnote() { # setnote uid new note goes here local uid newnote sedrv rv file str local id uid=$1 id=$(getid $uid) rv=0 shift newnote="$*" file="$dir/$uid.vcf" if grep -q ^DESCRIPTION: "$file"; then sed -i "s/^DESCRIPTION:.*/DESCRIPTION:$newnote/" "$file" sedrv=$? else str="END:VTODO" toadd="DESCRIPTION:$newnote" sed -i "/$str/i $toadd\n" "$file" sedrv=$? fi set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then if [[ -z $newnote ]]; then action "Removed note from task #${id}." else action "Set note of task #${id} to '$newnote'." fi loadtask "$file" rv=0 else error "Failed to set note of task #${id}." rv=1 fi return $rv } function modtag() { # setnote uid add|del new tag goes here local uid arg sedrv rv file str local id action x catarr newcat uid=$1 id=$(getid $uid) rv=0 shift action=$1 shift arg="$*" file="$dir/$uid.vcf" if [[ -z $arg ]]; then if [[ $action == "del" ]]; then sed -i "/^CATEGORIES:/d" "$file" sedrv=$? else error "No tag specified to add." return 1 fi else IFS="," catarr=( ${taskcats[$id]} ) IFS=" " for x in ${!catarr[@]} ; do if [[ $action == "add" || ${catarr[$x]} != $arg ]]; then [[ -z $newcat ]] && newcat="${catarr[$x]}" || newcat="$newcat,${catarr[$x]}" fi done if [[ $action == "add" ]]; then [[ -z $newcat ]] && newcat="${arg}" || newcat="$newcat,$arg" fi if grep -q ^CATEGORIES: "$file"; then sed -i "s/^CATEGORIES:.*/CATEGORIES:${newcat}/" "$file" sedrv=$? else str="END:VTODO" toadd="CATEGORIES:${newcat}" sed -i "/$str/i $toadd\n" "$file" sedrv=$? fi fi set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then if [[ $action == "del" ]]; then if [[ -z $arg ]]; then action "Removed all tags from task #${id}." else action "Removed tag '$arg' from task #${id}." fi else action "Added tag '$arg' to task #${id}." fi loadtask "$file" rv=0 else error "Failed to set/remove tag on task #${id}." rv=1 fi return $rv } function rename() { # rename uid new name goes here local uid newname sedrv rv file local id uid=$1 id=$(getid $uid) rv=0 shift newname="$*" file="$dir/$uid.vcf" sed -i "s/^SUMMARY:.*/SUMMARY:$newname/" "$file" sedrv=$? set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then action "Renamed task #${id} to '$newname'." loadtask "$file" rv=0 else error "failed to rename task #${id}." rv=1 fi return $rv } function toggle() { local uid id rv children uid=$1 id=$(getid $uid) # already done this one? if [[ $processed == *\ $uid\ * || $failed == *\ $uid\ * ]]; then return 1 fi # not a parent? children=$(getchildren $uid) if [[ -z ${children} ]]; then error "Can't fold/unfold task #${id} with no children." return 1 fi if [[ ${taskfolded[$id]} -eq 1 ]]; then unfold $uid rv=$? else fold $uid rv=$? fi return $rv } function fold() { local uid id sedrv rv file str local children uid=$1 id=$(getid $uid) # not a parent? children=$(getchildren $uid) if [[ -z ${children} ]]; then error "Can't fold task #${id} with no children." return 1 fi # already folded? if [[ ${taskfolded[$id]} -eq 1 ]]; then error "Task #${id} is already folded." return 1 fi file="$dir/$uid.vcf" str="END:VTODO" if grep -q ^X-OC-HIDESUBTASKS: "$file" ; then sed -i "s/X-OC-HIDESUBTASKS:.*/X-OC-HIDESUBTASKS:1/" "$file" sedrv=$? else sed -i "/$str/i X-OC-HIDESUBTASKS:1" "$file" sedrv=$? fi set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then action "Folded task #${id}." processed="$processed ${uid} " loadtask "$file" rv=0 else failed="$failed ${uid} " error "failed to fold task #${id}" rv=1 fi return $rv } function unfold() { local uid id sedrv rv file str local children uid=$1 id=$(getid $uid) # not a parent? children=$(getchildren $uid) if [[ -z ${children} ]]; then error "Can't unfold task #${id} with no children." return 1 fi # not folded? if [[ ${taskfolded[$id]} -ne 1 ]]; then error "Task #${id} is not folded." return 1 fi file="$dir/$uid.vcf" str="END:VTODO" if grep -q ^X-OC-HIDESUBTASKS: "$file" ; then sed -i "s/X-OC-HIDESUBTASKS:.*/X-OC-HIDESUBTASKS:0/" "$file" sedrv=$? else sed -i "/$str/i X-OC-HIDESUBTASKS:0" "$file" sedrv=$? fi set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then action "Unfolded task #${id}." processed="$processed ${uid} " loadtask "$file" rv=0 else failed="$failed ${uid} " error "failed to unfold task #${id}" rv=1 fi return $rv } function markdone() { local uid sedrv rv file toadd1 toadd2 toadd3 str now local children childuid ischild noun id uid=$1 id=$(getid $uid) [[ $# -gt 1 ]] && ischild=1 || ischild=0 [[ $ischild -eq 1 ]] && noun=subtask || noun=task # already complete? if [[ ${taskchecked[$id]} -eq 1 ]]; then error "$noun #${id} is already completed" return 1 fi str="END:VTODO" now=$(date -u +'%Y%m%dT%H%M%SZ') toadd1="STATUS:COMPLETED" toadd2="PERCENT-COMPLETE:100" toadd3="COMPLETED:$now" file="$dir/$uid.vcf" sed -i "/$str/i $toadd1\n$toadd2\n$toadd3" "$file" sedrv=$? set_lastmodified $uid sedrv=$((sedrv + $?)) if [[ $sedrv -eq 0 ]]; then action "Marked $noun #${id} as completed." processed="$processed ${uid} " loadtask "$file" rv=0 children=$(getchildren $uid) for childuid in ${children}; do markdone $childuid noprint rv=$((rv + $?)) done else failed="$failed ${uid} " error "failed to mark $noun #${id} as completed" rv=1 fi return $rv } function viewtask() { local uid id file uid=$1 id=$(getid $uid) file="$dir/$uid.vcf" action "Viewing task #$id ($file):" cat "$file" | sed -e 's/^/ /' return 0 } function randomuid() { local dupe uid dupe=1 while [[ $dupe -eq 1 ]]; do uid=$(( $RANDOM % 4 + 1)) uid="$uid$(( $RANDOM % 5 + 1))" while [[ ${#uid} -lt 19 ]]; do uid="$uid$(( $RANDOM % 9 + 1))" done dupe=0 for x in ${!taskuid[@]}; do if [[ $x == $uid ]]; then dupe=1 break fi done done echo $uid } function addtask() { local uid parent parentid parentuid file thisid if [[ $1 == "-p" ]]; then parentuid=$2 parentid=$(getid $parentuid) shift 2 else parentuid="" fi uid=$(randomuid) thisid=$nextid idmap[$thisid]=$uid taskid[$thisid]=$thisid taskuid[$thisid]=$uid tasksum[$thisid]="$*" taskdesc[$thisid]="" taskchecked[$thisid]=0 taskfolded[$thisid]=0 taskparent[$thisid]=$parentuid file="$dir/$uid.vcf" now=$(date -u +'%Y%m%dT%H%M%SZ') cat >"$file" <>"$file" fi cat >>"$file" < ${PLAIN}" read url if [[ ! $url =~ ^http.* ]]; then error "Provided URL 'url' appears invalid." return 1 fi echo -en "${BOLD}${MAGENTA}Caldav username: ${PLAIN}" read u echo -en "${BOLD}${MAGENTA}Caldav password: ${PLAIN}" read -s p cat >"${vdirsyncconfig}" </dev/null) count=$(echo "$cals" | wc -l | tr -d ' ') if [[ $count -eq 0 ]]; then action "Running initial vdirsyncer discover..." vdirsyncer discover action "Running initial vdirsyncer sync..." vdirsyncer sync fi getcals count cals_arr makedir "$confdir" || return 1 if [[ -e "${allcalsdir}" ]] ; then warn "Calendar symlink ${allcalsdir} already exists. Not overwriting it." else ln -s "${vdircaldir}" "${allcalsdir}" action "Created symlink to $vdircaldir" fi if [[ ! -e "${defaultcalfile}" ]] ; then selcal="" if [[ $count -gt 0 ]]; then local sel echo for d in ${!cals_arr[@]}; do echo -e "${MAGENTA}${BOLD}$((d + 1)). ${PLAIN}${MAGENTA}${cals_arr[$d]}${PLAIN}" done sel=-1 while [[ $sel -le 0 || $sel -gt ${#cals_arr[@]} ]]; do echo -en "${MAGENTA}${BOLD}Select calendar: ${PLAIN}" read sel done selcal=${cals_arr[$(($sel - 1))]} echo "$selcal" >"$defaultcalfile" else warn "No calendars found." fi fi if [[ -z $selcal ]]; then warn "No calendar selected " echo "${YELLOW}Please put default calendar name in ${defaultcalfile}" echo "${YELLOW}then: touch \"${idfile}_\$(cat $defaultcalfile)\"" selcal="" else selcal=${selcal// /_} if [[ -e "${idfile}_${selcal}" ]]; then warn "ID mapping file for $selcal already exists. Not overwriting it." else touch "${idfile}_${selcal}" fi fi info "Initialisation complete" [[ -z $selcal ]] && selcal="" echo -e "${CYAN} vdirsyncer config is in ${BOLD}${vdirsyncdir}${PLAIN}" echo -e "${CYAN} task_cli config is in ${BOLD}${confdir}${PLAIN}" echo -e "${CYAN} selected calendar is '${BOLD}${selcal}${PLAIN}'" echo } function tasksync() { local res upcount downcount noun if [[ $1 == "-a" ]]; then noun="Auto-sync" else noun="Sync" fi res=$(${VDIRSYNCER} sync 2>&1) rv=$? if [[ $rv -eq 0 ]]; then upcount=$(echo "$res" | grep -v ^Sync | grep -c ${VDS_REMOTE}) downcount=$(echo "$res" | grep -v ^Sync | grep -c ${VDS_LOCAL}) [[ $upcount -eq 1 ]] && upess="" || upess="s" [[ $downcount -eq 1 ]] && downess="" || downess="s" if [[ $upcount -eq 0 && $downcount -eq 0 ]]; then action "$noun complete - no changes." elif [[ $upcount -eq 0 ]]; then action "$noun complete - $downcount change$downess pulled." elif [[ $downcount -eq 0 ]]; then action "$noun complete - $upcount change$upess pushed." else action "$noun complete - $upcount change$upess pushed, $downcount change$downess pulled." fi else error "$noun failed. Output:" echo "$res" | sed -e 's/^/ /' fi } function getcals() { # countvar arrayvar local loc_cals loc_count thiscal local countvar arrayvar countvar=$1 arrayvar=$2 loc_cals=$(ls -1q $vdircaldir 2>/dev/null) loc_count=$(echo "$loc_cals" | wc -l | tr -d ' ') eval "$countvar=$loc_count" eval "$arrayvar=()" while read thiscal; do eval "$arrayvar+=(\"$thiscal\")" done <<< "$loc_cals" } function getcalname() { # cal_name_or_number local lc larr d found="" getcals lc larr for d in ${!larr[@]}; do if [[ ${larr[$d]} == $1 ]]; then found="${larr[$d]}" break elif [[ $1 =~ ^[0-9]+$ && $d -eq $(($1 - 1)) ]]; then # make this the default found="${larr[$d]}" break fi done echo "$found" if [[ -z $found ]]; then return 1 fi return 0 } # Should be in config file: #basedir=/Users/rpearce/scripts/t #dir=$basedir/docs #idfile=$basedir/idmappings.txt #VDS_LOCAL=$cal_local #VDS_REMOTE=$cal_remote #VDIRSYNCER=/usr/local/bin/vdirsyncer #VDS_BASEDIR="~/.config/vdirsyncer" #VDS_CONFIG="${VDS_BASEDIR}/config" #VDS_STATUSDIR="${VDS_BASEDIR}/status" #VDS_CALDIR="${VDS_BASEDIR}/calendar" # Base vdirsycner config in ${VDS_CONFIG}: #[general] #status_path = "${VDS_STATUSDIR}/" # #[pair cal] #a = "${VDS_LOCAL}" #b = "${VDS_REMOTE}" #collections = ["from a", "from b"] # #[storage ${VDS_LOCAL}] #type = "filesystem" #path = "${VDS_CALDIR}/" #fileext = ".vcf" # #[storage ${VDS_REMOTE}] #type = "caldav" #url = "https://tasks.haven.family/caldav.php/haven/" #username = "xxx" #password = "xxx" ### # # # Ask: # - where is this script located? # - remote<->local task id mapping file? [default: $thisdir/idmappings.txt ] # - url for your remote caldav # - username for your remote caldav # - password for your remote caldav # - vdirsyncer dir [default ${HOME}/.config/vdirsyncer] # - vdirsyncer status dir [default ${vdirsyncer_dir/status} # - vdirsyncer config file [default ${vdirsyncer_dir}/config] # - vdirsyncer calendar dir [default ${vdirsyncer_dir/calendar} ###### - vdirsyncer name for your local calender [default cal_local] ###### - vdirsyncer name for your remote calender [default cal_remote] DEFAULT_VDS_LOCAL=cal_local DEFAULT_VDS_REMOTE=cal_remote basedir=/Users/rpearce/code/task_cli confdir="${HOME}/.task_cli" allcalsdir="$confdir/allcals" # symlink to vdirsyncer/calendar/ defaultcalfile="$confdir/defaultcal" idfile="$confdir/idmappings" nextid=1 indent=0 indentamt=4 needsave=0 mode=list modes_that_support_all="" passive_modes="" # modes that do not change data vdirsyncdir="${HOME}/.config/vdirsyncer" vdircaldir="${HOME}/.config/vdirsyncer/calendar" vdirstatdir="${HOME}/.config/vdirsyncer/status" vdirsyncconfig="${vdirsyncdir}/config" # for pager printed=0 rows=$(tput lines) addcmd "list" "list [id1] .. [idX]" "List [just the specified] tasks" "list|ls|show" SUPPORTS_ALL PASSIVE addcmd "fold" "fold " "Fold a parent task (hide its children)" "fold|f|zc" SUPPORTS_ALL addcmd "unfold" "unfold " "Unfold a parent task (show its children)" "unfold|u|zo" SUPPORTS_ALL addcmd "toggle" "toggle " "Fold/Unfold a parent task" "toggle|z|zz" SUPPORTS_ALL addcmd "done" "done " "Complete a task" "done|x|complete" SUPPORTS_ALL addcmd "notdone" "notdone " "Uncomplete a task" "notdone|o|incomplete|uncomplete|clear" SUPPORTS_ALL addcmd "add" "add [parent] " "Add a new task [as subtask of parent]" "a|add|new|create" addcmd "del" "del [id1] .. [idX]" "Delete given task(s)" "del|rm|delete" addcmd "cleanup" "cleanup" "Delete all completed tasks" "cleanup|clean|flush|dc" addcmd "rename" "rename " "Rename given task" "rename" addcmd "left" "left " "Decrease indent of given task" "left|h|out|up" addcmd "right" "right " "Move task below the given parent" "right|l|mv|in" addcmd "note" "note " "Change notes for given task" "note|desc|description|comment" addcmd "tag" "tag " "Add a tag to the given task" "tag|t" addcmd "untag" "untag " "Remove a tag from the given task" "untag|ut" addcmd "sync" "sync" "Sync tasks using vdirsyncer" "sync" addcmd "view" "view " "Show detailed info for given task" "v|view|info|vcal" PASSIVE addcmd "showcals" "showcals" "List available calendars" "cals|showcals|cls" PASSIVE addcmd "setcal" "setcal" "Set the default calendar." "setcal|sc" DEBUG=0 TESTMODE=0 AUTOSYNC=0 CALID=$(cat "$confdir/defaultcal") WANTSORT=0 USEPAGER=1 ARGS="fhic:dnsStyl:r:" while getopts "$ARGS" i; do case "$i" in h) usage; exit 1; ;; i) init exit $? ;; c) CALID="$OPTARG" ;; l) VDS_LOCAL="$OPTARG" ;; n) USEPAGER=0 ;; r) VDS_REMOTE="$OPTARG" ;; d) DEBUG=1 ;; s) AUTOSYNC=1 ;; S) WANTSORT=1 ;; t) DEBUG=1 TESTMODE=1 ;; y|f) AUTOYES=1 ;; *) error "invalid argument: $i"; usage; exit 1 ;; esac done shift $((OPTIND - 1)) # Make sure vdirsyncer is installed if ! which -s vdirsyncer; then error "Can't find ${BOLD}vdirsyncer${PLAIN}${RED} - please install it." return 1 fi VDIRSYNCER=$(which vdirsyncer) # validate CALID foundcal=$(getcalname "$CALID") if [[ -n $foundcal ]]; then CALID="$foundcal" else error "Calendar '$CALID' does not exist." exit 1 fi dir=$allcalsdir/$CALID idfile="$confdir/idmappings_${CALID// /_}" if [[ ! -e $idfile ]]; then warn "No ID mapping file found for calendar '$CALID' ($idfile)" confirm "Create one from existing tasks?" if [[ $? -eq 0 ]]; then loadtasks -v saveids -v || exit 1 else die fi fi [[ -z $VDS_LOCAL ]] && VDS_LOCAL="$DEFAULT_VDS_LOCAL" [[ -z $VDS_REMOTE ]] && VDS_REMOTE="$DEFAULT_VDS_REMOTE" mode="" for x in $valid_modes; do thisone="words_$x" tomatch=${!thisone} if [[ $1 =~ ^(${tomatch})$ ]]; then mode=$x shift break fi done if [[ -z $mode ]]; then mode=list fi # Commands that don't need tasks loaded if [[ $mode == "sync" ]]; then tasksync exit $rv fi loadids || exit 1 loadtasks || exit 1 [[ $TESTMODE -eq 1 ]] && exit 1 [[ $needsave -eq 1 ]] && saveids # single-task commands if [[ $mode == "add" ]]; then if [[ $1 =~ ^[0-9]+$ ]]; then parentid=$1 parentuid=$(getuid_witherror $parentid "parent task") shift 1 else parentid="" parentuid="" fi newtasksum="$*" if [[ -z $parentuid ]]; then addtask "$newtasksum" else addtask -p $parentuid "$newtasksum" fi elif [[ $mode == "showcals" ]]; then getcals count cals_arr if [[ $count -gt 0 ]]; then echo -e "${BOLD}${MAGENTA}Available Calendars:$PLAIN" for d in ${!cals_arr[@]}; do echo -en "${MAGENTA}${BOLD}$((d + 1)). ${PLAIN}${MAGENTA}${cals_arr[$d]}${PLAIN}" [[ ${cals_arr[$d]} == $CALID ]] && echo -e "$BOLD$MAGENTA <- default$PLAIN" || echo done else error "No calendars found." fi elif [[ $mode == "setcal" ]]; then if [[ -z $1 ]]; then error "No calendar name provided." exit 1 fi foundcal=$(getcalname $1) if [[ -n $foundcal ]]; then action "Default calendar set to '${foundcal}'" echo "${foundcal}" > "$defaultcalfile" else error "Calendar '$1' not found" fi elif [[ $mode == "rename" ]]; then uid=$(getuid_witherror $1) shift rename $uid "$*" elif [[ $mode == "note" ]]; then uid=$(getuid_witherror $1) shift setnote $uid "$*" elif [[ $mode == "tag" ]]; then uid=$(getuid_witherror $1) shift modtag $uid add "$*" elif [[ $mode == "untag" ]]; then uid=$(getuid_witherror $1) shift modtag $uid del "$*" elif [[ $mode == "left" ]]; then uid=$(getuid_witherror $1) shift move_left $uid elif [[ $mode == "right" ]]; then pid=$BASH_ARGV if [[ $puid != "-" ]]; then puid=$(getuid_witherror $pid "parent task") fi while [[ $# -gt 1 ]]; do uid=$(getuid_witherror $1) move_right $uid $puid shift done elif [[ $mode == "view" ]]; then uid=$(getuid_witherror $1) viewtask $uid elif [[ $mode == "cleanup" ]]; then action "Deleting all completed tasks." for id in ${!taskuid[@]}; do uid=$(getuid $id) [[ ${taskchecked[$id]} -eq 1 ]] && delq_add $uid done delq_process # show results if [[ ! -z $processed ]]; then for uid in ${processed}; do showit $uid done fi else # multi-task commands filter=$* uids="" if [[ -z $filter ]]; then #for id in ${!taskid[@]}; do # [[ -z ${taskparent[$id]} ]] && uids="$uids ${taskuid[$id]}" #done filter=all fi if [[ $filter == "all" ]]; then na=0 if [[ ! $mode =~ $modes_that_support_all ]]; then error "Command '$mode' doesn't support 'all'" exit 1 fi kids="" for id in ${!taskid[@]}; do match=0 uid=$(getuid $id) children=$(getchildren $uid) [[ -z ${taskparent[$id]} ]] && hasparent=0 || hasparent=1 [[ -z $children ]] && haschildren=0 || haschildren=1 if [[ $mode == "fold" && $haschildren -eq 1 && ${taskfolded[$id]} -ne 1 ]]; then match=1 kids="--nochildren" elif [[ $mode == "unfold" && ${taskfolded[$id]} -eq 1 ]]; then match=1 kids="--nochildren" elif [[ $mode == "done" && ${taskchecked[$id]} -eq 0 ]]; then match=1 elif [[ $mode == "notdone" && ${taskchecked[$id]} -eq 1 ]]; then match=1 elif [[ $mode == "list" && $hasparent -eq 0 ]]; then match=1 fi if [[ $match -eq 1 ]]; then uids="$uids ${taskuid[$id]}" fi done if [[ $WANTSORT -eq 1 ]]; then uids=$(sortuidlist "$uids") fi if [[ ! $mode =~ $passive_modes ]]; then info "About to run '$mode' on the following tasks:" needtitle=0 for uid in $uids; do wantshow=0 id=$(getid $uid) # don't show if it's a subtask of one already in the list if [[ -z ${taskparent[$id]} ]]; then wantshow=1 elif [[ $uids == *${taskparent[$id]}\ * ]]; then wantshow=0 else wantshow=1 fi if [[ $wantshow -eq 1 ]]; then showit $kids $uid | sed -e 's/^/ /' fi done confirm "Really proceed" if [[ $? -ne 0 ]]; then action "Aborted." exit 1 fi fi elif [[ $filter =~ ^[0-9\ ]+$ ]]; then # list of task IDs for wantid in $filter; do uids="$uids ${taskuid[$wantid]}" done else # text to match a=$( echo ${filter} | tr 'A-Z' 'a-z') for id in ${!taskid[@]}; do b=$( echo ${tasksum[$id]} | tr 'A-Z' 'a-z') if [[ ${b} == *"${a}"* ]]; then uids="$uids ${taskuid[$id]}" fi done fi needtitle=1 if [[ -z $uids ]]; then error "no matching tasks found." exit 1 else processed="" failed="" for uid in ${uids}; do if [[ $mode == "list" ]]; then showit $uid elif [[ $mode == "done" ]]; then markdone $uid elif [[ $mode == "notdone" ]]; then marknotdone $uid elif [[ $mode == "fold" ]]; then fold $uid elif [[ $mode == "unfold" ]]; then unfold $uid elif [[ $mode == "toggle" ]]; then toggle $uid elif [[ $mode == "del" ]]; then delq_add $uid fi done # do queued actions if [[ $mode == "del" ]]; then delq_process fi # remove duplicates where list contains both parent # and child of parent if [[ ! -z $processed ]]; then for uid in ${processed}; do thisid=$(getid $uid) parentuid=${taskparent[$thisid]} # is this NOT the child of something in the list if [[ -z $parentuid || $processed != *$parentuid* ]]; then newprocessed="$newprocessed $uid" fi done processed="$newprocessed" fi if [[ ! -z $processed ]]; then # show results for uid in ${processed}; do showit $uid done fi fi fi if [[ $AUTOSYNC -eq 1 && $mode != "sync" ]]; then tasksync -a fi