Compare commits

..

No commits in common. "main" and "local" have entirely different histories.
main ... local

35 changed files with 1073 additions and 2252 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
fzf-vcal fzf-vcal
fzf-vcal.debug

View File

@ -1,7 +1,4 @@
A [fzf](https://github.com/junegunn/fzf)-based **calendar** application with CalDav support. A [fzf](https://github.com/junegunn/fzf)-based **calendar** application with CalDav support.
If you are interested in this, then you may also be interested in the
corresponding journaling application
[fzf-vjour](https://github.com/baumea/fzf-vjour).
Description and Use Case Description and Use Case
------------------------ ------------------------
@ -63,81 +60,37 @@ item_types = ["VEVENT"]
... ...
``` ```
Here is the complete list of configuration options:
```
### ROOT: Directory containing the collections
### COLLECTION_LABELS: Mappings between collections and labels
### SYNC_CMD (optional): Synchronization command
### DAY_START (optional): Hour of start of the day (defaults to 8)
### DAY_END (optional): Hour of end of the day (defaults to 18)
### EDITOR (optional): Your favorite editor, is usually already exported
### TZ (optional): Your favorite timezone, usually system's choice
### LC_TIME (optional): Your favorite locale for date and time
### ZI_DIR (optional): Location of tzdata, defaults to /usr/share/zoneinfo
```
Usage Usage
----- -----
Use the default `fzf` keys to navigate your calendar entries, e.g., `ctrl-j`
and `ctrl-k` for going down/up in the list.
After starting `fzf-vcal`, you are presented with a view on the current week. After starting `fzf-vcal`, you are presented with a view on the current week.
You can navigate that week using `j` and `h` for going down and up.
Hit `<enter>` on any day, and you will see all entries for that date, including Hit `<enter>` on any day, and you will see all entries for that date, including
previews. In both, the week and day views, you can add entries by hitting previews. In both, the week and day views, you can add entries by hitting
`ctrl-n`. `ctrl-n`.
Here is the list of all available keybindings: Here is the list of available keybindings:
| Key | View | Action |
### Week view | --- | ---- | ------ |
| `enter` | week view | Switch to day view |
| Key | Action | | `ctrl-n` | week view | Make a new entry |
| --- | ------ | | any letter | week view | Search in the list of all entries |
| `q` | quit | | `backspace` on empty query | week view | Undo search |
| `enter` | open day | | `ctrl-u` | week view | Go back one week |
| `j` | down | | `ctrl-d` | week view | Go forth one week |
| `k` | up | | `ctrl-alt-u` | week view | Go back one month |
| `l` | go to next week | | `ctrl-alt-d` | week view | Go forth one month |
| `h` | go to previous week | | `ctrl-s` | week view | Run the synchronization command |
| `ctrl-l` | go to next month | | `ctrl-l` | week view | Go to current week |
| `ctrl-h` | go to previous month | | `ctrl-g` | week view | Goto date |
| `alt-l` | go to next year | | `enter` | day view | Open selected calendar entry in your favorite `$EDITOR` |
| `alt-h` | go to previous year | | `ctrl-n` | day view | Make a new entry |
| `ctrl-r` | reload and go to week that contains `today` | | `esc`, `backspace` or `q` | day view | Go back to week view |
| `ctrl-g` | interactively go to specified week | | `ctrl-s` | day view | Run the synchronization command |
| `ctrl-t` | set timezon | | `ctrl-alt-d` | day view | Delete selected entry |
| `ctrl-s` | synchronize | | `j` | day view | Scroll down in preview window |
| `ctrl-n` | add new entry | | `k` | day view | Scroll up in preview window |
| `\` | search all appointment s | | `w` | day view | Toggle line wrap in preview window ||
| `x` | Cancel and confirm entry |
| `c` | Unconfirm and confirm entry |
### Day view
| Key | Action |
| --- | ------ |
| `enter` | edit appointment |
| `a` | open attachment list of appointment |
| `j` | down |
| `k` | up |
| `l` | go to next day |
| `h` | go to previous day |
| `ctrl-l` | go to next week |
| `ctrl-h` | go to previous week |
| `alt-l` | go to next month |
| `alt-h` | go to previous month |
| `ctrl-r` | reload and go to `today` |
| `ctrl-g` | interactively go to specified day |
| `ctrl-t` | set timezon |
| `ctrl-s` | synchronize |
| `ctrl-n` | add new entry |
| `ctrl-alt-d` | delete entry |
| `w` | toggle line wrap in preview |
| `ctrl-d` | down in preview |
| `ctrl-u` | up in preview |
| `alt-v` | view raw iCalendar file |
| `esc` | return to week view, you can also do this with `q` or `backspace` |
### There is more
You may also invoke the script with `--help` to see further command-line options. You may also invoke the script with `--help` to see further command-line options.
@ -145,10 +98,6 @@ Also, you may set `LC_TIME` to your preferred language, and `TZ` to your
preferred timezone. The latter is in particular helpful if you want to take a preferred timezone. The latter is in particular helpful if you want to take a
look at your calendar relative to being in another timezone. look at your calendar relative to being in another timezone.
Git support
-----------
You can track your events with `git` by simply running `fzf-vcal --git-init`.
License License
------- -------
This project is licensed under the [MIT License](./LICENSE). This project is licensed under the [MIT License](./LICENSE).

View File

@ -5,15 +5,7 @@ GREEN="\033[0;32m"
OFF="\033[m" OFF="\033[m"
NAME="fzf-vcal" NAME="fzf-vcal"
SRC="./src/main.sh" SRC="./src/main.sh"
echo "🐔 ${GREEN}Building${OFF} ${BOLD}$NAME${OFF}"
tmpdir=$(mktemp -d) sed -E 's|@@include (.+)$|cat \1|e' "$SRC" >"$NAME"
echo "🐔 ${GREEN}Internalize sourced files${OFF}"
sed -E 's|\. "([^$].+)"$|cat src/\1|e' "$SRC" >"$tmpdir/1.sh"
echo "🥚 ${GREEN}Internalize awk scripts${OFF}"
sed -E 's|@@include (.+)$|cat src/\1|e' "$tmpdir/1.sh" >"$tmpdir/2.sh"
echo "🐔 ${GREEN}Internalize awk libraries${OFF}"
sed -E 's|@include "(.+)"$|cat src/\1|e' "$tmpdir/2.sh" >"$NAME"
echo "🥚 ${GREEN}Make executable and cleanup${OFF}"
chmod +x "$NAME" chmod +x "$NAME"
rm -rf "$tmpdir" echo "🥚 ${GREEN}Done${OFF}"
echo "🍳 ${GREEN}Done:${OFF} Sucessfully built ${BOLD}${GREEN}$NAME${OFF}"

View File

@ -1,66 +0,0 @@
## src/awk/approx.awk
##
## Generate single-line approximate information for every iCalendar argument.
## The fields in each line are separated by "\t"
## The fields are the following:
## 1. "~" (constant, indicating that the lines contains approximate information)
## 2. start (this can be used in date (1))
## 3. end (this can be used in date (1)
## 4. string to display
## 5. filename (collection/name)
##
## @assign collection_labels: See configuration of the current program.
## @assign style_line: Style for each line
@include "lib/awk/icalendar.awk"
# Functions
# Get relative file path.
#
# @local variables: n, a
# @input path: Path to file
# @return: File path of depth 1
function fn(path, n, a) {
n = split(path, a, "/")
return a[n-1] "/" a[n]
}
# Generate title string that will be displayed to user. Here, the start date
# gets a monthly resolution.
#
# @input start: Parsed content of DTSTART field
# @input summary: Content of SUMMARY field
# @return: colorized single-line title string
function title(start, summary) {
summary = getcontent(summary)
gsub("\n", " ", summary) # This will be put on a single line
gsub("\t", " ", summary) # we use "\t" as delimiter
depth = split(FILENAME, path, "/")
collection = depth > 1 ? path[depth-1] : ""
collection = collection in collection2label ? collection2label[collection] : collection
return style_line "~ " collection " " gensub(/^[^0-9]*([0-9]{4})([0-9]{2}).*$/, "\\1-\\2", "1", start) " " summary OFF
}
# AWK program
BEGIN {
FS="[:;=]"
OFS="\t"
split(collection_labels, mapping, ";")
for (map in mapping)
{
split(mapping[map], m, "=")
collection2label[m[1]] = m[2]
}
# Colors
OFF = "\033[m"
}
BEGINFILE { inside = 0; rs = 0; dur = 0; summary = ""; start = "ERROR"; end = "ERROR" }
/^END:VEVENT/ { print "~", start, dur ? start " " end : end, title(start, summary), fn(FILENAME); nextfile }
/^DTSTART/ && inside { start = parse_dt(getparam($0), getcontent($0)) }
/^DTEND/ && inside { end = parse_dt(getparam($0), getcontent($0)) }
/^DURATION/ && inside { end = parse_duration($NF); dur = 1 }
/^[^ ]/ && rs { rs = 0 }
/^ / && rs { summary = summary substr($0, 2) }
/^SUMMARY/ && inside { rs = 1; summary = $0 }
/^BEGIN:VEVENT/ { inside = 1 }

View File

@ -1,35 +0,0 @@
## src/awk/attach.awk
## Prepend attachment to iCalendar file.
##
## @assign file: Path to base64-encoded content
## @assign mime: Mime
## @assign filename: Original filename
# Functions
# Write attachment
#
# @local variables: line, aline
function write_attachment( line, aline, fl) {
line = "ATTACH;ENCODING=BASE64;VALUE=BINARY;FMTTYPE="mime";FILENAME="filename":"
fl = 1
while (getline aline <file) {
line = line aline
if (fl && length(line) >= 73) {
print substr(line, 1, 73)
line = substr(line, 74)
fl = 0
}
while (length(line) >= 72) {
print " "substr(line, 1, 72)
line = substr(line, 73)
}
}
if (line)
print " "line
}
# AWK program
/^END:VEVENT$/ { write_attachment() }
{ print }

View File

@ -1,8 +0,0 @@
BEGIN { FS="[:;]" }
/^END:VEVENT$/ { ins = 0; exit }
/^[^ ]/ && a { a = 0 }
/^ / && a && p { print substr($0, 2); }
/^ / && a && !p { if (index($0, ":")) { p = 1; print substr($0, index($0, ":")+1) } }
/^ATTACH/ && ins { i++; }
/^ATTACH/ && ins && i == id { a = 1; if (index($0, ":")) { p = 1; print substr($0, index($0, ":")+1) } }
/^BEGIN:VEVENT$/ { ins = 1 }

View File

@ -1,41 +0,0 @@
# Decide if we need to read more to get all properties
#
# @input str: strin read so far
# @return: 1 if we need more data, 0 otherwise
function cont_reading(str) {
return index(str, ":") ? 0 : 1
}
# Get information about attachment
#
# @input i: Attachment index
# @input str: Attachment string (at least up to content separator `:`)
# @return: informative string
function att_info(i, str, cnt, k, info) {
str = substr(str, 1, index(str, ":") - 1)
cnt = split(str, props)
if (cnt > 1) {
for (k=2; k<=cnt; k++) {
pname = substr(props[k], 1, index(props[k], "=") - 1)
pvalu = substr(props[k], index(props[k], "=") + 1)
if (pname == "ENCODING" && pvalu = "BASE64")
enc = "base64"
if (pname == "FILENAME")
fin = pvalu
if (pname == "VALUE")
val = pvalu
if (pname == "FMTTYPE")
type = pvalu
}
if (enc)
info = "inline"
}
print i, fin, type, enc, info
}
BEGIN { FS="[:;]"; OFS="\t" }
/^END:VEVENT$/ { ins = 0; exit }
l && !r { att_info(i, l); l = "" }
/^ / && r { l = l substr($0, 2); r = cont_reading($0) }
/^ATTACH/ && ins { i++; l = $0; r = cont_reading($0) }
/^BEGIN:VEVENT$/ { ins = 1 }

View File

@ -1,13 +0,0 @@
## src/awk/attachrm.awk
## Remove attachment from iCalendar file.
##
## @assign id: Attachment number to remove
BEGIN { FS="[:;]" }
/^END:VEVENT$/ { ins = 0 }
/^[^ ]/ && a { a = 0 }
/^ / && a { next }
/^ATTACH/ && ins { i++; }
/^ATTACH/ && ins && i == id { a = 1; next }
/^BEGIN:VEVENT$/ { ins = 1 }
{ print }

18
src/awk/cal.awk Normal file
View File

@ -0,0 +1,18 @@
BEGIN {
BLACK = "\033[1;30m"
GREEN = "\033[1;32m"
RED = "\033[1;31m"
FAINT = "\033[2m"
BOLD = "\033[1m"
BG = "\033[41m"
OFF = "\033[m"
day = day + 0
cur = cur + 0
}
NR == 1 { print GREEN $0 OFF; next }
NR == 2 { print FAINT $0 OFF; next }
{
sub("\\y"cur"\\y", BG BLACK BOLD cur OFF)
sub("\\y"day"\\y", RED BOLD day OFF)
print
}

View File

@ -1,22 +0,0 @@
## src/awk/calannot.awk
## Annotate monthly calendar
##
## @assign cur: Day-of-month to mark as `today`
## @assign day: Day-of-month to highlight
## @assign style_month: Theme to use for month
## @assign style_weekdays: Theme to use for weekdays
## @assign style_cur: Theme to use for current day
## @assign style_highlight: Theme to use for highlighted day
BEGIN {
OFF = "\033[m"
day = day + 0
cur = cur + 0
}
NR == 1 { print style_month $0 OFF; next }
NR == 2 { print style_weekdays $0 OFF; next }
{
sub("\\y"cur"\\y", style_cur cur OFF)
sub("\\y"day"\\y", style_highlight day OFF)
print
}

View File

@ -1,23 +0,0 @@
## src/awk/calshift.awk
## Rearrange days of a monthly output from cal (1), such that Monday is the
## first day of the week.
BEGIN {
ORS = ""
W3 = " "
W17 = W3 W3 W3 W3 W3 " "
}
NR == 1 { i++; print $0 "\n"; next }
NR == 2 { i++; print substr($0, 4, 17) " " substr($0, 1, 3) " \n"; next }
NR == 3 && /^ 1/ { print W17; }
NR == 3 && /^ / { print substr($0, 4, 17); next }
/[0-9]/ {
i++
print " " substr($0, 1, 3) " \n" substr($0, 4, 17)
}
END {
i++
print " " W3 " \n"
for (i; i<8; i++)
print " " W17 W3 " \n"
}

View File

@ -1,107 +1,26 @@
## src/awk/dayview.awk # 11:00|13:00|1748422800|1748430000|fpath|desc...
## Take as input (tab-delimited): # 00:00|00:00|1748296800|1748383200|fpath|desc...
## 1. s (start time, as HH:MM) function allday(desc) {
## 2. e (end time, as HH:MM) return ITALIC FAINT " (allday) " OFF desc
## 3. starttime
## 4. endtime
## 5. fpath
## 6. collection
## 7. description
## 8. status
##
## filter out irrelevant lines, and generate the view of a day
## (tab-delimited), including empty hours:
## 1. start date
## 2. start time
## 3. end time
## 4. file path
## 5. collection
## 6. description
##
## @assign today: Date of `today` in the format %D (%m/%d/%y)
## @assign daystart: Hour of start of the day
## @assign dayend: Hour of end of the day
## @assign style_allday
## @assign style_timerange
## @assign style_confirmed
## @assign style_tentative
## @assign style_cancelled
## @assign style_hour
## @assign style_emptyhour
# Functions
# Set event color based on status
# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED
# @return: Color modifier
function color_from_status(status) {
return status == "CANCELLED" ? style_cancelled : status == "TENTATIVE" ? style_tentative : style_confirmed
} }
function endstoday(stop, desc) {
# Return line for all-day event. return CYAN " -- " stop OFF ": " desc
#
# @local variables: color
# @input collection: Collection symbol
# @input desc: Event description
# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED
# @return: Single-line string
function allday(collection, desc, status, color) {
color = color_from_status(status)
return collection " " style_allday color desc OFF
} }
function slice(start, stop, desc) {
# Return line for multi-day event, or event that starts at midnight, which ends today.
#
# @local variables: color
# @input stop: Time at which the event ends
# @input collection: Collection symbol
# @input desc: Event description
# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED
# @return: Single-line string
function endstoday(stop, collection, desc, status) {
color = color_from_status(status)
return collection " " style_timerange " → " stop ": " OFF color desc OFF
}
# Return line for event that starts sometime today.
#
# @local variables: color
# @input start: Time at which the event starts
# @input stop: Time at which the event ends
# @input collection: Collection symbol
# @input desc: Event description
# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED
# @return: Single-line string
function slice(start, stop, collection, desc, status) {
color = color_from_status(status)
if (stop == "00:00") if (stop == "00:00")
return collection " " style_timerange start " → " ": " OFF color desc OFF return CYAN start " -- " OFF ": " desc
else else
return collection " " style_timerange start " " stop ": " OFF color desc OFF return CYAN start OFF " -- " CYAN stop OFF ": " desc
} }
# Print line for a single hour entry.
#
# @input hour: Hour of the entry
function hrline(hour) { function hrline(hour) {
hour = hour < 10 ? "0"hour : hour hour = hour < 10 ? "0"hour : hour
print today, hour, "", "", "", " " style_hou hour ":00" OFF " " style_emptyhour print hour, "", "", "", FAINT hour ":00 ----------------------" OFF
} }
# Print lines for hour entries before an event that starts at `start` and stops
# at `stop`.
#
# @local variables: starth, stoph, tmp, i
# @input start: Time at which the event starts
# @input stop: Time at which the event ends
# @input h: Last event-free hour
# @return: Hour of now last event-free hour
function hrlines(start, stop, h, starth, stoph, tmp, i) { function hrlines(start, stop, h, starth, stoph, tmp, i) {
starth = substr(start, 1, 2) starth = substr(start, 1, 2)
stoph = substr(stop, 1, 2) stoph = substr(stop, 1, 2)
tmp = substr(start, 4, 2) == "00" ? 0 : 1 tmp = substr(start, 4, 2) == "00" ? 0 : 1
for (i=h; i < starth + tmp && i < dayend; i++) for (i=h; i < starth + tmp; i++)
hrline(i) hrline(i)
tmp = substr(stop, 4, 2) == "00" ? 0 : 1 tmp = substr(stop, 4, 2) == "00" ? 0 : 1
if (stoph + tmp < daystart) if (stoph + tmp < daystart)
@ -109,17 +28,23 @@ function hrlines(start, stop, h, starth, stoph, tmp, i) {
else else
return stoph + tmp return stoph + tmp
} }
# AWK program
BEGIN { BEGIN {
FS = "\t" FS = "|"
OFS = "\t" GREEN = "\033[1;32m"
RED = "\033[1;31m"
WHITE = "\033[1;97m"
CYAN = "\033[1;36m"
ITALIC = "\033[3m"
FAINT = "\033[2m"
OFF = "\033[m" OFF = "\033[m"
OFS = "|"
} }
$1 == "00:00" && $2 == "00:00" { print today, $1, $3, $4, $5, allday($6, $7, $8); next } $1 == "00:00" && $2 == "00:00" { print $1, $3, $4, $5, allday($6); next }
$1 == "00:00" { print today, $1, $3, $4, $5, endstoday($2, $6, $7, $8); next } $1 == "00:00" { print $1, $3, $4, $5, endstoday($2, $6); next }
$1 ~ /^[0-9]{2}:[0-9]{2}$/ { $1 ~ /^[0-9]{2}:[0-9]{2}$/ {
daystart = hrlines($1, $2, daystart) daystart = hrlines($1, $2, daystart, starth, stoph, tmp, i)
print today, $1, $3, $4, $5, slice($1, $2, $6, $7, $8) print $1, $3, $4, $5, slice($1, $2, $6)
}
END {
hrlines(dayend":00", 0, daystart, starth, stoph, tmp, i)
} }
END { hrlines(dayend":00", 0, daystart) }

View File

@ -1,18 +1,18 @@
## src/awk/get.awk # print content of field `field`
## Print content of a field of an iCalendar file. BEGIN { FS = ":"; regex = "^" field; }
##
## @assign field: Field name
@include "lib/awk/icalendar.awk"
BEGIN { FS = ":"; regex = "^" field }
/^BEGIN:VEVENT$/ { inside = 1 } /^BEGIN:VEVENT$/ { inside = 1 }
/^END:VEVENT$/ { exit } /^END:VEVENT$/ { exit }
$0 ~ regex { content = $0; next } $0 ~ regex { content = $0; next; }
/^ / && content { content = content substr($0, 2); next } /^ / && content { content = content substr($0, 2); next; }
/^[^ ]/ && content { exit } /^[^ ]/ && content { exit }
END { END {
if (!inside) { exit } if (!inside) { exit }
# Process content line # Process content line
print getcontent(content) content = substr(content, index(content, ":") + 1);
gsub("\\\\n", "\n", content);
gsub("\\\\N", "\n", content);
gsub("\\\\,", ",", content);
gsub("\\\\;", ";", content);
gsub("\\\\\\\\", "\\", content);
print content;
} }

View File

@ -1,10 +0,0 @@
## src/awk/has.awk
## Decide if VEVENT file has a specific field.
##
## @assign field: Field name
# AWK program
BEGIN { FS = "[:;]" }
/^BEGIN:VEVENT$/ { ins = 1 }
/^END:VEVENT$/ { exit 1 }
ins && $1 == field { exit 0 }

79
src/awk/lines.awk Normal file
View File

@ -0,0 +1,79 @@
function parse( dt) {
# Get timezone information
dt = "";
for (i=2; i<NF-1; i+=2) {
if ($i == "TZID") {
dt = "TZ=\"" $(i+1) "\" ";
break;
}
}
# Get date/datetime
return length($NF) == 8 ?
dt $NF :
dt gensub(/^([0-9]{8})T([0-9]{2})([0-9]{2})([0-9]{2})(Z)?$/, "\\1 \\2:\\3:\\4\\5", "g", $NF);
}
function parse_duration( dt, dta, i, n, a, seps) {
n = split($NF, a, /[PTWHMSD]/, seps);
delete dta;
for (i=2; i<=n; i++) {
if(seps[i] == "W") dta["weeks"] = a[i];
if(seps[i] == "H") dta["hours"] = a[i];
if(seps[i] == "M") dta["minutes"] = a[i];
if(seps[i] == "S") dta["seconds"] = a[i];
if(seps[i] == "D") dta["days"] = a[i];
}
dt = a[1] ? a[1] : "+";
for (i in dta) {
dt = dt " " dta[i] " " i;
}
return dt;
}
function fn(path, n, a) {
n = split(path, a, "/");
return a[n-1] "/" a[n];
}
function title(start, summary) {
summary = substr(summary, index(summary, ":") + 1);
#gsub("\\\\n", "\n", summary); # one-liner
#gsub("\\\\N", "\n", summary); # one-liner
gsub("\\\\n", " ", summary);
gsub("\\\\N", " ", summary);
gsub("\\\\,", ",", summary);
gsub("\\\\;", ";", summary);
gsub("\\\\\\\\", "\\", summary);
gsub("\\|", ":", summary); # we use "|" as delimiter
depth = split(FILENAME, path, "/");
collection = depth > 1 ? path[depth-1] : "";
collection = collection in collection2label ? collection2label[collection] : collection;
return FAINT "~ " collection " " gensub(/^[^0-9]*([0-9]{4})([0-9]{2}).*$/, "\\1-\\2", "1", start) " " summary OFF
}
BEGIN {
FS="[:;=]";
OFS="|"
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";
}
BEGINFILE { inside = 0; rs = 0; dur = 0; summary = ""; start = "ERROR"; end = "ERROR" }
/^END:VEVENT/ { print "~", start, dur ? start " " end : end, title(start, summary), fn(FILENAME, n, a); nextfile }
/^DTSTART/ && inside { start = parse( dt) }
/^DTEND/ && inside { end = parse( dt) }
/^DURATION/ && inside { end = parse_duration( dt, dta, i, n, a, seps); dur = 1 }
/^[^ ]/ && rs { rs = 0 }
/^ / && rs { summary = summary substr($0, 2); }
/^SUMMARY/ && inside { rs = 1; summary = $0; }
/^BEGIN:VEVENT/ { inside = 1 }

View File

@ -1,35 +1,17 @@
## src/awk/merge.awk BEGIN { FS="|"; i=0; dlt = -259200; spw = 604800; }
## Merge a file that contains pairs of lines for start and end dates of events
## with the approximate data file, and group the iCalendar file paths according
## to the weeks at which the events take place.
# AWK program
BEGIN { FS="\t"; OFS="\t" }
NR == FNR { NR == FNR {
i = i + 1 i = i + 1;
split($0, parts, ":") from[i] = int(($1 + dlt)/ spw);
from_year[i] = parts[1] getline;
from_week[i] = parts[2] to[i] = int(($1 + dlt) / spw);
getline
split($0, parts, ":")
to_year[i] = parts[1]
to_week[i] = parts[2]
next next
} # Load start and end week numbers from first file } # Load start and end week numbers from first file
{ {
year_i = from_year[FNR] if (from[FNR] > to[FNR])
week_i = from_week[FNR] print "FNR", FNR, ":", from[FNR],"-",to[FNR], " ",$0;
year_end = to_year[FNR] for(i=from[FNR]; i<=to[FNR]; i++) {
week_end = to_week[FNR] week[i] = week[i] " " $5
while(year_i <= year_end && (year_i < year_end || week_i <= week_end)) {
label = year_i ":" week_i ":"
week[label] = week[label] ? week[label] " " $5 : $5
week_i++
if (week_i > 53) {
week_i = 1
year_i++
}
} }
} }
END { for (label in week) print label, week[label] } END { for (i in week) print i week[i]; }

View File

@ -1,105 +1,95 @@
## src/awk/new.awk function escape(str)
## Generate iCalendar file from markdown description.
##
## @assign uid: UID to use
@include "lib/awk/icalendar.awk"
# AWK program
BEGIN {
FS=":"
zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1)
}
readdesc { desc = desc ? desc "\\n" escape($0) : escape($0); next }
{ {
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : "" gsub("\\\\", "\\\\", str);
if (!from) gsub(";", "\\\\;", str);
exit 1 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);
}
desc { desc = desc "\\n" $0; next; }
{
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : "";
getline getline
to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "" to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "";
if (!to)
exit 1
getline getline
location = substr($0, 1, 2) == "@ " ? escape(substr($0, 3)) : "" summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
if (location) getline
summary = substr($0, 1, 2) == "# " ? escape(substr($0, 3)) : ""
if (!summary)
exit 1
getline # This line should be empty getline # This line should be empty
if ($0 != "") getline # First line of description
exit 1 desc = $0;
readdesc = 1 next;
next
} }
END { END {
# Sanitize input # Sanitize input
# If nanoseconds are not 0, then we assume user entered "tomorrow" or # If nanoseconds are not 0, then we assume user enterd "tomorrow" or
# something the like, and we make this a date entry, as opposed to a # something the like, and we make this a date entry, as opposed to a
# date-time entry. # date-time entry.
# Similarly, if the time is 00:00, we make this a date, as opposed to a from = from ? from : "now"
# date-time entry. cmd = "date -d \"" from "\" +\"%N\"";
gsub("\"", "\\\"", from)
cmd = "date -d \"" from "\" +\"%N\""
cmd | getline n
close(cmd)
n = n + 0
cmd = "date -d \"" from "\" +\"%H%M\""
cmd | getline t cmd | getline t
close(cmd) close(cmd)
t = t + 0 t = t + 0
if (n != 0 || t == 0) { if (t == 0) {
from_type = "DATE"
cmd = "date -d \"" from "\" +\"%Y%m%d\""
} else {
from_type = "DATE-TIME" from_type = "DATE-TIME"
cmd = "date -d \"" from "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d" cmd = "date -d \"" from "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d"
} else {
from_type = "DATE"
cmd = "date -d \"" from "\" +\"%Y%m%d\"";
} }
suc = cmd | getline from cmd | getline from
close(cmd) close(cmd)
if (suc != 1) {
exit 1
}
# #
gsub("\"", "\\\"", to) to = to ? to : "now"
cmd = "date -d \"" to "\" +\"%N\"" cmd = "date -d \"" to "\" +\"%N\"";
cmd | getline n
close(cmd)
n = n + 0
cmd = "date -d \"" to "\" +\"%H%M\""
cmd | getline t cmd | getline t
close(cmd) close(cmd)
t = t + 0 t = t + 0
if (n != 0 || t == 0) { if (t == 0) {
to_type = "DATE"
cmd = "date -d \"" to "\" +\"%Y%m%d\""
} else {
to_type = "DATE-TIME" to_type = "DATE-TIME"
cmd = "date -d \"" to "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d" cmd = "date -d \"" to "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d"
} else {
to_type = "DATE"
cmd = "date -d \"" to "\" +\"%Y%m%d\"";
} }
suc = cmd | getline to cmd | getline to
close(cmd) close(cmd)
if (suc != 1) { escape(summary);
exit 1 escape(desc);
}
# print ical # print ical
print "BEGIN:VCALENDAR" print "BEGIN:VCALENDAR";
print "VERSION:2.0" print "VERSION:2.0";
print "CALSCALE:GREGORIAN" print "CALSCALE:GREGORIAN";
print "PRODID:-//fab//awk//EN" print "PRODID:-//fab//awk//EN";
print "BEGIN:VEVENT" print "BEGIN:VEVENT"
print "DTSTAMP:" zulu print "DTSTAMP:" zulu;
print "UID:" uid print "UID:" uid;
print "CLASS:PRIVATE" print "CLASS:PRIVATE";
print "CREATED:" zulu print "CREATED:" zulu;
print "SEQUENCE:1" print "SEQUENCE:1";
print "LAST-MODIFIED:" zulu print "LAST-MODIFIED:" zulu;
print "STATUS:CONFIRMED" print "STATUS:FINAL";
print "DTSTART;VALUE=" from_type ":" from print "DTSTART;VALUE=" from_type ":" from
print "DTEND;VALUE=" to_type ":" to print "DTEND;VALUE=" to_type ":" to
if (summary) print_fold("SUMMARY:", summary) if (summary) print_fold("SUMMARY:", summary, i, s);
if (desc) print_fold("DESCRIPTION:", desc) if (desc) print_fold("DESCRIPTION:", desc, i, s);
if (location) print_fold("LOCATION:", location)
print "END:VEVENT" print "END:VEVENT"
print "END:VCALENDAR" print "END:VCALENDAR"
} }

View File

@ -1,30 +1,46 @@
## src/awk/parse.awk function parse( dt) {
## Parse iCalendar file and print its key aspects: # Get timezone information
## ``` dt = "";
## <start> <end> <fpath> <collection> <status> <summary> for (i=2; i<NF-1; i+=2) {
## ```. if ($i == "TZID") {
## The output is space delimited. dt = "TZ=\"" $(i+1) "\" ";
## Summary may contain spaces, but it's the last in the list. break;
## }
## @assign collection_labels: See configuration of the current program. }
# Get date/datetime
return length($NF) == 8 ?
dt $NF :
dt gensub(/^([0-9]{8})T([0-9]{2})([0-9]{2})([0-9]{2})(Z)?$/, "\\1 \\2:\\3:\\4\\5", "g", $NF);
}
@include "lib/awk/icalendar.awk" function parse_duration( dt, dta, i, n, a, seps) {
n = split($NF, a, /[PTWHMSD]/, seps);
delete dta;
for (i=2; i<=n; i++) {
if(seps[i] == "W") dta["weeks"] = a[i];
if(seps[i] == "H") dta["hours"] = a[i];
if(seps[i] == "M") dta["minutes"] = a[i];
if(seps[i] == "S") dta["seconds"] = a[i];
if(seps[i] == "D") dta["days"] = a[i];
}
dt = a[1] ? a[1] : "+";
for (i in dta) {
dt = dt " " dta[i] " " i;
}
return dt;
}
# Print string of parsed data. function print_data(start, dur, end, summary, cmd, collection) {
# summary = substr(summary, index(summary, ":") + 1);
# @local variables: cmd, collection, depth, path gsub("\\\\n", " ", summary); # one-liner
# @input start: Start time of event gsub("\\\\N", " ", summary); # one-liner
# @input dur: Boolean that indicates that `end` specifies a duration gsub("\\\\,", ",", summary);
# @input end: End time of event, or event duration (see `dur`) gsub("\\\\;", ";", summary);
# @input summary: Content of SUMMARY field of the event gsub("\\\\\\\\", "\\", summary);
function print_data(start, dur, end, summary, cmd, collection, depth, path) { depth = split(FILENAME, path, "/");
summary = getcontent(summary)
gsub("\n", " ", summary) # This will be put on a single line
gsub("\t", " ", summary) # Generally, we use tab as delimiter.
depth = split(FILENAME, path, "/")
fpath = path[depth-1] "/" path[depth] fpath = path[depth-1] "/" path[depth]
collection = depth > 1 ? path[depth-1] : "" collection = depth > 1 ? path[depth-1] : "";
collection = collection in collection2label ? collection2label[collection] : collection collection = collection in collection2label ? collection2label[collection] : collection;
collection = collection2label[path[depth-1]] collection = collection2label[path[depth-1]]
end = dur ? start " " end : end end = dur ? start " " end : end
cmd = "date -d '" start "' +\"%s\"" cmd = "date -d '" start "' +\"%s\""
@ -33,26 +49,23 @@ function print_data(start, dur, end, summary, cmd, collection, depth, path) {
cmd = "date -d '" end "' +\"%s\"" cmd = "date -d '" end "' +\"%s\""
cmd | getline end cmd | getline end
close(cmd) close(cmd)
status = status ? status : "CONFIRMED" print start, end, fpath, collection, summary
print start, end, fpath, collection, status, summary
} }
# AWK program
BEGIN { BEGIN {
FS="[:;=]" FS="[:;=]";
split(collection_labels, mapping, ";") split(collection_labels, mapping, ";");
for (map in mapping) for (map in mapping)
{ {
split(mapping[map], m, "=") split(mapping[map], m, "=");
collection2label[m[1]] = m[2] collection2label[m[1]] = m[2];
} }
} }
/^END:VEVENT/ && inside { print_data(start, dur, end, summary); exit } /^END:VEVENT/ && inside { print_data(start, dur, end, summary, cmd, collection); exit }
/^DTSTART/ && inside { start = parse_dt(getparam($0), getcontent($0)) } /^DTSTART/ && inside { start = parse( dt) }
/^DTEND/ && inside { end = parse_dt(getparam($0), getcontent($0)) } /^DTEND/ && inside { end = parse( dt) }
/^DURATION/ && inside { end = parse_duration($NF); dur = 1 } /^DURATION/ && inside { end = parse_duration( dt, dta, i, n, a, seps); dur = 1 }
/^STATUS/ && inside { status = $NF }
/^[^ ]/ && rs { rs = 0 } /^[^ ]/ && rs { rs = 0 }
/^ / && rs { summary = summary substr($0, 2) } /^ / && rs { summary = summary substr($0, 2); }
/^SUMMARY/ && inside { rs = 1; summary = $0 } /^SUMMARY/ && inside { rs = 1; summary = $0; }
/^BEGIN:VEVENT/ { inside = 1 } /^BEGIN:VEVENT/ { inside = 1 }

View File

@ -1,27 +0,0 @@
## src/awk/set.awk
## Set or replace the content of a specified field in the iCalendar file.
##
## @assign field: iCalendar field
## @assign value: Content to set it to
##
## LIMITATION: This program does not fold long content lines.
@include "lib/awk/icalendar.awk"
BEGIN { FS = "[:;]"; zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1) }
/^BEGIN:VEVENT$/ { inside = 1 }
/^END:VEVENT$/ {
inside = 0
if (!duplic)
print field ":" escape(value)
seq = seq ? seq + 1 : 1
print "SEQUENCE:" seq
print "LAST-MODIFIED:" zulu
}
$1 == field && inside { con = 1; duplic = 1; print field ":" escape(value); next }
$1 == field && duplic { con = 1; next }
/^ / && con { next }
/^[^ ]/ && con { con = 0 }
/^SEQUENCE/ && inside { seq = $2; next } # store sequence number and skip
/^LAST-MODIFIED/ && inside { next }
{ print }

