Compare commits
44 Commits
6973fb9f04
...
local
Author | SHA1 | Date | |
---|---|---|---|
6dcaf3fbab | |||
e1d26fa2e1 | |||
29f1672020 | |||
87a41be6de | |||
e91035c43a | |||
66ca05f744 | |||
9e59e7a923 | |||
afdf8430d0 | |||
b33acff21e | |||
f463b957bd | |||
e254463b0e | |||
9291137b94 | |||
ad32fcdb0f | |||
f025ecd0bb | |||
5ebcc4feb6 | |||
3a95c1e162 | |||
c9759a0fc5 | |||
9558a1d593 | |||
7cd8d0ffa8 | |||
e4fe586db3 | |||
6357eddbec | |||
7a22f73d36 | |||
a32507a7fb | |||
12bde0fa39 | |||
5d8b913d78 | |||
8824959715 | |||
c7f5082671 | |||
cf19b724ea | |||
6e2df59c93 | |||
f85e766a5a | |||
af5356f07b | |||
54b10ad726 | |||
5586619abf | |||
801108c70c | |||
7ea35e539e | |||
7e9ab45492 | |||
3961234b12 | |||
b5682bf435 | |||
1fd35de475 | |||
19c65d7ce1 | |||
e5134fd0a0 | |||
558f8330ca | |||
08adcd2f98 | |||
aaf3c56565 |
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright © 2025 Ämin Baumeler
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
103
README.md
Normal file
103
README.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
A [fzf](https://github.com/junegunn/fzf)-based **calendar** application with CalDav support.
|
||||||
|
|
||||||
|
Description and Use Case
|
||||||
|
------------------------
|
||||||
|
This application allows for a keyboard-controlled maneuvering of your calendar entries.
|
||||||
|
These entries are stored as [iCalendar](https://datatracker.ietf.org/doc/html/rfc5545) files of the type `VEVENT`.
|
||||||
|
|
||||||
|
For instance, you could use this application in a setup with a CalDav server,
|
||||||
|
such as [Radicale](https://radicale.org/), and a synchronization tool like
|
||||||
|
[vdirsyncer](http://vdirsyncer.pimutils.org/).
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
Download the file `fzf-vcal` from the [latest release](https://github.com/baumea/fzf-vcal/releases/latest), or run `./scripts/build.sh`, then
|
||||||
|
copy `fzf-vcal` 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.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
--------------
|
||||||
|
This application is configured with a file located at `$HOME/.config/fzf-vcal/config`.
|
||||||
|
The entry `ROOT` specifies the root directory of your calendar entries.
|
||||||
|
This directory may contain several subfolders, called _collections_.
|
||||||
|
The entry `COLLECTION_LABELS` is a `;`-delimited list, where each item specifies a subfolder and a label (see example below).
|
||||||
|
In the application, the user sees the collection labels instead of the collection names.
|
||||||
|
This is particularly useful, because some servers use randomly generated names.
|
||||||
|
Finally, a third entry `SYNC_CMD` specifies the command to be executed for synchronizing.
|
||||||
|
|
||||||
|
Consider the following example:
|
||||||
|
```sh
|
||||||
|
ROOT=~/.calendar/
|
||||||
|
COLLECTION_LABELS="745ae7a0-d723-4cd8-80c4-75f52f5b7d90=👫🏼;12cacb18-d3e1-4ad4-a1d0-e5b209012e85=💼;"
|
||||||
|
SYNC_CMD="vdirsyncer sync calendar"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Here, the files are stored in
|
||||||
|
`~/.journal/12cacb18-d3e1-4ad4-a1d0-e5b209012e85` (work-related entries)
|
||||||
|
and
|
||||||
|
`~/.journal/745ae7a0-d723-4cd8-80c4-75f52f5b7d90` (shared collection).
|
||||||
|
|
||||||
|
This configuration will work well with a `vdirsyncer` configuration such as
|
||||||
|
```confini
|
||||||
|
[pair calendar]
|
||||||
|
a = "local"
|
||||||
|
b = "remote"
|
||||||
|
collections = ["from a", "from b"]
|
||||||
|
|
||||||
|
[storage local]
|
||||||
|
type = "filesystem"
|
||||||
|
fileext = ".ics"
|
||||||
|
path = "~/.calendar"
|
||||||
|
|
||||||
|
[storage remote]
|
||||||
|
type = "caldav"
|
||||||
|
item_types = ["VEVENT"]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
`ctrl-n`.
|
||||||
|
|
||||||
|
Here is the list of available keybindings:
|
||||||
|
| Key | View | Action |
|
||||||
|
| --- | ---- | ------ |
|
||||||
|
| `enter` | week view | Switch to day view |
|
||||||
|
| `ctrl-n` | week view | Make a new entry |
|
||||||
|
| any letter | week view | Search in the list of all entries |
|
||||||
|
| `backspace` on empty query | week view | Undo search |
|
||||||
|
| `ctrl-u` | week view | Go back one week |
|
||||||
|
| `ctrl-d` | week view | Go forth one week |
|
||||||
|
| `ctrl-alt-u` | week view | Go back one month |
|
||||||
|
| `ctrl-alt-d` | week view | Go forth one month |
|
||||||
|
| `ctrl-s` | week view | Run the synchronization command |
|
||||||
|
| `ctrl-l` | week view | Go to current week |
|
||||||
|
| `ctrl-g` | week view | Goto date |
|
||||||
|
| `enter` | day view | Open selected calendar entry in your favorite `$EDITOR` |
|
||||||
|
| `ctrl-n` | day view | Make a new entry |
|
||||||
|
| `esc`, `backspace` or `q` | day view | Go back to week view |
|
||||||
|
| `ctrl-s` | day view | Run the synchronization command |
|
||||||
|
| `ctrl-alt-d` | day view | Delete selected entry |
|
||||||
|
| `j` | day view | Scroll down in preview window |
|
||||||
|
| `k` | day view | Scroll up in preview window |
|
||||||
|
| `w` | day view | Toggle line wrap in preview window ||
|
||||||
|
|
||||||
|
You may also invoke the script with `--help` to see further command-line options.
|
||||||
|
|
||||||
|
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
|
||||||
|
look at your calendar relative to being in another timezone.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
This project is licensed under the [MIT License](./LICENSE).
|
18
src/awk/cal.awk
Normal file
18
src/awk/cal.awk
Normal 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
|
||||||
|
}
|
50
src/awk/dayview.awk
Normal file
50
src/awk/dayview.awk
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 11:00|13:00|1748422800|1748430000|fpath|desc...
|
||||||
|
# 00:00|00:00|1748296800|1748383200|fpath|desc...
|
||||||
|
function allday(desc) {
|
||||||
|
return ITALIC FAINT " (allday) " OFF desc
|
||||||
|
}
|
||||||
|
function endstoday(stop, desc) {
|
||||||
|
return CYAN " -- " stop OFF ": " desc
|
||||||
|
}
|
||||||
|
function slice(start, stop, desc) {
|
||||||
|
if (stop == "00:00")
|
||||||
|
return CYAN start " -- " OFF ": " desc
|
||||||
|
else
|
||||||
|
return CYAN start OFF " -- " CYAN stop OFF ": " desc
|
||||||
|
}
|
||||||
|
function hrline(hour) {
|
||||||
|
hour = hour < 10 ? "0"hour : hour
|
||||||
|
print hour, "", "", "", FAINT hour ":00 ----------------------" OFF
|
||||||
|
}
|
||||||
|
function hrlines(start, stop, h, starth, stoph, tmp, i) {
|
||||||
|
starth = substr(start, 1, 2)
|
||||||
|
stoph = substr(stop, 1, 2)
|
||||||
|
tmp = substr(start, 4, 2) == "00" ? 0 : 1
|
||||||
|
for (i=h; i < starth + tmp; i++)
|
||||||
|
hrline(i)
|
||||||
|
tmp = substr(stop, 4, 2) == "00" ? 0 : 1
|
||||||
|
if (stoph + tmp < daystart)
|
||||||
|
return daystart
|
||||||
|
else
|
||||||
|
return stoph + tmp
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
FS = "|"
|
||||||
|
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"
|
||||||
|
OFS = "|"
|
||||||
|
}
|
||||||
|
$1 == "00:00" && $2 == "00:00" { print $1, $3, $4, $5, allday($6); next }
|
||||||
|
$1 == "00:00" { print $1, $3, $4, $5, endstoday($2, $6); next }
|
||||||
|
$1 ~ /^[0-9]{2}:[0-9]{2}$/ {
|
||||||
|
daystart = hrlines($1, $2, daystart, starth, stoph, tmp, i)
|
||||||
|
print $1, $3, $4, $5, slice($1, $2, $6)
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
hrlines(dayend":00", 0, daystart, starth, stoph, tmp, i)
|
||||||
|
}
|
18
src/awk/get.awk
Normal file
18
src/awk/get.awk
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# print content of field `field`
|
||||||
|
BEGIN { FS = ":"; regex = "^" field; }
|
||||||
|
/^BEGIN:VEVENT$/ { inside = 1 }
|
||||||
|
/^END:VEVENT$/ { exit }
|
||||||
|
$0 ~ regex { content = $0; next; }
|
||||||
|
/^ / && content { content = content substr($0, 2); next; }
|
||||||
|
/^[^ ]/ && content { exit }
|
||||||
|
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;
|
||||||
|
}
|
@@ -48,7 +48,7 @@ function title(start, summary) {
|
|||||||
depth = split(FILENAME, path, "/");
|
depth = split(FILENAME, path, "/");
|
||||||
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;
|
||||||
return FAINT "~ " collection " " gensub(/^[^0-9]*([0-9]{4})([0-9]{2}).*$/, "\\1-\\2", "1", start) " " summary " ;" start OFF
|
return FAINT "~ " collection " " gensub(/^[^0-9]*([0-9]{4})([0-9]{2}).*$/, "\\1-\\2", "1", start) " " summary OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
@@ -69,7 +69,7 @@ BEGIN {
|
|||||||
OFF = "\033[m";
|
OFF = "\033[m";
|
||||||
}
|
}
|
||||||
BEGINFILE { inside = 0; rs = 0; dur = 0; summary = ""; start = "ERROR"; end = "ERROR" }
|
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 }
|
/^END:VEVENT/ { print "~", start, dur ? start " " end : end, title(start, summary), fn(FILENAME, n, a); nextfile }
|
||||||
/^DTSTART/ && inside { start = parse( dt) }
|
/^DTSTART/ && inside { start = parse( dt) }
|
||||||
/^DTEND/ && inside { end = parse( dt) }
|
/^DTEND/ && inside { end = parse( dt) }
|
||||||
/^DURATION/ && inside { end = parse_duration( dt, dta, i, n, a, seps); dur = 1 }
|
/^DURATION/ && inside { end = parse_duration( dt, dta, i, n, a, seps); dur = 1 }
|
||||||
|
@@ -11,7 +11,7 @@ NR == FNR {
|
|||||||
if (from[FNR] > to[FNR])
|
if (from[FNR] > to[FNR])
|
||||||
print "FNR", FNR, ":", from[FNR],"-",to[FNR], " ",$0;
|
print "FNR", FNR, ":", from[FNR],"-",to[FNR], " ",$0;
|
||||||
for(i=from[FNR]; i<=to[FNR]; i++) {
|
for(i=from[FNR]; i<=to[FNR]; i++) {
|
||||||
week[i] = week[i] ? week[i] ";" $4 : $4
|
week[i] = week[i] " " $5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
END { for (i in week) print i, week[i]; }
|
END { for (i in week) print i week[i]; }
|
||||||
|
95
src/awk/new.awk
Normal file
95
src/awk/new.awk
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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 {
|
||||||
|
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
|
||||||
|
to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "";
|
||||||
|
getline
|
||||||
|
summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
|
||||||
|
getline # This line should be empty
|
||||||
|
getline # First line of description
|
||||||
|
desc = $0;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
# Sanitize input
|
||||||
|
# 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
|
||||||
|
# date-time entry.
|
||||||
|
from = from ? from : "now"
|
||||||
|
cmd = "date -d \"" from "\" +\"%N\"";
|
||||||
|
cmd | getline t
|
||||||
|
close(cmd)
|
||||||
|
t = t + 0
|
||||||
|
if (t == 0) {
|
||||||
|
from_type = "DATE-TIME"
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
cmd | getline from
|
||||||
|
close(cmd)
|
||||||
|
#
|
||||||
|
to = to ? to : "now"
|
||||||
|
cmd = "date -d \"" to "\" +\"%N\"";
|
||||||
|
cmd | getline t
|
||||||
|
close(cmd)
|
||||||
|
t = t + 0
|
||||||
|
if (t == 0) {
|
||||||
|
to_type = "DATE-TIME"
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
cmd | getline to
|
||||||
|
close(cmd)
|
||||||
|
escape(summary);
|
||||||
|
escape(desc);
|
||||||
|
|
||||||
|
# print ical
|
||||||
|
print "BEGIN:VCALENDAR";
|
||||||
|
print "VERSION:2.0";
|
||||||
|
print "CALSCALE:GREGORIAN";
|
||||||
|
print "PRODID:-//fab//awk//EN";
|
||||||
|
print "BEGIN:VEVENT"
|
||||||
|
print "DTSTAMP:" zulu;
|
||||||
|
print "UID:" uid;
|
||||||
|
print "CLASS:PRIVATE";
|
||||||
|
print "CREATED:" zulu;
|
||||||
|
print "SEQUENCE:1";
|
||||||
|
print "LAST-MODIFIED:" zulu;
|
||||||
|
print "STATUS:FINAL";
|
||||||
|
print "DTSTART;VALUE=" from_type ":" from
|
||||||
|
print "DTEND;VALUE=" to_type ":" to
|
||||||
|
if (summary) print_fold("SUMMARY:", summary, i, s);
|
||||||
|
if (desc) print_fold("DESCRIPTION:", desc, i, s);
|
||||||
|
print "END:VEVENT"
|
||||||
|
print "END:VCALENDAR"
|
||||||
|
}
|
@@ -38,8 +38,10 @@ function print_data(start, dur, end, summary, cmd, collection) {
|
|||||||
gsub("\\\\;", ";", summary);
|
gsub("\\\\;", ";", summary);
|
||||||
gsub("\\\\\\\\", "\\", summary);
|
gsub("\\\\\\\\", "\\", summary);
|
||||||
depth = split(FILENAME, path, "/");
|
depth = split(FILENAME, path, "/");
|
||||||
|
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]]
|
||||||
end = dur ? start " " end : end
|
end = dur ? start " " end : end
|
||||||
cmd = "date -d '" start "' +\"%s\""
|
cmd = "date -d '" start "' +\"%s\""
|
||||||
cmd | getline start
|
cmd | getline start
|
||||||
@@ -47,7 +49,7 @@ function print_data(start, dur, end, summary, cmd, collection) {
|
|||||||
cmd = "date -d '" end "' +\"%s\""
|
cmd = "date -d '" end "' +\"%s\""
|
||||||
cmd | getline end
|
cmd | getline end
|
||||||
close(cmd)
|
close(cmd)
|
||||||
print start, end, collection, summary
|
print start, end, fpath, collection, summary
|
||||||
}
|
}
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
@@ -58,13 +60,6 @@ BEGIN {
|
|||||||
split(mapping[map], m, "=");
|
split(mapping[map], m, "=");
|
||||||
collection2label[m[1]] = m[2];
|
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";
|
|
||||||
}
|
}
|
||||||
/^END:VEVENT/ && inside { print_data(start, dur, end, summary, cmd, collection); exit }
|
/^END:VEVENT/ && inside { print_data(start, dur, end, summary, cmd, collection); exit }
|
||||||
/^DTSTART/ && inside { start = parse( dt) }
|
/^DTSTART/ && inside { start = parse( dt) }
|
||||||
|
101
src/awk/update.awk
Normal file
101
src/awk/update.awk
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
function getcontent(content_line, prop)
|
||||||
|
{
|
||||||
|
return substr(content_line[prop], index(content_line[prop], ":") + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
FS=":";
|
||||||
|
zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ENDFILE {
|
||||||
|
if (NR == FNR)
|
||||||
|
{
|
||||||
|
# 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
|
||||||
|
# date-time entry.
|
||||||
|
from = from ? from : "now"
|
||||||
|
cmd = "date -d \"" from "\" +\"%N\"";
|
||||||
|
cmd | getline t
|
||||||
|
close(cmd)
|
||||||
|
t = t + 0
|
||||||
|
if (t == 0) {
|
||||||
|
from_type = "DATE-TIME"
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
cmd | getline from
|
||||||
|
close(cmd)
|
||||||
|
#
|
||||||
|
to = to ? to : "now"
|
||||||
|
cmd = "date -d \"" to "\" +\"%N\"";
|
||||||
|
cmd | getline t
|
||||||
|
close(cmd)
|
||||||
|
t = t + 0
|
||||||
|
if (t == 0) {
|
||||||
|
to_type = "DATE-TIME"
|
||||||
|
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\"";
|
||||||
|
}
|
||||||
|
cmd | getline to
|
||||||
|
close(cmd)
|
||||||
|
}
|
||||||
|
escape(summary);
|
||||||
|
escape(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
NR == FNR && desc { desc = desc "\\n" $0; next; }
|
||||||
|
NR == FNR {
|
||||||
|
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : "";
|
||||||
|
getline
|
||||||
|
to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "";
|
||||||
|
getline
|
||||||
|
summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
|
||||||
|
getline # This line should be empty
|
||||||
|
getline # First line of description
|
||||||
|
desc = $0;
|
||||||
|
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$/ {
|
||||||
|
seq = seq ? seq + 1 : 1
|
||||||
|
print "SEQUENCE:" seq
|
||||||
|
print "LAST-MODIFIED:" zulu
|
||||||
|
print "DTSTART;VALUE=" from_type ":" from
|
||||||
|
print "DTEND;VALUE=" to_type ":" to
|
||||||
|
print_fold("SUMMARY:", summary, i, s)
|
||||||
|
print_fold("DESCRIPTION:", desc, i, s)
|
||||||
|
inside = ""
|
||||||
|
}
|
||||||
|
{ print }
|
@@ -8,12 +8,13 @@ BEGIN {
|
|||||||
CYAN = "\033[1;36m";
|
CYAN = "\033[1;36m";
|
||||||
FAINT = "\033[2m";
|
FAINT = "\033[2m";
|
||||||
OFF = "\033[m";
|
OFF = "\033[m";
|
||||||
|
OFS = "|"
|
||||||
}
|
}
|
||||||
/^[0-7] 00:00 -- 00:00/ { dayline = dayline " " c(); next }
|
/^[0-7] 00:00 -- 00:00/ { dayline = dayline " " c(); next }
|
||||||
/^[0-7] 00:00 -- / { dayline = dayline " <-|" $4 " " c(); next }
|
/^[0-7] 00:00 -- / { dayline = dayline " <--" $4 " " c(); next }
|
||||||
/^[0-7] [0-9]{2}:[0-9]{2} -- 00:00/ { dayline = dayline " " $2 "|-> " c(); next }
|
/^[0-7] [0-9]{2}:[0-9]{2} -- 00:00/ { dayline = dayline " " $2 "--> " c(); next }
|
||||||
/^[0-7] [0-9]{2}:[0-9]{2} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next }
|
/^[0-7] [0-9]{2}:[0-9]{2} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next }
|
||||||
/^[0-7]$/ && dayline { print dayline " ;" startofweek " +" $0 " days"; }
|
/^[0-7]$/ && dayline { print "+", startofweek " +" $0-1 " days", "", dayline; }
|
||||||
/^[0-7]$/ {
|
/^[0-7]$/ {
|
||||||
cmd = "date -d '" startofweek " +" $0 " days' +\"%a %e %b %Y\"";
|
cmd = "date -d '" startofweek " +" $0 " days' +\"%a %e %b %Y\"";
|
||||||
cmd | getline dayline;
|
cmd | getline dayline;
|
||||||
|
526
src/main.sh
526
src/main.sh
@@ -25,9 +25,14 @@ if [ -z "${FZF_VCAL_USE_EXPORTED:-}" ]; then
|
|||||||
export SYNC_CMD
|
export SYNC_CMD
|
||||||
export COLLECTION_LABELS
|
export COLLECTION_LABELS
|
||||||
|
|
||||||
|
DAY_START=${DAY_START:-8}
|
||||||
|
DAY_END=${DAY_END:-18}
|
||||||
|
export DAY_START
|
||||||
|
export DAY_END
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
if command -v "fzf" >/dev/null; then
|
if command -v "fzf" >/dev/null; then
|
||||||
FZF="fzf"
|
FZF="fzf --black"
|
||||||
else
|
else
|
||||||
err "Did not find the command-line fuzzy finder fzf."
|
err "Did not find the command-line fuzzy finder fzf."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -73,22 +78,59 @@ EOF
|
|||||||
)
|
)
|
||||||
export AWK_PARSE
|
export AWK_PARSE
|
||||||
|
|
||||||
AWK_WEEKS=$(
|
|
||||||
cat <<'EOF'
|
|
||||||
@@include src/awk/weeks.awk
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
export AWK_WEEKS
|
|
||||||
|
|
||||||
AWK_WEEKVIEW=$(
|
AWK_WEEKVIEW=$(
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@include src/awk/weekview.awk
|
@@include src/awk/weekview.awk
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
export AWK_WEEKVIEW
|
export AWK_WEEKVIEW
|
||||||
|
|
||||||
|
AWK_DAYVIEW=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include src/awk/dayview.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
export AWK_DAYVIEW
|
||||||
|
|
||||||
|
AWK_GET=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include src/awk/get.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
export AWK_GET
|
||||||
|
|
||||||
|
AWK_UPDATE=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include src/awk/update.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
export AWK_UPDATE
|
||||||
|
|
||||||
|
AWK_NEW=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include src/awk/new.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
export AWK_NEW
|
||||||
|
|
||||||
|
AWK_CAL=$(
|
||||||
|
cat <<'EOF'
|
||||||
|
@@include src/awk/cal.awk
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
export AWK_CAL
|
||||||
### END OF AWK SCRIPTS
|
### END OF AWK SCRIPTS
|
||||||
FZF_VJOUR_USE_EXPORTED="yes"
|
|
||||||
export FZF_VJOUR_USE_EXPORTED
|
## Colors
|
||||||
|
export GREEN="\033[1;32m"
|
||||||
|
export RED="\033[1;31m"
|
||||||
|
export WHITE="\033[1;97m"
|
||||||
|
export CYAN="\033[1;36m"
|
||||||
|
export ITALIC="\033[3m"
|
||||||
|
export FAINT="\033[2m"
|
||||||
|
export OFF="\033[m"
|
||||||
|
|
||||||
|
export FZF_VJOUR_USE_EXPORTED="yes"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
__load_approx_data() {
|
__load_approx_data() {
|
||||||
@@ -100,23 +142,71 @@ __load_approx_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__load_weeks() {
|
__load_weeks() {
|
||||||
dates=$(awk -F'|' '{ print $1; print $2 }' "$APPROX_DATA_FILE")
|
dates=$(awk -F'|' '{ print $2; print $3 }' "$APPROX_DATA_FILE")
|
||||||
file_dates=$(mktemp)
|
file_dates=$(mktemp)
|
||||||
echo "$dates" | date --file="/dev/stdin" +"%s" >"$file_dates"
|
echo "$dates" | date --file="/dev/stdin" +"%s" >"$file_dates"
|
||||||
awk "$AWK_MERGE" "$file_dates" "$APPROX_DATA_FILE"
|
awk "$AWK_MERGE" "$file_dates" "$APPROX_DATA_FILE"
|
||||||
rm "$file_dates"
|
rm "$file_dates"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__show_day() {
|
||||||
|
weeknr=$(date -d "$DISPLAY_DATE" +"%s")
|
||||||
|
weeknr=$(((weeknr - 259200) / 604800)) # shift, because epoch origin is a Thursday
|
||||||
|
files=$(grep "^$weeknr " "$WEEKLY_DATA_FILE" | cut -d " " -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
|
||||||
|
})
|
||||||
|
if [ -n "$sef" ]; then
|
||||||
|
today=$(date -d "$DISPLAY_DATE" +"%D")
|
||||||
|
sef=$(echo "$sef" | while IFS= read -r line; do
|
||||||
|
set -- $line
|
||||||
|
starttime="$1"
|
||||||
|
shift
|
||||||
|
endtime="$1"
|
||||||
|
shift
|
||||||
|
fpath="$(echo "$1" | sed 's/|/ /g')" # we will use | as delimiter (need to convert back!)
|
||||||
|
shift
|
||||||
|
description="$(echo "$*" | sed 's/|/:/g')" # we will use | as delimiter
|
||||||
|
#
|
||||||
|
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
|
||||||
|
echo "$s|$e|$starttime|$endtime|$fpath|$description"
|
||||||
|
done)
|
||||||
|
fi
|
||||||
|
echo "$sef" | sort -n | awk -v daystart="$DAY_START" -v dayend="$DAY_END" "$AWK_DAYVIEW"
|
||||||
|
}
|
||||||
|
|
||||||
__list() {
|
__list() {
|
||||||
weeknr=$(date -d "$DISPLAY_DATE" +"%s")
|
weeknr=$(date -d "$DISPLAY_DATE" +"%s")
|
||||||
weeknr=$(((weeknr - 259200) / 604800)) # shift, because epoch origin is a Thursday
|
weeknr=$(((weeknr - 259200) / 604800)) # shift, because epoch origin is a Thursday
|
||||||
files=$(grep "^$weeknr " "$WEEKLY_DATA_FILE" | cut -d " " -f 2)
|
files=$(grep "^$weeknr " "$WEEKLY_DATA_FILE" | cut -d " " -f 2-)
|
||||||
dayofweek=$(date -d "$DISPLAY_DATE" +"%u")
|
dayofweek=$(date -d "$DISPLAY_DATE" +"%u")
|
||||||
delta=$((1 - dayofweek))
|
delta=$((1 - dayofweek))
|
||||||
startofweek=$(date -d "$DISPLAY_DATE -$delta days" +"%D")
|
startofweek=$(date -d "$DISPLAY_DATE -$delta days" +"%D")
|
||||||
# loop over files
|
# loop over files
|
||||||
sef=$({
|
sef=$({
|
||||||
IFS=';'
|
|
||||||
set -- $files
|
set -- $files
|
||||||
for file in "$@"; do
|
for file in "$@"; do
|
||||||
file="$ROOT/$file"
|
file="$ROOT/$file"
|
||||||
@@ -132,6 +222,8 @@ __list() {
|
|||||||
shift
|
shift
|
||||||
endtime="$1"
|
endtime="$1"
|
||||||
shift
|
shift
|
||||||
|
#fpath="$1"
|
||||||
|
shift
|
||||||
description="$*"
|
description="$*"
|
||||||
for i in $(seq 0 7); do
|
for i in $(seq 0 7); do
|
||||||
daystart=$(date -d "$startofweek +$i days 00:00:00" +"%s")
|
daystart=$(date -d "$startofweek +$i days 00:00:00" +"%s")
|
||||||
@@ -165,59 +257,399 @@ __list() {
|
|||||||
# LC_ALL=c xargs -I {} date -d "{}" +"%a %e %b %Y"
|
# LC_ALL=c xargs -I {} date -d "{}" +"%a %e %b %Y"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -z "${APPROX_DATA_FILE:-}" ]; then
|
__canonical_datetime_hm() {
|
||||||
echo "GOING TO LOAD"
|
s="$1"
|
||||||
|
t=$(date -d "@$s" +"%R")
|
||||||
|
dfmt="%F"
|
||||||
|
if [ "$t" != "00:00" ]; then
|
||||||
|
dfmt="$dfmt %R"
|
||||||
|
fi
|
||||||
|
date -d "@$s" +"$dfmt"
|
||||||
|
}
|
||||||
|
|
||||||
|
__canonical_datetime() {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
__edit() {
|
||||||
|
start=$(__canonical_datetime_hm "$1")
|
||||||
|
end=$(__canonical_datetime_hm "$2")
|
||||||
|
fpath="$3"
|
||||||
|
summary=$(awk -v field="SUMMARY" "$AWK_GET" "$fpath")
|
||||||
|
description=$(awk -v field="DESCRIPTION" "$AWK_GET" "$fpath")
|
||||||
|
filetmp=$(mktemp --suffix='.md')
|
||||||
|
(
|
||||||
|
echo "::: |> $start"
|
||||||
|
echo "::: <| $end"
|
||||||
|
echo "# $summary"
|
||||||
|
echo ""
|
||||||
|
echo "$description"
|
||||||
|
) >"$filetmp"
|
||||||
|
checksum=$(cksum "$filetmp")
|
||||||
|
$EDITOR "$filetmp" >/dev/tty
|
||||||
|
|
||||||
|
# Update only if changes are detected
|
||||||
|
if [ "$checksum" != "$(cksum "$filetmp")" ]; then
|
||||||
|
filenew="$filetmp.ics"
|
||||||
|
awk "$AWK_UPDATE" "$filetmp" "$fpath" >"$filenew"
|
||||||
|
mv "$filenew" "$fpath"
|
||||||
|
__refresh_data
|
||||||
|
fi
|
||||||
|
rm "$filetmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
__refresh_data() {
|
||||||
|
if [ -n "${APPROX_DATA_FILE:-}" ]; then
|
||||||
|
rm "$APPROX_DATA_FILE"
|
||||||
|
fi
|
||||||
|
if [ -n "${WEEKLY_DATA_FILE:-}" ]; then
|
||||||
|
rm "$WEEKLY_DATA_FILE"
|
||||||
|
fi
|
||||||
APPROX_DATA_FILE=$(mktemp)
|
APPROX_DATA_FILE=$(mktemp)
|
||||||
__load_approx_data >"$APPROX_DATA_FILE"
|
__load_approx_data >"$APPROX_DATA_FILE"
|
||||||
export APPROX_DATA_FILE
|
export APPROX_DATA_FILE
|
||||||
fi
|
|
||||||
if [ -z "${WEEKLY_DATA_FILE:-}" ]; then
|
|
||||||
echo "GOING TO LOAD WD"
|
|
||||||
WEEKLY_DATA_FILE=$(mktemp)
|
WEEKLY_DATA_FILE=$(mktemp)
|
||||||
__load_weeks >"$WEEKLY_DATA_FILE"
|
__load_weeks >"$WEEKLY_DATA_FILE"
|
||||||
export WEEKLY_DATA_FILE
|
export WEEKLY_DATA_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
## Start
|
||||||
|
if [ "${1:-}" = "--help" ]; then
|
||||||
|
echo "Usage: $0 [OPTION]"
|
||||||
|
echo ""
|
||||||
|
echo "You may specify at most one option."
|
||||||
|
echo " --help Show this help and exit"
|
||||||
|
echo " --new Create new entry"
|
||||||
|
echo " --today Show today's appointments"
|
||||||
|
echo " --goto Interactively enter date to jump to"
|
||||||
|
echo " --day <day> Show appointments of specified day"
|
||||||
|
echo " --date <date> Show week of specified date"
|
||||||
|
echo ""
|
||||||
|
echo "You may also start this program with setting locale and timezone"
|
||||||
|
echo "information. For instance, to see and modify all of your calendar"
|
||||||
|
echo "entries from the perspective of Saigon, run"
|
||||||
|
echo "TZ='Asia/Saigon' $0"
|
||||||
|
echo "Likewise, you may specify the usage of Greek with"
|
||||||
|
echo "LC_TIME=el_GR.UTF-8 $0"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--today" ]; then
|
||||||
|
exec $0 --day "today"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--goto" ]; then
|
||||||
|
DISPLAY_DATE=""
|
||||||
|
while [ -z "$DISPLAY_DATE" ]; do
|
||||||
|
printf "Enter date you want to jump to, e.g., today + 1 month or 2024-1-14: " >/dev/tty
|
||||||
|
read -r tmp
|
||||||
|
if date -d "$tmp"; then
|
||||||
|
DISPLAY_DATE="$tmp"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--new" ]; then
|
||||||
|
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
|
||||||
|
startsec=$(date -d "${2:-today 8:00}" +"%s")
|
||||||
|
endsec=$((startsec + 3600))
|
||||||
|
start=$(__canonical_datetime_hm "$startsec")
|
||||||
|
end=$(__canonical_datetime_hm "$endsec")
|
||||||
|
filetmp=$(mktemp --suffix='.md')
|
||||||
|
(
|
||||||
|
echo "::: |> $start"
|
||||||
|
echo "::: <| $end"
|
||||||
|
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"
|
||||||
|
awk -v uid="$uuid" "$AWK_NEW" "$filetmp" >"$filenew"
|
||||||
|
mv "$filenew" "$fpath"
|
||||||
|
__refresh_data
|
||||||
|
fi
|
||||||
|
rm "$filetmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${APPROX_DATA_FILE:-}" ]; then
|
||||||
|
__refresh_data
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--day" ]; then
|
||||||
|
DISPLAY_DATE="${2:-today}"
|
||||||
|
export DISPLAY_DATE
|
||||||
|
selection=$(
|
||||||
|
__show_day |
|
||||||
|
$FZF \
|
||||||
|
--reverse \
|
||||||
|
--ansi \
|
||||||
|
--no-sort \
|
||||||
|
--no-input \
|
||||||
|
--margin='20%,5%' \
|
||||||
|
--border='double' \
|
||||||
|
--border-label="🗓️ $(date -d "$DISPLAY_DATE" +"%A %e %B %Y")" \
|
||||||
|
--color=label:bold:green \
|
||||||
|
--border-label-pos=3 \
|
||||||
|
--cycle \
|
||||||
|
--delimiter='|' \
|
||||||
|
--with-nth='{5}' \
|
||||||
|
--accept-nth='1,2,3,4' \
|
||||||
|
--preview="$0 --preview {}" \
|
||||||
|
--expect="ctrl-n,esc,backspace,q" \
|
||||||
|
--bind='start:hide-preview' \
|
||||||
|
--bind='ctrl-j:down+hide-preview+transform:echo {} | grep \|\| || echo show-preview' \
|
||||||
|
--bind='ctrl-k:up+hide-preview+transform:echo {} | grep \|\| || echo show-preview' \
|
||||||
|
--bind="ctrl-s:execute($SYNC_CMD ; printf 'Press <enter> to continue.'; read -r tmp)" \
|
||||||
|
--bind="ctrl-alt-d:become($0 --delete {})" \
|
||||||
|
--bind="j:preview-down" \
|
||||||
|
--bind="k:preview-down" \
|
||||||
|
--bind="w:toggle-preview-wrap"
|
||||||
|
)
|
||||||
|
key=$(echo "$selection" | head -1)
|
||||||
|
line=$(echo "$selection" | tail -1)
|
||||||
|
if [ "$line" = "$key" ]; then
|
||||||
|
line=""
|
||||||
|
fi
|
||||||
|
hour=$(echo "$line" | cut -d '|' -f 1)
|
||||||
|
start=$(echo "$line" | cut -d '|' -f 2)
|
||||||
|
end=$(echo "$line" | cut -d '|' -f 3)
|
||||||
|
fpath=$(echo "$line" | cut -d '|' -f 4 | sed "s/ /|/g")
|
||||||
|
if [ "$key" = "ctrl-n" ]; then
|
||||||
|
if echo "$hour" | grep ":"; then
|
||||||
|
hour="$DAY_START"
|
||||||
|
fi
|
||||||
|
exec $0 --new "$DISPLAY_DATE $hour:00"
|
||||||
|
elif [ -z "$key" ] && [ -n "$fpath" ]; then
|
||||||
|
fpath="$ROOT/$fpath"
|
||||||
|
__edit "$start" "$end" "$fpath"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DISPLAY_DATE="today"
|
|
||||||
if [ "${1:-}" = "--date" ]; then
|
if [ "${1:-}" = "--date" ]; then
|
||||||
DISPLAY_DATE="$2"
|
DISPLAY_DATE="$2"
|
||||||
echo "Jumping to date $2!"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--preview" ]; then
|
||||||
|
hour=$(echo "$2" | cut -d '|' -f 1)
|
||||||
|
start=$(echo "$2" | cut -d '|' -f 2)
|
||||||
|
end=$(echo "$2" | cut -d '|' -f 3)
|
||||||
|
fpath=$(echo "$2" | cut -d '|' -f 4 | sed "s/ /|/g")
|
||||||
|
if [ -n "$hour" ] && [ -n "$fpath" ]; then
|
||||||
|
fpath="$ROOT/$fpath"
|
||||||
|
start=$(__canonical_datetime "$start" "%a ")
|
||||||
|
end=$(__canonical_datetime "$end" "%a ")
|
||||||
|
echo "${GREEN}From: ${OFF}${CYAN}$start${OFF}"
|
||||||
|
echo "${GREEN}To: ${OFF}${CYAN}$end${OFF}"
|
||||||
|
echo ""
|
||||||
|
awk -v field="DESCRIPTION" "$AWK_GET" "$fpath" | $CAT
|
||||||
|
fi
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
month_previous() {
|
||||||
|
month="$1"
|
||||||
|
year="$2"
|
||||||
|
if [ "$month" -eq 1 ]; then
|
||||||
|
month=12
|
||||||
|
year=$((year - 1))
|
||||||
|
else
|
||||||
|
month=$((month - 1))
|
||||||
|
fi
|
||||||
|
echo "$month $year"
|
||||||
|
}
|
||||||
|
|
||||||
|
month_next() {
|
||||||
|
month="$1"
|
||||||
|
year="$2"
|
||||||
|
if [ "$month" -eq 12 ]; then
|
||||||
|
month=1
|
||||||
|
year=$((year + 1))
|
||||||
|
else
|
||||||
|
month=$((month + 1))
|
||||||
|
fi
|
||||||
|
echo "$month $year"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--preview-week" ]; then
|
||||||
|
sign=$(echo "$2" | cut -d '|' -f 1)
|
||||||
|
if [ "$sign" = "+" ]; then
|
||||||
|
startdate=$(echo "$2" | cut -d '|' -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 -v cur="${var_pre2:-}" "$AWK_CAL"
|
||||||
|
cal "$month_pre" "$year_pre" | awk -v cur="${var_pre:-}" "$AWK_CAL"
|
||||||
|
cal "$month" "$year" | awk -v cur="${var:-}" -v day="$day" "$AWK_CAL"
|
||||||
|
cal "$month_nex" "$year_nex" | awk -v cur="${var_nex:-}" "$AWK_CAL"
|
||||||
|
cal "$month_nex2" "$year_nex2" | awk -v cur="${var_nex2:-}" "$AWK_CAL"
|
||||||
|
cal "$month_nex3" "$year_nex3" | awk -v cur="${var_nex3:-}" "$AWK_CAL"
|
||||||
|
) | awk '{ l[NR%8] = l[NR%8] " " $0 } END {for (i in l) if (i>0) print l[i] }'
|
||||||
|
fi
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--delete" ]; then
|
||||||
|
fpath=$(echo "$2" | cut -d '|' -f 4 | sed "s/ /|/g")
|
||||||
|
if [ -n "$fpath" ]; then
|
||||||
|
fpath="$ROOT/$fpath"
|
||||||
|
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")
|
||||||
|
rm -v "$fpath"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
"no")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Please answer \"yes\" or \"no\"." >/dev/tty
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
__refresh_data
|
||||||
|
exec $0 --day "$DISPLAY_DATE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--all" ]; then
|
||||||
|
cat "$APPROX_DATA_FILE"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISPLAY_DATE=${DISPLAY_DATE:-today}
|
||||||
DISPLAY_DATE=$(date -d "$DISPLAY_DATE" +"%D")
|
DISPLAY_DATE=$(date -d "$DISPLAY_DATE" +"%D")
|
||||||
DISPLAY_DATE_PREV=$(date -d "$DISPLAY_DATE -1 week" +"%D")
|
DISPLAY_POS=$((8 - $(date -d "$DISPLAY_DATE" +"%u")))
|
||||||
DISPLAY_DATE_NEXT=$(date -d "$DISPLAY_DATE +1 week" +"%D")
|
|
||||||
|
|
||||||
selection=$( (
|
if [ "${1:-}" = "--list" ]; then
|
||||||
cut -d '|' -f 3 "$APPROX_DATA_FILE"
|
shift
|
||||||
yes " " | head -n 50
|
DISPLAY_DATE=${*:-today}
|
||||||
|
DISPLAY_POS=$((8 - $(date -d "$DISPLAY_DATE" +"%u")))
|
||||||
__list
|
__list
|
||||||
) |
|
exit
|
||||||
$FZF \
|
fi
|
||||||
--tac \
|
|
||||||
--no-sort \
|
|
||||||
--no-hscroll \
|
|
||||||
--ellipsis='' \
|
|
||||||
--ansi \
|
|
||||||
--no-clear \
|
|
||||||
--bind="ctrl-p:become($0 --date '$DISPLAY_DATE_PREV')" \
|
|
||||||
--bind="ctrl-n:become($0 --date '$DISPLAY_DATE_NEXT')" \
|
|
||||||
--bind="ctrl-l:become($0)")
|
|
||||||
|
|
||||||
if [ -z "$selection" ]; then
|
selection=$(
|
||||||
|
__list |
|
||||||
|
$FZF \
|
||||||
|
--tac \
|
||||||
|
--no-sort \
|
||||||
|
--no-hscroll \
|
||||||
|
--ellipsis="" \
|
||||||
|
--delimiter="|" \
|
||||||
|
--with-nth="{4}" \
|
||||||
|
--accept-nth=1,2 \
|
||||||
|
--ansi \
|
||||||
|
--gap 1 \
|
||||||
|
--no-scrollbar \
|
||||||
|
--info=right \
|
||||||
|
--info-command="printf \"$(date +"%R %Z")\"" \
|
||||||
|
--preview-window=up,7,border-bottom \
|
||||||
|
--preview="$0 --preview-week {}" \
|
||||||
|
--expect="ctrl-n" \
|
||||||
|
--bind="ctrl-j:transform:[ \$FZF_POS -le 1 ] &&
|
||||||
|
echo unbind\(load\)+reload:$0 --list {2} '+1 day'||
|
||||||
|
echo down" \
|
||||||
|
--bind="ctrl-k:transform:[ \$FZF_POS -ge 7 ] &&
|
||||||
|
echo unbind\(load\)+reload:$0 --list {2} '-1 day'||
|
||||||
|
echo up" \
|
||||||
|
--bind="change:reload($0 --all)+hide-preview" \
|
||||||
|
--bind="backward-eof:rebind(load)+reload($0 --list)+show-preview" \
|
||||||
|
--bind="load:pos($DISPLAY_POS)" \
|
||||||
|
--bind="ctrl-u:unbind(load)+reload:$0 --list {2} '-1 week'" \
|
||||||
|
--bind="ctrl-d:unbind(load)+reload:$0 --list {2} '+1 week'" \
|
||||||
|
--bind="ctrl-alt-u:unbind(load)+reload:$0 --list {2} '-1 month'" \
|
||||||
|
--bind="ctrl-alt-d:unbind(load)+reload:$0 --list {2} '+1 month'" \
|
||||||
|
--bind="ctrl-s:execute($SYNC_CMD ; printf 'Press <enter> to continue.'; read -r tmp)" \
|
||||||
|
--bind="ctrl-g:become($0 --goto)" \
|
||||||
|
--bind="ctrl-l:rebind(load)+reload:$0 --list"
|
||||||
|
)
|
||||||
|
|
||||||
|
key=$(echo "$selection" | head -1)
|
||||||
|
line=$(echo "$selection" | tail -1)
|
||||||
|
if [ "$line" = "$key" ]; then
|
||||||
|
line=""
|
||||||
|
fi
|
||||||
|
sign=$(echo "$line" | cut -d '|' -f 1)
|
||||||
|
startdate=$(echo "$line" | cut -d '|' -f 2)
|
||||||
|
if [ "$key" = "ctrl-n" ]; then
|
||||||
|
# Add new
|
||||||
|
if [ "$sign" = "~" ]; then
|
||||||
|
startdate=""
|
||||||
|
fi
|
||||||
|
exec $0 --new "${startdate:-today} $DAY_START:00"
|
||||||
|
fi
|
||||||
|
if [ -z "$key" ] && [ -z "$line" ]; then
|
||||||
rm "$WEEKLY_DATA_FILE" "$APPROX_DATA_FILE"
|
rm "$WEEKLY_DATA_FILE" "$APPROX_DATA_FILE"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$selection" in
|
if [ "$sign" = "~" ]; then
|
||||||
"~"*)
|
exec $0 --date "$startdate"
|
||||||
start=$(echo "$selection" | rev | cut -d';' -f 1 | rev)
|
else
|
||||||
exec $0 --date "$start"
|
exec $0 --day "$startdate"
|
||||||
;;
|
fi
|
||||||
*)
|
|
||||||
day=$(echo "$selection" | rev | cut -d';' -f 1 | rev)
|
|
||||||
exec $0 --day "$day"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
echo "Going to end..."
|
echo "Going to end..."
|
||||||
echo "$selection"
|
echo "$selection"
|
||||||
echo "STOPPING NOW"
|
echo "STOPPING NOW"
|
||||||
|
Reference in New Issue
Block a user