Tons of changes

This commit is contained in:
2025-05-23 14:04:18 +02:00
parent 5bbde62142
commit 9a5aaea387

567
fzf-vjour
View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
set -eu set -u
# Read configuration # Read configuration
CONFIGFILE="$HOME/.config/fzf-vjour/config.yaml" CONFIGFILE="$HOME/.config/fzf-vjour/config.yaml"
@@ -24,28 +24,115 @@ SED_COLLECTIONLABELS_TO_NAMES=$(
yq '.collections[] | "s|\ *" + .label + "\ *|/" + .name + "/|"' <"$CONFIGFILE" | yq '.collections[] | "s|\ *" + .label + "\ *|/" + .name + "/|"' <"$CONFIGFILE" |
xargs printf "-e \"%s\" " xargs printf "-e \"%s\" "
) )
COLLECTION_LABEL_MAX_LEN=$(yq '[.collections[].label | length] | max' <"$CONFIGFILE") COLLECTION_NAME_MAX_LEN=$(yq '[.collections[].name | length] | max' <"$CONFIGFILE")
LABLES=$(yq '.collections[].label' <"$CONFIGFILE") LABLES=$(yq '.collections[].label' <"$CONFIGFILE")
SYNC_CMD=$(yq '.sync_cmd' <"$CONFIGFILE") SYNC_CMD=$(yq '.sync_cmd' <"$CONFIGFILE")
__vjournalnew() { __vtodopriority() {
python3 -c '
import sys
from datetime import datetime
from icalendar.cal import Todo
if not len(sys.argv) == 3:
print("Pass ical file as first argument!", file=sys.stderr)
sys.exit(1)
increase = 1 if sys.argv[2] == "1" else -1
with open(sys.argv[1], "r") as f:
try:
ical = Todo.from_ical(f.read())
except Exception as e:
print(f"Failed to read vjournal file: {e}", file=sys.stderr)
sys.exit(1)
tlist = [component for component in ical.walk("VTODO")]
if len(tlist) == 0:
print("ical file is not a VTODO", file=sys.stderr)
sys.exit(1)
t = tlist[0]
# Update ical
priority = t.pop("PRIORITY")
priority = (int(priority) if priority else 0) + increase
priority = 0 if priority < 0 else 9 if priority > 9 else priority
t["PRIORITY"] = priority
# Print
print(ical.to_ical().decode().replace("\r\n", "\n"))
' "$@"
}
__vtodotogglecompleted() {
python3 -c '
import sys
from datetime import datetime
from icalendar.cal import Todo
from icalendar.prop import vDDDTypes
if not len(sys.argv) == 2:
print("Pass ical file as first argument!", file=sys.stderr)
sys.exit(1)
with open(sys.argv[1], "r") as f:
try:
ical = Todo.from_ical(f.read())
except Exception as e:
print(f"Failed to read vjournal file: {e}", file=sys.stderr)
sys.exit(1)
tlist = [component for component in ical.walk("VTODO")]
if len(tlist) == 0:
print("ical file is not a VTODO", file=sys.stderr)
sys.exit(1)
t = tlist[0]
# Update ical
if t.has_key("STATUS") and t["STATUS"] == "COMPLETED":
# Mark as not completed
t["STATUS"] = "NEEDS-ACTION"
t["PERCENT-COMPLETE"] = 0
if t.has_key("COMPLETED"): t.pop("COMPLETED")
else:
t["STATUS"] = "COMPLETED"
t["PERCENT-COMPLETE"] = 100
t["COMPLETED"] = vDDDTypes(datetime.utcnow())
# Print
print(ical.to_ical().decode().replace("\r\n", "\n"))
' "$@"
}
__vicalnew() {
python3 -c ' python3 -c '
import sys import sys
from datetime import date, datetime from datetime import date, datetime
from icalendar.cal import Calendar, Journal from icalendar.cal import Calendar, Journal, Todo
from icalendar.prop import vDDDTypes
if not len(sys.argv) == 2: if not len(sys.argv) == 2:
print("Pass uid as first argument!", file=sys.stderr) print("Pass UID as first argument!", file=sys.stderr)
sys.exit(1) sys.exit(1)
UID = sys.argv[1] UID = sys.argv[1]
ical = Calendar()
j = Journal()
isnote = True start = None
due = None
line = sys.stdin.readline().strip() line = sys.stdin.readline().strip()
if line[:4] == "::: ": if line[:6] == "::: |>":
isnote = False start = datetime.utcnow().date()
line = sys.stdin.readline().strip()
if line[:6] == "::: <|":
lst = line.split(" ")
due = True
if len(lst) >= 3:
try:
duedate = datetime.strptime(lst[2], "%Y-%m-%d").date()
due = duedate
except Exception as e:
pass
line = sys.stdin.readline().strip() line = sys.stdin.readline().strip()
if not line[:2] == "# ": if not line[:2] == "# ":
@@ -55,11 +142,11 @@ summary = line[2:]
line = sys.stdin.readline().strip() line = sys.stdin.readline().strip()
if not line[:2] == "> " and not line == ">": if not line[:2] == "> " and not line == ">":
print("Error: Categories line is corrupt!", file=sys.stderr) categories = []
sys.exit(1) else:
categories = line[2:].split(",") categories = line[2:].split(",")
line = sys.stdin.readline().strip()
line = sys.stdin.readline().strip()
if not line == "": if not line == "":
print("Error: Missing separating line!", file=sys.stderr) print("Error: Missing separating line!", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -67,28 +154,84 @@ if not line == "":
description = sys.stdin.read() description = sys.stdin.read()
# Create ical # Create ical
j["SUMMARY"] = summary now = datetime.utcnow()
j.categories = categories ical = Calendar()
j["DESCRIPTION"] = description if due:
j.LAST_MODIFIED = datetime.utcnow() o = Todo()
j.DTSTAMP = datetime.utcnow()
j["UID"] = UID # The following are REQUIRED, but MUST NOT occur more than once.
j["SEQUENCE"] = 1 # dtstamp / uid /
j["STATUS"] = "FINAL" o.DTSTAMP = now
if not isnote: o["UID"] = UID
j.DTSTART = date.today()
ical.add_component(j) # The following are OPTIONAL, but MUST NOT occur more than once.
# class / completed / created / description / dtstart / geo / last-mod /
# location / organizer / percent / priority / recurid / seq / status /
# summary / url /
o["CLASS"] = "PRIVATE"
o["CREATED"] = vDDDTypes(now)
o["DESCRIPTION"] = description.strip()
o.LAST_MODIFIED = now
o["PRIORITY"] = 0
o["SEQUENCE"] = 0
o["STATUS"] = "NEEDS-ACTION"
o["SUMMARY"] = summary
# The following is OPTIONAL, but SHOULD NOT occur more than once.
# rrule /
# Either "due" or "duration" MAY appear in a "todoprop", but "due" and
# "duration" MUST NOT occur in the same "todoprop". If "duration" appear in
# a "todoprop", then "dtstart" MUST also appear in the same "todoprop".
# due / duration /
if isinstance(due, date):
o.DUE = due
# The following are OPTIONAL, and MAY occur more than once.
# attach / attendee / categories / comment / contact / exdate / rstatus /
# related / resources / rdate / x-prop / iana-prop
o.categories = categories
else:
o = Journal()
# The following are REQUIRED, but MUST NOT occur more than once.
# dtstamp / uid /
o.DTSTAMP = now
o["UID"] = UID
# The following are OPTIONAL, but MUST NOT occur more than once.
# class / created / dtstart / last-mod / organizer / recurid / seq / status
# / summary / url /
o["CLASS"] = "PRIVATE"
o["CREATED"] = vDDDTypes(now)
o.DTSTART = start
o.LAST_MODIFIED = now
o["SEQUENCE"] = 0
o["STATUS"] = "FINAL"
o["SUMMARY"] = summary
# The following is OPTIONAL, but SHOULD NOT occur more than once.
# rrule /
# The following are OPTIONAL, and MAY occur more than once.
# attach / attendee / categories / comment / contact / description / exdate
# / related / rdate / rstatus / x-prop / iana-prop
o.categories = categories
o["DESCRIPTION"] = description.strip()
ical.add_component(o)
ical["PRODID"] = "fzf-vjour/basic"
ical["VERSION"] = "2.0" ical["VERSION"] = "2.0"
ical["PRODID"] = "vjournew/basic"
# Print # Print
print(ical.to_ical().decode().replace("\r\n", "\n"))' "$@" print(ical.to_ical().decode().replace("\r\n", "\n"))' "$@"
} }
__vjournalupdate() { __icalupdate() {
python3 -c ' python3 -c '
import sys import sys
from datetime import datetime from datetime import date, datetime
from icalendar.cal import Journal from icalendar.cal import Calendar, Todo
if not len(sys.argv) == 2: if not len(sys.argv) == 2:
print("Pass ical file as first argument!", file=sys.stderr) print("Pass ical file as first argument!", file=sys.stderr)
@@ -96,18 +239,33 @@ if not len(sys.argv) == 2:
with open(sys.argv[1], "r") as f: with open(sys.argv[1], "r") as f:
try: try:
ical = Journal.from_ical(f.read()) ical = Calendar.from_ical(f.read())
except Exception as e: except Exception as e:
print(f"Failed to read vjournal file: {e}", file=sys.stderr) print(f"Failed to read ical file: {e}", file=sys.stderr)
sys.exit(1) sys.exit(1)
jlist = [component for component in ical.walk("VJOURNAL")] olist = [component for component in ical.walk(select=lambda c:c.name in ["VJOURNAL", "VTODO"])]
if len(jlist) == 0: if len(olist) == 0:
print("ical file is not a VJOURNAL", file=sys.stderr) sys.exit(0)
sys.exit(1)
j = jlist[0] o = olist[0]
line = sys.stdin.readline().strip() line = sys.stdin.readline().strip()
due = None
if isinstance(o, Todo):
if not line[:6] == "::: <|":
print("Error: Due date line is corrupt!", file=sys.stderr)
sys.exit(1)
lst = line.split(" ")
due = True
if len(lst) >= 3:
try:
duedate = datetime.strptime(lst[2], "%Y-%m-%d").date()
due = duedate
except Exception as e:
pass
line = sys.stdin.readline().strip()
if not line[:2] == "# ": if not line[:2] == "# ":
print("Error: Summary line is corrupt!", file=sys.stderr) print("Error: Summary line is corrupt!", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -115,11 +273,11 @@ summary = line[2:]
line = sys.stdin.readline().strip() line = sys.stdin.readline().strip()
if not line[:2] == "> " and not line == ">": if not line[:2] == "> " and not line == ">":
print("Error: Categories line is corrupt!", file=sys.stderr) categories = []
sys.exit(1) else:
categories = line[2:].split(",") categories = line[2:].split(",")
line = sys.stdin.readline().strip()
line = sys.stdin.readline().strip()
if not line == "": if not line == "":
print("Error: Missing separating line!", file=sys.stderr) print("Error: Missing separating line!", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -127,54 +285,95 @@ if not line == "":
description = sys.stdin.read() description = sys.stdin.read()
# Update ical # Update ical
j["SUMMARY"] = summary if due:
j.categories = categories if isinstance(due, date):
j["DESCRIPTION"] = description o.DUE = due
j.LAST_MODIFIED = datetime.utcnow() elif "DUE" in o.keys():
o.pop("DUE")
o["SUMMARY"] = summary
o.categories = categories
o["DESCRIPTION"] = description.strip()
o.LAST_MODIFIED = datetime.utcnow()
seq = o["SEQUENCE"] if "SEQUENCE" in o.keys() else 0
o["SEQUENCE"] = seq + 1
# Print # Print
print(ical.to_ical().decode().replace("\r\n", "\n")) print(ical.to_ical().decode().replace("\r\n", "\n"))
' "$@" ' "$@"
} }
__vjournal2json() { __ical2json() {
python3 -c ' python3 -c '
import sys import sys
import json import json
from datetime import datetime from datetime import datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from icalendar.cal import Journal from icalendar.cal import Calendar, Todo
input_data = sys.stdin.read() input_data = sys.stdin.read()
try: try:
ical = Journal.from_ical(input_data) ical = Calendar.from_ical(input_data)
except Exception as e: except Exception as e:
print(f"Failed to read vjournal file: {e}", file=sys.stderr) print(f"Failed to read ical file: {e}", file=sys.stderr)
sys.exit(1) sys.exit(1)
jlist = [component for component in ical.walk("VJOURNAL")] olist = [component for component in ical.walk(select=lambda c:c.name in ["VJOURNAL", "VTODO"])]
if len(jlist) == 0: if len(olist) == 0:
sys.exit(0) sys.exit(0)
j = jlist[0] o = olist[0]
local_tz = ZoneInfo("localtime") local_tz = ZoneInfo("localtime")
data = { data = {
"summary": j.get("SUMMARY"), "summary": o.get("SUMMARY"),
"description": j.get("DESCRIPTION"), "description": o.get("DESCRIPTION") if "DESCRIPTION" in o.keys() else "",
"categories": j.categories, "categories": o.categories,
"class": j.get("CLASS"), "class": o.get("CLASS"),
"created": str(j.DTSTAMP.astimezone(local_tz)), "created": str(o.DTSTAMP.astimezone(local_tz)) if o.DTSTAMP else "",
"last_modified": str(j.LAST_MODIFIED.astimezone(local_tz)), "last_modified": str(o.LAST_MODIFIED.astimezone(local_tz)) if o.LAST_MODIFIED else "",
"start": str( "start": str(
j.DTSTART.astimezone(local_tz) o.DTSTART.astimezone(local_tz)
if isinstance(j.DTSTART, datetime) if isinstance(o.DTSTART, datetime)
else j.DTSTART or "" else o.DTSTART or ""
), ),
} }
if isinstance(o, Todo):
data["due"] = str(o.DUE) if o.DUE else ""
print(json.dumps(data))' print(json.dumps(data))'
} }
__date_not_in_future() {
date_target=$(date -d "$1" +%s)
date_today=$(date -d "00:00" +%s)
date_delta=$((date_target - date_today))
if [ "$date_delta" -le 0 ]; then
echo 1
fi
}
__date_to_expression() {
date_target=$(date -d "$1" +%s)
date_today=$(date -d "00:00" +%s)
date_delta=$(((date_target - date_today) / 86400))
date_expr=$date_delta
if [ "$date_delta" -eq 0 ]; then
date_expr="today"
elif [ "$date_delta" -eq -1 ]; then
date_expr="yesterday"
elif [ "$date_delta" -eq 1 ]; then
date_expr="tomorrow"
elif [ "$date_delta" -lt -1 ] && [ "$date_delta" -ge -7 ]; then
date_expr="last $(date -d "$1" +%A)"
elif [ "$date_delta" -gt 1 ] && [ "$date_delta" -le 7 ]; then
date_expr="next $(date -d "$1" +%A)"
else
date_expr=$(date -d "$1" +%x)
fi
echo "$date_expr"
}
# Process each file # Process each file
# This function takes two arguments: # This function takes two arguments:
# #
@@ -183,68 +382,70 @@ print(json.dumps(data))'
__filepath_to_searchline() { __filepath_to_searchline() {
filepath="$1" filepath="$1"
maxlen="$2" maxlen="$2"
totallen=$((maxlen + ${#ROOT})) totallen=$((maxlen + ${#ROOT} + COLLECTION_NAME_MAX_LEN + 2))
filepathpad=$(printf "%-${totallen}s" "$filepath") #filepathpad=$(printf "%-${totallen}s" "$filepath")
filepathpad=$(dirname "$filepath")/$(printf "%-${maxlen}s" "$(basename "$filepath")")
# Color support # Color support
GREEN=$(printf '\033[1;32m') GREEN=$(printf '\033[1;32m')
RED=$(printf '\033[1;31m')
WHITE=$(printf '\033[1;97m') WHITE=$(printf '\033[1;97m')
FAINT=$(printf '\033[2m') FAINT=$(printf '\033[2m')
OFF=$(printf '\033[m') OFF=$(printf '\033[m')
# Parse file # Parse file
summary="" summary=$(grep '^SUMMARY:' "$filepath" | cut -d ':' -f 2)
categories="" categories=$(grep '^CATEGORIES:' "$filepath" | cut -d ':' -f 2)
dtstamp="" dtstamp=$(grep '^DTSTAMP:' "$filepath" | cut -d ':' -f 2)
dtstart="" dtstart=$(grep '^DTSTART' "$filepath" | grep -oE '[0-9]{8}')
while IFS= read -r line; do due=$(grep '^DUE' "$filepath" | grep -oE '[0-9]{8}')
case "$line" in priority=$(grep '^PRIORITY:' "$filepath" | cut -d ':' -f 2)
SUMMARY:*) task=$(grep '^BEGIN:VTODO' "$filepath")
summary="${line#SUMMARY:}" completed=$(grep '^STATUS:COMPLETED' "$filepath")
;;
CATEGORIES:*)
categories="${line#CATEGORIES:}"
;;
DTSTAMP:*)
dtstamp="${line#DTSTAMP:}"
;;
DTSTART*:[0-9]*)
dtstart=$(echo "$line" | grep -oE '[0-9]{8}')
;;
esac
if [ -n "$summary" ] && [ -n "$categories" ] && [ -n "$dtstamp" ] && [ -n "$dtstart" ]; then
break
fi
done <"$filepath"
# Parse date # Parse date
if [ -n "$dtstart" ]; then if [ -n "$dtstart" ]; then
date_target=$(date -d "$dtstart" +%s) emoji="📘"
date_today=$(date +%s) date_expr=$(__date_to_expression "$dtstart")
date_delta=$(((date_target - date_today) / 86400))
date_expr=$date_delta
if [ "$date_delta" -eq 0 ]; then
date_expr="today"
elif [ "$date_delta" -eq -1 ]; then
date_expr="yesterday"
elif [ "$date_delta" -eq 1 ]; then
date_expr="tomorrow"
elif [ "$date_delta" -lt -1 ] && [ "$date_delta" -ge -7 ]; then
date_expr="last $(date -d "$dtstart" +%A)"
elif [ "$date_delta" -gt 1 ] && [ "$date_delta" -le 7 ]; then
date_expr="next $(date -d "$dtstart" +%A)"
else
date_expr=$(date -d "$dtstart" +%x)
fi
date_emoji="📘"
else else
date_emoji="🗒️" emoji="🗒️"
date_expr="" date_expr=""
fi fi
# Check if this is a task
if [ -n "$task" ]; then
emoji="🔲"
if [ -n "$completed" ]; then
emoji="✅"
fi
fi
# Check Priority
if [ -n "$priority" ] && [ "$priority" -gt 0 ]; then
prioritymsg="❗($priority) "
priority=$((10 - priority))
else
prioritymsg=""
priority=0
fi
# Check due date
if [ -n "$due" ]; then
date_expr=$(__date_to_expression "$due")
fi
date_expr=$(printf "%12s" "$date_expr") date_expr=$(printf "%12s" "$date_expr")
# Color date
notinfuture=$(__date_not_in_future "$due")
if [ -n "$notinfuture" ] && [ -n "$due" ]; then
date_expr="$RED$date_expr$OFF"
summary_color="$RED"
else
date_expr="$WHITE$date_expr$OFF"
summary_color="$GREEN"
fi
# Print line # Print line
echo "$dtstamp $filepath $WHITE$date_expr$OFF $date_emoji $GREEN$summary$OFF $FAINT$categories$OFF" echo "$priority $dtstamp $filepathpad $date_expr $emoji $prioritymsg$summary_color$summary$OFF $FAINT$categories$OFF"
} }
__lines() { __lines() {
@@ -280,59 +481,127 @@ __filepath_from_selection() {
# Program starts here # Program starts here
if [ "${1:-}" = "--help" ]; then
echo "Usage: $0 [OPTION]"
echo ""
echo "You may specify at most one option."
echo " --help Show this help and exit"
echo " --tasks Show tasks only"
echo " --no-tasks Ignore tasks"
echo " --completed Show completed tasks only"
echo " --no-completed Ignore completed tasks"
echo " --new Create new entry"
echo ""
echo "The following options are for internal use."
echo " --reload Reload list"
echo " --preview <selection> Generate preview"
echo " --delete <selection> Delete selected entry"
echo " --decrease-priority <selection> Decrease priority of selected task"
echo " --increase-priority <selection> Increase priority of selected task"
echo " --toggle-completed <selection> Toggle completion flag of task"
exit
fi
# Command line arguments to be self-contained # Command line arguments to be self-contained
# Generate preview of file from selection # Generate preview of file from selection
if [ "${1:-}" = "--preview" ]; then if [ "${1:-}" = "--preview" ]; then
vjfile=$(__filepath_from_selection "$2") vjfile=$(__filepath_from_selection "$2")
__vjournal2json <"$vjfile" | jq -r ".description" | batcat --color=always --style=numbers --language=md __ical2json <"$vjfile" | jq -r ".description" | batcat --color=always --style=numbers --language=md
exit exit
fi fi
# Delete file from selection # Delete file from selection
if [ "${1:-}" = "--delete" ]; then if [ "${1:-}" = "--delete" ]; then
vjfile=$(__filepath_from_selection "$2") vjfile=$(__filepath_from_selection "$2")
rm -v "$vjfile" rm -i "$vjfile"
fi fi
# Generate new entry # Generate new entry
if [ "${1:-}" = "--new" ]; then if [ "${1:-}" = "--new" ]; then
label_new=$(echo "$LABLES" | fzf \ collection=$(echo "$LABLES" | fzf \
--margin 20% \ --margin 20% \
--prompt="Select collection> ") --prompt="Select collection> ")
uuid_new=$(uuidgen) file=""
vjfile_new=$(__filepath_from_selection "$label_new $uuid_new.ics") while [ -f "$file" ] || [ -z "$file" ]; do
if [ -f "$vjfile_new" ]; then uuid=$(uuidgen)
echo "Bad luck..." file=$(__filepath_from_selection "$collection $uuid.ics")
return 1 done
fi tmpmd=$(mktemp --suffix='.md')
# tmpsha="$tmpmd.sha"
TMPFILE=$(mktemp --suffix='.md')
SHAFILE="$TMPFILE.sha"
{ {
echo "::: Keep this line if you want to add a **JOURNAL** entry (associated to today), else we will add a **NOTE**" echo "::: |> <!-- keep this line to associate the entry to _today_ -->"
echo "# <write summary here>" echo "::: <| <!-- specify the due date for to-dos, can be empty -->"
echo "> <comma-separated list of categories>" echo "# <!-- write summary here -->"
echo "> <!-- comma-separated list of categories -->"
echo "" echo ""
} >"$TMPFILE" } >"$tmpmd"
sha1sum "$TMPFILE" >"$SHAFILE" sha1sum "$tmpmd" >"$tmpsha"
# Open in editor # Open in editor
$EDITOR "$TMPFILE" >/dev/tty $EDITOR "$tmpmd" >/dev/tty
# Update if changes are detected # Update if changes are detected
if ! sha1sum -c "$SHAFILE" >/dev/null 2>&1; then if ! sha1sum -c "$tmpsha" >/dev/null 2>&1; then
vjfile_tmp="$TMPFILE.ics" tmpfile="$tmpmd.ics"
__vjournalnew "$uuid_new" <"$TMPFILE" >"$vjfile_tmp" && mv "$vjfile_tmp" "$vjfile_new" || rm "$vjfile_tmp" tmpferr="$tmpmd.err"
if __vicalnew "$uuid" <"$tmpmd" >"$tmpfile" 2>"$tmpferr"; then
mv "$tmpfile" "$file"
else
rm "$tmpfile"
less "$tmpferr"
fi
rm "$tmpferr"
fi fi
rm "$TMPFILE" "$SHAFILE" rm "$tmpmd" "$tmpsha"
fi
# Toggle completed flag
if [ "${1:-}" = "--toggle-completed" ]; then
vtfile=$(__filepath_from_selection "$2")
vtfile_tmp=$(mktemp)
__vtodotogglecompleted "$vtfile" >"$vtfile_tmp" && mv "$vtfile_tmp" "$vtfile" || rm "$vtfile_tmp"
fi
# Increase priority
if [ "${1:-}" = "--increase-priority" ]; then
vtfile=$(__filepath_from_selection "$2")
vtfile_tmp=$(mktemp)
__vtodopriority "$vtfile" "1" >"$vtfile_tmp" && mv "$vtfile_tmp" "$vtfile" || rm "$vtfile_tmp"
fi
# Decrease priority
if [ "${1:-}" = "--decrease-priority" ]; then
vtfile=$(__filepath_from_selection "$2")
vtfile_tmp=$(mktemp)
__vtodopriority "$vtfile" "-1" >"$vtfile_tmp" && mv "$vtfile_tmp" "$vtfile" || rm "$vtfile_tmp"
fi fi
if [ "${1:-}" = "--reload" ]; then if [ "${1:-}" = "--reload" ]; then
__lines __lines
exit exit
fi fi
query="${FZF_QUERY:-}"
if [ "${1:-}" = "--no-completed" ]; then
query="!✅"
fi
if [ "${1:-}" = "--completed" ]; then
query="✅"
fi
if [ "${1:-}" = "--tasks" ]; then
query="✅ | 🔲"
fi
if [ "${1:-}" = "--no-tasks" ]; then
query="!✅ !🔲"
fi
if [ -z "$query" ]; then
query="!✅"
fi
query=$(echo "$query" | sed 's/ *$//g')
selection=$( selection=$(
__lines | fzf --ansi \ __lines | fzf --ansi \
--query="$query " \
--no-sort \
--no-hscroll \
--preview="$0 --preview {}" \ --preview="$0 --preview {}" \
--bind="ctrl-d:become($0 --delete {})" \ --bind="ctrl-d:become($0 --delete {})" \
--bind="ctrl-x:become($0 --toggle-completed {})" \
--bind="alt-up:become($0 --increase-priority {})" \
--bind="alt-down:become($0 --decrease-priority {})" \
--bind="ctrl-n:become($0 --new)" \ --bind="ctrl-n:become($0 --new)" \
--bind="ctrl-s:execute($SYNC_CMD)" \ --bind="ctrl-s:execute($SYNC_CMD)" \
--bind="ctrl-r:reload-sync($0 --reload)" --bind="ctrl-r:reload-sync($0 --reload)"
@@ -341,40 +610,44 @@ if [ -z "$selection" ]; then
return 0 return 0
fi fi
VJ_FILE=$(__filepath_from_selection "$selection") file=$(__filepath_from_selection "$selection")
if [ ! -f "$VJ_FILE" ]; then if [ ! -f "$file" ]; then
echo "ERROR: File '$VJ_FILE' does not exist!" echo "ERROR: File '$file' does not exist!"
return 1 return 1
fi fi
# Parse vjournal file and save as json # Parse vjournal file and save as json
TMPJSON=$(mktemp) filejson=$(mktemp)
__vjournal2json <"$VJ_FILE" >"$TMPJSON" __ical2json <"$file" >"$filejson"
# Prepare file to be edited # Prepare file to be edited
TMPFILE=$(mktemp --suffix='.md') filetmp=$(mktemp --suffix='.md')
SHAFILE="$TMPFILE.sha" filesha="$filetmp.sha"
SUMMARY=$(jq -r '.summary' "$TMPJSON") if jq -e '.due' "$filejson"; then
CATEGORIES=$(jq -r '.categories | join(",")' "$TMPJSON") due=$(jq -r '.due' "$filejson")
echo "::: <| $due" >"$filetmp"
fi
summary=$(jq -r '.summary' "$filejson")
categories=$(jq -r '.categories | join(",")' "$filejson")
{ {
echo "# $SUMMARY" echo "# $summary"
echo "> $CATEGORIES" echo "> $categories"
echo "" echo ""
jq -r '.description' "$TMPJSON" jq -r '.description' "$filejson"
} >"$TMPFILE" } >>"$filetmp"
rm "$TMPJSON" rm "$filejson"
sha1sum "$TMPFILE" >"$SHAFILE" sha1sum "$filetmp" >"$filesha"
# Open in editor # Open in editor
$EDITOR "$TMPFILE" $EDITOR "$filetmp"
# Update only if changes are detected # Update only if changes are detected
if ! sha1sum -c "$SHAFILE" >/dev/null 2>&1; then if ! sha1sum -c "$filesha" >/dev/null 2>&1; then
echo "Uh... chages detected!" echo "Uh... chages detected!"
vj_file_new="$TMPFILE.ics" vj_file_new="$filetmp.ics"
__vjournalupdate "$VJ_FILE" <"$TMPFILE" >"$vj_file_new" && mv "$vj_file_new" "$VJ_FILE" || rm "$vj_file_new" __icalupdate "$file" <"$filetmp" >"$vj_file_new" && mv "$vj_file_new" "$file" || rm "$vj_file_new"
fi fi
rm "$TMPFILE" "$SHAFILE" rm "$filetmp" "$filesha"
exec "$0" exec "$0"