View File

@ -1,103 +1,101 @@
## src/awk/update.awk function getcontent(content_line, prop)
## Update iCalendar file from markdown file. {
return substr(content_line[prop], index(content_line[prop], ":") + 1);
}
@include "lib/awk/icalendar.awk" function escape(str)
{
gsub("\\\\", "\\\\", str);
gsub(";", "\\\\;", 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 { BEGIN {
FS=":" FS=":";
zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1) zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1);
} }
ENDFILE { ENDFILE {
if (NR == FNR) if (NR == FNR)
{ {
# If nanoseconds are not 0, then we assume user entered "tomorrow" or # If nanoseconds are not 0, then we assume user enterd "tomorrow" or
# something the like, and we make this a date entry, as opposed to a # something the like, and we make this a date entry, as opposed to a
# date-time entry. # date-time entry.
gsub("\"", "\\\"", from) from = from ? from : "now"
cmd = "date -d \"" from "\" +\"%N\"" cmd = "date -d \"" from "\" +\"%N\"";
cmd | getline n
close(cmd)
n = n + 0
cmd = "date -d \"" from "\" +\"%H%M\""
cmd | getline t cmd | getline t
close(cmd) close(cmd)
t = t + 0 t = t + 0
if (n != 0 || t == 0) { if (t == 0) {
from_type = "DATE"
cmd = "date -d \"" from "\" +\"%Y%m%d\""
} else {
from_type = "DATE-TIME" from_type = "DATE-TIME"
cmd = "date -d \"" from "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d" cmd = "date -d \"" from "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d"
} else {
from_type = "DATE"
cmd = "date -d \"" from "\" +\"%Y%m%d\"";
} }
suc = cmd | getline from cmd | getline from
close(cmd) close(cmd)
if (suc != 1) {
exit 1
}
# #
gsub("\"", "\\\"", to) to = to ? to : "now"
cmd = "date -d \"" to "\" +\"%N\"" cmd = "date -d \"" to "\" +\"%N\"";
cmd | getline n
close(cmd)
n = n + 0
cmd = "date -d \"" to "\" +\"%H%M\""
cmd | getline t cmd | getline t
close(cmd) close(cmd)
t = t + 0 t = t + 0
if (n != 0 || t == 0) { if (t == 0) {
to_type = "DATE"
cmd = "date -d \"" to "\" +\"%Y%m%d\""
} else {
to_type = "DATE-TIME" to_type = "DATE-TIME"
cmd = "date -d \"" to "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d" cmd = "date -d \"" to "\" +\"@%s\" | xargs date -u +\"%Y%m%dT%H%M00Z\" -d"
} else {
to_type = "DATE"
cmd = "date -d \"" to "\" +\"%Y%m%d\"";
} }
suc = cmd | getline to cmd | getline to
close(cmd) close(cmd)
if (suc != 1) {
exit 1
}
} }
escape(summary);
escape(desc);
} }
NR == FNR && readdesc { desc = desc ? desc "\\n" escape($0) : escape($0); next } NR == FNR && desc { desc = desc "\\n" $0; next; }
NR == FNR { NR == FNR {
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : "" from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : "";
if (!from)
exit 1
getline getline
to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "" to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "";
if (!to)
exit 1
getline getline
location = substr($0, 1, 2) == "@ " ? escape(substr($0, 3)) : "" summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
if (location) getline
summary = substr($0, 1, 2) == "# " ? escape(substr($0, 3)) : ""
if (!summary)
exit 1
getline # This line should be empty getline # This line should be empty
if ($0 != "") getline # First line of description
exit 1 desc = $0;
readdesc = 1 next;
next
} }
/^BEGIN:VEVENT$/ { inside = 1; print; next }
/^X-ALT-DESC/ && inside { next } # drop this alternative description
/^ / && inside { next } # drop this folded line (the only content with folded lines will be updated)
/^(DTSTART|DTEND|SUMMARY|CATEGORIES|DESCRIPTION|LAST-MODIFIED)/ && inside { next } # skip for now, we will write updated fields at the end
/^SEQUENCE/ && inside { seq = $2; next } # store sequence number and skip
/^END:VEVENT$/ { /^END:VEVENT$/ {
seq = seq ? seq + 1 : 1 seq = seq ? seq + 1 : 1
print "SEQUENCE:" seq print "SEQUENCE:" seq
print "LAST-MODIFIED:" zulu print "LAST-MODIFIED:" zulu
print "DTSTART;VALUE=" from_type ":" from print "DTSTART;VALUE=" from_type ":" from
print "DTEND;VALUE=" to_type ":" to print "DTEND;VALUE=" to_type ":" to
print_fold("SUMMARY:", summary) print_fold("SUMMARY:", summary, i, s)
print_fold("DESCRIPTION:", desc) print_fold("DESCRIPTION:", desc, i, s)
print_fold("LOCATION:", location)
inside = "" inside = ""
skipf = 0
} }
/^BEGIN:VEVENT$/ { inside = 1 }
/^ / && skipf { next } # drop this folded line
/^[^ ]/ && skipf { skipf = 0 }
/^(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 } { print }

