From 50c438e656fb5c96771c2b8f67c2b762e3fc6717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=84min=20Baumeler?= Date: Wed, 28 May 2025 14:14:58 +0200 Subject: [PATCH] awk internalized --- fzf-vjour | 522 +++++++++++++++++++++++++++++++++++++++++++++- src/altertodo.awk | 40 ---- src/export.awk | 39 ---- src/get.awk | 18 -- src/list.awk | 215 ------------------- src/new.awk | 96 --------- src/update.awk | 85 -------- 7 files changed, 512 insertions(+), 503 deletions(-) delete mode 100644 src/altertodo.awk delete mode 100644 src/export.awk delete mode 100644 src/get.awk delete mode 100644 src/list.awk delete mode 100644 src/new.awk delete mode 100644 src/update.awk diff --git a/fzf-vjour b/fzf-vjour index 3c0ea78..49c225e 100755 --- a/fzf-vjour +++ b/fzf-vjour @@ -10,14 +10,516 @@ if [ -z "$ROOT" ] || [ -z "$SYNC_CMD" ] || [ -z "$COLLECTION_LABELS" ]; then exit 1 fi +### AWK SCRIPTS +AWK_ALTERTODO='# Increase/decrease priority, or toggle completed status +# +# If `delta` is specified using `-v`, then the priority value is increased by +# `delta.` If `delta` is unspecified (or equal to 0), then the completeness +# status is toggled. +BEGIN { + FS=":"; + zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1); + delta = delta + 0; # cast as integer +} +/^END:VTODO/ && inside { + # Print sequence and last-modified, if not yet printed + if (!seq) print "SEQUENCE:1"; + if (!lm) print "LAST-MODIFIED:" zulu; + + # Print priority + prio = prio ? prio + delta : 0 + delta; + prio = prio < 0 ? 0 : prio; + prio = prio > 9 ? 9 : prio; + print "PRIORITY:" prio; + + # Print status (toggle if needed) + bit_toggle = delta ? 0 : 1; + percent = xor(completed, bit_toggle) ? 100 : 0; + status = xor(completed, bit_toggle) ? "COMPLETED" : "NEEDS-ACTION"; + print "STATUS:" status + print "PERCENT-COMPLETE:" percent + + # print rest + inside = ""; + print $0; + next +} +/^BEGIN:VTODO/ { inside = 1; print; next } +/^SEQUENCE/ && inside { seq = 1; print "SEQUENCE:" $2+1; next } +/^LAST-MODIFIED/ && inside { lm = 1; print "LAST-MODIFIED:" zulu; next } +/^PRIORITY:/ && inside { prio = $2; next } +/^STATUS:COMPLETED/ && inside { completed = 1; next } +/^PERCENT-COMPLETE/ && inside { next } # ignore, we take STATUS:COMPLETED as reference +{ print }' + +AWK_EXPORT='function getcontent(content_line, prop) +{ + return substr(content_line[prop], index(content_line[prop], ":") + 1); +} + +function storetext_line(content_line, c, prop) +{ + c[prop] = getcontent(content_line, prop); + gsub("\\\\n", "\n", c[prop]); + gsub("\\\\N", "\n", c[prop]); + gsub("\\\\,", ",", c[prop]); + gsub("\\\\;", ";", c[prop]); + gsub("\\\\\\\\", "\\", c[prop]); +} + +BEGIN { FS = "[:;]"; } +/^BEGIN:(VJOURNAL|VTODO)/ { type = $2 } +/^END:/ && $2 == type { exit } +/^(CATEGORIES|DESCRIPTION|SUMMARY|DUE)/ { prop = $1; content_line[prop] = $0; next; } +/^[^ ]/ && prop { prop = ""; next; } +/^ / && prop { content_line[prop] = content_line[prop] substr($0, 2); next; } + +END { + if (!type) { + exit + } + # Process content lines + storetext_line(content_line, c, "CATEGORIES" ); + storetext_line(content_line, c, "DESCRIPTION"); + storetext_line(content_line, c, "SUMMARY" ); + storetext_line(content_line, c, "DUE" ); + # Print + if (c["DUE"]) + print "::: <| " substr(c["DUE"], 1, 4) "-" substr(c["DUE"], 5, 2) "-" substr(c["DUE"], 7, 2); + print "# " c["SUMMARY"]; + print "> " c["CATEGORIES"]; + print ""; + print c["DESCRIPTION"]; +}' + +AWK_GET='# print content of field `field` +BEGIN { FS = ":"; regex = "^" field; } +/^BEGIN:(VJOURNAL|VTODO)/ { type = $2 } +/^END:/ && $2 == type { exit } +$0 ~ field { content = $0; next; } +/^ / && content { content = content substr($0, 2); next; } +/^[^ ]/ && content { exit } +END { + if (!type) { exit } + # Process content line + content = substr(content, index(content, ":") + 1); + gsub("\\\\n", "\n", content); + gsub("\\\\N", "\n", content); + gsub("\\\\,", ",", content); + gsub("\\\\;", ";", content); + gsub("\\\\\\\\", "\\", content); + print content; +}' + +AWK_LIST='# awk script to generate summary line for iCalendar VJOURNAL and VTODO entries +# +# See https://datatracker.ietf.org/doc/html/rfc5545 for the RFC 5545 that +# describes iCalendar, and its syntax + +function getcontent(content_line, prop) +{ + return substr(content_line[prop], index(content_line[prop], ":") + 1); +} + +function storetext_line(content_line, c, prop) +{ + c[prop] = getcontent(content_line, prop); + gsub("\\\\n", " ", c[prop]); + gsub("\\\\N", " ", c[prop]); + gsub("\\\\,", ",", c[prop]); + gsub("\\\\;", ";", c[prop]); + gsub("\\\\\\\\", "\\", c[prop]); + #gsub(" ", "_", c[prop]); +} + +function storeinteger(content_line, c, prop) +{ + c[prop] = getcontent(content_line, prop); + c[prop] = c[prop] ? c[prop] : 0; +} + +function storedatetime(content_line, c, prop) +{ + c[prop] = getcontent(content_line, prop); +} + +function storedate(content_line, c, prop) +{ + c[prop] = substr(getcontent(content_line, prop), 1, 8); +} + +function formatdate(date, today, todaystamp, ts, ts_y, ts_m, ts_d, delta) +{ + ts_y = substr(date, 1, 4); + ts_m = substr(date, 5, 2); + ts_d = substr(date, 7); + ts = mktime(ts_y " " ts_m " " ts_d " 00 00 00"); + delta = (ts - todaystamp) / 86400; + if (delta >= 0 && delta < 1) { + return " today"; + } + if (delta >= 1 && delta < 2) { + return " tomorrow"; + } + if (delta >= 2 && delta < 3) { + return " in two days"; + } + if (delta >= 3 && delta < 4) { + return " in three days"; + } + if (delta < 0 && delta >= -1) { + return " yesterday"; + } + if (delta < -1 && delta >= -2) { + return " two days ago"; + } + if (delta < -2 && delta >= -3) { + return "three days ago"; + } + return " " substr(date, 1, 4) "-" substr(date, 5, 2) "-" substr(date, 7); +} + +BEGIN { + # We require the following variables to be set using -v + # collection_lables: ;-delimited collection=label strings + # flag_open: symbol for open to-dos + # flag_completed: symbol for completed to-dos + # flag_journal: symbol for journal entries + # flag_note: symbol for note entries + + FS = "[:;]"; + # Collections + split(collection_labels, mapping, ";"); + for (map in mapping) + { + split(mapping[map], m, "="); + collection2label[m[1]] = m[2]; + } + # Colors + GREEN = "\033[1;32m"; + RED = "\033[1;31m"; + WHITE = "\033[1;97m"; + CYAN = "\033[1;36m"; + FAINT = "\033[2m"; + OFF = "\033[m"; + + # For date comparision + today = strftime("%Y%m%d"); + todaystamp = mktime(substr(today, 1, 4) " " substr(today, 5, 2) " " substr(today, 7) " 00 00 00"); +} + +# Reset variables +BEGINFILE { + type = ""; + prop = ""; + delete content_line; + delete c; + +} + +/^BEGIN:(VJOURNAL|VTODO)/ { + type = $2 +} + +/^END:/ && $2 == type { + nextfile +} + +/^(CATEGORIES|DESCRIPTION|PRIORITY|STATUS|SUMMARY|COMPLETED|DUE|DTSTART|DURATION|CREATED|DTSTAMP|LAST-MODIFIED)/ { + prop = $1; + content_line[prop] = $0; + next; +} +/^[^ ]/ && prop { + prop = ""; + next; +} +/^ / && prop { + content_line[prop] = content_line[prop] substr($0, 2); + next; +} + +ENDFILE { + if (!type) { + exit + } + # Process content lines + storetext_line(content_line, c, "CATEGORIES" ); + storetext_line(content_line, c, "DESCRIPTION" ); + storeinteger( content_line, c, "PRIORITY" ); + storetext_line(content_line, c, "STATUS" ); + storetext_line(content_line, c, "SUMMARY" ); + storedatetime( content_line, c, "COMPLETED" ); + storedate( content_line, c, "DUE" ); + storedate( content_line, c, "DTSTART" ); + storedatetime( content_line, c, "DURATION" ); + storedatetime( content_line, c, "CREATED" ); + storedatetime( content_line, c, "DTSTAMP" ); + storedatetime( content_line, c, "LAST-MODIFIED"); + + # Priority field, primarly used for sorting + priotext = ""; + prio = 0; + if (c["PRIORITY"] > 0) + { + priotext = "❗(" c["PRIORITY"] ") "; + prio = 10 - c["PRIORITY"]; + } + + # Last modification/creation time stamp, used for sorting + # LAST-MODIFIED: Optional field for VTODO and VJOURNAL entries, date-time in + # UTC time format + # DTSTAMP: mandatory field in VTODO and VJOURNAL, date-time in UTC time + # format + mod = c["LAST-MODIFIED"] ? c["LAST-MODIFIED"] : c["DTSTAMP"]; + + # Collection name + depth = split(FILENAME, path, "/"); + collection = depth > 1 ? path[depth-1] : ""; + collection = collection in collection2label ? collection2label[collection] : collection; + + # Date field. For VTODO entries, we show the due date, for journal entries, + # the associated date. + datecolor = CYAN; + summarycolor = GREEN; + + if (type == "VTODO") + { + # Either DUE or DURATION may appear. If DURATION appears, then also DTSTART + d = c["DUE"] ? c["DUE"] : + (c["DURATION"] ? c["DTSTART"] " for " c["DURATION"] : ""); + if (d && d <= today && c["STATUS"] != "COMPLETED") + { + datecolor = RED; + summarycolor = RED; + } + } else { + d = c["DTSTART"]; + } + d = d ? formatdate(d, today, todaystamp ts, ts_y, ts_m, ts_d, delta) : " "; + + # flag: - "journal" for VJOURNAL with DTSTART + # - "note" for VJOURNAL without DTSTART + # - "completed" for VTODO with c["STATUS"] == COMPLETED + # - "open" for VTODO with c["STATUS"] != COMPLETED + if (type == "VTODO") + flag = c["STATUS"] == "COMPLETED" ? flag_completed : flag_open; + else + flag = c["DTSTART"] ? flag_journal : flag_note; + + # summary + # c["SUMMARY"] + summary = c["SUMMARY"] ? c["SUMMARY"] : " " + + # categories + categories = c["CATEGORIES"] ? c["CATEGORIES"] : " " + + # filename + # FILENAME + + print prio, + mod, + collection, + datecolor d OFF, + flag, + priotext summarycolor summary OFF, + WHITE categories OFF, + " " FAINT FILENAME OFF; +}' + +AWK_NEW='function escape_categories(str) +{ + gsub("\\\\", "\\\\", str); + gsub(";", "\\\\;", str); +} + +function escape(str) +{ + escape_categories(str) + gsub(",", "\\\\,", str); +} + +function print_fold(nameparam, content, i, s) +{ + i = 74 - length(nameparam); + s = substr(content, 1, i); + print nameparam s; + s = substr(content, i+1, 73); + i = i + 73; + while (s) + { + print " " s; + s = substr(content, i+1, 73); + i = i + 73; + } +} + +BEGIN { + FS=":"; + type = "VJOURNAL"; + zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1); +} +desc { desc = desc "\\n" $0; next; } +{ + if (substr($0, 1, 6) == "::: |>") + { + start = substr(zulu, 1, 8); + getline; + } + if (substr($0, 1, 6) == "::: <|") + { + type = "VTODO" + due = substr($0, 8); + getline; + } + summary = substr($0, 1, 2) != "# " ? "" : substr($0, 3); + getline; + categories = substr($0, 1, 1) != ">" ? "" : substr($0, 3); + getline; # This line should be empty + getline; # First line of description + desc = $0; + next; +} +END { + # Sanitize input + if (due) { + # Use command line `date` for parsing + cmd = "date -d \"" due "\" +\"%Y%m%d\""; + cmd | getline res + due = res ? res : "" + } + escape(summary); + escape(desc); + escape_categories(categories); + + # print ical + print "BEGIN:VCALENDAR"; + print "VERSION:2.0"; + print "CALSCALE:GREGORIAN"; + print "PRODID:-//fab//awk//EN"; + print "BEGIN:" type; + print "DTSTAMP:" zulu; + print "UID:" uid; + print "CLASS:PRIVATE"; + print "CREATED:" zulu; + print "SEQUENCE:1"; + print "LAST-MODIFIED:" zulu; + if (type == "VTODO") + { + print "STATUS:NEEDS-ACTION"; + print "PERCENT-COMPLETE:0"; + if (due) + print "DUE;VALUE=DATE:" due; + } + else + { + print "STATUS:FINAL"; + if (start) + print "DTSTART;VALUE=DATE:" start; + } + if (summary) print_fold("SUMMARY:", summary, i, s); + if (categories) print_fold("CATEGORIES:", categories, i, s); + if (desc) print_fold("DESCRIPTION:", desc, i, s); + print "END:" type; + print "END:VCALENDAR" +}' + +AWK_UPDATE='function getcontent(content_line, prop) +{ + return substr(content_line[prop], index(content_line[prop], ":") + 1); +} + +function escape_categories(str) +{ + gsub("\\\\", "\\\\", str); + gsub(";", "\\\\;", str); +} + +function escape(str) +{ + escape_categories(str) + gsub(",", "\\\\,", str); +} + +function print_fold(nameparam, content, i, s) +{ + i = 74 - length(nameparam); + s = substr(content, 1, i); + print nameparam s; + s = substr(content, i+1, 73); + i = i + 73; + while (s) + { + print " " s; + s = substr(content, i+1, 73); + i = i + 73; + } +} + +BEGIN { + FS=":"; + zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1); +} + +ENDFILE { + if (NR == FNR) + { + # Sanitize input + if (due) { + # Use command line `date` for parsing + cmd = "date -d \"" due "\" +\"%Y%m%d\""; + cmd | getline res + due = res ? res : "" + } + escape(summary); + escape(desc); + escape_categories(categories); + } +} + +NR == FNR && desc { desc = desc "\\n" $0; next; } +NR == FNR { + if (substr($0, 1, 6) == "::: <|") + { + due = substr($0, 8); + getline; + } + summary = substr($0, 1, 2) != "# " ? "" : substr($0, 3); + getline; + categories = substr($0, 1, 1) != ">" ? "" : substr($0, 3); + getline; # This line should be empty + getline; # First line of description + desc = $0; + next; +} + +/^BEGIN:(VJOURNAL|VTODO)/ { type = $2; print; next } +/^X-ALT-DESC/ && type { next } # drop this alternative description +/^ / && type { next } # drop this folded line (the only content with folded lines will be updated) +/^(DUE|SUMMARY|CATEGORIES|DESCRIPTION|LAST-MODIFIED)/ && type { next } # skip for now, we will write updated fields at the end +/^SEQUENCE/ && type { seq = $2; next } # store sequence number and skip +/^END:/ && type == $2 { + seq = seq ? seq + 1 : 1; + print "SEQUENCE:" seq; + print "LAST-MODIFIED:" zulu; + if (due) print "DUE;VALUE=DATE:" due; + print_fold("SUMMARY:", summary, i, s); + print_fold("CATEGORIES:", categories, i, s); + print_fold("DESCRIPTION:", desc, i, s); + type = ""; +} +{ print }' +### END OF AWK SCRIPTS + __lines() { find "$ROOT" -type f -name '*.ics' -print0 | xargs -0 -P 0 \ - awk -f "./src/list.awk" \ + awk \ -v collection_labels="$COLLECTION_LABELS" \ -v flag_open="🔲" \ -v flag_completed="✅" \ -v flag_journal="📘" \ - -v flag_note="🗒️" | + -v flag_note="🗒️" \ + "$AWK_LIST" | sort -g -r | cut -d ' ' -f 3- } @@ -56,14 +558,14 @@ fi # Generate preview of file from selection if [ "${1:-}" = "--preview" ]; then file=$(__filepath_from_selection "$2") - awk -v field="DESCRIPTION" -f "src/get.awk" "$file" | + awk -v field="DESCRIPTION" "$AWK_GET" "$file" | batcat --color=always --style=numbers --language=md exit fi # Delete file from selection if [ "${1:-}" = "--delete" ]; then file=$(__filepath_from_selection "$2") - summary=$(awk -v field="SUMMARY" -f "src/get.awk" "$file") + summary=$(awk -v field="SUMMARY" "$AWK_GET" "$file") while true; do printf "Do you want to delete the entry with the title \"%s\"? " "$summary" >/dev/tty read -r yn @@ -113,7 +615,7 @@ if [ "${1:-}" = "--new" ]; then # Update if changes are detected if ! sha1sum -c "$tmpsha" >/dev/null 2>&1; then tmpfile="$tmpmd.ics" - awk -v uid="$uuid" -f src/new.awk "$tmpmd" >"$tmpfile" + awk -v uid="$uuid" "$AWK_NEW" "$tmpmd" >"$tmpfile" mv "$tmpfile" "$file" fi rm "$tmpmd" "$tmpsha" @@ -122,21 +624,21 @@ fi if [ "${1:-}" = "--toggle-completed" ]; then file=$(__filepath_from_selection "$2") tmpfile=$(mktemp) - awk -f "src/altertodo.awk" "$file" >"$tmpfile" + awk "$AWK_ALTERTODO" "$file" >"$tmpfile" mv "$tmpfile" "$file" fi # Increase priority if [ "${1:-}" = "--increase-priority" ]; then file=$(__filepath_from_selection "$2") tmpfile=$(mktemp) - awk -v delta="1" -f "src/altertodo.awk" "$file" >"$tmpfile" + awk -v delta="1" "$AWK_ALTERTODO" "$file" >"$tmpfile" mv "$tmpfile" "$file" fi # Decrease priority if [ "${1:-}" = "--decrease-priority" ]; then file=$(__filepath_from_selection "$2") tmpfile=$(mktemp) - awk -v delta="-1" -f "src/altertodo.awk" "$file" >"$tmpfile" + awk -v delta="-1" "$AWK_ALTERTODO" "$file" >"$tmpfile" mv "$tmpfile" "$file" fi if [ "${1:-}" = "--reload" ]; then @@ -207,7 +709,7 @@ fi # Prepare file to be edited filetmp=$(mktemp --suffix='.md') filesha="$filetmp.sha" -awk -f src/export.awk "$file" >"$filetmp" +awk "$AWK_EXPORT" "$file" >"$filetmp" sha1sum "$filetmp" >"$filesha" # Open in editor @@ -217,7 +719,7 @@ $EDITOR "$filetmp" >/dev/tty if ! sha1sum -c "$filesha" >/dev/null 2>&1; then echo "Uh... chages detected!" >/dev/tty file_new="$filetmp.ics" - awk -f src/update.awk "$filetmp" "$file" >"$file_new" + awk "$AWK_UPDATE" "$filetmp" "$file" >"$file_new" mv "$file_new" "$file" fi rm "$filetmp" "$filesha" diff --git a/src/altertodo.awk b/src/altertodo.awk deleted file mode 100644 index 4be828f..0000000 --- a/src/altertodo.awk +++ /dev/null @@ -1,40 +0,0 @@ -# Increase/decrease priority, or toggle completed status -# -# If `delta` is specified using `-v`, then the priority value is increased by -# `delta.` If `delta` is unspecified (or equal to 0), then the completeness -# status is toggled. -BEGIN { - FS=":"; - zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1); - delta = delta + 0; # cast as integer -} -/^END:VTODO/ && inside { - # Print sequence and last-modified, if not yet printed - if (!seq) print "SEQUENCE:1"; - if (!lm) print "LAST-MODIFIED:" zulu; - - # Print priority - prio = prio ? prio + delta : 0 + delta; - prio = prio < 0 ? 0 : prio; - prio = prio > 9 ? 9 : prio; - print "PRIORITY:" prio; - - # Print status (toggle if needed) - bit_toggle = delta ? 0 : 1; - percent = xor(completed, bit_toggle) ? 100 : 0; - status = xor(completed, bit_toggle) ? "COMPLETED" : "NEEDS-ACTION"; - print "STATUS:" status - print "PERCENT-COMPLETE:" percent - - # print rest - inside = ""; - print $0; - next -} -/^BEGIN:VTODO/ { inside = 1; print; next } -/^SEQUENCE/ && inside { seq = 1; print "SEQUENCE:" $2+1; next } -/^LAST-MODIFIED/ && inside { lm = 1; print "LAST-MODIFIED:" zulu; next } -/^PRIORITY:/ && inside { prio = $2; next } -/^STATUS:COMPLETED/ && inside { completed = 1; next } -/^PERCENT-COMPLETE/ && inside { next } # ignore, we take STATUS:COMPLETED as reference -{ print } diff --git a/src/export.awk b/src/export.awk deleted file mode 100644 index f81f74f..0000000 --- a/src/export.awk +++ /dev/null @@ -1,39 +0,0 @@ -function getcontent(content_line, prop) -{ - return substr(content_line[prop], index(content_line[prop], ":") + 1); -} - -function storetext_line(content_line, c, prop) -{ - c[prop] = getcontent(content_line, prop); - gsub("\\\\n", "\n", c[prop]); - gsub("\\\\N", "\n", c[prop]); - gsub("\\\\,", ",", c[prop]); - gsub("\\\\;", ";", c[prop]); - gsub("\\\\\\\\", "\\", c[prop]); -} - -BEGIN { FS = "[:;]"; } -/^BEGIN:(VJOURNAL|VTODO)/ { type = $2 } -/^END:/ && $2 == type { exit } -/^(CATEGORIES|DESCRIPTION|SUMMARY|DUE)/ { prop = $1; content_line[prop] = $0; next; } -/^[^ ]/ && prop { prop = ""; next; } -/^ / && prop { content_line[prop] = content_line[prop] substr($0, 2); next; } - -END { - if (!type) { - exit - } - # Process content lines - storetext_line(content_line, c, "CATEGORIES" ); - storetext_line(content_line, c, "DESCRIPTION"); - storetext_line(content_line, c, "SUMMARY" ); - storetext_line(content_line, c, "DUE" ); - # Print - if (c["DUE"]) - print "::: <| " substr(c["DUE"], 1, 4) "-" substr(c["DUE"], 5, 2) "-" substr(c["DUE"], 7, 2); - print "# " c["SUMMARY"]; - print "> " c["CATEGORIES"]; - print ""; - print c["DESCRIPTION"]; -} diff --git a/src/get.awk b/src/get.awk deleted file mode 100644 index a013736..0000000 --- a/src/get.awk +++ /dev/null @@ -1,18 +0,0 @@ -# print content of field `field` -BEGIN { FS = ":"; regex = "^" field; } -/^BEGIN:(VJOURNAL|VTODO)/ { type = $2 } -/^END:/ && $2 == type { exit } -$0 ~ field { content = $0; next; } -/^ / && content { content = content substr($0, 2); next; } -/^[^ ]/ && content { exit } -END { - if (!type) { exit } - # Process content line - content = substr(content, index(content, ":") + 1); - gsub("\\\\n", "\n", content); - gsub("\\\\N", "\n", content); - gsub("\\\\,", ",", content); - gsub("\\\\;", ";", content); - gsub("\\\\\\\\", "\\", content); - print content; -} diff --git a/src/list.awk b/src/list.awk deleted file mode 100644 index 66d368a..0000000 --- a/src/list.awk +++ /dev/null @@ -1,215 +0,0 @@ -# awk script to generate summary line for iCalendar VJOURNAL and VTODO entries -# -# See https://datatracker.ietf.org/doc/html/rfc5545 for the RFC 5545 that -# describes iCalendar, and its syntax - -function getcontent(content_line, prop) -{ - return substr(content_line[prop], index(content_line[prop], ":") + 1); -} - -function storetext_line(content_line, c, prop) -{ - c[prop] = getcontent(content_line, prop); - gsub("\\\\n", " ", c[prop]); - gsub("\\\\N", " ", c[prop]); - gsub("\\\\,", ",", c[prop]); - gsub("\\\\;", ";", c[prop]); - gsub("\\\\\\\\", "\\", c[prop]); - #gsub(" ", "_", c[prop]); -} - -function storeinteger(content_line, c, prop) -{ - c[prop] = getcontent(content_line, prop); - c[prop] = c[prop] ? c[prop] : 0; -} - -function storedatetime(content_line, c, prop) -{ - c[prop] = getcontent(content_line, prop); -} - -function storedate(content_line, c, prop) -{ - c[prop] = substr(getcontent(content_line, prop), 1, 8); -} - -function formatdate(date, today, todaystamp, ts, ts_y, ts_m, ts_d, delta) -{ - ts_y = substr(date, 1, 4); - ts_m = substr(date, 5, 2); - ts_d = substr(date, 7); - ts = mktime(ts_y " " ts_m " " ts_d " 00 00 00"); - delta = (ts - todaystamp) / 86400; - if (delta >= 0 && delta < 1) { - return " today"; - } - if (delta >= 1 && delta < 2) { - return " tomorrow"; - } - if (delta >= 2 && delta < 3) { - return " in two days"; - } - if (delta >= 3 && delta < 4) { - return " in three days"; - } - if (delta < 0 && delta >= -1) { - return " yesterday"; - } - if (delta < -1 && delta >= -2) { - return " two days ago"; - } - if (delta < -2 && delta >= -3) { - return "three days ago"; - } - return " " substr(date, 1, 4) "-" substr(date, 5, 2) "-" substr(date, 7); -} - -BEGIN { - # We require the following variables to be set using -v - # collection_lables: ;-delimited collection=label strings - # flag_open: symbol for open to-do's - # flag_completed: symbol for completed to-do's - # flag_journal: symbol for journal entries - # flag_note: symbol for note entries - - FS = "[:;]"; - # Collections - split(collection_labels, mapping, ";"); - for (map in mapping) - { - split(mapping[map], m, "="); - collection2label[m[1]] = m[2]; - } - # Colors - GREEN = "\033[1;32m"; - RED = "\033[1;31m"; - WHITE = "\033[1;97m"; - CYAN = "\033[1;36m"; - FAINT = "\033[2m"; - OFF = "\033[m"; - - # For date comparision - today = strftime("%Y%m%d"); - todaystamp = mktime(substr(today, 1, 4) " " substr(today, 5, 2) " " substr(today, 7) " 00 00 00"); -} - -# Reset variables -BEGINFILE { - type = ""; - prop = ""; - delete content_line; - delete c; - -} - -/^BEGIN:(VJOURNAL|VTODO)/ { - type = $2 -} - -/^END:/ && $2 == type { - nextfile -} - -/^(CATEGORIES|DESCRIPTION|PRIORITY|STATUS|SUMMARY|COMPLETED|DUE|DTSTART|DURATION|CREATED|DTSTAMP|LAST-MODIFIED)/ { - prop = $1; - content_line[prop] = $0; - next; -} -/^[^ ]/ && prop { - prop = ""; - next; -} -/^ / && prop { - content_line[prop] = content_line[prop] substr($0, 2); - next; -} - -ENDFILE { - if (!type) { - exit - } - # Process content lines - storetext_line(content_line, c, "CATEGORIES" ); - storetext_line(content_line, c, "DESCRIPTION" ); - storeinteger( content_line, c, "PRIORITY" ); - storetext_line(content_line, c, "STATUS" ); - storetext_line(content_line, c, "SUMMARY" ); - storedatetime( content_line, c, "COMPLETED" ); - storedate( content_line, c, "DUE" ); - storedate( content_line, c, "DTSTART" ); - storedatetime( content_line, c, "DURATION" ); - storedatetime( content_line, c, "CREATED" ); - storedatetime( content_line, c, "DTSTAMP" ); - storedatetime( content_line, c, "LAST-MODIFIED"); - - # Priority field, primarly used for sorting - priotext = ""; - prio = 0; - if (c["PRIORITY"] > 0) - { - priotext = "❗(" c["PRIORITY"] ") "; - prio = 10 - c["PRIORITY"]; - } - - # Last modification/creation time stamp, used for sorting - # LAST-MODIFIED: Optional field for VTODO and VJOURNAL entries, date-time in - # UTC time format - # DTSTAMP: mandatory field in VTODO and VJOURNAL, date-time in UTC time - # format - mod = c["LAST-MODIFIED"] ? c["LAST-MODIFIED"] : c["DTSTAMP"]; - - # Collection name - depth = split(FILENAME, path, "/"); - collection = depth > 1 ? path[depth-1] : ""; - collection = collection in collection2label ? collection2label[collection] : collection; - - # Date field. For VTODO entries, we show the due date, for journal entries, - # the associated date. - datecolor = CYAN; - summarycolor = GREEN; - - if (type == "VTODO") - { - # Either DUE or DURATION may appear. If DURATION appears, then also DTSTART - d = c["DUE"] ? c["DUE"] : - (c["DURATION"] ? c["DTSTART"] " for " c["DURATION"] : ""); - if (d && d <= today && c["STATUS"] != "COMPLETED") - { - datecolor = RED; - summarycolor = RED; - } - } else { - d = c["DTSTART"]; - } - d = d ? formatdate(d, today, todaystamp ts, ts_y, ts_m, ts_d, delta) : " "; - - # flag: - "journal" for VJOURNAL with DTSTART - # - "note" for VJOURNAL without DTSTART - # - "completed" for VTODO with c["STATUS"] == COMPLETED - # - "open" for VTODO with c["STATUS"] != COMPLETED - if (type == "VTODO") - flag = c["STATUS"] == "COMPLETED" ? flag_completed : flag_open; - else - flag = c["DTSTART"] ? flag_journal : flag_note; - - # summary - # c["SUMMARY"] - summary = c["SUMMARY"] ? c["SUMMARY"] : " " - - # categories - categories = c["CATEGORIES"] ? c["CATEGORIES"] : " " - - # filename - # FILENAME - - print prio, - mod, - collection, - datecolor d OFF, - flag, - priotext summarycolor summary OFF, - WHITE categories OFF, - " " FAINT FILENAME OFF; -} diff --git a/src/new.awk b/src/new.awk deleted file mode 100644 index ad3834d..0000000 --- a/src/new.awk +++ /dev/null @@ -1,96 +0,0 @@ -function escape_categories(str) -{ - gsub("\\\\", "\\\\", str); - gsub(";", "\\\\;", str); -} - -function escape(str) -{ - escape_categories(str) - gsub(",", "\\\\,", str); -} - -function print_fold(nameparam, content, i, s) -{ - i = 74 - length(nameparam); - s = substr(content, 1, i); - print nameparam s; - s = substr(content, i+1, 73); - i = i + 73; - while (s) - { - print " " s; - s = substr(content, i+1, 73); - i = i + 73; - } -} - -BEGIN { - FS=":"; - type = "VJOURNAL"; - zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1); -} -desc { desc = desc "\\n" $0; next; } -{ - if (substr($0, 1, 6) == "::: |>") - { - start = substr(zulu, 1, 8); - getline; - } - if (substr($0, 1, 6) == "::: <|") - { - type = "VTODO" - due = substr($0, 8); - getline; - } - summary = substr($0, 1, 2) != "# " ? "" : substr($0, 3); - getline; - categories = substr($0, 1, 1) != ">" ? "" : substr($0, 3); - getline; # This line should be empty - getline; # First line of description - desc = $0; - next; -} -END { - # Sanitize input - if (due) { - # Use command line `date` for parsing - cmd = "date -d \"" due "\" +\"%Y%m%d\""; - cmd | getline res - due = res ? res : "" - } - escape(summary); - escape(desc); - escape_categories(categories); - - # print ical - print "BEGIN:VCALENDAR"; - print "VERSION:2.0"; - print "CALSCALE:GREGORIAN"; - print "PRODID:-//fab//awk//EN"; - print "BEGIN:" type; - print "DTSTAMP:" zulu; - print "UID:" uid; - print "CLASS:PRIVATE"; - print "CREATED:" zulu; - print "SEQUENCE:1"; - print "LAST-MODIFIED:" zulu; - if (type == "VTODO") - { - print "STATUS:NEEDS-ACTION"; - print "PERCENT-COMPLETE:0"; - if (due) - print "DUE;VALUE=DATE:" due; - } - else - { - print "STATUS:FINAL"; - if (start) - print "DTSTART;VALUE=DATE:" start; - } - if (summary) print_fold("SUMMARY:", summary, i, s); - if (categories) print_fold("CATEGORIES:", categories, i, s); - if (desc) print_fold("DESCRIPTION:", desc, i, s); - print "END:" type; - print "END:VCALENDAR" -} diff --git a/src/update.awk b/src/update.awk deleted file mode 100644 index ddcff8c..0000000 --- a/src/update.awk +++ /dev/null @@ -1,85 +0,0 @@ -function getcontent(content_line, prop) -{ - return substr(content_line[prop], index(content_line[prop], ":") + 1); -} - -function escape_categories(str) -{ - gsub("\\\\", "\\\\", str); - gsub(";", "\\\\;", str); -} - -function escape(str) -{ - escape_categories(str) - gsub(",", "\\\\,", str); -} - -function print_fold(nameparam, content, i, s) -{ - i = 74 - length(nameparam); - s = substr(content, 1, i); - print nameparam s; - s = substr(content, i+1, 73); - i = i + 73; - while (s) - { - print " " s; - s = substr(content, i+1, 73); - i = i + 73; - } -} - -BEGIN { - FS=":"; - zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1); -} - -ENDFILE { - if (NR == FNR) - { - # Sanitize input - if (due) { - # Use command line `date` for parsing - cmd = "date -d \"" due "\" +\"%Y%m%d\""; - cmd | getline res - due = res ? res : "" - } - escape(summary); - escape(desc); - escape_categories(categories); - } -} - -NR == FNR && desc { desc = desc "\\n" $0; next; } -NR == FNR { - if (substr($0, 1, 6) == "::: <|") - { - due = substr($0, 8); - getline; - } - summary = substr($0, 1, 2) != "# " ? "" : substr($0, 3); - getline; - categories = substr($0, 1, 1) != ">" ? "" : substr($0, 3); - getline; # This line should be empty - getline; # First line of description - desc = $0; - next; -} - -/^BEGIN:(VJOURNAL|VTODO)/ { type = $2; print; next } -/^X-ALT-DESC/ && type { next } # drop this alternative description -/^ / && type { next } # drop this folded line (the only content with folded lines will be updated) -/^(DUE|SUMMARY|CATEGORIES|DESCRIPTION|LAST-MODIFIED)/ && type { next } # skip for now, we will write updated fields at the end -/^SEQUENCE/ && type { seq = $2; next } # store sequence number and skip -/^END:/ && type == $2 { - seq = seq ? seq + 1 : 1; - print "SEQUENCE:" seq; - print "LAST-MODIFIED:" zulu; - if (due) print "DUE;VALUE=DATE:" due; - print_fold("SUMMARY:", summary, i, s); - print_fold("CATEGORIES:", categories, i, s); - print_fold("DESCRIPTION:", desc, i, s); - type = ""; -} -{ print }