Compare commits

...

4 Commits

Author SHA1 Message Date
76fa32da39 bugfix: escaping 2025-06-16 13:04:01 +02:00
609d9712a6 no reference to built files, BIY 2025-06-16 11:12:45 +02:00
de4650d1ac fixed escaping 2025-06-15 20:23:08 +02:00
7656da2a71 Merge pull request #1 from BlueInGreen68/main 2025-06-15 20:22:30 +02:00
6 changed files with 210 additions and 134 deletions

View File

@@ -13,14 +13,22 @@ with a CalDav server, such as [Radicale](https://radicale.org/), and a synchroni
Installation
------------
Download the file `fzf-vjour` from the [latest release](https://github.com/baumea/fzf-vjour/releases/latest), or run `./scripts/build.sh`, then
copy `fzf-vjour` to your preferred location, e.g., `~/.local/bin`, and make it executable.
### Manual
Run `./scripts/build.sh`, then copy `fzf-vjour` to your preferred location, e.g., `~/.local/bin`, and make it executable.
### Requirements
This is a POSIX script with inline `awk` elements.
Make sure you have [fzf](https://github.com/junegunn/fzf) installed.
I also suggest to install [batcat](https://github.com/sharkdp/bat) for colorful previews.
### Arch Linux
```bash
yay -S fzf-vjour-git
```
Configuration
--------------
This application is configured with a file located at `$HOME/.config/fzf-vjour/config`.

View File

@@ -1,34 +1,41 @@
function getcontent(content_line, prop)
{
return substr(content_line[prop], index(content_line[prop], ":") + 1);
# unescape
# Isolate and unescape the content part of an iCalendar line.
#
# @local variables: tmp
# @input str: String
# @return: Unescaped string
function unescape(str) {
gsub("\\\\n", "\n", str)
gsub("\\\\N", "\n", str)
gsub("\\\\,", ",", str)
gsub("\\\\;", ";", str)
gsub("\\\\\\\\", "\\", str)
return str
}
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]);
# getcontent
# 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))
}
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; }
BEGIN { FS = "[:;]"; }
/^BEGIN:(VJOURNAL|VTODO)/ { type = $2 }
/^END:/ && $2 == type { exit }
/^(CATEGORIES|DESCRIPTION|SUMMARY|DUE)/ { prop = $1; c[prop] = $0; next; }
/^[^ ]/ && prop { prop = ""; next; }
/^ / && prop { c[prop] = c[prop] substr($0, 2); next; }
END {
if (!type) {
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" );
c["CATEGORIES"] = getcontent(c["CATEGORIES"])
c["DESCRIPTION"] = getcontent(c["DESCRIPTION"])
c["SUMMARY"] = getcontent(c["SUMMARY"])
c["DUE"] = getcontent(c["DUE"])
# Print
if (c["DUE"])
print "::: <| " substr(c["DUE"], 1, 4) "-" substr(c["DUE"], 5, 2) "-" substr(c["DUE"], 7, 2);

View File

@@ -1,18 +1,36 @@
# unescape
# Isolate and unescape the content part of an iCalendar line.
#
# @local variables: tmp
# @input str: String
# @return: Unescaped string
function unescape(str) {
gsub("\\\\n", "\n", str)
gsub("\\\\N", "\n", str)
gsub("\\\\,", ",", str)
gsub("\\\\;", ";", str)
gsub("\\\\\\\\", "\\", str)
return str
}
# getcontent
# 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))
}
# 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 }
$0 ~ field { line = $0; next; }
/^ / && line { line = line substr($0, 2); next; }
/^[^ ]/ && line { 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;
# Process line
print getcontent(line)
}

View File

@@ -3,9 +3,28 @@
# 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);
# unescape
# Isolate and unescape the content part of an iCalendar line.
#
# @local variables: tmp
# @input str: String
# @return: Unescaped string
function unescape(str) {
gsub("\\\\n", "\n", str)
gsub("\\\\N", "\n", str)
gsub("\\\\,", ",", str)
gsub("\\\\;", ";", str)
gsub("\\\\\\\\", "\\", str)
return str
}
# getcontent
# 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))
}
function storetext_line(content_line, c, prop)
@@ -19,23 +38,14 @@ function storetext_line(content_line, 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)
# formatdate
# Generate kind-of-pretty date strings.
#
# @local variables: ts, ts_y, ts_m, ts_d, delta
# @input date: Date in the format YYYYMMDD
# @input todaystamp: Today, seconds since epoch
# @return: string
function formatdate(date, todaystamp, ts, ts_y, ts_m, ts_d, delta)
{
ts_y = substr(date, 1, 4);
ts_m = substr(date, 5, 2);
@@ -87,7 +97,6 @@ BEGIN {
RED = "\033[1;31m";
WHITE = "\033[1;97m";
CYAN = "\033[1;36m";
FAINT = "\033[2m";
OFF = "\033[m";
# For date comparision
@@ -99,7 +108,6 @@ BEGIN {
BEGINFILE {
type = "";
prop = "";
delete content_line;
delete c;
}
@@ -114,7 +122,7 @@ BEGINFILE {
/^(CATEGORIES|DESCRIPTION|PRIORITY|STATUS|SUMMARY|COMPLETED|DUE|DTSTART|DURATION|CREATED|DTSTAMP|LAST-MODIFIED)/ {
prop = $1;
content_line[prop] = $0;
c[prop] = $0;
next;
}
/^[^ ]/ && prop {
@@ -122,7 +130,7 @@ BEGINFILE {
next;
}
/^ / && prop {
content_line[prop] = content_line[prop] substr($0, 2);
c[prop] = c[prop] substr($0, 2);
next;
}
@@ -146,26 +154,33 @@ ENDFILE {
collection = collection in collection2label ? collection2label[collection] : collection;
# 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");
# strings
cat = unescape(getcontent(c["CATEGORIES"]))
des = unescape(getcontent(c["DESCRIPTION"]))
sta = unescape(getcontent(c["STATUS"]))
sum = unescape(getcontent(c["SUMMARY"]))
# integers
pri = unescape(getcontent(c["PRIORITY"]))
pri = pri ? pri + 0 : 0
# dates
due = substr(unescape(getcontent(c["DUE"])), 1, 8)
dts = substr(unescape(getcontent(c["DTSTART"])), 1, 8)
# date-times
com = unescape(getcontent(c["COMPLETED"]))
dur = unescape(getcontent(c["DURATION"]))
cre = unescape(getcontent(c["CREATED"]))
stp = unescape(getcontent(c["DTSTAMP"]))
lmd = unescape(getcontent(c["LAST-MODIFIED"]))
# Priority field, primarly used for sorting
priotext = "";
prio = 0;
if (c["PRIORITY"] > 0)
psort = 0;
if (pri > 0)
{
priotext = "❗(" c["PRIORITY"] ") ";
prio = 10 - c["PRIORITY"];
priotext = "❗(" pri ") "
psort = 10 - pri
}
# Last modification/creation time stamp, used for sorting
@@ -173,7 +188,7 @@ ENDFILE {
# 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"];
mod = lmd ? lmd : stp
# Date field. For VTODO entries, we show the due date, for journal entries,
# the associated date.
@@ -183,9 +198,9 @@ ENDFILE {
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")
d = due ? due :
(dur ? dts " for " dur : "");
if (d && d <= today && sta != "COMPLETED")
{
datecolor = RED;
summarycolor = RED;
@@ -193,28 +208,28 @@ ENDFILE {
} else {
d = c["DTSTART"];
}
d = d ? formatdate(d, today, todaystamp ts, ts_y, ts_m, ts_d, delta) : " ";
d = d ? formatdate(d, todaystamp) : " ";
# 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;
flag = sta == "COMPLETED" ? flag_completed : flag_open;
else
flag = c["DTSTART"] ? flag_journal : flag_note;
flag = dts ? flag_journal : flag_note;
# summary
# c["SUMMARY"]
summary = c["SUMMARY"] ? c["SUMMARY"] : " "
summary = sum ? sum : " "
# categories
categories = c["CATEGORIES"] ? c["CATEGORIES"] : " "
categories = cat ? cat : " "
# filename
# FILENAME
print prio,
print psort,
mod,
fpath,
collection,

View File

@@ -1,27 +1,44 @@
function escape_categories(str)
{
gsub("\\\\", "\\\\", str);
gsub(";", "\\\\;", str);
}
# Escape string to be used as content in iCalendar files.
#
# @input str: String to escape
# @return: Escaped string
function escape(str)
{
escape_categories(str)
gsub(",", "\\\\,", str);
gsub("\\\\", "\\", str)
gsub(";", "\\;", str)
gsub(",", "\\,", str)
return str
}
# Escape string to be used as content in iCalendar files.
#
# @input str: String to escape
# @return: Escaped string
function escape_categories(str)
{
gsub("\\\\", "\\", str)
gsub(";", "\\;", str)
return str
}
# Print property with its content and fold according to the iCalendar
# specification.
#
# @local variables: i, s
# @input nameparam: Property name with optional parameters
# @input content: Escaped content
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;
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;
print " " s
s = substr(content, i+1, 73)
i = i + 73
}
}
@@ -59,9 +76,9 @@ END {
cmd | getline res
due = res ? res : ""
}
escape(summary);
escape(desc);
escape_categories(categories);
summary = escape(summary);
desc = escape(desc);
categories = escape_categories(categories);
# print ical
print "BEGIN:VCALENDAR";
@@ -88,9 +105,9 @@ END {
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);
if (summary) print_fold("SUMMARY:", summary);
if (categories) print_fold("CATEGORIES:", categories);
if (desc) print_fold("DESCRIPTION:", desc);
print "END:" type;
print "END:VCALENDAR"
}

View File

@@ -1,33 +1,44 @@
function getcontent(content_line, prop)
{
return substr(content_line[prop], index(content_line[prop], ":") + 1);
}
function escape_categories(str)
{
gsub("\\\\", "\\\\", str);
gsub(";", "\\;", str);
}
# Escape string to be used as content in iCalendar files.
#
# @input str: String to escape
# @return: Escaped string
function escape(str)
{
escape_categories(str)
gsub(",", "\\,", str);
gsub("\\\\", "\\", str)
gsub(";", "\\;", str)
gsub(",", "\\,", str)
return str
}
# Escape string to be used as content in iCalendar files.
#
# @input str: String to escape
# @return: Escaped string
function escape_categories(str)
{
gsub("\\\\", "\\", str)
gsub(";", "\\;", str)
return str
}
# Print property with its content and fold according to the iCalendar
# specification.
#
# @local variables: i, s
# @input nameparam: Property name with optional parameters
# @input content: Escaped content
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;
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;
print " " s
s = substr(content, i+1, 73)
i = i + 73
}
}
@@ -78,9 +89,9 @@ NR == FNR {
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);
print_fold("SUMMARY:", summary);
print_fold("CATEGORIES:", categories);
print_fold("DESCRIPTION:", desc);
type = "";
}
{ print }