44
src/awk/weeks.awk Normal file
View File

@ -0,0 +1,44 @@
function parse( dt) {
# Get timezone information
dt = "";
for (i=2; i<NF-1; i+=2) {
if ($i == "TZID") {
dt = "TZ=\"" $(i+1) "\" ";
break;
}
}
# Get date/datetime
return length($NF) == 8 ?
dt $NF :
dt gensub(/^([0-9]{8})T([0-9]{2})([0-9]{2})([0-9]{2})(Z)?$/, "\\1 \\2:\\3:\\4\\5", "g", $NF);
}
function parse_duration( dt, dta, i, n, a, seps) {
n = split($NF, a, /[PTWHMSD]/, seps);
delete dta;
for (i=2; i<=n; i++) {
if(seps[i] == "W") dta["weeks"] = a[i];
if(seps[i] == "H") dta["hours"] = a[i];
if(seps[i] == "M") dta["minutes"] = a[i];
if(seps[i] == "S") dta["seconds"] = a[i];
if(seps[i] == "D") dta["days"] = a[i];
}
dt = a[1] ? a[1] : "+";
for (i in dta) {
dt = dt " " dta[i] " " i;
}
return dt;
}
function fn(path, n, a) {
n = split(path, a, "/");
return a[n-1] "/" a[n];
}
BEGIN { FS="[:;=]"; OFS="|" }
BEGINFILE { inside = 0; dur = 0; start = "ERROR"; end = "ERROR" }
/^END:VEVENT/ { print start, dur ? start " " end : end, fn(FILENAME, n, a); nextfile }
/^DTSTART/ && inside { start = parse( dt) }
/^DTEND/ && inside { end = parse( dt) }
/^DURATION/ && inside { end = parse_duration( dt, dta, i, n, a, seps); dur = 1 }
/^BEGIN:VEVENT/ { inside = 1 }

