Compare commits
25 Commits
4d0148e2a3
...
main
Author | SHA1 | Date | |
---|---|---|---|
5330864ae5 | |||
9d840d95d2 | |||
a5942d2860 | |||
d7dc02979b | |||
96b1c76137 | |||
7c6de7e19c | |||
80dbfc0264 | |||
8a23f451b3 | |||
9097874854 | |||
8509f17889 | |||
5b2a524301 | |||
8a6c11b6b5 | |||
a79dfc575e | |||
3b8c412885 | |||
428b9de85c | |||
5a3668d6a9 | |||
8970e89cc0 | |||
387688caca | |||
a9201a7060 | |||
760c33b225 | |||
2e96e31a5b | |||
6d1d5ce1c6 | |||
648ff6c016 | |||
1d92534ffd | |||
81c1f94daf |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
fzf-vcal
|
||||
fzf-vcal.debug
|
||||
|
@@ -115,6 +115,7 @@ Here is the list of all available keybindings:
|
||||
| Key | Action |
|
||||
| --- | ------ |
|
||||
| `enter` | edit appointment |
|
||||
| `a` | open attachment list of appointment |
|
||||
| `j` | down |
|
||||
| `k` | up |
|
||||
| `l` | go to next day |
|
||||
|
@@ -5,7 +5,15 @@ GREEN="\033[0;32m"
|
||||
OFF="\033[m"
|
||||
NAME="fzf-vcal"
|
||||
SRC="./src/main.sh"
|
||||
echo "🐔 ${GREEN}Building${OFF} ${BOLD}$NAME${OFF}"
|
||||
sed -E 's|@@include (.+)$|cat \1|e' "$SRC" >"$NAME"
|
||||
|
||||
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}"
|
||||
chmod +x "$NAME"
|
||||
echo "🥚 ${GREEN}Done${OFF}"
|
||||
rm -rf "$tmpdir"
|
||||
echo "🍳 ${GREEN}Done:${OFF} Sucessfully built ${BOLD}${GREEN}$NAME${OFF}"
|
||||
|
@@ -1,48 +1,21 @@
|
||||
## 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
|
||||
|
||||
# Time-zone aware parsing of the date/date-time entry at the current record.
|
||||
#
|
||||
# @local variables: dt
|
||||
# @return: date or date-time string that can be used in date (1)
|
||||
function parse( dt) {
|
||||
# Get timezone information
|
||||
for (i=2; i<NF-1; i+=2) {
|
||||
if ($i == "TZID") {
|
||||
dt = "TZ=\"" $(i+1) "\" "
|
||||
break
|
||||
}
|
||||
}
|
||||
# Get date/date-time
|
||||
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)
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
# Get relative file path.
|
||||
#
|
||||
# @local variables: n, a
|
||||
@@ -60,23 +33,19 @@ function fn(path, n, a) {
|
||||
# @input summary: Content of SUMMARY field
|
||||
# @return: colorized single-line title string
|
||||
function title(start, summary) {
|
||||
summary = substr(summary, index(summary, ":") + 1)
|
||||
gsub("\\\\n", " ", summary)
|
||||
gsub("\\\\N", " ", summary)
|
||||
gsub("\\\\,", ",", summary)
|
||||
gsub("\\\\;", ";", summary)
|
||||
gsub("\\\\\\\\", "\\", summary)
|
||||
gsub("\\|", ":", summary) # we use "|" as delimiter
|
||||
summary = getcontent(summary)
|
||||
gsub("\n", " ", summary) # This will be put on a single line
|
||||
gsub("\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 FAINT "~ " collection " " gensub(/^[^0-9]*([0-9]{4})([0-9]{2}).*$/, "\\1-\\2", "1", start) " " summary OFF
|
||||
return style_line "~ " collection " " gensub(/^[^0-9]*([0-9]{4})([0-9]{2}).*$/, "\\1-\\2", "1", start) " " summary OFF
|
||||
}
|
||||
|
||||
# AWK program
|
||||
BEGIN {
|
||||
FS="[:;=]"
|
||||
OFS="|"
|
||||
OFS="\t"
|
||||
split(collection_labels, mapping, ";")
|
||||
for (map in mapping)
|
||||
{
|
||||
@@ -84,13 +53,12 @@ BEGIN {
|
||||
collection2label[m[1]] = m[2]
|
||||
}
|
||||
# Colors
|
||||
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); nextfile }
|
||||
/^DTSTART/ && inside { start = parse() }
|
||||
/^DTEND/ && inside { end = parse() }
|
||||
/^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) }
|
||||
|
@@ -3,22 +3,24 @@
|
||||
##
|
||||
## @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 {
|
||||
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 }
|
||||
NR == 1 { print style_month $0 OFF; next }
|
||||
NR == 2 { print style_weekdays $0 OFF; next }
|
||||
{
|
||||
sub("\\y"cur"\\y", BG BLACK BOLD cur OFF)
|
||||
sub("\\y"day"\\y", RED BOLD day OFF)
|
||||
if (day == cur) {
|
||||
sub("\\y"cur"\\y", style_highlight style_cur cur OFF)
|
||||
} else {
|
||||
sub("\\y"cur"\\y", style_cur cur OFF)
|
||||
sub("\\y"day"\\y", style_highlight day OFF)
|
||||
}
|
||||
print
|
||||
}
|
||||
|
@@ -1,12 +1,33 @@
|
||||
## src/awk/dayview.awk
|
||||
## Generate the view of a day from lines of the form
|
||||
## ```
|
||||
## <start_date>|<end_date>|<start_time>|<end_time>|<file_path>|<collection>|<description>
|
||||
## ```.
|
||||
## 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
|
||||
|
||||
@@ -15,7 +36,7 @@
|
||||
# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED
|
||||
# @return: Color modifier
|
||||
function color_from_status(status) {
|
||||
return status == "CANCELLED" ? STRIKE CYAN : status == "TENTATIVE" ? FAINT CYAN : CYAN
|
||||
return status == "CANCELLED" ? style_cancelled : status == "TENTATIVE" ? style_tentative : style_confirmed
|
||||
}
|
||||
|
||||
# Return line for all-day event.
|
||||
@@ -27,7 +48,7 @@ function color_from_status(status) {
|
||||
# @return: Single-line string
|
||||
function allday(collection, desc, status, color) {
|
||||
color = color_from_status(status)
|
||||
return collection " " LIGHT_CYAN ITALIC FAINT " (allday) " OFF color desc OFF
|
||||
return collection " " style_allday color desc OFF
|
||||
}
|
||||
|
||||
# Return line for multi-day event, or event that starts at midnight, which ends today.
|
||||
@@ -40,7 +61,7 @@ function allday(collection, desc, status, color) {
|
||||
# @return: Single-line string
|
||||
function endstoday(stop, collection, desc, status) {
|
||||
color = color_from_status(status)
|
||||
return collection " " LIGHT_CYAN " -- " stop OFF ": " color desc OFF
|
||||
return collection " " style_timerange " → " stop ": " OFF color desc OFF
|
||||
}
|
||||
|
||||
# Return line for event that starts sometime today.
|
||||
@@ -55,9 +76,9 @@ function endstoday(stop, collection, desc, status) {
|
||||
function slice(start, stop, collection, desc, status) {
|
||||
color = color_from_status(status)
|
||||
if (stop == "00:00")
|
||||
return collection " " LIGHT_CYAN start " -- " OFF ": " color desc OFF
|
||||
return collection " " style_timerange start " → " ": " OFF color desc OFF
|
||||
else
|
||||
return collection " " LIGHT_CYAN start OFF " -- " LIGHT_CYAN stop OFF ": " color desc OFF
|
||||
return collection " " style_timerange start " – " stop ": " OFF color desc OFF
|
||||
}
|
||||
|
||||
# Print line for a single hour entry.
|
||||
@@ -65,7 +86,7 @@ function slice(start, stop, collection, desc, status) {
|
||||
# @input hour: Hour of the entry
|
||||
function hrline(hour) {
|
||||
hour = hour < 10 ? "0"hour : hour
|
||||
print today, hour, "", "", "", " " FAINT hour ":00 ----------------------" OFF
|
||||
print today, hour, "", "", "", " " style_hou hour ":00" OFF " " style_emptyhour
|
||||
}
|
||||
|
||||
# Print lines for hour entries before an event that starts at `start` and stops
|
||||
@@ -91,14 +112,9 @@ function hrlines(start, stop, h, starth, stoph, tmp, i) {
|
||||
|
||||
# AWK program
|
||||
BEGIN {
|
||||
FS = "|"
|
||||
LIGHT_CYAN = "\033[1;36m"
|
||||
CYAN = "\033[1;36m"
|
||||
ITALIC = "\033[3m"
|
||||
FAINT = "\033[2m"
|
||||
STRIKE = "\033[9m"
|
||||
FS = "\t"
|
||||
OFS = "\t"
|
||||
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 }
|
||||
|
@@ -3,7 +3,8 @@
|
||||
##
|
||||
## @assign field: Field name
|
||||
|
||||
# AWK program
|
||||
@include "lib/awk/icalendar.awk"
|
||||
|
||||
BEGIN { FS = ":"; regex = "^" field }
|
||||
/^BEGIN:VEVENT$/ { inside = 1 }
|
||||
/^END:VEVENT$/ { exit }
|
||||
@@ -13,11 +14,5 @@ $0 ~ regex { content = $0; next }
|
||||
END {
|
||||
if (!inside) { exit }
|
||||
# Process content line
|
||||
content = substr(content, index(content, ":") + 1)
|
||||
gsub("\\\\n", "\n", content)
|
||||
gsub("\\\\N", "\n", content)
|
||||
gsub("\\\\,", ",", content)
|
||||
gsub("\\\\;", ";", content)
|
||||
gsub("\\\\\\\\", "\\", content)
|
||||
print content
|
||||
print getcontent(content)
|
||||
}
|
||||
|
@@ -4,14 +4,16 @@
|
||||
## to the weeks at which the events take place.
|
||||
|
||||
# AWK program
|
||||
BEGIN { FS="|" }
|
||||
BEGIN { FS="\t"; OFS="\t" }
|
||||
NR == FNR {
|
||||
i = i + 1
|
||||
from_year[i] = $1
|
||||
from_week[i] = $2
|
||||
split($0, parts, ":")
|
||||
from_year[i] = parts[1]
|
||||
from_week[i] = parts[2]
|
||||
getline
|
||||
to_year[i] = $1
|
||||
to_week[i] = $2
|
||||
split($0, parts, ":")
|
||||
to_year[i] = parts[1]
|
||||
to_week[i] = parts[2]
|
||||
next
|
||||
} # Load start and end week numbers from first file
|
||||
|
||||
@@ -21,8 +23,8 @@ NR == 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] " " $5
|
||||
label = year_i ":" week_i ":"
|
||||
week[label] = week[label] ? week[label] " " $5 : $5
|
||||
week_i++
|
||||
if (week_i > 53) {
|
||||
week_i = 1
|
||||
@@ -30,4 +32,4 @@ NR == FNR {
|
||||
}
|
||||
}
|
||||
}
|
||||
END { for (label in week) print label week[label] }
|
||||
END { for (label in week) print label, week[label] }
|
||||
|
@@ -3,47 +3,14 @@
|
||||
##
|
||||
## @assign uid: UID to use
|
||||
|
||||
# Functions
|
||||
|
||||
# Escape string to be used as content in iCalendar files.
|
||||
#
|
||||
# @input str: String to escape
|
||||
# @return: Escaped string
|
||||
function escape(str)
|
||||
{
|
||||
gsub("\\\\", "\\\\", 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
|
||||
}
|
||||
}
|
||||
@include "lib/awk/icalendar.awk"
|
||||
|
||||
# AWK program
|
||||
BEGIN {
|
||||
FS=":"
|
||||
zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1)
|
||||
}
|
||||
desc { desc = desc "\\n" $0; next }
|
||||
readdesc { desc = desc ? desc "\\n" escape($0) : escape($0); next }
|
||||
{
|
||||
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : ""
|
||||
if (!from)
|
||||
@@ -53,14 +20,15 @@ desc { desc = desc "\\n" $0; next }
|
||||
if (!to)
|
||||
exit 1
|
||||
getline
|
||||
location = substr($0, 1, 2) == "@ " ? substr($0, 3) : ""
|
||||
location = substr($0, 1, 2) == "@ " ? escape(substr($0, 3)) : ""
|
||||
if (location) getline
|
||||
summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
|
||||
summary = substr($0, 1, 2) == "# " ? escape(substr($0, 3)) : ""
|
||||
if (!summary)
|
||||
exit 1
|
||||
getline # This line should be empty
|
||||
getline # First line of description
|
||||
desc = $0
|
||||
if ($0 != "")
|
||||
exit 1
|
||||
readdesc = 1
|
||||
next
|
||||
}
|
||||
END {
|
||||
@@ -113,9 +81,6 @@ END {
|
||||
if (suc != 1) {
|
||||
exit 1
|
||||
}
|
||||
summary = escape(summary)
|
||||
location = escape(location)
|
||||
desc = escape(desc)
|
||||
|
||||
# print ical
|
||||
print "BEGIN:VCALENDAR"
|
||||
|
@@ -3,48 +3,12 @@
|
||||
## ```
|
||||
## <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.
|
||||
|
||||
# Functions
|
||||
|
||||
# Time-zone aware parsing of the date/date-time entry at the current record.
|
||||
#
|
||||
# @local variables: dt
|
||||
# @return: date or date-time string that can be used in date (1)
|
||||
function parse( dt) {
|
||||
# Get timezone information
|
||||
for (i=2; i<NF-1; i+=2) {
|
||||
if ($i == "TZID") {
|
||||
dt = "TZ=\"" $(i+1) "\" "
|
||||
break
|
||||
}
|
||||
}
|
||||
# Get date/date-time
|
||||
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)
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
@include "lib/awk/icalendar.awk"
|
||||
|
||||
# Print string of parsed data.
|
||||
#
|
||||
@@ -54,12 +18,9 @@ function parse_duration(duration, dt, dta, i, n, a, seps) {
|
||||
# @input end: End time of event, or event duration (see `dur`)
|
||||
# @input summary: Content of SUMMARY field of the event
|
||||
function print_data(start, dur, end, summary, cmd, collection, depth, path) {
|
||||
summary = substr(summary, index(summary, ":") + 1)
|
||||
gsub("\\\\n", " ", summary) # one-liner
|
||||
gsub("\\\\N", " ", summary) # one-liner
|
||||
gsub("\\\\,", ",", summary)
|
||||
gsub("\\\\;", ";", summary)
|
||||
gsub("\\\\\\\\", "\\", summary)
|
||||
summary = getcontent(summary)
|
||||
gsub("\n", " ", summary) # This will be put on a single line
|
||||
gsub("\t", " ", summary) # Generally, we use tab as delimiter.
|
||||
depth = split(FILENAME, path, "/")
|
||||
fpath = path[depth-1] "/" path[depth]
|
||||
collection = depth > 1 ? path[depth-1] : ""
|
||||
@@ -87,8 +48,8 @@ BEGIN {
|
||||
}
|
||||
}
|
||||
/^END:VEVENT/ && inside { print_data(start, dur, end, summary); exit }
|
||||
/^DTSTART/ && inside { start = parse() }
|
||||
/^DTEND/ && inside { end = parse() }
|
||||
/^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 }
|
||||
/^[^ ]/ && rs { rs = 0 }
|
||||
|
@@ -6,21 +6,7 @@
|
||||
##
|
||||
## LIMITATION: This program does not fold long content lines.
|
||||
|
||||
# Functions
|
||||
|
||||
# Escape string to be used as content.
|
||||
#
|
||||
# @input str: Content string
|
||||
# @return: Escaped string
|
||||
function escape(str)
|
||||
{
|
||||
gsub("\\\\", "\\\\", str)
|
||||
gsub(";", "\\;", str)
|
||||
gsub(",", "\\,", str)
|
||||
return str
|
||||
}
|
||||
|
||||
# AWK program
|
||||
@include "lib/awk/icalendar.awk"
|
||||
|
||||
BEGIN { FS = "[:;]"; zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1) }
|
||||
/^BEGIN:VEVENT$/ { inside = 1 }
|
||||
@@ -35,7 +21,6 @@ BEGIN { FS = "[:;]"; zulu = strftime("%Y%m%dT%H%M%SZ", systime()
|
||||
$1 == field && inside { con = 1; duplic = 1; print field ":" escape(value); next }
|
||||
$1 == field && duplic { con = 1; next }
|
||||
/^ / && con { next }
|
||||
/^ / && con { next }
|
||||
/^[^ ]/ && con { con = 0 }
|
||||
/^SEQUENCE/ && inside { seq = $2; next } # store sequence number and skip
|
||||
/^LAST-MODIFIED/ && inside { next }
|
||||
|
@@ -1,42 +1,7 @@
|
||||
## src/awk/update.awk
|
||||
## Update iCalendar file from markdown file.
|
||||
|
||||
# Functions
|
||||
|
||||
# Escape string to be used as content in iCalendar files.
|
||||
#
|
||||
# @input str: String to escape
|
||||
# @return: Escaped string
|
||||
function escape(str)
|
||||
{
|
||||
gsub("\\\\", "\\\\", 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
|
||||
}
|
||||
}
|
||||
|
||||
# AWK program
|
||||
@include "lib/awk/icalendar.awk"
|
||||
|
||||
BEGIN {
|
||||
FS=":"
|
||||
@@ -92,13 +57,10 @@ ENDFILE {
|
||||
if (suc != 1) {
|
||||
exit 1
|
||||
}
|
||||
summary = escape(summary)
|
||||
location = escape(location)
|
||||
desc = escape(desc)
|
||||
}
|
||||
}
|
||||
|
||||
NR == FNR && desc { desc = desc "\\n" $0; next }
|
||||
NR == FNR && readdesc { desc = desc ? desc "\\n" escape($0) : escape($0); next }
|
||||
NR == FNR {
|
||||
from = substr($0, 1, 6) == "::: |>" ? substr($0, 8) : ""
|
||||
if (!from)
|
||||
@@ -108,22 +70,18 @@ NR == FNR {
|
||||
if (!to)
|
||||
exit 1
|
||||
getline
|
||||
location = substr($0, 1, 2) == "@ " ? substr($0, 3) : ""
|
||||
location = substr($0, 1, 2) == "@ " ? escape(substr($0, 3)) : ""
|
||||
if (location) getline
|
||||
summary = substr($0, 1, 2) == "# " ? substr($0, 3) : ""
|
||||
summary = substr($0, 1, 2) == "# " ? escape(substr($0, 3)) : ""
|
||||
if (!summary)
|
||||
exit 1
|
||||
getline # This line should be empty
|
||||
getline # First line of description
|
||||
desc = $0
|
||||
if ($0 != "")
|
||||
exit 1
|
||||
readdesc = 1
|
||||
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|LOCATION|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
|
||||
@@ -134,5 +92,12 @@ NR == FNR {
|
||||
print_fold("DESCRIPTION:", desc)
|
||||
print_fold("LOCATION:", location)
|
||||
inside = ""
|
||||
skipf = 0
|
||||
}
|
||||
/^BEGIN:VEVENT$/ { inside = 1 }
|
||||
/^ / && skipf { next } # drop this folded line
|
||||
/^[^ ]/ && skipf { skipf = 0 }
|
||||
/^(DTSTART|DTEND|SUMMARY|LOCATION|CATEGORIES|DESCRIPTION|LAST-MODIFIED)/ && 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 }
|
||||
|
@@ -1,34 +1,39 @@
|
||||
## 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() {
|
||||
return CYAN substr($0, index($0, ">") + 1) OFF " " RED "/" OFF
|
||||
function c(desc) {
|
||||
return style_summary desc OFF " " style_event_delim
|
||||
}
|
||||
|
||||
# AWK program
|
||||
|
||||
BEGIN {
|
||||
GREEN = "\033[1;32m"
|
||||
RED = "\033[1;31m"
|
||||
CYAN = "\033[1;36m"
|
||||
FS = "\t"
|
||||
OFS = "\t"
|
||||
OFF = "\033[m"
|
||||
OFS = "|"
|
||||
}
|
||||
/^[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\""
|
||||
$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 = GREEN dayline ": " OFF
|
||||
dayline = style_day dayline ": " OFF
|
||||
}
|
||||
|
121
src/lib/awk/icalendar.awk
Normal file
121
src/lib/awk/icalendar.awk
Normal file
@@ -0,0 +1,121 @@
|
||||
# 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
|
||||
}
|
1229
src/main.sh
1229
src/main.sh
File diff suppressed because it is too large
Load Diff
121
src/sh/awkscripts.sh
Normal file
121
src/sh/awkscripts.sh
Normal file
@@ -0,0 +1,121 @@
|
||||
# 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
|
129
src/sh/cliextra.sh
Normal file
129
src/sh/cliextra.sh
Normal file
@@ -0,0 +1,129 @@
|
||||
# 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
|
100
src/sh/clipreview.sh
Normal file
100
src/sh/clipreview.sh
Normal file
@@ -0,0 +1,100 @@
|
||||
# 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
|
31
src/sh/clireload.sh
Normal file
31
src/sh/clireload.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
# 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
|
66
src/sh/config.sh
Normal file
66
src/sh/config.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
# 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
|
233
src/sh/icalendar.sh
Normal file
233
src/sh/icalendar.sh
Normal file
@@ -0,0 +1,233 @@
|
||||
# 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"
|
||||
}
|
45
src/sh/load.sh
Normal file
45
src/sh/load.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
# 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"
|
||||
}
|
44
src/sh/misc.sh
Normal file
44
src/sh/misc.sh
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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
|
||||
}
|
51
src/sh/preview.sh
Normal file
51
src/sh/preview.sh
Normal file
@@ -0,0 +1,51 @@
|
||||
# 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"
|
||||
}
|
47
src/sh/theme.sh
Normal file
47
src/sh/theme.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
# 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
Normal file
157
src/sh/view.sh
Normal file
@@ -0,0 +1,157 @@
|
||||
# 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_TENTATIVE"
|
||||
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"
|
||||
}
|
Reference in New Issue
Block a user