From 648ff6c0163bd23e77ce2f103a03312765022cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=84min=20Baumeler?= Date: Tue, 17 Jun 2025 14:56:10 +0200 Subject: [PATCH] bugfix: escape, again --- src/awk/approx.awk | 44 +++++++++++++++++++++++++++++++++++++------- src/awk/get.awk | 42 +++++++++++++++++++++++++++++++++++------- src/awk/parse.awk | 42 ++++++++++++++++++++++++++++++++++++------ src/awk/set.awk | 1 - src/awk/update.awk | 3 ++- src/main.sh | 13 +++---------- 6 files changed, 113 insertions(+), 32 deletions(-) diff --git a/src/awk/approx.awk b/src/awk/approx.awk index 93a507b..a966cfb 100644 --- a/src/awk/approx.awk +++ b/src/awk/approx.awk @@ -5,6 +5,40 @@ # Functions +# Unescape string +# +# @local variables: i, c, c2, res +# @input str: String +# @return: Unescaped string +function unescape(str, i, c, c2, res) { + for(i=1; i<=length(str);i++) { + c = substr(str, i, 1) + if (c != "\\") { + res = res c + continue + } + i++ + c2 = substr(str, i, 1) + if (c2 == "n" || c2 == "N") { + res = res "\n" + continue + } + # Alternatively, c2 is "\\" or "," or ";". In each case, append res with + # c2. If the strings has been escaped correctly, then the character c2 + # cannot be anything else. To be fail-safe, simply append res with c2. + res = res c2 + } + return res +} + +# Isolate content part of an iCalendar line, and unescape. +# +# @input str: String +# @return: Unescaped content part +function getcontent(str) { + return unescape(substr(str, index(str, ":") + 1)) +} + # Time-zone aware parsing of the date/date-time entry at the current record. # # @local variables: dt @@ -60,13 +94,9 @@ function fn(path, n, a) { # @input summary: Content of SUMMARY field # @return: colorized single-line title string function title(start, summary) { - summary = substr(summary, index(summary, ":") + 1) - gsub("\\\\n", " ", summary) - gsub("\\\\N", " ", summary) - gsub("\\\\,", ",", summary) - gsub("\\\\;", ";", summary) - gsub("\\\\\\\\", "\\", summary) - gsub("\\|", ":", summary) # we use "|" as delimiter + summary = getcontent(summary) + gsub("\n", " ", summary) # This will be put on a single line + gsub("|", ":", summary) # we use "|" as delimiter depth = split(FILENAME, path, "/") collection = depth > 1 ? path[depth-1] : "" collection = collection in collection2label ? collection2label[collection] : collection diff --git a/src/awk/get.awk b/src/awk/get.awk index d858dc8..baaac2b 100644 --- a/src/awk/get.awk +++ b/src/awk/get.awk @@ -3,6 +3,40 @@ ## ## @assign field: Field name +# Unescape string +# +# @local variables: i, c, c2, res +# @input str: String +# @return: Unescaped string +function unescape(str, i, c, c2, res) { + for(i=1; i<=length(str);i++) { + c = substr(str, i, 1) + if (c != "\\") { + res = res c + continue + } + i++ + c2 = substr(str, i, 1) + if (c2 == "n" || c2 == "N") { + res = res "\n" + continue + } + # Alternatively, c2 is "\\" or "," or ";". In each case, append res with + # c2. If the strings has been escaped correctly, then the character c2 + # cannot be anything else. To be fail-safe, simply append res with c2. + res = res c2 + } + return res +} + +# Isolate content part of an iCalendar line, and unescape. +# +# @input str: String +# @return: Unescaped content part +function getcontent(str) { + return unescape(substr(str, index(str, ":") + 1)) +} + # AWK program BEGIN { FS = ":"; regex = "^" field } /^BEGIN:VEVENT$/ { inside = 1 } @@ -13,11 +47,5 @@ $0 ~ regex { content = $0; next } END { if (!inside) { 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 + print getcontent(content) } diff --git a/src/awk/parse.awk b/src/awk/parse.awk index b6a5577..07ad4d0 100644 --- a/src/awk/parse.awk +++ b/src/awk/parse.awk @@ -8,6 +8,40 @@ # Functions +# Unescape string +# +# @local variables: i, c, c2, res +# @input str: String +# @return: Unescaped string +function unescape(str, i, c, c2, res) { + for(i=1; i<=length(str);i++) { + c = substr(str, i, 1) + if (c != "\\") { + res = res c + continue + } + i++ + c2 = substr(str, i, 1) + if (c2 == "n" || c2 == "N") { + res = res "\n" + continue + } + # Alternatively, c2 is "\\" or "," or ";". In each case, append res with + # c2. If the strings has been escaped correctly, then the character c2 + # cannot be anything else. To be fail-safe, simply append res with c2. + res = res c2 + } + return res +} + +# Isolate content part of an iCalendar line, and unescape. +# +# @input str: String +# @return: Unescaped content part +function getcontent(str) { + return unescape(substr(str, index(str, ":") + 1)) +} + # Time-zone aware parsing of the date/date-time entry at the current record. # # @local variables: dt @@ -54,12 +88,8 @@ function parse_duration(duration, dt, dta, i, n, a, seps) { # @input end: End time of event, or event duration (see `dur`) # @input summary: Content of SUMMARY field of the event function print_data(start, dur, end, summary, cmd, collection, depth, path) { - summary = substr(summary, index(summary, ":") + 1) - gsub("\\\\n", " ", summary) # one-liner - gsub("\\\\N", " ", summary) # one-liner - gsub("\\\\,", ",", summary) - gsub("\\\\;", ";", summary) - gsub("\\\\\\\\", "\\", summary) + summary = getcontent(summary) + gsub("\n", " ", summary) # This will be put on a single line depth = split(FILENAME, path, "/") fpath = path[depth-1] "/" path[depth] collection = depth > 1 ? path[depth-1] : "" diff --git a/src/awk/set.awk b/src/awk/set.awk index 66e8306..de97bbd 100644 --- a/src/awk/set.awk +++ b/src/awk/set.awk @@ -35,7 +35,6 @@ BEGIN { FS = "[:;]"; zulu = strftime("%Y%m%dT%H%M%SZ", systime() $1 == field && inside { con = 1; duplic = 1; print field ":" escape(value); next } $1 == field && duplic { con = 1; next } /^ / && con { next } -/^ / && con { next } /^[^ ]/ && con { con = 0 } /^SEQUENCE/ && inside { seq = $2; next } # store sequence number and skip /^LAST-MODIFIED/ && inside { next } diff --git a/src/awk/update.awk b/src/awk/update.awk index a3d1aac..3296a9f 100644 --- a/src/awk/update.awk +++ b/src/awk/update.awk @@ -126,11 +126,12 @@ NR == FNR { print_fold("DESCRIPTION:", desc) print_fold("LOCATION:", location) inside = "" + skipf = 0 } /^BEGIN:VEVENT$/ { inside = 1 } /^ / && skipf { next } # drop this folded line /^[^ ]/ && skipf { skipf = 0 } -/^(DTSTART|DTEND|SUMMARY|LOCATION|CATEGORIES|DESCRIPTION|LAST-MODIFIED|X-ALT-DESC)/ && inside { skipf = 1; next } # skip for now, we will write updated fields at the end +/^(DTSTART|DTEND|SUMMARY|LOCATION|CATEGORIES|DESCRIPTION|LAST-MODIFIED)/ && inside { skipf = 1; next } # skip for now, we will write updated fields at the end /^X-ALT-DESC/ && inside { skipf = 1; next } # skip /^SEQUENCE/ && inside { seq = $2; next } # store sequence number and skip { print } diff --git a/src/main.sh b/src/main.sh index 13eb19c..a6e6f3f 100755 --- a/src/main.sh +++ b/src/main.sh @@ -705,18 +705,11 @@ __edit() { summary=$(awk -v field="SUMMARY" "$AWK_GET" "$fpath") description=$(awk -v field="DESCRIPTION" "$AWK_GET" "$fpath") filetmp=$(mktemp --suffix='.md') - ( - echo "::: |> $start" - echo "::: <| $end" - ) >"$filetmp" + printf "::: |> %s\n::: <| %s\n" "$start" "$end" >"$filetmp" if [ -n "$location" ]; then - echo "@ $location" >>"$filetmp" + printf "@ %s\n" "$location" >>"$filetmp" fi - ( - echo "# $summary" - echo "" - echo "$description" - ) >>"$filetmp" + printf "# %s\n\n%s\n" "$summary" "$description" >>"$filetmp" checksum=$(cksum "$filetmp") $EDITOR "$filetmp" >/dev/tty