View File

@ -1,39 +1,23 @@
## src/awk/weekview.awk function c() {
## Print view of all appointments of the current week. return CYAN substr($0, index($0, ">") + 1) OFF " " RED "/" OFF
## Generates view from
## printf "%s\t%s\t%s\t%s\n" "$i" "$s" "$e" "$description"
##
## @assign startofweek: Date of first day in the week
## @assign style_day: Style for dates
## @assign style_event_delim: Event delimiter
## @assign style_summary: Style for summary lines
## @assign style_time: Style for times
# Functions
# Compose line that will display a day in the week.
#
# @input desc: String with a description of the event
# @return: Single-line string
function c(desc) {
return style_summary desc OFF " " style_event_delim
} }
# AWK program
BEGIN { BEGIN {
FS = "\t" GREEN = "\033[1;32m";
OFS = "\t" RED = "\033[1;31m";
OFF = "\033[m" WHITE = "\033[1;97m";
CYAN = "\033[1;36m";
FAINT = "\033[2m";
OFF = "\033[m";
OFS = "|"
} }
$2 == "00:00" && $3 == "00:00" { dayline = dayline " " c($4); next } /^[0-7] 00:00 -- 00:00/ { dayline = dayline " " c(); next }
$2 == "00:00" { dayline = dayline style_time " → " $3 OFF " " c($4); next } /^[0-7] 00:00 -- / { dayline = dayline " <--" $4 " " c(); next }
$3 == "00:00" { dayline = dayline style_time " " $2 " → " OFF c($4); next } /^[0-7] [0-9]{2}:[0-9]{2} -- 00:00/ { dayline = dayline " " $2 "--> " c(); next }
NF == 4 { dayline = dayline style_time " " $2 " " $3 OFF " " c($4); next } /^[0-7] [0-9]{2}:[0-9]{2} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next }
NF == 1 && dayline { print "+", startofweek " +" $1-1 " days", "", dayline } /^[0-7]$/ && dayline { print "+", startofweek " +" $0-1 " days", "", dayline; }
NF == 1 { /^[0-7]$/ {
cmd = "date -d '" startofweek " +" $1 " days' +\"%a %e %b %Y\"" cmd = "date -d '" startofweek " +" $0 " days' +\"%a %e %b %Y\"";
cmd | getline dayline cmd | getline dayline;
close(cmd) close(cmd);
dayline = style_day dayline ": " OFF dayline = GREEN dayline ": " OFF
} }

