task_cli/t.sh

2233 lines
58 KiB
Bash
Executable File

#!/opt/homebrew/bin/bash
#set -x
trap "exit 1" TERM
export MYPID=$$
if [[ ${BASH_VERSION:0:1} == 5 ]]; then
b5=1
declare -A idmap
else
b5=0
fi
. ${HOME}/code/bashtools/bashtools.sh
if [[ -z $HAVE_BASHTOOLS ]]; then
echo "ERROR: bashtools not installed download from https://git.nethack.net/rob/bashtools" >/dev/stderr
exit 1
fi
INFORMCOL="$GREEN"
INFORMCOLB="$GREEN$BOLD"
brack="$CYAN"
brackfold="$CYAN"
HILITE="\033[43;1m"
GREYBG="\033[48;2;30;30;30m"
TOPTITLE="\033[44;1m"
LEFTTITLE="\033[31;1m"
FOLDBG="$BOLDD"
function genidx() {
local idxname=$1 x
shift
local -a vals=("$@")
unset ${idxname}
declare -g -A ${idxname}
for x in "${!vals[@]}"; do
#echo "setting ${idxname}[${vals[$x]}] to $x" >/dev/stderr
eval ${idxname}["${vals[$x]}"]=$x
done
}
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 " -m Maintenance mode - find and fix bad idmapping entries, then exit."
echo " -p Enable profiling"
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
if [[ $b5 ]]; then
if [[ "${i_idmap[$1]}" ]]; then
echo "${i_idmap[$1]}"
return 0
fi
return 1
else
for id in ${!idmap[@]}; do
if [[ ${idmap[$id]} == $1 ]]; then
echo "$id"
return 0
fi
done
fi
return 1
}
function getuid() {
local res id
id=$1
res=${idmap[$id]}
if [[ -z $res ]]; then
return 1
fi
echo $res
return 0
}
function updatenextid() {
local nextid2=1
local db=0
nextid2=1
if [[ $b5 -eq 1 ]]; then
while [[ " ${i_idmap[@]}" == *\ $nextid2* ]]; do
nextid2=$((nextid2 + 1))
done
else
while [[ " ${taskid[@]}" == *\ $nextid2* ]]; do
nextid2=$((nextid2 + 1))
done
fi
nextid=$nextid2
}
function addidmap() { #1=id 2=uid
local id="$1" uid="$2"
idmap[$id]=$uid
[[ $b5 -eq 1 ]] && eval i_idmap["$uid"]=$id
}
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
profile "start"
# check for duplicate id mappings
sort -o $idfile -t: -k1,1 --stable --unique "$idfile"
profile "finished removing dupes"
max=0
while read LINE; do
uid=${LINE%:*}
id=${LINE#*:}
#uid=$(echo "$LINE" | sed 's/:.*$//g')
#id=$(echo "$LINE" | sed 's/^.*://g')
addidmap $id $uid
if [[ -z $uid || -z $id ]]; then
error "invalid idmapping line: '$LINE'"
profile "end"
return 1
fi
[[ $id -gt $max ]] && max=$id
count=$((count + 1))
done < "$idfile"
updatenextid
profile "end"
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 ]] && inform "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
profile "start"
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}
profile "finished loadtask calls"
updatenextid
dblog "Calculating max indent level"
# figure out max indent level and next id
for x in ${!taskuid[@]}; do
show=1
par=${taskparent[$x]}
if [[ -n $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
profile "got max indent"
[[ $ntasks -eq 1 ]] && ess="" || ess="s"
[[ $verbose -eq 1 ]] && inform "Finished loading $ntasks task$ess."
dblog "Finished loading $ntasks task$ess."
profile "end"
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 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
local blockreason
local didalloc=0
#local re
#if iscached "$1"; then
#fi
dblog "open $1"
file="$1"
#re="^(CATEGORIES:|DESCRIPTION:|REQUEST-STATUS:4.1;|RELATED-TO|X-OC-HIDESUBTASKS:1|STATUS:)"
res=$(cat "$file" | mawk -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); } /^REQUEST-STATUS:4.1;/ { sub("REQUEST-STATUS:4.1;",""); printf("breason©%s©",$0);} /^RELATED-TO/ { sub("^RELATED-TO.*:",""); printf("parent©%s©",$0); } /^X-OC-HIDESUBTASKS:1/ { folded=1; } /^STATUS:/ { stat=substr($0,8); if (stat == "COMPLETED") { checked=2; } else if (stat == "NEEDS-inform") { checked=3; } else if (stat == "IN-PROCESS") { checked = 1 } } END { printf("checked©%d©folded©%d\n",checked,folded); }' )
toadd2="REQUEST-STATUS:4.1;$reason"
IFS='©'
read -ra tok <<< "$res"
parent=""
i=0
while [[ ! -z ${tok[$i]} ]]; do
dblog "process token '${tok[$i]}'"
if [[ ${tok[$i]} == "uid" ]]; then
uid=${tok[$((i + 1))]}
id=$(getid $uid)
if [[ -z $id ]]; then
id=$nextid
addidmap $nextid $uid
didalloc=1
needsave=1
fi
elif [[ ${tok[$i]} == "sum" ]]; then
sum="${tok[$((i + 1))]}"
elif [[ ${tok[$i]} == "cats" ]]; then
cats="${tok[$((i + 1))]}"
elif [[ ${tok[$i]} == "breason" ]]; then
blockreason="${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
taskblockreason[$id]="$blockreason"
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 "taskblockreason[$id]=$blockreason"
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}
[[ $didalloc -eq 1 ]] && updatenextid
if [[ -z $uid ]]; then
error "task has no uid:"
echo "$res"
return 1
fi
# cache it
#cachetask "$file"
return 0
}
function cachetask() { #1=cardfile
stat -f "%Uc" "$1" >${cachedir}/$(basename "$1")
}
function iscached() { #1=cardfile
local changetime cachetime
changetime=$(stat -f "%Uc" "$1")
cachetime=$(cat ${cachedir}/$(basename "$1"))
if [[ $changetime -eq $cachetime ]]; then
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"
#sed -i '/^COMPLETED:/d;/^STATUS:/d;/PERCENT-COMPLETE:/d' "$file"
clearstatus "$file"
sedrv=$((sedrv + $?))
set_lastmodified $uid
sedrv=$((sedrv + $?))
if [[ $sedrv -eq 0 ]]; then
inform "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"
inform "$count task$ess removed."
else
inform "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
inform "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
inform "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
inform "Removed note from task #${id}."
else
inform "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 inform x catarr newcat
uid=$1
id=$(getid $uid)
rv=0
shift
inform=$1
shift
arg="$*"
file="$dir/$uid.vcf"
if [[ -z $arg ]]; then
if [[ $inform == "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 [[ $inform == "add" || ${catarr[$x]} != $arg ]]; then
[[ -z $newcat ]] && newcat="${catarr[$x]}" || newcat="$newcat,${catarr[$x]}"
fi
done
if [[ $inform == "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 [[ $inform == "del" ]]; then
if [[ -z $arg ]]; then
inform "Removed all tags from task #${id}."
else
inform "Removed tag '$arg' from task #${id}."
fi
else
inform "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
inform "Renamed task #${id} to '$newname'."
loadtask "$file"
rv=0
else
error "failed to rename task #${id}."
rv=1
fi
return $rv
}
function sedmod() { # sedmod uid 'sed command'
local uid sedscript newname sedrv rv file
local id one two cur new
uid=$1
id=$(getid $uid)
rv=0
shift
if [[ $# -ge 2 ]]; then
sedscript="s/$1/$2/g"
else
sedscript="$*"
fi
newname=$(echo "${tasksum[$id]}" | sed "$sedscript" 2>/dev/null )
if [[ $? -ne 0 ]]; then
error "Invalid sed script: $BOLD$sedscript"
return 1
fi
cur="${tasksum[$id]}"
new="${newname}"
if [[ $sedscript =~ ^s\/.*\/.*\/.*$ ]]; then
one=$(echo "$sedscript" | awk -F/ '{ print $2 }')
two=$(echo "$sedscript" | awk -F/ '{ print $3 }')
# ooo
cur=${cur/$one/$BOLD$STRIKE$RED$one^p^b}
new=${new/$two/$BOLD$GREEN$two^b}
fi
info "Current name: ${BOLD}${cur}"
info "New name: ${BOLD}${new}"
confirm "Is this correct"
if [[ $? -ne 0 ]]; then
inform "Aborted."
return 1
fi
file="$dir/$uid.vcf"
sed -i "s/^SUMMARY:.*/SUMMARY:$newname/" "$file"
sedrv=$?
set_lastmodified $uid
sedrv=$((sedrv + $?))
if [[ $sedrv -eq 0 ]]; then
inform "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
inform "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
inform "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 clearstatus() {
local file
file="$1"
[[ -e $file ]] && sed -i '/^COMPLETED:/d;/^STATUS:COMPLETED/d;/PERCENT-COMPLETE:/d;/^STATUS:IN-PROCESS:/d;/^STATUS:NEEDS-inform/d;/^$/d;/^REQUEST-STATUS:/d;/^STATUS:IN-PROCESS/d' "$file"
}
function markblocked() { # $1=uid $2=reason
local uid sedrv rv file toadd1 str now
local children childuid ischild noun id
uid=$1
reason="$2"
id=$(getid $uid)
[[ $# -gt 1 ]] && ischild=1 || ischild=0
[[ $ischild -eq 1 ]] && noun=subtask || noun=task
# already blocked?
if [[ ${taskchecked[$id]} -eq 3 ]]; then
if [[ ${taskblockreason[$id]} == "$reason" ]]; then
error "$noun #${id} is already blocked"
return 1
fi
fi
str="END:VTODO"
now=$(date -u +'%Y%m%dT%H%M%SZ')
file="$dir/$uid.vcf"
# first remove any existing completion
#sed -i '/^COMPLETED:/d;/^STATUS:COMPLETED/d;/PERCENT-COMPLETE:/d;/STATUS:IN-PROCESS:/d;/STATUS:NEEDS-inform/d' "$file"
clearstatus "$file"
# then add blocked
toadd1="STATUS:NEEDS-inform"
sed -i "/$str/i $toadd1\n" "$file"
sedrv=$?
if [[ -n $reason ]]; then
toadd2="REQUEST-STATUS:4.1;$reason"
sed -i "/$str/i $toadd2\n" "$file"
sedrv=$((sedrv + $?))
fi
set_lastmodified $uid
sedrv=$((sedrv + $?))
if [[ $sedrv -eq 0 ]]; then
local txt
txt="Marked $noun #${id} as blocked"
if [[ -n $reason ]]; then
txt="${txt} due to ${BOLD}$reason$PLAIN"
fi
txt="${txt}."
inform "$txt"
processed="$processed ${uid} "
loadtask "$file"
rv=0
# dont block children
#children=$(getchildren $uid)
#for childuid in ${children}; do
# markblocked $childuid noprint
# rv=$((rv + $?))
#done
#echo "file: $file"
#cat $file
else
failed="$failed ${uid} "
error "failed to mark $noun #${id} as blocked"
rv=1
fi
return $rv
}
function markinprogress() {
local uid sedrv rv file toadd1 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 in progress?
if [[ ${taskchecked[$id]} -eq 1 ]]; then
error "$noun #${id} is already in progress"
return 1
fi
str="END:VTODO"
now=$(date -u +'%Y%m%dT%H%M%SZ')
file="$dir/$uid.vcf"
# first remove any existing completion
#sed -i '/^COMPLETED:/d;/^STATUS:COMPLETED/d;/PERCENT-COMPLETE:/d;/STATUS:NEEDS-inform/d;' "$file"
clearstatus "$file"
# then add partial completion
toadd1="STATUS:IN-PROCESS"
sed -i "/$str/i $toadd1\n" "$file"
sedrv=$?
set_lastmodified $uid
sedrv=$((sedrv + $?))
if [[ $sedrv -eq 0 ]]; then
inform "Marked $noun #${id} as in-progress."
processed="$processed ${uid} "
loadtask "$file"
rv=0
children=$(getchildren $uid)
for childuid in ${children}; do
markinprogress $childuid noprint
rv=$((rv + $?))
done
else
failed="$failed ${uid} "
error "failed to mark $noun #${id} as in-progress"
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 2 ]]; 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"
clearstatus "$file"
sed -i "/$str/i $toadd1\n$toadd2\n$toadd3" "$file"
sedrv=$?
set_lastmodified $uid
sedrv=$((sedrv + $?))
if [[ $sedrv -eq 0 ]]; then
inform "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"
inform "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
addidmap $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" <<EOF
BEGIN:VCALENDAR
VERSION:2.0
PRODID:+//IDN tasks.org//android-111003//EN
BEGIN:VTODO
DTSTAMP:$now
UID:$uid
CREATED:$now
LAST-MODIFIED:$now
SUMMARY:${tasksum[$thisid]}
EOF
if [[ ! -z ${taskparent[$thisid]} ]]; then
echo "RELATED-TO;RELTYPE=PARENT:${taskparent[$thisid]}" >>"$file"
fi
cat >>"$file" <<EOF2
PRIORITY:9
X-APPLE-SORT-ORDER:611376630
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR
EOF2
updatenextid
saveids
needtitle=1
if [[ -z ${parentuid} ]]; then
inform "Created new task #${taskid[$thisid]}."
showit $uid
else
inform "Created new subtask #${taskid[$thisid]} under '${tasksum[${parentid}]}'."
showit ${taskparent[$thisid]} $uid
fi
}
function nextline() {
local dummy morestring rv=0
morestring="<------ more ------>"
printed=$((printed + 1));
if [[ $USEPAGER -eq 1 ]]; then
if [[ $printed -ge $((rows - 2)) ]]; then
echo -en "${CYAN}${morestring}${PLAIN}"
read -n1 -s dummy
[[ $dummy == "q" ]] && { echo; exit 0; }
printf "\r%*s\r" ${#morestring}
printed=0
needtitle=1
fi
fi
}
function showit() {
local uid childuid children char hilite col wid screenwid id ilev
local children nchildren charcol
local notecol bcol bl br space pre end
local showkids
local reset catstr catcol lrogcol
local blockedcol blockedrcol blockstr
reset="$PLAIN"
catcol="\033[38;2;255;165;0m"
progcol="\033[38;2;212;0;255m"
blockedcol="\033[38;2;212;0;0m"
blockedrcol="$blockedcol$BOLD"
showkids=1
if [[ $1 == "--nochildren" ]]; then
showkids=0
shift
fi
uid=$1
id=$(getid $uid)
children=$(getchildren $uid)
childrenarray=( $children )
nchildren=${#childrenarray[@]}
if [[ $# -ge 2 ]]; then
hilite=$2
fi
if [[ $needtitle -eq 1 ]]; then
cols=$(tput cols)
screenwid=$((cols - $numwid - 2 - 1))
wid=$(echo "scale=0; 4 + ($maxindent * $indentamt) + $maxtaskwidth + 3" | bc)
[[ $wid -gt $screenwid ]] && wid=$screenwid
printf "$TOPTITLE%-${numwid}s %-${wid}s$reset\n" "ID" "Task"
needtitle=0
fi
col="$reset"
donecol="$GREY"
notecol="$YELLOW"
end=""
if [[ ${taskchecked[$id]} -eq 2 ]]; then
char="✓"
charcol="$GREEN"
col="$donecol$STRIKE"
notecol="$notecol$STRIKE"
elif [[ ${taskchecked[$id]} -eq 1 ]]; then
char="&"
charcol="$progcol"
end="${progcol}...${reset}"
elif [[ ${taskchecked[$id]} -eq 3 ]]; then
#char="🛑"
#char="⛔"
char="⬣"
charcol="$blockedcol"
col="$blockedcol"
else
char=" "
charcol="$GREEN"
fi
if [[ $uid == $hilite ]]; then
col="$col$HILITE"
notecol="$notecol$HILITE"
fi
if [[ $nchildren -gt 0 && ${taskfolded[$id]} -eq 1 ]]; then
bcol=$brackfold
bl="("
br=")"
#col="$col$FOLDBG"
space=" "
pre=""
end=" $end$reset$CYAN${BOLD}[$nchildren subtasks]"
if [[ $char == " " ]]; then
char="+"
charcol="$BOLD$CYAN"
fi
else
bcol=$brack
bl="["
br="]"
space=" "
pre=""
fi
if [[ -z ${taskblockreason[$id]} ]]; then
blockstr=""
else
blockstr="${blockedrcol} (${taskblockreason[$id]}$reset$blockedrcol)"
fi
if [[ -z ${taskcats[$id]} ]]; then
catstr=""
else
catstr="${catcol} (${taskcats[$id]}$reset$catcol)"
fi
printf "${LEFTTITLE}%-${numwid}d${reset}" ${id}
printf "$space$space"
if [[ $space == " " ]]; then
printf "%*s" $indent
elif [[ $indent -gt 0 ]]; then
for x in $(eval echo {1..${indent} ); do
echo -n "$space"
done
fi
ilev=$(getindentlevel $id)
printf "$reset"
printf "$bcol$bl$reset$charcol%s$reset$bcol$br$reset ${col}${pre}%s$end${reset}${blockstr}${reset}${catstr}${reset}\n" "${char}" "${tasksum[$id]}"
nextline
if [[ ! -z ${taskdesc[$id]} ]]; then
printf "${LEFTTITLE}%*s${reset} " $numwid
printf "%*s" $(( $indent + 4))
printf "${notecol}${ITALIC}%s${reset}\n" "${taskdesc[$id]}"
nextline
fi
if [[ $showkids -eq 1 && ${taskfolded[$id]} -ne 1 ]]; then
inc
for childuid in ${children}; do
showit $childuid $hilite
done
dec
fi
}
function addcmd() { # name usage description alt1|alt2|etc supportsall
local cmd usage desc alts
cmd="$1"
usage="$2"
desc="$3"
alts="$4"
[[ -z $valid_modes ]] && valid_modes=$cmd || valid_modes="$valid_modes $cmd"
eval "usage_${cmd}=\"${usage}\""
eval "desc_${cmd}=\"${desc}\""
eval "words_${cmd}=\"${alts}\""
if [[ $* == *SUPPORTS_ALL* ]]; then
if [[ -z $modes_that_support_all ]]; then
modes_that_support_all="$cmd"
else
modes_that_support_all="$modes_that_support_all|$cmd"
fi
fi
if [[ $* == *PASSIVE* ]]; then
if [[ -z $passive_modes ]]; then
passive_modes="$cmd"
else
passive_modes="$passive_modes|$cmd"
fi
fi
}
function sortuidlist() { # sort a list of task uids by task summng aries
local unsorted uidandsum withnl sorted_arr sorted unsorted_arr uid
unsorted="$1"
# change "123 456 789" to
# ("123#do something" "234#other task" "789#something#)
unsorted_arr=""
for uid in ${unsorted}; do
id=$(getid $uid)
unsorted_arr+=("${taskuid[$id]}#${tasksum[$id]}")
done
IFS=$'\n'
withnl=("${unsorted_arr[*]}")
sorted_arr=($(sort -df -t '#' -k2 <<<"${withnl[*]}"))
sorted=""
for uidandsum in ${sorted_arr[@]}; do
sorted="$sorted ${uidandsum%#*}"
done
unset IFS
echo "$sorted"
}
function getchildren() { # parentuid
local uid childuid children id
uid=$1
children=""
for id in ${!taskid[@]}; do
if [[ ${taskparent[$id]} == $uid ]]; then
children="$children ${taskuid[$id]}"
fi
done
[[ -z $children ]] && return 1
if [[ $WANTSORT -eq 1 ]]; then
children=$(sortuidlist "$children")
fi
echo "$children"
return 0
}
function makedir() {
local d
d="$1"
if [[ -d "$d" ]]; then
info "Directory $d already exists"
else
mkdir -p "${d}"
if [[ $? -eq 0 ]]; then
inform "Created directory ${BOLD}$d${PLAIN}"
else
error "Couldn't mkdir ${BOLD}$d${PLAIN}${RED}"
return 1
fi
fi
return 0
}
function init() {
local gotvdconf=0 d url count cals cals_arr thiscal selcal
inform "Initialising configration"
if ! which -s vdirsyncer; then
error "Can't find ${BOLD}vdirsyncer${PLAIN}${RED} - please install it."
return 1
fi
if [[ -e $vdirsyncconfig ]]; then
confirm "Use existing vdirsyncer config in ${BOLD}$vdirsyncconfig${PLAIN}${MAGENTA}"
if [[ $? -eq 0 ]]; then
gotvdconf=1
fi
fi
if [[ $gotvdconf -ne 1 ]]; then
for d in "$vdirsyncdir" "$vdircaldir" "$vdirstatdir"; do
makedir "$d" || return 1
done
echo -e "${MAGENTA}Please provide the URL of your caldav server.${PLAIN}"
echo -en "${BOLD}${MAGENTA}==> ${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}" <<EOF
[general]
status_path = "$vdirstatdir"
[pair cal]
a = "cal_local"
b = "cal_remote"
collections = ["from a", "from b"]
[storage cal_local]
type = "filesystem"
path = "$vdircaldir"
fileext = ".vcf"
[storage cal_remote]
type = "caldav"
url = "$url"
username = "$u"
password = "$p"
EOF
unset u
unset p
info "Created vdirsyncer config in ${vdirsyncconfig}"
fi
cals=$(ls -1q $vdircaldir 2>/dev/null)
count=$(echo "$cals" | wc -l | tr -d ' ')
if [[ $count -eq 0 ]]; then
inform "Running initial vdirsyncer discover..."
vdirsyncer discover
inform "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}"
inform "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="<none>"
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="Sync" mode="sync"
while [[ $# -ge 1 ]]; do
if [[ $1 == "-a" ]]; then
noun="Auto-sync"
elif [[ $1 == "push" ]]; then
mode="push"
noun="Push"
elif [[ $1 == "pull" ]]; then
mode="pull"
noun="Pull"
fi
shift
done
notify "${noun}ing tasks"
# adjust conflict resolution
## remove existing setting
sed -i '/^conflict/ s/^/#/' $vdirsyncconfig
## add new one
if [[ $mode == "push" ]]; then
sed -i '/conflict.*a wins/ s/^#\+//' $vdirsyncconfig
elif [[ $mode == "pull" ]]; then
sed -i '/conflict.*b wins/ s/^#\+//' $vdirsyncconfig
fi
res=$(${VDIRSYNCER} sync 2>&1)
rv=$?
[[ $rv -eq 0 ]] && ok || fail
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
inform "$noun complete - no changes."
elif [[ $upcount -eq 0 ]]; then
inform "$noun complete - $downcount change$downess pulled."
elif [[ $downcount -eq 0 ]]; then
inform "$noun complete - $upcount change$upess pushed."
else
inform "$noun complete - $upcount change$upess pushed, $downcount change$downess pulled."
fi
else
errmsg="$noun failed."
if [[ $mode == "sync" ]]; then
errmsg="${errmsg} Try ^bt push^p or ^bt pull^p."
fi
error "$errmsg"
if [[ $mode != "sync" ]]; then
cecho -s "$RED" "^bDetails:^p"
cecho -s "$RED" "$res" | sed -e 's/^/ /'
echo
cecho -s "$RED" "^bConflict settings:^p"
errmsg=$(grep "conflict" $vdirsyncconfig 2>&1)
cecho -s "$YELLOW" "$errmsg" | sed -e 's/^/ /'
fi
fi
## remove conflict setting again
sed -i '/^conflict/ s/^/#/' $vdirsyncconfig
}
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"
cachedir="${confdir}/cache"
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 <id>" "Fold a parent task (hide its children)" "fold|f|zc" SUPPORTS_ALL
addcmd "unfold" "unfold <id>" "Unfold a parent task (show its children)" "unfold|u|zo" SUPPORTS_ALL
addcmd "toggle" "toggle <id>" "Fold/Unfold a parent task" "toggle|z|zz" SUPPORTS_ALL
addcmd "done" "done <taskid>" "Complete a task" "done|x|complete" SUPPORTS_ALL
addcmd "notdone" "notdone <taskid>" "Uncomplete a task" "notdone|o|incomplete|uncomplete|clear" SUPPORTS_ALL
addcmd "inprogress" "inprogress <taskid>" "Mark a task as in progress" "inprogress|progress|inp|prog|i|p" SUPPORTS_ALL
addcmd "blocked" "blocked <taskid>" "Mark a task as blocked" "blocked|block|b" SUPPORTS_ALL
addcmd "add" "add [parent] <name>" "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 <id> <newname>" "Rename given task" "rename"
addcmd "mod" "mod <id> (<sed_script>|<lookfor> <replacewith>)" "Rename given task using sed script" "mod|sed"
addcmd "left" "left <id>" "Decrease indent of given task" "left|h|out|up"
addcmd "right" "right <id> <parent>" "Move task below the given parent" "right|l|mv|in"
addcmd "note" "note <id> <notes>" "Change notes for given task" "note|desc|description|comment"
addcmd "tag" "tag <id> <tag>" "Add a tag to the given task" "tag|t"
addcmd "untag" "untag <id> <tag>" "Remove a tag from the given task" "untag|ut"
addcmd "sync" "sync" "Sync tasks using vdirsyncer" "sync"
addcmd "push" "push" "Push all tasks to server" "push"
addcmd "view" "view <id>" "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"
addcmd "renumber" "renumber" "Re-generate IDs for all tasks with lowest possible numbers" "rn|gen"
[[ ! -e $cachedir ]] && mkdir -p "$cachedir"
DEBUG=0
TESTMODE=0
AUTOSYNC=0
CALID=$(cat "$confdir/defaultcal")
WANTSORT=0
USEPAGER=1
MAINTMODE=0
ARGS="fhic:dmnsStyl:r:p"
while getopts "$ARGS" i; do
case "$i" in
m)
MAINTMODE=1
;;
h)
usage;
exit 1;
;;
i)
init
exit $?
;;
c)
CALID="$OPTARG"
;;
l)
VDS_LOCAL="$OPTARG"
;;
n)
USEPAGER=0
;;
p)
__PROFILE=1
;;
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
elif [[ $mode == "push" ]]; then
tasksync push
exit $rv
elif [[ $mode == "renumber" ]]; then
idfilebackup="$idfile.bak.$(date +%Y%m%d%H%M%S)"
idfilenew=$(mktemp /tmp/newid.XXXXXXXX)
# figure out what summing program to use
sumprog=""
for x in md5 md5sum; do
which $x >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
sumprog="$x"
break
fi
done
if [[ -n $sumprog ]]; then
notify "Checking current task IDs"
cat "$idfile" | awk 'BEGIN { id=1; FS=":"; } /.*:.*/{ print $1 ":" id++; } ' > "$idfilenew"
md5old=$($sumprog "$idfile")
md5new=$($sumprog "$idfilenew")
if [[ $sumprog == "md5" ]]; then
md5old=$(echo "$md5old" | awk '{ print $NF }')
md5new=$(echo "$md5new" | awk '{ print $NF }')
else
md5old=$(echo "$md5old" | awk '{ print $1 }')
md5new=$(echo "$md5new" | awk '{ print $1 }')
fi
ok
if [[ $md5old == $md5new ]]; then
inform "No changes required based on checksums."
echo "old: $md5sumold"
echo "new: $md5sumnew"
exit 0
fi
else
warn "No checksumming program found - install md5 or md5sum."
fi
oldmin=$(cat "$idfile" | cut -d: -f2 | sort -nr | tail -1)
oldmax=$(cat "$idfile" | cut -d: -f2 | sort -n | tail -1)
min=$(cat "$idfilenew" | cut -d: -f2 | sort -nr | tail -1)
max=$(cat "$idfilenew" | cut -d: -f2 | sort -n | tail -1)
if [[ $oldmin -eq $min && $newmin -eq $min ]]; then
inform "No changes required based on task IDs."
exit 0
fi
inform "This process will renumber all tasks:"
inform " Current ID range: ^b$oldmin^p-^b$oldmax^p"
inform " New ID range: ^b$min^p-^b$max^p"
confirm "Really proceed"
if [[ $? -ne 0 ]]; then
inform "Aborted."
exit 1
fi
notify "Backing up mapping file to ^b$idfilebackup^p"
cp -a "$idfile" "$idfilebackup"
[[ $? -eq 0 ]] && ok || { fail; exit 1; }
notify "Re-generating task IDs"
mv -f "$idfilenew" "$idfile"
[[ $? -eq 0 ]] && ok || { fail; exit 1; }
inform "Task ID renumbering complete."
exit 0
fi
notify "Processing"
loadids || exit 1
loadtasks || exit 1
ok
if [[ $MAINTMODE -eq 1 ]]; then
# check for id mappings with missing tasks
notify "Checking ID mappings"
nbad=0
for x in ${!idmap[@]}; do
if (! IFS=$'\n'; echo "${taskid[*]}" ) | grep -qFx "${idmap[$x]}" ; then
warn "idmap has missing task ${idmap[$x]}"
tofix="$tofix $x "
nbad=$(( nbad + 1))
fi
done
[[ $nbad -ge 1 ]] && partial "$nbad need fixing" || ok
if [[ -n $tofix ]]; then
notify "Fixing ID mappings for missing tasks"
for x in $tofix; do
[[ $b5 -eq 1 ]] && unset i_idmap[${idmap[$x]}];
unset idmap[$x]
done
ok
idfilebackup="$idfile.bak.$(date +%Y%m%d%H%M%S)"
inform "idmap backup is in $idfilebackup"
cp -a "$idfile" "$idfilebackup"
saveids
fi
exit 0
fi
[[ $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
inform "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 == "mod" ]]; then
uid=$(getuid_witherror $1)
shift
if [[ $# -ge 2 ]]; then
sedmod $uid "$1" "$2"
else
sedmod $uid "$*"
fi
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 == "blocked" ]]; then
uid=$(getuid_witherror $1)
shift
markblocked $uid "$*"
elif [[ $mode == "right" ]]; then
pid=$BASH_ARGV
if [[ $puid != "-" ]]; then
puid=$(getuid_witherror $pid "parent task")
fi
while [[ $# -gt 1 ]]; do
if [[ $1 =~ ^[0-9]+-[0-9]+$ ]]; then
s=${1%-*}
e=${1#*-}
for ((w2=s; w2<=e; w2++)); do
uid=$(getuid_witherror $w2)
move_right $uid $puid
done
else
uid=$(getuid_witherror $1)
move_right $uid $puid
fi
shift
done
elif [[ $mode == "view" ]]; then
uid=$(getuid_witherror $1)
viewtask $uid
elif [[ $mode == "cleanup" ]]; then
inform "Deleting all completed tasks."
for id in ${!taskuid[@]}; do
uid=$(getuid $id)
[[ ${taskchecked[$id]} -eq 2 ]] && 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]} -ne 2 ]]; then
match=1
elif [[ $mode == "notdone" && ${taskchecked[$id]} -ne 0 ]]; then
match=1
elif [[ $mode == "inprogress" && ${taskchecked[$id]} -ne 1 ]]; then
match=1
elif [[ $mode == "blocked" && ${taskchecked[$id]} -ne 3 ]]; 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
inform "Aborted."
exit 1
fi
fi
elif [[ $filter =~ ^[0-9\ \-]+$ ]]; then
# list of task IDs
for wantid in $filter; do
if [[ $wantid =~ ^[0-9]+-[0-9]+$ ]]; then
s=${wantid%-*}
e=${wantid#*-}
for ((w2=s; w2<=e; w2++)); do
uids="$uids ${taskuid[$w2]}"
done
else
uids="$uids ${taskuid[$wantid]}"
fi
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 == "inprogress" ]]; then
markinprogress $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 informs
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