Compare commits
No commits in common. "main" and "local" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
fzf-vcal
|
||||
fzf-vcal.debug
|
||||
|
99
README.md
99
README.md
@ -1,7 +1,4 @@
|
||||
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
|
||||
------------------------
|
||||
@ -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
|
||||
-----
|
||||
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.
|
||||
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
|
||||
previews. In both, the week and day views, you can add entries by hitting
|
||||
`ctrl-n`.
|
||||
|
||||
Here is the list of all available keybindings:
|
||||
|
||||
### Week view
|
||||
|
||||
| Key | Action |
|
||||
| --- | ------ |
|
||||
| `q` | quit |
|
||||
| `enter` | open day |
|
||||
| `j` | down |
|
||||
| `k` | up |
|
||||
| `l` | go to next week |
|
||||
| `h` | go to previous week |
|
||||
| `ctrl-l` | go to next month |
|
||||
| `ctrl-h` | go to previous month |
|
||||
| `alt-l` | go to next year |
|
||||
| `alt-h` | go to previous year |
|
||||
| `ctrl-r` | reload and go to week that contains `today` |
|
||||
| `ctrl-g` | interactively go to specified week |
|
||||
| `ctrl-t` | set timezon |
|
||||
| `ctrl-s` | synchronize |
|
||||
| `ctrl-n` | add new entry |
|
||||
| `\` | search all appointment s |
|
||||
| `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
|
||||
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.
|
||||
|
||||
@ -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
|
||||
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
|
||||
-------
|
||||
This project is licensed under the [MIT License](./LICENSE).
|
||||
|
@ -5,15 +5,7 @@ GREEN="\033[0;32m"
|
||||
OFF="\033[m"
|
||||
NAME="fzf-vcal"
|
||||
SRC="./src/main.sh"
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
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}"
|
||||
echo "🐔 ${GREEN}Building${OFF} ${BOLD}$NAME${OFF}"
|
||||
sed -E 's|@@include (.+)$|cat \1|e' "$SRC" >"$NAME"
|
||||
chmod +x "$NAME"
|
||||
rm -rf "$tmpdir"
|
||||
echo "🍳 ${GREEN}Done:${OFF} Sucessfully built ${BOLD}${GREEN}$NAME${OFF}"
|
||||
echo "🥚 ${GREEN}Done${OFF}"
|
||||
|
@ -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 }
|
@ -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 }
|
@ -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 }
|
@ -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 }
|
@ -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
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
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
@ -1,107 +1,26 @@
|
||||
## src/awk/dayview.awk
|
||||
## Take as input (tab-delimited):
|
||||
## 1. s (start time, as HH:MM)
|
||||
## 2. e (end time, as HH:MM)
|
||||
## 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
|
||||
# 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
|
||||
}
|
||||
|
||||
# Return line for all-day event.
|
||||
#
|
||||
# @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 endstoday(stop, desc) {
|
||||
return CYAN " -- " stop OFF ": " 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)
|
||||
function slice(start, stop, desc) {
|
||||
if (stop == "00:00")
|
||||
return collection " " style_timerange start " → " ": " OFF color desc OFF
|
||||
return CYAN start " -- " OFF ": " desc
|
||||
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) {
|
||||
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) {
|
||||
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 < dayend; i++)
|
||||
for (i=h; i < starth + tmp; i++)
|
||||
hrline(i)
|
||||
tmp = substr(stop, 4, 2) == "00" ? 0 : 1
|
||||
if (stoph + tmp < daystart)
|
||||
@ -109,17 +28,23 @@ function hrlines(start, stop, h, starth, stoph, tmp, i) {
|
||||
else
|
||||
return stoph + tmp
|
||||
}
|
||||
|
||||
# AWK program
|
||||
BEGIN {
|
||||
FS = "\t"
|
||||
OFS = "\t"
|
||||
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 today, $1, $3, $4, $5, allday($6, $7, $8); next }
|
||||
$1 == "00:00" { print today, $1, $3, $4, $5, endstoday($2, $6, $7, $8); next }
|
||||
$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)
|
||||
print today, $1, $3, $4, $5, slice($1, $2, $6, $7, $8)
|
||||
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)
|
||||
}
|
||||
END { hrlines(dayend":00", 0, daystart) }
|
||||
|
@ -1,18 +1,18 @@
|
||||
## src/awk/get.awk
|
||||
## Print content of a field of an iCalendar file.
|
||||
##
|
||||
## @assign field: Field name
|
||||
|
||||
@include "lib/awk/icalendar.awk"
|
||||
|
||||
BEGIN { FS = ":"; regex = "^" field }
|
||||
# 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 }
|
||||
$0 ~ regex { content = $0; next; }
|
||||
/^ / && content { content = content substr($0, 2); next; }
|
||||
/^[^ ]/ && content { exit }
|
||||
END {
|
||||
if (!inside) { exit }
|
||||
# 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;
|
||||
}
|
||||
|
@ -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
79
src/awk/lines.awk
Normal 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 }
|
@ -1,35 +1,17 @@
|
||||
## src/awk/merge.awk
|
||||
## 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" }
|
||||
BEGIN { FS="|"; i=0; dlt = -259200; spw = 604800; }
|
||||
NR == FNR {
|
||||
i = i + 1
|
||||
split($0, parts, ":")
|
||||
from_year[i] = parts[1]
|
||||
from_week[i] = parts[2]
|
||||
getline
|
||||
split($0, parts, ":")
|
||||
to_year[i] = parts[1]
|
||||
to_week[i] = parts[2]
|
||||
i = i + 1;
|
||||
from[i] = int(($1 + dlt)/ spw);
|
||||
getline;
|
||||
to[i] = int(($1 + dlt) / spw);
|
||||
next
|
||||
} # Load start and end week numbers from first file
|
||||
|
||||
{
|
||||
year_i = from_year[FNR]
|
||||
week_i = from_week[FNR]
|
||||
year_end = to_year[FNR]
|
||||
week_end = to_week[FNR]
|
||||
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++
|
||||
if (from[FNR] > to[FNR])
|
||||
print "FNR", FNR, ":", from[FNR],"-",to[FNR], " ",$0;
|
||||
for(i=from[FNR]; i<=to[FNR]; i++) {
|
||||
week[i] = week[i] " " $5
|
||||
}
|
||||
}
|
||||
}
|
||||
END { for (label in week) print label, week[label] }
|
||||
END { for (i in week) print i week[i]; }
|
||||
|
136
src/awk/new.awk
136
src/awk/new.awk
@ -1,105 +1,95 @@
|
||||
## src/awk/new.awk
|
||||
## 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 }
|
||||
function escape(str)
|
||||
{
|
||||
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : ""
|
||||
if (!from)
|
||||
exit 1
|
||||
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) : ""
|
||||
if (!to)
|
||||
exit 1
|
||||
to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "";
|
||||
getline
|
||||
location = substr($0, 1, 2) == "@ " ? escape(substr($0, 3)) : ""
|
||||
if (location) getline
|
||||
summary = substr($0, 1, 2) == "# " ? escape(substr($0, 3)) : ""
|
||||
if (!summary)
|
||||
exit 1
|
||||
summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
|
||||
getline # This line should be empty
|
||||
if ($0 != "")
|
||||
exit 1
|
||||
readdesc = 1
|
||||
next
|
||||
getline # First line of description
|
||||
desc = $0;
|
||||
next;
|
||||
}
|
||||
END {
|
||||
# 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
|
||||
# date-time entry.
|
||||
# Similarly, if the time is 00:00, we make this a date, as opposed to a
|
||||
# date-time entry.
|
||||
gsub("\"", "\\\"", from)
|
||||
cmd = "date -d \"" from "\" +\"%N\""
|
||||
cmd | getline n
|
||||
close(cmd)
|
||||
n = n + 0
|
||||
cmd = "date -d \"" from "\" +\"%H%M\""
|
||||
from = from ? from : "now"
|
||||
cmd = "date -d \"" from "\" +\"%N\"";
|
||||
cmd | getline t
|
||||
close(cmd)
|
||||
t = t + 0
|
||||
if (n != 0 || t == 0) {
|
||||
from_type = "DATE"
|
||||
cmd = "date -d \"" from "\" +\"%Y%m%d\""
|
||||
} else {
|
||||
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\"";
|
||||
}
|
||||
suc = cmd | getline from
|
||||
cmd | getline from
|
||||
close(cmd)
|
||||
if (suc != 1) {
|
||||
exit 1
|
||||
}
|
||||
#
|
||||
gsub("\"", "\\\"", to)
|
||||
cmd = "date -d \"" to "\" +\"%N\""
|
||||
cmd | getline n
|
||||
close(cmd)
|
||||
n = n + 0
|
||||
cmd = "date -d \"" to "\" +\"%H%M\""
|
||||
to = to ? to : "now"
|
||||
cmd = "date -d \"" to "\" +\"%N\"";
|
||||
cmd | getline t
|
||||
close(cmd)
|
||||
t = t + 0
|
||||
if (n != 0 || t == 0) {
|
||||
to_type = "DATE"
|
||||
cmd = "date -d \"" to "\" +\"%Y%m%d\""
|
||||
} else {
|
||||
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\"";
|
||||
}
|
||||
suc = cmd | getline to
|
||||
cmd | getline to
|
||||
close(cmd)
|
||||
if (suc != 1) {
|
||||
exit 1
|
||||
}
|
||||
escape(summary);
|
||||
escape(desc);
|
||||
|
||||
# print ical
|
||||
print "BEGIN:VCALENDAR"
|
||||
print "VERSION:2.0"
|
||||
print "CALSCALE:GREGORIAN"
|
||||
print "PRODID:-//fab//awk//EN"
|
||||
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:CONFIRMED"
|
||||
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)
|
||||
if (desc) print_fold("DESCRIPTION:", desc)
|
||||
if (location) print_fold("LOCATION:", location)
|
||||
if (summary) print_fold("SUMMARY:", summary, i, s);
|
||||
if (desc) print_fold("DESCRIPTION:", desc, i, s);
|
||||
print "END:VEVENT"
|
||||
print "END:VCALENDAR"
|
||||
}
|
||||
|
@ -1,30 +1,46 @@
|
||||
## src/awk/parse.awk
|
||||
## Parse iCalendar file and print its key aspects:
|
||||
## ```
|
||||
## <start> <end> <fpath> <collection> <status> <summary>
|
||||
## ```.
|
||||
## The output is space delimited.
|
||||
## Summary may contain spaces, but it's the last in the list.
|
||||
##
|
||||
## @assign collection_labels: See configuration of the current program.
|
||||
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);
|
||||
}
|
||||
|
||||
@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.
|
||||
#
|
||||
# @local variables: cmd, collection, depth, path
|
||||
# @input start: Start time of event
|
||||
# @input dur: Boolean that indicates that `end` specifies a duration
|
||||
# @input end: End time of event, or event duration (see `dur`)
|
||||
# @input summary: Content of SUMMARY field of the event
|
||||
function print_data(start, dur, end, summary, cmd, collection, depth, path) {
|
||||
summary = 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, "/")
|
||||
function print_data(start, dur, end, summary, cmd, collection) {
|
||||
summary = substr(summary, index(summary, ":") + 1);
|
||||
gsub("\\\\n", " ", summary); # one-liner
|
||||
gsub("\\\\N", " ", summary); # one-liner
|
||||
gsub("\\\\,", ",", summary);
|
||||
gsub("\\\\;", ";", summary);
|
||||
gsub("\\\\\\\\", "\\", summary);
|
||||
depth = split(FILENAME, path, "/");
|
||||
fpath = path[depth-1] "/" path[depth]
|
||||
collection = depth > 1 ? path[depth-1] : ""
|
||||
collection = collection in collection2label ? collection2label[collection] : collection
|
||||
collection = depth > 1 ? path[depth-1] : "";
|
||||
collection = collection in collection2label ? collection2label[collection] : collection;
|
||||
collection = collection2label[path[depth-1]]
|
||||
end = dur ? start " " end : end
|
||||
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 | getline end
|
||||
close(cmd)
|
||||
status = status ? status : "CONFIRMED"
|
||||
print start, end, fpath, collection, status, summary
|
||||
print start, end, fpath, collection, summary
|
||||
}
|
||||
|
||||
# AWK program
|
||||
BEGIN {
|
||||
FS="[:;=]"
|
||||
split(collection_labels, mapping, ";")
|
||||
FS="[:;=]";
|
||||
split(collection_labels, mapping, ";");
|
||||
for (map in mapping)
|
||||
{
|
||||
split(mapping[map], m, "=")
|
||||
collection2label[m[1]] = m[2]
|
||||
split(mapping[map], m, "=");
|
||||
collection2label[m[1]] = m[2];
|
||||
}
|
||||
}
|
||||
/^END:VEVENT/ && inside { print_data(start, dur, end, summary); exit }
|
||||
/^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 }
|
||||
/^STATUS/ && inside { status = $NF }
|
||||
/^END:VEVENT/ && inside { print_data(start, dur, end, summary, cmd, collection); exit }
|
||||
/^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 }
|
||||
/^ / && rs { summary = summary substr($0, 2); }
|
||||
/^SUMMARY/ && inside { rs = 1; summary = $0; }
|
||||
/^BEGIN:VEVENT/ { inside = 1 }
|
||||
|
@ -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 }
|
@ -1,103 +1,101 @@
|
||||
## src/awk/update.awk
|
||||
## Update iCalendar file from markdown file.
|
||||
function getcontent(content_line, prop)
|
||||
{
|
||||
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 {
|
||||
FS=":"
|
||||
zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1)
|
||||
FS=":";
|
||||
zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1);
|
||||
}
|
||||
|
||||
ENDFILE {
|
||||
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
|
||||
# date-time entry.
|
||||
gsub("\"", "\\\"", from)
|
||||
cmd = "date -d \"" from "\" +\"%N\""
|
||||
cmd | getline n
|
||||
close(cmd)
|
||||
n = n + 0
|
||||
cmd = "date -d \"" from "\" +\"%H%M\""
|
||||
from = from ? from : "now"
|
||||
cmd = "date -d \"" from "\" +\"%N\"";
|
||||
cmd | getline t
|
||||
close(cmd)
|
||||
t = t + 0
|
||||
if (n != 0 || t == 0) {
|
||||
from_type = "DATE"
|
||||
cmd = "date -d \"" from "\" +\"%Y%m%d\""
|
||||
} else {
|
||||
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\"";
|
||||
}
|
||||
suc = cmd | getline from
|
||||
cmd | getline from
|
||||
close(cmd)
|
||||
if (suc != 1) {
|
||||
exit 1
|
||||
}
|
||||
#
|
||||
gsub("\"", "\\\"", to)
|
||||
cmd = "date -d \"" to "\" +\"%N\""
|
||||
cmd | getline n
|
||||
close(cmd)
|
||||
n = n + 0
|
||||
cmd = "date -d \"" to "\" +\"%H%M\""
|
||||
to = to ? to : "now"
|
||||
cmd = "date -d \"" to "\" +\"%N\"";
|
||||
cmd | getline t
|
||||
close(cmd)
|
||||
t = t + 0
|
||||
if (n != 0 || t == 0) {
|
||||
to_type = "DATE"
|
||||
cmd = "date -d \"" to "\" +\"%Y%m%d\""
|
||||
} else {
|
||||
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\"";
|
||||
}
|
||||
suc = cmd | getline to
|
||||
cmd | getline to
|
||||
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 {
|
||||
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : ""
|
||||
if (!from)
|
||||
exit 1
|
||||
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : "";
|
||||
getline
|
||||
to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : ""
|
||||
if (!to)
|
||||
exit 1
|
||||
to = substr($0, 1, 6) == "::: <|" ? substr($0, 8) : "";
|
||||
getline
|
||||
location = substr($0, 1, 2) == "@ " ? escape(substr($0, 3)) : ""
|
||||
if (location) getline
|
||||
summary = substr($0, 1, 2) == "# " ? escape(substr($0, 3)) : ""
|
||||
if (!summary)
|
||||
exit 1
|
||||
summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
|
||||
getline # This line should be empty
|
||||
if ($0 != "")
|
||||
exit 1
|
||||
readdesc = 1
|
||||
next
|
||||
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)
|
||||
print_fold("DESCRIPTION:", desc)
|
||||
print_fold("LOCATION:", location)
|
||||
print_fold("SUMMARY:", summary, i, s)
|
||||
print_fold("DESCRIPTION:", desc, i, s)
|
||||
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 }
|
||||
|
44
src/awk/weeks.awk
Normal file
44
src/awk/weeks.awk
Normal 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 }
|
@ -1,39 +1,23 @@
|
||||
## src/awk/weekview.awk
|
||||
## Print view of all appointments of the current week.
|
||||
## 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
|
||||
function c() {
|
||||
return CYAN substr($0, index($0, ">") + 1) OFF " " RED "/" OFF
|
||||
}
|
||||
|
||||
# AWK program
|
||||
|
||||
BEGIN {
|
||||
FS = "\t"
|
||||
OFS = "\t"
|
||||
OFF = "\033[m"
|
||||
GREEN = "\033[1;32m";
|
||||
RED = "\033[1;31m";
|
||||
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 }
|
||||
$2 == "00:00" { dayline = dayline style_time " → " $3 OFF " " c($4); next }
|
||||
$3 == "00:00" { dayline = dayline style_time " " $2 " → " OFF c($4); next }
|
||||
NF == 4 { dayline = dayline style_time " " $2 " – " $3 OFF " " c($4); next }
|
||||
NF == 1 && dayline { print "+", startofweek " +" $1-1 " days", "", dayline }
|
||||
NF == 1 {
|
||||
cmd = "date -d '" startofweek " +" $1 " days' +\"%a %e %b %Y\""
|
||||
cmd | getline dayline
|
||||
close(cmd)
|
||||
dayline = style_day dayline ": " OFF
|
||||
/^[0-7] 00:00 -- 00:00/ { dayline = dayline " " 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} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next }
|
||||
/^[0-7]$/ && dayline { print "+", startofweek " +" $0-1 " days", "", dayline; }
|
||||
/^[0-7]$/ {
|
||||
cmd = "date -d '" startofweek " +" $0 " days' +\"%a %e %b %Y\"";
|
||||
cmd | getline dayline;
|
||||
close(cmd);
|
||||
dayline = GREEN dayline ": " OFF
|
||||
}
|
||||
|
@ -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
|
||||
}
|
910
src/main.sh
910
src/main.sh
@ -1,151 +1,401 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
# TODO: Make sensitive to failures. I don't want to miss appointments!
|
||||
# TODO Ensure safe use of delimiters
|
||||
|
||||
if [ "${1:-}" = "--help" ]; then
|
||||
cat <<EOF
|
||||
Usage: $0 [OPTION]
|
||||
err() {
|
||||
echo "❌ $1" >/dev/tty
|
||||
}
|
||||
|
||||
You may specify at most one of the following options:
|
||||
--help Show this help and exit
|
||||
--today Show today's appointments
|
||||
--yesterday Show yesterday's appointments
|
||||
--tomorrow Show tomorrow's appointments
|
||||
--goto Interactively enter date to jump to
|
||||
--new [date/date-time] Create new entry (today)
|
||||
--day [date] Show appointments of specified day (today)
|
||||
--week [date] Show week of specified date (today)
|
||||
--import file Import iCalendar file
|
||||
--import-ni file Import iCalendar file non-interactively
|
||||
--git cmd Run git command cmd relative to calendar root
|
||||
--git-init Enable the use of git
|
||||
if [ -z "${FZF_VCAL_USE_EXPORTED:-}" ]; then
|
||||
# Read configuration
|
||||
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 "${SYNC_CMD:-}" ] || [ -z "${COLLECTION_LABELS:-}" ]; then
|
||||
err "Configuration is incomplete."
|
||||
exit 1
|
||||
fi
|
||||
export ROOT
|
||||
export SYNC_CMD
|
||||
export COLLECTION_LABELS
|
||||
|
||||
You may also start this program with setting locale and timezone information.
|
||||
DAY_START=${DAY_START:-8}
|
||||
DAY_END=${DAY_END:-18}
|
||||
export DAY_START
|
||||
export DAY_END
|
||||
|
||||
For instance, to see and modify all of your calendar entries from the
|
||||
perspective of Saigon, run
|
||||
TZ='Asia/Saigon' $0
|
||||
# Tools
|
||||
if command -v "fzf" >/dev/null; then
|
||||
FZF="fzf --black"
|
||||
else
|
||||
err "Did not find the command-line fuzzy finder fzf."
|
||||
exit 1
|
||||
fi
|
||||
export FZF
|
||||
|
||||
Likewise, you may see your calendar in the Greek language with
|
||||
LC_TIME=el_GR.UTF-8 $0
|
||||
if command -v "uuidgen" >/dev/null; then
|
||||
UUIDGEN="uuidgen"
|
||||
else
|
||||
err "Did not find the uuidgen command."
|
||||
exit 1
|
||||
fi
|
||||
export UUIDGEN
|
||||
|
||||
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}
|
||||
CAT=${CAT:-cat}
|
||||
export CAT
|
||||
|
||||
### AWK SCRIPTS
|
||||
AWK_LINES=$(
|
||||
cat <<'EOF'
|
||||
@@include src/awk/lines.awk
|
||||
EOF
|
||||
)
|
||||
export AWK_LINES
|
||||
|
||||
AWK_MERGE=$(
|
||||
cat <<'EOF'
|
||||
@@include src/awk/merge.awk
|
||||
EOF
|
||||
)
|
||||
export AWK_MERGE
|
||||
|
||||
AWK_PARSE=$(
|
||||
cat <<'EOF'
|
||||
@@include src/awk/parse.awk
|
||||
EOF
|
||||
)
|
||||
export AWK_PARSE
|
||||
|
||||
AWK_WEEKVIEW=$(
|
||||
cat <<'EOF'
|
||||
@@include src/awk/weekview.awk
|
||||
EOF
|
||||
)
|
||||
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
|
||||
|
||||
## 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
|
||||
|
||||
__load_approx_data() {
|
||||
find "$ROOT" -type f -name '*.ics' -print0 |
|
||||
xargs -0 -P0 \
|
||||
awk \
|
||||
-v collection_labels="$COLLECTION_LABELS" \
|
||||
"$AWK_LINES"
|
||||
}
|
||||
|
||||
__load_weeks() {
|
||||
dates=$(awk -F'|' '{ print $2; print $3 }' "$APPROX_DATA_FILE")
|
||||
file_dates=$(mktemp)
|
||||
echo "$dates" | date --file="/dev/stdin" +"%s" >"$file_dates"
|
||||
awk "$AWK_MERGE" "$file_dates" "$APPROX_DATA_FILE"
|
||||
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() {
|
||||
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-)
|
||||
dayofweek=$(date -d "$DISPLAY_DATE" +"%u")
|
||||
delta=$((1 - dayofweek))
|
||||
startofweek=$(date -d "$DISPLAY_DATE -$delta days" +"%D")
|
||||
# loop over 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
|
||||
sef=$(echo "$sef" | while IFS= read -r line; do
|
||||
set -- $line
|
||||
starttime="$1"
|
||||
shift
|
||||
endtime="$1"
|
||||
shift
|
||||
#fpath="$1"
|
||||
shift
|
||||
description="$*"
|
||||
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")
|
||||
s="$s -"
|
||||
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")
|
||||
e="- $e"
|
||||
elif [ "$endtime" -ge "$dayend" ] && [ "$starttime" -lt "$dayend" ]; then
|
||||
e="- 00:00"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
echo "$i $s$e >$description"
|
||||
done
|
||||
done)
|
||||
fi
|
||||
sef=$({
|
||||
echo "$sef"
|
||||
seq 0 7
|
||||
} | sort -n)
|
||||
echo "$sef" | awk -v startofweek="$startofweek" "$AWK_WEEKVIEW"
|
||||
#seq -f "$startofweek +%g days" 0 6 |
|
||||
# LC_ALL=c xargs -I {} date -d "{}" +"%a %e %b %Y"
|
||||
}
|
||||
|
||||
__canonical_datetime_hm() {
|
||||
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)
|
||||
__load_approx_data >"$APPROX_DATA_FILE"
|
||||
export APPROX_DATA_FILE
|
||||
WEEKLY_DATA_FILE=$(mktemp)
|
||||
__load_weeks >"$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
|
||||
|
||||
# Configuration
|
||||
. "sh/config.sh"
|
||||
|
||||
# Theme
|
||||
. "sh/theme.sh"
|
||||
|
||||
# Misc helper functions
|
||||
. "sh/misc.sh"
|
||||
|
||||
# Preview utilities
|
||||
. "sh/preview.sh"
|
||||
|
||||
# Preview command-line options
|
||||
. "sh/clipreview.sh"
|
||||
|
||||
# View utilities
|
||||
. "sh/view.sh"
|
||||
|
||||
# Reloading command-line options
|
||||
. "sh/clireload.sh"
|
||||
|
||||
# Access to awk scripts
|
||||
. "sh/awkscripts.sh"
|
||||
|
||||
# Functions to load calendar data
|
||||
. "sh/load.sh"
|
||||
|
||||
# Functions to modify iCalendar files
|
||||
. "sh/icalendar.sh"
|
||||
|
||||
# Extra, run-and-exit command-line options
|
||||
. "sh/cliextra.sh"
|
||||
|
||||
### Start
|
||||
__refresh_data
|
||||
|
||||
###
|
||||
### Main loop with the command-line argument
|
||||
### --today
|
||||
### --yesterday
|
||||
### --tomorrow
|
||||
### --goto
|
||||
### --new <optional date/date-time argument>
|
||||
### --day <optional date/date-time argument>
|
||||
### --week <optional date/date-time argument>
|
||||
### --set-tz
|
||||
###
|
||||
### The command-line argument defaults to "--week today".
|
||||
|
||||
while true; do
|
||||
export DISPLAY_DATE WEEKLY_DATA_FILE APPROX_DATA_FILE
|
||||
|
||||
case "${1:-}" in
|
||||
--today | --yesterday | --tomorrow | --goto | --new | --day | --week | --set-tz) ;;
|
||||
*)
|
||||
DISPLAY_DATE="today"
|
||||
set -- "--week" "$DISPLAY_DATE"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$1" = "--today" ]; then
|
||||
DISPLAY_DATE="today"
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
if [ "${1:-}" = "--today" ]; then
|
||||
exec $0 --day "today"
|
||||
fi
|
||||
|
||||
if [ "$1" = "--yesterday" ]; then
|
||||
DISPLAY_DATE="yesterday"
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
fi
|
||||
|
||||
if [ "$1" = "--tomorrow" ]; then
|
||||
DISPLAY_DATE="tomorrow"
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
fi
|
||||
|
||||
if [ "$1" = "--goto" ]; then
|
||||
if [ "${1:-}" = "--goto" ]; then
|
||||
DISPLAY_DATE=""
|
||||
while [ -z "${DISPLAY_DATE:-}" ]; do
|
||||
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" >/dev/null; then
|
||||
DISPLAY_DATE="$(date -d "$tmp" +"%D")"
|
||||
if date -d "$tmp"; then
|
||||
DISPLAY_DATE="$tmp"
|
||||
fi
|
||||
done
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
fi
|
||||
|
||||
if [ "$1" = "--set-tz" ]; then
|
||||
new_tz=$(find "$ZI_DIR" -type f | sed "s|^$ZI_DIR/*||" | $FZF)
|
||||
if [ -n "$new_tz" ]; then
|
||||
TZ="$new_tz"
|
||||
__refresh_data
|
||||
__export
|
||||
fi
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--new" ]; then
|
||||
__new "${2:-}"
|
||||
if [ -n "$start" ]; then
|
||||
DISPLAY_DATE="$start"
|
||||
else
|
||||
DISPLAY_DATE="${2:-}"
|
||||
fi
|
||||
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
|
||||
__export
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
fi
|
||||
rm "$filetmp"
|
||||
fi
|
||||
|
||||
if [ "$1" = "--day" ]; then
|
||||
if [ -z "${APPROX_DATA_FILE:-}" ]; then
|
||||
__refresh_data
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--day" ]; then
|
||||
DISPLAY_DATE="${2:-today}"
|
||||
__export
|
||||
export DISPLAY_DATE
|
||||
selection=$(
|
||||
__view_day |
|
||||
__show_day |
|
||||
$FZF \
|
||||
--reverse \
|
||||
--ansi \
|
||||
@ -153,240 +403,227 @@ while true; do
|
||||
--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 \
|
||||
--list-border="top" \
|
||||
--list-label-pos=3 \
|
||||
--cycle \
|
||||
--delimiter='\t' \
|
||||
--with-nth='{6}' \
|
||||
--accept-nth='1,2,3,4,5' \
|
||||
--preview="$0 --preview-event {}" \
|
||||
--expect="ctrl-n,ctrl-t,ctrl-g,ctrl-alt-d,esc,backspace,q,alt-v,x,c,a" \
|
||||
--bind='load:pos(1)+transform(
|
||||
echo change-border-label:🗓️ $(date -d {1} +"%A %e %B %Y")
|
||||
)+transform(
|
||||
[ -n "${TZ:-}" ] && echo "change-list-label:$STYLE_DV_TZ($TZ)$OFF"
|
||||
)+transform(
|
||||
[ -n {5} ] && echo show-preview
|
||||
)' \
|
||||
--bind="start:hide-preview" \
|
||||
--bind="j:down" \
|
||||
--bind="k:up" \
|
||||
--bind="l:reload:$0 --reload-day {1} '+1 day'" \
|
||||
--bind="h:reload:$0 --reload-day {1} '-1 day'" \
|
||||
--bind="right:reload:$0 --reload-day {1} '+1 day'" \
|
||||
--bind="left:reload:$0 --reload-day {1} '-1 day'" \
|
||||
--bind="ctrl-l:reload:$0 --reload-day {1} '+1 week'" \
|
||||
--bind="ctrl-h:reload:$0 --reload-day {1} '-1 week'" \
|
||||
--bind="alt-l:reload:$0 --reload-day {1} '+1 month'" \
|
||||
--bind="alt-h:reload:$0 --reload-day {1} '-1 month'" \
|
||||
--bind="ctrl-r:reload:$0 --reload-day today" \
|
||||
--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='tab:down' \
|
||||
--bind='shift-tab:up' \
|
||||
--bind='focus:hide-preview+transform(
|
||||
[ "$FZF_KEY" = "tab" ] && [ -z {5} ] && [ "$FZF_POS" -lt "$FZF_TOTAL_COUNT" ] && echo down
|
||||
[ "$FZF_KEY" = "shift-tab" ] && [ -z {5} ] && [ "$FZF_POS" -gt "1" ] && echo up
|
||||
)+transform(
|
||||
[ -n {5} ] && echo show-preview
|
||||
)' \
|
||||
--bind="w:toggle-preview-wrap" \
|
||||
--bind="ctrl-d:preview-down" \
|
||||
--bind="ctrl-u:preview-up"
|
||||
--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
|
||||
DISPLAY_DATE=$(echo "$line" | cut -f 1)
|
||||
hour=$(echo "$line" | cut -f 2)
|
||||
start=$(echo "$line" | cut -f 3)
|
||||
end=$(echo "$line" | cut -f 4)
|
||||
fpath=$(echo "$line" | cut -f 5)
|
||||
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
|
||||
set -- "--new" "$DISPLAY_DATE $hour:00"
|
||||
elif [ "$key" = "ctrl-alt-d" ] && [ -n "$fpath" ]; then
|
||||
__delete "$fpath"
|
||||
__refresh_data
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
elif [ "$key" = "ctrl-g" ]; then
|
||||
set -- "--goto"
|
||||
elif [ "$key" = "ctrl-t" ]; then
|
||||
set -- "--set-tz" "--day" "$DISPLAY_DATE"
|
||||
elif [ "$key" = "esc" ] || [ "$key" = "backspace" ] || [ "$key" = "q" ]; then
|
||||
set -- "--week" "$DISPLAY_DATE"
|
||||
elif [ "$key" = "alt-v" ] && [ -f "$ROOT/$fpath" ]; then
|
||||
$EDITOR "$ROOT/$fpath"
|
||||
elif [ "$key" = "x" ] && [ -f "$ROOT/$fpath" ]; then
|
||||
__cancel_toggle "$fpath"
|
||||
elif [ "$key" = "c" ] && [ -f "$ROOT/$fpath" ]; then
|
||||
__tentative_toggle "$fpath"
|
||||
elif [ "$key" = "a" ] && [ -f "$ROOT/$fpath" ]; then
|
||||
att=$(
|
||||
awk "$AWK_ATTACHLS" "$ROOT/$fpath" |
|
||||
$FZF \
|
||||
--delimiter="\t" \
|
||||
--accept-nth=1,2,3,4 \
|
||||
--with-nth="Attachment {1}: \"{2}\" {3} ({5})" \
|
||||
--no-sort \
|
||||
--tac \
|
||||
--margin="30%,30%" \
|
||||
--border=bold \
|
||||
--border-label="Attachment View Keys: <enter> open, <ctrl-alt-d> delete, <shift-a> add" \
|
||||
--expect="A" \
|
||||
--expect="ctrl-c,ctrl-g,ctrl-q,ctrl-d,esc,q,backspace" \
|
||||
--print-query \
|
||||
--bind="start:hide-input" \
|
||||
--bind="ctrl-alt-d:show-input+change-query(ctrl-alt-d)+accept" \
|
||||
--bind='load:transform:[ "$FZF_TOTAL_COUNT" -eq 0 ] && echo "unbind(enter)+unbind(ctrl-alt-d)"' \
|
||||
--bind="w:toggle-wrap" \
|
||||
--bind="j:down" \
|
||||
--bind="k:up" ||
|
||||
true
|
||||
)
|
||||
key=$(echo "$att" | head -2 | xargs)
|
||||
sel=$(echo "$att" | tail -1)
|
||||
if [ "$key" = "ctrl-c" ] ||
|
||||
[ "$key" = "ctrl-g" ] ||
|
||||
[ "$key" = "ctrl-q" ] ||
|
||||
[ "$key" = "ctrl-d" ] ||
|
||||
[ "$key" = "esc" ] ||
|
||||
[ "$key" = "q" ] ||
|
||||
[ "$key" = "backspace" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ "$key" = "A" ]; then
|
||||
__add_attachment "$fpath"
|
||||
__refresh_data
|
||||
continue
|
||||
fi
|
||||
attid=$(echo "$sel" | cut -f 1)
|
||||
attname=$(echo "$sel" | cut -f 2)
|
||||
attfmt=$(echo "$sel" | cut -f 3)
|
||||
attenc=$(echo "$sel" | cut -f 4)
|
||||
if [ -z "$attid" ]; then
|
||||
# This line should be unreachable
|
||||
continue
|
||||
fi
|
||||
if [ "$key" = "ctrl-alt-d" ]; then
|
||||
while true; do
|
||||
printf "Are you sure you want to delete attachment \"%s\"? (yes/no): " "$attid" >/dev/tty
|
||||
read -r yn
|
||||
case $yn in
|
||||
"yes")
|
||||
filetmp=$(mktemp)
|
||||
awk -v id="$attid" "$AWK_ATTACHRM" "$ROOT/$fpath" >"$filetmp"
|
||||
mv "$filetmp" "$ROOT/$fpath"
|
||||
if [ -n "${GIT:-}" ]; then
|
||||
$GIT add "$fpath"
|
||||
$GIT commit -q -m "Deleted attachment from event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
|
||||
fi
|
||||
__refresh_data
|
||||
break
|
||||
;;
|
||||
"no")
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Please answer \"yes\" or \"no\"." >/dev/tty
|
||||
;;
|
||||
esac
|
||||
done
|
||||
continue
|
||||
fi
|
||||
if [ "$attenc" != "base64" ]; then
|
||||
err "Unsupported attachment encoding: $attenc"
|
||||
read -r tmp
|
||||
continue
|
||||
fi
|
||||
if [ -n "$attname" ]; then
|
||||
tmpdir=$(mktemp -d)
|
||||
attpath="$tmpdir/$attname"
|
||||
elif [ -n "$attfmt" ]; then
|
||||
attext=$(echo "$attfmt" | cut -d "/" -f 2)
|
||||
attpath=$(mktemp --suffix="$attext")
|
||||
else
|
||||
attpath=$(mktemp)
|
||||
fi
|
||||
# Get file and uncode
|
||||
awk -v id="$attid" "$AWK_ATTACHDD" "$ROOT/$fpath" | base64 -d >"$attpath"
|
||||
fn=$(file "$attpath")
|
||||
while true; do
|
||||
printf "Are you sure you want to open \"%s\"? (yes/no): " "$fn" >/dev/tty
|
||||
read -r yn
|
||||
case $yn in
|
||||
"yes")
|
||||
$OPEN "$attpath"
|
||||
printf "Press <enter> to continue." >/dev/tty
|
||||
read -r tmp
|
||||
break
|
||||
;;
|
||||
"no")
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Please answer \"yes\" or \"no\"." >/dev/tty
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Clean up
|
||||
rm -f "$attpath"
|
||||
if [ -n "${tmpdir:-}" ] && [ -d "${tmpdir:-}" ]; then
|
||||
rm -rf "$tmpdir"
|
||||
fi
|
||||
exec $0 --new "$DISPLAY_DATE $hour:00"
|
||||
elif [ -z "$key" ] && [ -n "$fpath" ]; then
|
||||
fpath="$ROOT/$fpath"
|
||||
__edit "$start" "$end" "$fpath"
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
fi
|
||||
__export
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--week" ]; then
|
||||
DISPLAY_DATE="${2:-today}"
|
||||
if [ "${1:-}" = "--date" ]; then
|
||||
DISPLAY_DATE="$2"
|
||||
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_POS=$((8 - $(date -d "$DISPLAY_DATE" +"%u")))
|
||||
__export
|
||||
|
||||
if [ "${1:-}" = "--list" ]; then
|
||||
shift
|
||||
DISPLAY_DATE=${*:-today}
|
||||
DISPLAY_POS=$((8 - $(date -d "$DISPLAY_DATE" +"%u")))
|
||||
__list
|
||||
exit
|
||||
fi
|
||||
|
||||
selection=$(
|
||||
__view_week |
|
||||
__list |
|
||||
$FZF \
|
||||
--tac \
|
||||
--no-sort \
|
||||
--no-hscroll \
|
||||
--ellipsis="" \
|
||||
--delimiter="\t" \
|
||||
--delimiter="|" \
|
||||
--with-nth="{4}" \
|
||||
--accept-nth=1,2 \
|
||||
--ansi \
|
||||
--gap 1 \
|
||||
--no-scrollbar \
|
||||
--no-input \
|
||||
--info=right \
|
||||
--margin="1" \
|
||||
--info-command="printf \"$(date +"%R %Z")\"; [ -n \"\${TZ:-}\" ] && printf \" (\$TZ)\"" \
|
||||
--preview-window=up,8,border-bottom \
|
||||
--info-command="printf \"$(date +"%R %Z")\"" \
|
||||
--preview-window=up,7,border-bottom \
|
||||
--preview="$0 --preview-week {}" \
|
||||
--bind="load:pos($DISPLAY_POS)+unbind(load)" \
|
||||
--expect="ctrl-n,ctrl-g,ctrl-t" \
|
||||
--bind="q:abort" \
|
||||
--bind="j:down" \
|
||||
--bind="k:up" \
|
||||
--bind="l:reload:$0 --reload-week {2} '+1 week'" \
|
||||
--bind="h:reload:$0 --reload-week {2} '-1 week'" \
|
||||
--bind="right:reload:$0 --reload-week {2} '+1 week'" \
|
||||
--bind="left:reload:$0 --reload-week {2} '-1 week'" \
|
||||
--bind="ctrl-l:reload:$0 --reload-week {2} '+1 month'" \
|
||||
--bind="ctrl-h:reload:$0 --reload-week {2} '-1 month'" \
|
||||
--bind="alt-l:reload:$0 --reload-week {2} '+1 year'" \
|
||||
--bind="alt-h:reload:$0 --reload-week {2} '-1 year'" \
|
||||
--bind="ctrl-r:rebind(load)+reload($0 --reload-week today)+show-preview" \
|
||||
--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="/:show-input+unbind(q)+unbind(j)+unbind(k)+unbind(l)+unbind(h)+unbind(ctrl-l)+unbind(ctrl-h)+unbind(alt-l)+unbind(alt-h)+unbind(load)+hide-preview+reload:$0 --reload-all" \
|
||||
--bind="backward-eof:hide-input+rebind(q)+rebind(j)+rebind(k)+rebind(l)+rebind(h)+rebind(ctrl-l)+rebind(ctrl-h)+rebind(alt-l)+rebind(alt-h)+rebind(load)+show-preview+reload:$0 --reload-week today" \
|
||||
--bind="esc:clear-query+hide-input+rebind(q)+rebind(j)+rebind(k)+rebind(l)+rebind(h)+rebind(ctrl-l)+rebind(ctrl-h)+rebind(alt-l)+rebind(alt-h)+rebind(load)+show-preview+reload:$0 --reload-week today"
|
||||
--bind="ctrl-g:become($0 --goto)" \
|
||||
--bind="ctrl-l:rebind(load)+reload:$0 --list"
|
||||
)
|
||||
|
||||
key=$(echo "$selection" | head -1)
|
||||
@ -394,24 +631,25 @@ while true; do
|
||||
if [ "$line" = "$key" ]; then
|
||||
line=""
|
||||
fi
|
||||
sign=$(echo "$line" | cut -f 1)
|
||||
DISPLAY_DATE=$(echo "$line" | cut -f 2)
|
||||
sign=$(echo "$line" | cut -d '|' -f 1)
|
||||
startdate=$(echo "$line" | cut -d '|' -f 2)
|
||||
if [ "$key" = "ctrl-n" ]; then
|
||||
# Add new
|
||||
if [ "$sign" = "~" ]; then
|
||||
DISPLAY_DATE=""
|
||||
startdate=""
|
||||
fi
|
||||
set -- "--new" "${DISPLAY_DATE:-today} $DAY_START:00"
|
||||
elif [ "$key" = "ctrl-g" ]; then
|
||||
set -- "--goto"
|
||||
elif [ "$key" = "ctrl-t" ]; then
|
||||
set -- "--set-tz" "$*"
|
||||
else
|
||||
exec $0 --new "${startdate:-today} $DAY_START:00"
|
||||
fi
|
||||
if [ -z "$key" ] && [ -z "$line" ]; then
|
||||
rm "$WEEKLY_DATA_FILE" "$APPROX_DATA_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$sign" = "~" ]; then
|
||||
set -- "--week" "$DISPLAY_DATE"
|
||||
exec $0 --date "$startdate"
|
||||
else
|
||||
set -- "--day" "$DISPLAY_DATE"
|
||||
exec $0 --day "$startdate"
|
||||
fi
|
||||
fi
|
||||
__export
|
||||
fi
|
||||
done
|
||||
echo "Going to end..."
|
||||
echo "$selection"
|
||||
echo "STOPPING NOW"
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
@ -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}"
|
157
src/sh/view.sh
157
src/sh/view.sh
@ -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"
|
||||
}
|
Loading…
Reference in New Issue
Block a user