View File

@ -1,121 +0,0 @@
# Escape string to be used as content in iCalendar files.
#
# @input str: String to escape
# @return: Escaped string
function escape(str)
{
gsub("\\\\", "\\\\", str)
gsub("\\n", "\\n", 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
while (s)
{
print " " s
s = substr(content, i+1, 73)
i = i + 73
}
}
# 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 parameter part of an iCalendar line.
#
# @input str: String
# @return: Parameter part
function getparam(str, i) {
i = index(str, ";")
if (!i)
return ""
return substr(str, i + 1, index(str, ":") - i)
}
# 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 DTSTART or DTEND entries.
#
# @local variables: tz
# @input dt_param: iCalendar DTSTART or DTEND parameter string
# @input dt_content: iCalendar DTSTART or DTEND content string
# @return: date or date-time string that can be used in date (1)
function parse_dt(dt_param, dt_content, tz, a, i, k) {
if (dt_param) {
split(dt_param, a, ";")
for (i in a) {
k = index(a[i], "=")
if (substr(a[i], 1, k-1) == "TZID") {
tz = "TZ=\"" substr(a[i], k + 1) "\" "
break
}
}
}
# Get date/date-time
return length(dt_content) == 8 ?
dt dt_content :
dt gensub(/^([0-9]{8})T([0-9]{2})([0-9]{2})([0-9]{2})(Z)?$/, "\\1 \\2:\\3:\\4\\5", "g", dt_content)
}
# Map iCalendar duration specification into the format to be used in date (1).
#
# @local variables: dt, dta, i, n, a, seps
# @input duration: iCalendar duration string
# @return: relative-date/date-time specification to be used in date (1)
function parse_duration(duration, dt, dta, i, n, a, seps) {
n = split(duration, a, /[PTWHMSD]/, seps)
for (i=2; i<=n; i++) {
if(seps[i] == "W") dta["weeks"] = a[i]
if(seps[i] == "H") dta["hours"] = a[i]
if(seps[i] == "M") dta["minutes"] = a[i]
if(seps[i] == "S") dta["seconds"] = a[i]
if(seps[i] == "D") dta["days"] = a[i]
}
dt = a[1] ? a[1] : "+"
for (i in dta)
dt = dt " " dta[i] " " i
return dt
}

File diff suppressed because it is too large Load Diff

View File

@ -1,121 +0,0 @@
# AWK scripts
# - AWK_APPROX: Generate approximate data of all files
# - AWK_CALSHIFT: Shift calendar to start weeks on Mondays
# - AWK_CALANNOT: Annotate calendar
# - AWK_DAYVIEW: Generate view of the day
# - AWK_GET: Print field of iCalendar file
# - AWK_MERGE: Generate list of weeks with associated iCalendar files
# - AWK_NEW: Make new iCalendar file
# - AWK_PARSE: Timezone aware parsing of iCalendar file for day view
# - AWK_SET: Set value of specific field in iCalendar file
# - AWK_UPDATE: Update iCalendar file
# - AWK_WEEKVIEW: Generate view of the week
# - AWK_ATTACHLS: List attachments
# - AWK_ATTACHDD: Store attachment
# - AWK_ATTACHRM: Remove attachment
# - AWK_ATTACH: Add attachment
AWK_APPROX=$(
cat <<'EOF'
@@include awk/approx.awk
EOF
)
export AWK_APPROX
AWK_MERGE=$(
cat <<'EOF'
@@include awk/merge.awk
EOF
)
export AWK_MERGE
AWK_PARSE=$(
cat <<'EOF'
@@include awk/parse.awk
EOF
)
export AWK_PARSE
AWK_WEEKVIEW=$(
cat <<'EOF'
@@include awk/weekview.awk
EOF
)
export AWK_WEEKVIEW
AWK_DAYVIEW=$(
cat <<'EOF'
@@include awk/dayview.awk
EOF
)
export AWK_DAYVIEW
AWK_GET=$(
cat <<'EOF'
@@include awk/get.awk
EOF
)
export AWK_GET
AWK_UPDATE=$(
cat <<'EOF'
@@include awk/update.awk
EOF
)
export AWK_UPDATE
AWK_NEW=$(
cat <<'EOF'
@@include awk/new.awk
EOF
)
export AWK_NEW
AWK_CALSHIFT=$(
cat <<'EOF'
@@include awk/calshift.awk
EOF
)
export AWK_CALSHIFT
AWK_CALANNOT=$(
cat <<'EOF'
@@include awk/calannot.awk
EOF
)
export AWK_CALANNOT
AWK_SET=$(
cat <<'EOF'
@@include awk/set.awk
EOF
)
export AWK_SET
AWK_ATTACHLS=$(
cat <<'EOF'
@@include awk/attachls.awk
EOF
)
export AWK_ATTACHLS
AWK_ATTACHDD=$(
cat <<'EOF'
@@include awk/attachdd.awk
EOF
)
export AWK_ATTACHDD
AWK_ATTACHRM=$(
cat <<'EOF'
@@include awk/attachrm.awk
EOF
)
export AWK_ATTACHRM
AWK_ATTACH=$(
cat <<'EOF'
@@include awk/attach.awk
EOF
)
export AWK_ATTACH

View File

@ -1,129 +0,0 @@
# Extra command-line options
# - --import-ni
# - --import
# - --git
# - --git-init
# Import iCalendar file noninteractively
#
# @input $2: Absolute path to iCalendar file
# @input $3: Collection
# @return: On success, returns 0, otherwise 1
if [ "${1:-}" = "--import-ni" ]; then
shift
file="${1:-}"
collection="${2:-}"
if [ ! -f "$file" ]; then
err "File \"$file\" does not exist"
exit 1
fi
for c in $(echo "$COLLECTION_LABELS" | sed "s|=[^;]*;| |g"); do
if [ "$collection" = "$c" ]; then
cexists="yes"
break
fi
done
if [ -n "${cexists:-}" ] && [ -d "$ROOT/$collection" ]; then
__import_to_collection "$file" "$collection"
else
err "Collection \"$collection\" does not exist"
exit 1
fi
exit
fi
# Import iCalendar file.
#
# @input $2: Absolute path to iCalendar file
# @return: On success, returns 0, otherwise 1
if [ "${1:-}" = "--import" ]; then
shift
file="${1:-}"
if [ ! -f "$file" ]; then
err "File \"$file\" does not exist"
return 1
fi
line=$(awk \
-v collection_labels="$COLLECTION_LABELS" \
"$AWK_PARSE" "$file")
set -- $line
startsec="${1:-}"
endsec="${2:-}"
if [ -z "$line" ] || [ -z "$startsec" ] || [ -z "$endsec" ]; then
err "File \"$file\" does not look like an iCalendar file containing an event"
return 1
fi
start=$(__datetime_human_machine "$startsec")
end=$(__datetime_human_machine "$endsec")
location=$(awk -v field="LOCATION" "$AWK_GET" "$file")
summary=$(awk -v field="SUMMARY" "$AWK_GET" "$file")
description=$(awk -v field="DESCRIPTION" "$AWK_GET" "$file")
filetmp=$(mktemp --suffix='.md')
(
echo "::: |> $start"
echo "::: <| $end"
) >"$filetmp"
if [ -n "$location" ]; then
echo "@ $location" >>"$filetmp"
fi
(
echo "# $summary"
echo ""
echo "$description"
) >>"$filetmp"
$CAT "$filetmp" >/dev/tty
while true; do
printf "Do you want to import this entry? (yes/no): " >/dev/tty
read -r yn
case $yn in
"yes")
collection=$(echo "$COLLECTION_LABELS" | tr ';' '\n' | awk '/./ {print}' | $FZF --margin="30%" --no-info --delimiter='=' --with-nth=2 --accept-nth=1)
if [ -z "$collection" ]; then
exit
fi
__import_to_collection "$file" "$collection"
break
;;
"no")
break
;;
*)
echo "Please answer \"yes\" or \"no\"." >/dev/tty
;;
esac
done
rm -f "$filetmp"
exit
fi
# Run git command
#
# @input $2..: Git command
# @return: On success, returns 0, otherwise 1
if [ "${1:-}" = "--git" ]; then
if [ -z "${GIT:-}" ]; then
err "Git not supported, run \`$0 --git-init\` first"
return 1
fi
shift
$GIT "$@"
exit
fi
# Enable the ues of git
#
# @return: On success, returns 0, otherwise 1
if [ "${1:-}" = "--git-init" ]; then
if [ -n "${GIT:-}" ]; then
err "Git already enabled"
return 1
fi
if ! command -v "git" >/dev/null; then
err "Git command not found"
return 1
fi
git -C "$ROOT" init
git -C "$ROOT" add -A
git -C "$ROOT" commit -m 'Initial commit: Start git tracking'
exit
fi

View File

@ -1,100 +0,0 @@
# Preview command-line options
# - --preview-event
# - --preview_week
# Print preview of event and exit.
#
# @input $2: Line from day view containing an event
if [ "${1:-}" = "--preview-event" ]; then
hour=$(echo "$2" | cut -f 2)
start=$(echo "$2" | cut -f 3)
end=$(echo "$2" | cut -f 4)
fpath=$(echo "$2" | cut -f 5)
if [ -n "$hour" ] && [ -n "$fpath" ]; then
fpath="$ROOT/$fpath"
start=$(datetime_str "$start" "%a ")
end=$(datetime_str "$end" "%a ")
location=$(awk -v field="LOCATION" "$AWK_GET" "$fpath")
status=$(awk -v field="STATUS" "$AWK_GET" "$fpath")
if [ "$status" = "TENTATIVE" ]; then
symb="🟡"
elif [ "$status" = "CANCELLED" ]; then
symb="❌"
fi
echo "📅${symb:-} ${STYLE_EPV_DATETIME}$start${OFF}${STYLE_EPV_DATETIME}$end${OFF}"
if [ -n "${location:-}" ]; then
echo "📍 ${STYLE_EPV_LOCATION}$location${OFF}"
fi
attcnt=$(awk "$AWK_ATTACHLS" "$fpath" | wc -l)
if [ "$attcnt" -gt 0 ]; then
echo "🔗 $attcnt attachments"
fi
echo ""
awk -v field="DESCRIPTION" "$AWK_GET" "$fpath" | $CAT
fi
exit
fi
# Print preview of week.
#
# @input $2: Line from week view
if [ "${1:-}" = "--preview-week" ]; then
sign=$(echo "$2" | cut -f 1)
if [ "$sign" = "+" ]; then
startdate=$(echo "$2" | cut -f 2)
set -- $(date -d "$startdate" +"%Y %m %d")
year=$1
month=$2
day=$3
set -- $(date -d "today" +"%Y %m %d")
year_cur=$1
month_cur=$2
day_cur=$3
# Previous months
set -- $(month_previous "$month" "$year")
month_pre="$1"
year_pre="$2"
set -- $(month_previous "$month_pre" "$year_pre")
month_pre2="$1"
year_pre2="$2"
# Next months
set -- $(month_next "$month" "$year")
month_nex="$1"
year_nex="$2"
set -- $(month_next "$month_nex" "$year_nex")
month_nex2="$1"
year_nex2="$2"
set -- $(month_next "$month_nex2" "$year_nex2")
month_nex3="$1"
year_nex3="$2"
# Highlight today
if [ "$month_pre2" -eq "$month_cur" ] && [ "$year_pre2" -eq "$year_cur" ]; then
var_pre2=$day_cur
fi
if [ "$month_pre" -eq "$month_cur" ] && [ "$year_pre" -eq "$year_cur" ]; then
var_pre=$day_cur
fi
if [ "$month" -eq "$month_cur" ] && [ "$year" -eq "$year_cur" ]; then
var=$day_cur
fi
if [ "$month_nex" -eq "$month_cur" ] && [ "$year_nex" -eq "$year_cur" ]; then
var_nex=$day_cur
fi
if [ "$month_nex2" -eq "$month_cur" ] && [ "$year_nex2" -eq "$year_cur" ]; then
var_nex2=$day_cur
fi
if [ "$month_nex3" -eq "$month_cur" ] && [ "$year_nex3" -eq "$year_cur" ]; then
var_nex3=$day_cur
fi
# show
(
cal "$month_pre2" "$year_pre2" | awk "$AWK_CALSHIFT" | awk -v cur="${var_pre2:-}" -v style_month="$STYLE_CALENDAR_MONTH" -v style_weekdays="$STYLE_CALENDAR_WEEKDAYS" -v style_cur="$STYLE_CALENDAR_CURRENT_DAY" -v style_highlight="$STYLE_CALENDAR_HL_DAY" -v style_weekdays="$STYLE_CALENDAR_WEEKDAYS" -v style_cur="$STYLE_CALENDAR_CURRENT_DAY" -v style_highlight="$STYLE_CALENDAR_HL_DAY" "$AWK_CALANNOT"
cal "$month_pre" "$year_pre" | awk "$AWK_CALSHIFT" | awk -v cur="${var_pre:-}" -v style_month="$STYLE_CALENDAR_MONTH" -v style_weekdays="$STYLE_CALENDAR_WEEKDAYS" -v style_cur="$STYLE_CALENDAR_CURRENT_DAY" -v style_highlight="$STYLE_CALENDAR_HL_DAY" "$AWK_CALANNOT"
cal "$month" "$year" | awk "$AWK_CALSHIFT" | awk -v cur="${var:-}" -v day="$day" -v style_month="$STYLE_CALENDAR_MONTH" -v style_weekdays="$STYLE_CALENDAR_WEEKDAYS" -v style_cur="$STYLE_CALENDAR_CURRENT_DAY" -v style_highlight="$STYLE_CALENDAR_HL_DAY" "$AWK_CALANNOT"
cal "$month_nex" "$year_nex" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex:-}" -v style_month="$STYLE_CALENDAR_MONTH" -v style_weekdays="$STYLE_CALENDAR_WEEKDAYS" -v style_cur="$STYLE_CALENDAR_CURRENT_DAY" -v style_highlight="$STYLE_CALENDAR_HL_DAY" "$AWK_CALANNOT"
cal "$month_nex2" "$year_nex2" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex2:-}" -v style_month="$STYLE_CALENDAR_MONTH" -v style_weekdays="$STYLE_CALENDAR_WEEKDAYS" -v style_cur="$STYLE_CALENDAR_CURRENT_DAY" -v style_highlight="$STYLE_CALENDAR_HL_DAY" "$AWK_CALANNOT"
cal "$month_nex3" "$year_nex3" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex3:-}" -v style_month="$STYLE_CALENDAR_MONTH" -v style_weekdays="$STYLE_CALENDAR_WEEKDAYS" -v style_cur="$STYLE_CALENDAR_CURRENT_DAY" -v style_highlight="$STYLE_CALENDAR_HL_DAY" "$AWK_CALANNOT"
) | awk '{ l[(NR-1)%8] = l[(NR-1)%8] " " $0 } END {for (i in l) print l[i] }'
fi
exit
fi

View File

@ -1,31 +0,0 @@
# Command-line Arguments for reloading views
# - --reload-day
# - --reload-week
# - --reload-all
# Reload view of specified day.
#
# @input $2.. (optional): Specification of day, defaults to `today`
if [ "${1:-}" = "--reload-day" ]; then
shift
DISPLAY_DATE=${*:-today}
__view_day
exit
fi
# Reload view of the week containing the specified date.
#
# @input $2.. (optional): Specification of day, defaults to `today`
if [ "${1:-}" = "--reload-week" ]; then
shift
DISPLAY_DATE=${*:-today}
DISPLAY_POS=$((8 - $(date -d "$DISPLAY_DATE" +"%u")))
__view_week
exit
fi
# Reload view of all entries.
if [ "${1:-}" = "--reload-all" ]; then
__view_all
exit
fi

View File

@ -1,66 +0,0 @@
# Load Configuration
# - ROOT: Directory containing the collections
# - COLLECTION_LABELS: Mappings between collections and labels
# - SYNC_CMD (optional): Synchronization command
# - DAY_START (optional): Hour of start of the day (defaults to 8)
# - DAY_END (optional): Hour of end of the day (defaults to 18)
# - EDITOR (optional): Your favorite editor, is usually already exported
# - TZ (optional): Your favorite timezone, usually system's choice
# - LC_TIME (optional): Your favorite locale for date and time
# - ZI_DIR (optional): Location of tzdata, defaults to /usr/share/zoneinfo
CONFIGFILE="$HOME/.config/fzf-vcal/config"
if [ ! -f "$CONFIGFILE" ]; then
err "Configuration '$CONFIGFILE' not found."
exit 1
fi
# shellcheck source=/dev/null
. "$CONFIGFILE"
if [ -z "${ROOT:-}" ] || [ -z "${COLLECTION_LABELS:-}" ]; then
err "Configuration is incomplete."
exit 1
fi
export ROOT COLLECTION_LABELS
export SYNC_CMD=${SYNC_CMD:-echo 'Synchronization disabled'}
export DAY_START=${DAY_START:-8}
export DAY_END=${DAY_END:-18}
export ZI_DIR=${ZI_DIR:-/usr/share/zoneinfo/posix}
if [ ! -d "$ZI_DIR" ]; then
err "Could not determine time-zone information"
exit 1
fi
export OPEN=${OPEN:-open}
# Check and load required tools
# - FZF: Fuzzy finder `fzf``
# - UUIDGEN: Tool `uuidgen` to generate random uids
# - CAT: `bat` or `batcat` or `cat`
# - GIT: `git` if it exists
#
# The presence of POSIX tools is not checked.
if command -v "fzf" >/dev/null; then
export FZF="fzf --black"
else
err "Did not find the command-line fuzzy finder fzf."
exit 1
fi
if command -v "uuidgen" >/dev/null; then
export UUIDGEN="uuidgen"
else
err "Did not find the uuidgen command."
exit 1
fi
if command -v "bat" >/dev/null; then
CAT="bat"
elif command -v "batcat" >/dev/null; then
CAT="batcat"
fi
CAT=${CAT:+$CAT --color=always --style=numbers --language=md}
export CAT=${CAT:-cat}
if command -v "git" >/dev/null && [ -d "$ROOT/.git" ]; then
export GIT="git -C $ROOT"
fi

View File

@ -1,233 +0,0 @@
# iCalendar modification wrapper
# - __edit
# - __new
# - __delete
# - __import_to_collection
# - __cancel_toggle
# - __tentative_toggle
# - __add_attachment
# Edit iCalendar file.
#
# @input $1: Start date/date-time
# @input $2: End date/date-time
# @input $3: Path to iCalendar file (relative to `$ROOT`)
__edit() {
start=$(__datetime_human_machine "$1")
end=$(__datetime_human_machine "$2")
fpath="$ROOT/$3"
location=$(awk -v field="LOCATION" "$AWK_GET" "$fpath" | tr -d "\n")
summary=$(awk -v field="SUMMARY" "$AWK_GET" "$fpath" | tr -d "\n")
description=$(awk -v field="DESCRIPTION" "$AWK_GET" "$fpath")
filetmp=$(mktemp --suffix='.md')
printf "::: |> %s\n::: <| %s\n" "$start" "$end" >"$filetmp"
if [ -n "$location" ]; then
printf "@ %s\n" "$location" >>"$filetmp"
fi
printf "# %s\n\n%s\n" "$summary" "$description" >>"$filetmp"
checksum=$(cksum "$filetmp")
$EDITOR "$filetmp" >/dev/tty
# Update only if changes are detected
if [ "$checksum" != "$(cksum "$filetmp")" ]; then
filenew="$filetmp.ics"
if awk "$AWK_UPDATE" "$filetmp" "$fpath" >"$filenew"; then
mv "$filenew" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Modified event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
fi
__refresh_data
else
rm -f "$filenew"
err "Failed to edit entry. Press <enter> to continue."
read -r tmp
fi
fi
rm "$filetmp"
}
# Generate new iCalendar file
#
# This function also sets the `$start` variable to the start of the new entry.
# On failure, start will be empty.
#
# If some start has been specified and the nanoseconds are not 0, we assume
# that the user entered "tomorrow" or something like that, and did not
# specify the time. So, we will use the `$DAY_START` time of that date.
# If the user specified a malformed date/date-time, we fail.
#
# @input $1 (optional): Date or datetime, defaults to today.
__new() {
collection=$(echo "$COLLECTION_LABELS" | tr ';' '\n' | awk '/./ {print}' | $FZF --margin="30%" --no-info --delimiter='=' --with-nth=2 --accept-nth=1)
fpath=""
while [ -f "$fpath" ] || [ -z "$fpath" ]; do
uuid=$($UUIDGEN)
fpath="$ROOT/$collection/$uuid.ics"
done
d="today $DAY_START"
if [ -n "${1:-}" ]; then
d="$1"
if [ "$(date -d "$1" +"%N")" -ne 0 ]; then
d="$d $DAY_START:00"
fi
fi
startsec=$(date -d "$d" +"%s")
endsec=$((startsec + 3600))
start=$(__datetime_human_machine "$startsec")
end=$(__datetime_human_machine "$endsec")
filetmp=$(mktemp --suffix='.md')
(
echo "::: |> $start"
echo "::: <| $end"
echo "@ <!-- write location here, optional line -->"
echo "# <!-- write summary here -->"
echo ""
) >"$filetmp"
checksum=$(cksum "$filetmp")
$EDITOR "$filetmp" >/dev/tty
# Update only if changes are detected
if [ "$checksum" != "$(cksum "$filetmp")" ]; then
filenew="$filetmp.ics"
if awk -v uid="$uuid" "$AWK_NEW" "$filetmp" >"$filenew"; then
mv "$filenew" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Added event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
fi
start=$(awk -v field="DTSTART" "$AWK_GET" "$fpath" | grep -o '[0-9]\{8\}')
else
rm -f "$filenew"
start=""
err "Failed to create new entry. Press <enter> to continue."
read -r tmp
fi
fi
rm "$filetmp"
}
# Delete iCalendar file
#
# @input $1: Path to iCalendar file, relative to `$ROOT`
__delete() {
fpath="$ROOT/$1"
summary=$(awk -v field="SUMMARY" "$AWK_GET" "$fpath")
while true; do
printf "Do you want to delete the entry with the title \"%s\"? (yes/no): " "$summary" >/dev/tty
read -r yn
case $yn in
"yes")
sfg="$(__summary_for_commit "$fpath")"
rm -v "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Deleted event '$sfg ...'" -- "$fpath"
fi
break
;;
"no")
break
;;
*)
echo "Please answer \"yes\" or \"no\"." >/dev/tty
;;
esac
done
}
# Import iCalendar file to specified collection. The only modification made to
# the file is setting the UID.
#
# @input $1: path to iCalendar file
# @input $2: collection name
__import_to_collection() {
file="$1"
collection="$2"
fpath=""
while [ -f "$fpath" ] || [ -z "$fpath" ]; do
uuid=$($UUIDGEN)
fpath="$ROOT/$collection/$uuid.ics"
done
filetmp=$(mktemp)
awk -v field="UID" -v value="$uuid" "$AWK_SET" "$file" >"$filetmp"
mv "$filetmp" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Imported event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
fi
}
# Set status of appointment to CANCELLED or CONFIRMED (toggle)
#
# @input $1: path to iCalendar file
__cancel_toggle() {
fpath="$ROOT/$1"
status=$(awk -v field="STATUS" "$AWK_GET" "$fpath")
newstatus="CANCELLED"
if [ "${status:-}" = "$newstatus" ]; then
newstatus="CONFIRMED"
fi
filetmp=$(mktemp)
awk -v field="STATUS" -v value="$newstatus" "$AWK_SET" "$fpath" >"$filetmp"
mv "$filetmp" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Event '$(__summary_for_commit "$fpath") ...' has now status $status" -- "$fpath"
fi
}
# Toggle status flag: CONFIRMED <-> TENTATIVE
#
# @input $1: path to iCalendar file
__tentative_toggle() {
fpath="$ROOT/$1"
status=$(awk -v field="STATUS" "$AWK_GET" "$fpath")
newstatus="TENTATIVE"
if [ "${status:-}" = "$newstatus" ]; then
newstatus="CONFIRMED"
fi
filetmp=$(mktemp)
awk -v field="STATUS" -v value="$newstatus" "$AWK_SET" "$fpath" >"$filetmp"
mv "$filetmp" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Event '$(__summary_for_commit "$fpath") ...' has now status $status" -- "$fpath"
fi
}
# Prepend attachment to iCalendar file
#
# @input $1: path to iCalendar file
__add_attachment() {
fpath="$ROOT/$1"
sel=$(
$FZF --prompt="Select attachment> " \
--walker="file,hidden" \
--walker-root="$HOME" \
--expect="ctrl-c,ctrl-g,ctrl-q,esc"
)
key=$(echo "$sel" | head -1)
f=$(echo "$sel" | tail -1)
if [ -n "$key" ]; then
f=""
fi
if [ -z "$f" ] || [ ! -f "$f" ]; then
return
fi
filename=$(basename "$f")
mime=$(file -b -i "$f" | cut -d ';' -f 1)
if [ -z "$mime" ]; then
mime="application/octet-stream"
fi
fenc=$(mktemp)
base64 "$f" >"$fenc"
filetmp=$(mktemp)
awk -v file="$fenc" -v mime="$mime" -v filename="$filename" "$AWK_ATTACH" "$fpath" >"$filetmp"
mv "$filetmp" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Added attachment to '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
fi
rm "$fenc"
}

View File

@ -1,45 +0,0 @@
# Loading functions
# - __load_approx_data
# - __load_weeks
# - __refresh_data
# Print approximate data from iCalendar files in `$ROOT`
__load_approx_data() {
find "$ROOT" -type f -name '*.ics' -print0 |
xargs -0 -P0 \
awk \
-v collection_labels="$COLLECTION_LABELS" \
-v style_line="$STYLE_LV" \
"$AWK_APPROX"
}
# For every relevant week, print associated iCalendar files
__load_weeks() {
dates=$(awk -F'\t' '{ print $2; print $3 }' "$APPROX_DATA_FILE")
file_dates=$(mktemp)
echo "$dates" | date --file="/dev/stdin" +"%G:%V:" >"$file_dates"
awk "$AWK_MERGE" "$file_dates" "$APPROX_DATA_FILE"
rm "$file_dates"
}
# Refresh approximate data and per-week data.
#
# This functions stores the output of `__load_approx_data` in the temporary
# file `$APPROX_DATA_FILE` and the output of `__load_weeks` in the temporary
# file `@WEEKLY_DATA_FILE`.
__refresh_data() {
if [ -z "${APPROX_DATA_FILE:-}" ]; then
APPROX_DATA_FILE=$(mktemp)
trap 'rm -f "$APPROX_DATA_FILE"' EXIT INT
fi
if [ -z "${WEEKLY_DATA_FILE:-}" ]; then
WEEKLY_DATA_FILE=$(mktemp)
trap 'rm -f "$WEEKLY_DATA_FILE"' EXIT INT
fi
debug "__refresh_data(): going to load approx data"
__load_approx_data >"$APPROX_DATA_FILE"
debug "__refresh_data(): approx data loaded"
debug "__refresh_data(): going to load weeks"
__load_weeks >"$WEEKLY_DATA_FILE"
debug "__refresh_data(): weeks loaded"
}

View File

@ -1,44 +0,0 @@
# err()
# This is a helper function to print errors.
#
# @input $1: Error message
err() {
echo "$1" >/dev/tty
}
# debug()
# Pring debug message to fzf-vcal.debug
#
# @input $1: Debug message
debug() {
echo "$(date +"%D %T.%N"): $1" >>"/tmp/fzf-vcal.debug"
}
# Print date or datetime in a human and machine readable form.
#
# @input $1: Seconds since epoch
__datetime_human_machine() {
s="$1"
t=$(date -d "@$s" +"%R")
dfmt="%F"
if [ "$t" != "00:00" ]; then
dfmt="$dfmt %R"
fi
date -d "@$s" +"$dfmt"
}
# Get summary string that can be used in for git-commit messages.
#
# @input $1: iCalendar file path
__summary_for_commit() {
awk -v field="SUMMARY" "$AWK_GET" "$1" | tr -c -d "[:alnum:][:blank:]" | head -c 15
}
# Re-export dynamical variables to subshells.
__export() {
DISPLAY_DATE=$(date -R -d "$DISPLAY_DATE")
export DISPLAY_DATE
if [ -n "${TZ:-}" ]; then
export TZ
fi
}

View File

@ -1,51 +0,0 @@
# Preview helper functions
# - month_previous
# - month_next
# - datetime_str
# Print previous month of specified input month as <month> <year>.
#
# @input $1: Month
# @input $2: Year
month_previous() {
month=$(echo "$1" | sed 's/^0//')
year=$(echo "$2" | sed 's/^0//')
if [ "$month" -eq 1 ]; then
month=12
year=$((year - 1))
else
month=$((month - 1))
fi
echo "$month $year"
}
# Print next month of specified input month as <month> <year>.
#
# @input $1: Month
# @input $2: Year
month_next() {
month=$(echo "$1" | sed 's/^0//')
year=$(echo "$2" | sed 's/^0//')
if [ "$month" -eq 12 ]; then
month=1
year=$((year + 1))
else
month=$((month + 1))
fi
echo "$month $year"
}
# Print date or datetime in a human readable form.
#
# @input $1: Seconds since epoch
# @input $2.. (optoinal): Prepend date format
datetime_str() {
s="$1"
shift
t=$(date -d "@$s" +"%R")
dfmt="$*%e %b %Y"
if [ "$t" != "00:00" ]; then
dfmt="$dfmt %R %Z"
fi
date -d "@$s" +"$dfmt"
}

View File

@ -1,47 +0,0 @@
# Colors
GREEN="\033[1;32m"
BLACK="\033[1;30m"
RED="\033[1;31m"
WHITE="\033[1;97m"
CYAN="\033[1;36m"
LIGHT_CYAN="\033[1;36m"
STRIKE="\033[9m"
ITALIC="\033[3m"
FAINT="\033[2m"
BOLD="\033[1m"
BG="\033[41m"
export OFF="\033[m"
# Style
# Calendar
export STYLE_CALENDAR_MONTH="${STYLE_CALENDAR_MONTH:-$GREEN}"
export STYLE_CALENDAR_WEEKDAYS="${STYLE_CALENDAR_WEEKDAYS:-$FAINT}"
export STYLE_CALENDAR_CURRENT_DAY="${STYLE_CALENDAR_CURRENT_DAY:-$BLACK$BG}"
export STYLE_CALENDAR_HL_DAY="${STYLE_CALENDAR_HL_DAY:-$BOLD$RED}"
# Week view
export STYLE_WV_DAY="${STYLE_WV_DAY:-$GREEN}"
export STYLE_WV_EVENT_DELIM="${STYLE_WV_EVENT_DELIM:-$RED / $OFF}"
export STYLE_WV_SUMMARY="${STYLE_WV_SUMMARY:-$CYAN}"
export STYLE_WV_TIME="${STYLE_WV_TIME:-$WHITE}"
export STYLE_WV_CONFIRMED="${STYLE_WV_CONFIRMED:-$CYAN}"
export STYLE_WV_TENTATIVE="${STYLE_WV_TENTATIVE:-$FAINT$CYAN}"
export STYLE_WV_CANCELLED="${STYLE_WV_CANCELLED:-$STRIKE$FAINT$CYAN}"
# List view
export STYLE_LV="${STYLE_LV:-$FAINT}"
# Day view
export STYLE_DV_ALLDAY="${STYLE_DV_ALLDAY:-$LIGHT_CYAN$ITALIC$FAINT (allday) $OFF}"
export STYLE_DV_TIME="${STYLE_DV_TIME:-$LIGHT_CYAN}"
export STYLE_DV_CONFIRMED="${STYLE_DV_CONFIRMED:-$CYAN}"
export STYLE_DV_TENTATIVE="${STYLE_DV_TENTATIVE:-$FAINT$CYAN}"
export STYLE_DV_CANCELLED="${STYLE_DV_CANCELLED:-$STRIKE$FAINT$CYAN}"
export STYLE_DV_HOUR="${STYLE_DV_HOUR:-$FAINT}"
export STYLE_DV_EMPTYHOUR="${STYLE_DV_EMPTYHOUR:-$FAINT----------------------$OFF}"
export STYLE_DV_TZ="$WHITE$ITALIC"
# Event preview
export STYLE_EPV_DATETIME="${STYLE_EPV_DATETIME:-$CYAN}"
export STYLE_EPV_LOCATION="${STYLE_EPV_LOCATION:-$GREEN}"

View File

@ -1,157 +0,0 @@
# View Functions
# - __view_day
# - __view_week
# - __view_all
# This function prints the view for the day specified in `$DISPLAY_DATE`, in
# the tab-delimited format with the fields:
# 1. start date
# 2. start time
# 3. end time
# 4. file path
# 5. collection
# 6. description
__view_day() {
weeknr=$(date -d "$DISPLAY_DATE" +"%G:%V:")
files=$(grep "^$weeknr" "$WEEKLY_DATA_FILE" | cut -f 2)
# Find relevant files in list of week files
sef=$({
set -- $files
for file in "$@"; do
file="$ROOT/$file"
awk \
-v collection_labels="$COLLECTION_LABELS" \
"$AWK_PARSE" "$file"
done
})
# $sef holds (space-delimited): <start> <end> <fpath> <collection> <status> <summary>
today=$(date -d "$DISPLAY_DATE" +"%D")
if [ -n "$sef" ]; then
sef=$(echo "$sef" | while IFS= read -r line; do
set -- $line
starttime="$1"
shift
endtime="$1"
shift
fpath="$1" # we will use | as delimiter (need to convert back!)
shift
collection="$1"
shift
status="$1"
shift
description="$*"
#
daystart=$(date -d "$today 00:00:00" +"%s")
dayend=$(date -d "$today 23:59:59" +"%s")
line=""
if [ "$starttime" -gt "$daystart" ] && [ "$starttime" -lt "$dayend" ]; then
s=$(date -d "@$starttime" +"%R")
elif [ "$starttime" -le "$daystart" ] && [ "$endtime" -gt "$daystart" ]; then
s="00:00"
else
continue
fi
if [ "$endtime" -gt "$daystart" ] && [ "$endtime" -lt "$dayend" ]; then
e=$(date -d "@$endtime" +"%R")
elif [ "$endtime" -ge "$dayend" ] && [ "$starttime" -lt "$dayend" ]; then
e="00:00"
else
continue
fi
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$s" "$e" "$starttime" "$endtime" "$fpath" "$collection" "$description" "$status"
done)
fi
echo "$sef" | sort -n | awk \
-v today="$today" \
-v daystart="$DAY_START" \
-v dayend="$DAY_END" \
-v style_allday="$STYLE_DV_ALLDAY" \
-v style_timerange="$STYLE_DV_TIME" \
-v style_confirmed="$STYLE_DV_CONFIRMED" \
-v style_tentative="$STYLE_DV_TENTATIVE" \
-v style_cancelled="$STYLE_DV_CANCELLED" \
-v style_hour="$STYLE_DV_HOUR" \
-v style_emptyhour="$STYLE_DV_EMPTYHOUR" \
"$AWK_DAYVIEW"
}
# This function prints the view for the week that contains the day specified in `$DISPLAY_DATE`.
__view_week() {
debug "__view_week(): Enter"
weeknr=$(date -d "$DISPLAY_DATE" +"%G:%V:")
files=$(grep "^$weeknr" "$WEEKLY_DATA_FILE" | cut -f 2)
dayofweek=$(date -d "$DISPLAY_DATE" +"%u")
delta=$((1 - dayofweek))
startofweek=$(date -d "$DISPLAY_DATE -$delta days" +"%D")
# loop over files
debug "__view_week(): loop over files"
sef=$({
printf "%s" "$files" | xargs -d " " -I {} -P0 \
awk \
-v collection_labels="$COLLECTION_LABELS" \
"$AWK_PARSE" "$ROOT/{}"
})
debug "__view_week(): loop over files ended"
debug "__view_week(): prepare week view"
if [ -n "$sef" ]; then
sef=$(echo "$sef" | while IFS= read -r line; do
set -- $line
starttime="$1"
shift
endtime="$1"
shift
#fpath="$1"
shift
collection="$1"
shift
status="$1"
shift
if [ "$status" = "TENTATIVE" ]; then
symb="$STYLE_WV_CANCELLED"
elif [ "$status" = "CANCELLED" ]; then
symb="$STYLE_WV_CANCELLED"
else
symb="$STYLE_WV_CONFIRMED"
fi
description="${symb:-}$*$OFF"
for i in $(seq 0 7); do
daystart=$(date -d "$startofweek +$i days 00:00:00" +"%s")
dayend=$(date -d "$startofweek +$i days 23:59:59" +"%s")
if [ "$starttime" -gt "$daystart" ] && [ "$starttime" -lt "$dayend" ]; then
s=$(date -d "@$starttime" +"%H:%M")
elif [ "$starttime" -le "$daystart" ] && [ "$endtime" -gt "$daystart" ]; then
s="00:00"
else
continue
fi
if [ "$endtime" -gt "$daystart" ] && [ "$endtime" -lt "$dayend" ]; then
e=$(date -d "@$endtime" +"%H:%M")
elif [ "$endtime" -ge "$dayend" ] && [ "$starttime" -lt "$dayend" ]; then
e="00:00"
else
continue
fi
printf "%s\t%s\t%s\t%s\n" "$i" "$s" "$e" "$description"
done
done)
fi
debug "__view_week(): prepare week view ended"
debug "__view_week(): generate week view"
sef=$({
echo "$sef"
seq 0 7
} | sort -n)
echo "$sef" | awk \
-v startofweek="$startofweek" \
-v style_day="$STYLE_WV_DAY" \
-v style_event_delim="$STYLE_WV_EVENT_DELIM" \
-v style_summary="$STYLE_WV_SUMMARY" \
-v style_time="$STYLE_WV_TIME" \
"$AWK_WEEKVIEW"
debug "__view_week(): generate week view ended"
}
# This function prints all entries.
__view_all() {
cat "$APPROX_DATA_FILE"
}