Compare commits

..

32 Commits

Author SHA1 Message Date
4d0148e2a3 improvement: adding attachments 2025-06-17 11:25:55 +02:00
fef86eef7a bugfix: open attachment 2025-06-17 11:14:18 +02:00
871a000cbd improvement: attachment feature 2025-06-17 10:59:39 +02:00
c39c45a23a feat: Basic attachment support 2025-06-17 08:38:15 +02:00
cb84445159 bugfix: using date in RFC 5322 format increases stability 2025-06-16 21:31:07 +02:00
1093bc15e5 feat: STATUS support 2025-06-16 15:07:58 +02:00
9e2e3bc35b new colors and informative git commits 2025-06-16 14:15:05 +02:00
caec86c5a0 bugfix: esc 2025-06-16 13:47:04 +02:00
83beaa3ad5 cleaned awk scripts, str escape bugfix, proper use of local variables 2025-06-16 11:04:32 +02:00
ee02a7647b improvement: add q action 2025-06-15 21:57:03 +02:00
bcbd2a9077 feat: non-interactive imports 2025-06-15 21:42:45 +02:00
0b8066923b improvement: Calendar preview: weeks start on Mondays 2025-06-15 21:07:15 +02:00
06020740cc improvements: presentation
- collection label before time
- UTF8 arrow insead of `->`
2025-06-14 23:11:55 +02:00
903c870dba improvement: rearranged keys 2025-06-14 22:30:21 +02:00
4a17512819 bugfix: status was from vjournal not vevent 2025-06-13 23:07:31 +02:00
16193b5554 bug fix: recognize all-day events, improvment: view source with v 2025-06-13 22:39:23 +02:00
e09c38ee29 ctrl-g in dayview 2025-06-13 15:44:11 +02:00
7ed2df2399 feat: git support 2025-06-13 15:31:59 +02:00
23cbe26d94 improvement: week view with ctrl-h and ctrl-l 2025-06-13 14:58:49 +02:00
13aebae71f bug fix: return from dayview 2025-06-13 14:57:17 +02:00
93317350f1 bugfix: cleanup at tend 2025-06-13 14:51:56 +02:00
e67fcca02c feat: import 2025-06-13 14:47:01 +02:00
735665bb92 extended description of configuration 2025-06-13 14:12:29 +02:00
0ffa57373a feat:change tz from within application 2025-06-13 14:07:44 +02:00
6d8520a016 non-recursive, clean 2025-06-13 13:04:35 +02:00
ff898c84c8 feat:location support 2025-06-12 21:47:30 +02:00
dc88d5a965 feat: dayview: jump to next/previous day 2025-06-12 15:22:27 +02:00
cdc008e361 bugfix: corrected week calculation (iso) 2025-06-12 14:57:36 +02:00
7549acb20c bugfix: handle wrong date input in entry creation 2025-06-12 12:44:55 +02:00
aee1a1bf24 improvement: no jump to "day" line in search view 2025-06-12 11:40:47 +02:00
4ebcbe36e3 bugfix: disable up/down limits in search view 2025-06-12 11:39:14 +02:00
acc231027b update readme 2025-06-11 22:27:53 +02:00
20 changed files with 2085 additions and 880 deletions

View File

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

98
src/awk/approx.awk Normal file
View File

@@ -0,0 +1,98 @@
## src/awk/approx.awk
## Generate single-line approximate information for every iCalendar argument.
##
## @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
}
# 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 = substr(summary, index(summary, ":") + 1)
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
}
# AWK program
BEGIN {
FS="[:;=]"
OFS="|"
split(collection_labels, mapping, ";")
for (map in mapping)
{
split(mapping[map], m, "=")
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() }
/^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 }

35
src/awk/attach.awk Normal file
View File

@@ -0,0 +1,35 @@
## 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 }

8
src/awk/attachdd.awk Normal file
View File

@@ -0,0 +1,8 @@
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 }

41
src/awk/attachls.awk Normal file
View File

@@ -0,0 +1,41 @@
# 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 }

13
src/awk/attachrm.awk Normal file
View File

@@ -0,0 +1,13 @@
## 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 }

View File

@@ -1,3 +1,9 @@
## src/awk/calannot.awk
## Annotate monthly calendar
##
## @assign cur: Day-of-month to mark as `today`
## @assign day: Day-of-month to highlight
BEGIN { BEGIN {
BLACK = "\033[1;30m" BLACK = "\033[1;30m"
GREEN = "\033[1;32m" GREEN = "\033[1;32m"

23
src/awk/calshift.awk Normal file
View File

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

View File

@@ -1,26 +1,86 @@
# 11:00|13:00|1748422800|1748430000|fpath|desc... ## src/awk/dayview.awk
# 00:00|00:00|1748296800|1748383200|fpath|desc... ## Generate the view of a day from lines of the form
function allday(desc) { ## ```
return ITALIC FAINT " (allday) " OFF desc ## <start_date>|<end_date>|<start_time>|<end_time>|<file_path>|<collection>|<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
# 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" ? STRIKE CYAN : status == "TENTATIVE" ? FAINT CYAN : CYAN
} }
function endstoday(stop, desc) {
return CYAN " -- " stop 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 " " LIGHT_CYAN ITALIC FAINT " (allday) " OFF color desc OFF
} }
function slice(start, stop, desc) {
# Return line for multi-day event, or event that starts at midnight, which ends today.
#
# @local variables: color
# @input stop: Time at which the event ends
# @input collection: Collection symbol
# @input desc: Event description
# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED
# @return: Single-line string
function endstoday(stop, collection, desc, status) {
color = color_from_status(status)
return collection " " LIGHT_CYAN " -- " stop OFF ": " color desc OFF
}
# Return line for event that starts sometime today.
#
# @local variables: color
# @input start: Time at which the event starts
# @input stop: Time at which the event ends
# @input collection: Collection symbol
# @input desc: Event description
# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED
# @return: Single-line string
function slice(start, stop, collection, desc, status) {
color = color_from_status(status)
if (stop == "00:00") if (stop == "00:00")
return CYAN start " -- " OFF ": " desc return collection " " LIGHT_CYAN start " -- " OFF ": " color desc OFF
else else
return CYAN start OFF " -- " CYAN stop OFF ": " desc return collection " " LIGHT_CYAN start OFF " -- " LIGHT_CYAN stop OFF ": " color desc OFF
} }
# Print line for a single hour entry.
#
# @input hour: Hour of the entry
function hrline(hour) { function hrline(hour) {
hour = hour < 10 ? "0"hour : hour hour = hour < 10 ? "0"hour : hour
print hour, "", "", "", FAINT hour ":00 ----------------------" OFF print today, hour, "", "", "", " " FAINT hour ":00 ----------------------" OFF
} }
# Print lines for hour entries before an event that starts at `start` and stops
# at `stop`.
#
# @local variables: starth, stoph, tmp, i
# @input start: Time at which the event starts
# @input stop: Time at which the event ends
# @input h: Last event-free hour
# @return: Hour of now last event-free hour
function hrlines(start, stop, h, starth, stoph, tmp, i) { function hrlines(start, stop, h, starth, stoph, tmp, i) {
starth = substr(start, 1, 2) starth = substr(start, 1, 2)
stoph = substr(stop, 1, 2) stoph = substr(stop, 1, 2)
tmp = substr(start, 4, 2) == "00" ? 0 : 1 tmp = substr(start, 4, 2) == "00" ? 0 : 1
for (i=h; i < starth + tmp; i++) for (i=h; i < starth + tmp && i < dayend; i++)
hrline(i) hrline(i)
tmp = substr(stop, 4, 2) == "00" ? 0 : 1 tmp = substr(stop, 4, 2) == "00" ? 0 : 1
if (stoph + tmp < daystart) if (stoph + tmp < daystart)
@@ -28,23 +88,22 @@ function hrlines(start, stop, h, starth, stoph, tmp, i) {
else else
return stoph + tmp return stoph + tmp
} }
# AWK program
BEGIN { BEGIN {
FS = "|" FS = "|"
GREEN = "\033[1;32m" LIGHT_CYAN = "\033[1;36m"
RED = "\033[1;31m"
WHITE = "\033[1;97m"
CYAN = "\033[1;36m" CYAN = "\033[1;36m"
ITALIC = "\033[3m" ITALIC = "\033[3m"
FAINT = "\033[2m" FAINT = "\033[2m"
STRIKE = "\033[9m"
OFF = "\033[m" OFF = "\033[m"
OFS = "|" OFS = "|"
} }
$1 == "00:00" && $2 == "00:00" { print $1, $3, $4, $5, allday($6); next } $1 == "00:00" && $2 == "00:00" { print today, $1, $3, $4, $5, allday($6, $7, $8); next }
$1 == "00:00" { print $1, $3, $4, $5, endstoday($2, $6); next } $1 == "00:00" { print today, $1, $3, $4, $5, endstoday($2, $6, $7, $8); next }
$1 ~ /^[0-9]{2}:[0-9]{2}$/ { $1 ~ /^[0-9]{2}:[0-9]{2}$/ {
daystart = hrlines($1, $2, daystart, starth, stoph, tmp, i) daystart = hrlines($1, $2, daystart)
print $1, $3, $4, $5, slice($1, $2, $6) print today, $1, $3, $4, $5, slice($1, $2, $6, $7, $8)
}
END {
hrlines(dayend":00", 0, daystart, starth, stoph, tmp, i)
} }
END { hrlines(dayend":00", 0, daystart) }

View File

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

10
src/awk/has.awk Normal file
View File

@@ -0,0 +1,10 @@
## 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 }

View File

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

View File

@@ -1,17 +1,33 @@
BEGIN { FS="|"; i=0; dlt = -259200; spw = 604800; } ## 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="|" }
NR == FNR { NR == FNR {
i = i + 1; i = i + 1
from[i] = int(($1 + dlt)/ spw); from_year[i] = $1
getline; from_week[i] = $2
to[i] = int(($1 + dlt) / spw); getline
to_year[i] = $1
to_week[i] = $2
next next
} # Load start and end week numbers from first file } # Load start and end week numbers from first file
{ {
if (from[FNR] > to[FNR]) year_i = from_year[FNR]
print "FNR", FNR, ":", from[FNR],"-",to[FNR], " ",$0; week_i = from_week[FNR]
for(i=from[FNR]; i<=to[FNR]; i++) { year_end = to_year[FNR]
week[i] = week[i] " " $5 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
week_i++
if (week_i > 53) {
week_i = 1
year_i++
} }
} }
END { for (i in week) print i week[i]; } }
END { for (label in week) print label week[label] }

View File

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

View File

@@ -1,46 +1,69 @@
## src/awk/parse.awk
## Parse iCalendar file and print its key aspects:
## ```
## <start> <end> <fpath> <collection> <status> <summary>
## ```.
##
## @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) { function parse( dt) {
# Get timezone information # Get timezone information
dt = "";
for (i=2; i<NF-1; i+=2) { for (i=2; i<NF-1; i+=2) {
if ($i == "TZID") { if ($i == "TZID") {
dt = "TZ=\"" $(i+1) "\" "; dt = "TZ=\"" $(i+1) "\" "
break; break
} }
} }
# Get date/datetime # Get date/date-time
return length($NF) == 8 ? return length($NF) == 8 ?
dt $NF : 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); 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) { # Map iCalendar duration specification into the format to be used in date (1).
n = split($NF, a, /[PTWHMSD]/, seps); #
delete dta; # @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++) { for (i=2; i<=n; i++) {
if(seps[i] == "W") dta["weeks"] = a[i]; if(seps[i] == "W") dta["weeks"] = a[i]
if(seps[i] == "H") dta["hours"] = a[i]; if(seps[i] == "H") dta["hours"] = a[i]
if(seps[i] == "M") dta["minutes"] = a[i]; if(seps[i] == "M") dta["minutes"] = a[i]
if(seps[i] == "S") dta["seconds"] = a[i]; if(seps[i] == "S") dta["seconds"] = a[i]
if(seps[i] == "D") dta["days"] = a[i]; if(seps[i] == "D") dta["days"] = a[i]
} }
dt = a[1] ? a[1] : "+"; dt = a[1] ? a[1] : "+"
for (i in dta) { for (i in dta)
dt = dt " " dta[i] " " i; dt = dt " " dta[i] " " i
} return dt
return dt;
} }
function print_data(start, dur, end, summary, cmd, collection) { # Print string of parsed data.
summary = substr(summary, index(summary, ":") + 1); #
gsub("\\\\n", " ", summary); # one-liner # @local variables: cmd, collection, depth, path
gsub("\\\\N", " ", summary); # one-liner # @input start: Start time of event
gsub("\\\\,", ",", summary); # @input dur: Boolean that indicates that `end` specifies a duration
gsub("\\\\;", ";", summary); # @input end: End time of event, or event duration (see `dur`)
gsub("\\\\\\\\", "\\", summary); # @input summary: Content of SUMMARY field of the event
depth = split(FILENAME, path, "/"); 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)
depth = split(FILENAME, path, "/")
fpath = path[depth-1] "/" path[depth] fpath = path[depth-1] "/" path[depth]
collection = depth > 1 ? path[depth-1] : ""; collection = depth > 1 ? path[depth-1] : ""
collection = collection in collection2label ? collection2label[collection] : collection; collection = collection in collection2label ? collection2label[collection] : collection
collection = collection2label[path[depth-1]] collection = collection2label[path[depth-1]]
end = dur ? start " " end : end end = dur ? start " " end : end
cmd = "date -d '" start "' +\"%s\"" cmd = "date -d '" start "' +\"%s\""
@@ -49,23 +72,26 @@ function print_data(start, dur, end, summary, cmd, collection) {
cmd = "date -d '" end "' +\"%s\"" cmd = "date -d '" end "' +\"%s\""
cmd | getline end cmd | getline end
close(cmd) close(cmd)
print start, end, fpath, collection, summary status = status ? status : "CONFIRMED"
print start, end, fpath, collection, status, summary
} }
# AWK program
BEGIN { BEGIN {
FS="[:;=]"; FS="[:;=]"
split(collection_labels, mapping, ";"); split(collection_labels, mapping, ";")
for (map in mapping) for (map in mapping)
{ {
split(mapping[map], m, "="); split(mapping[map], m, "=")
collection2label[m[1]] = m[2]; collection2label[m[1]] = m[2]
} }
} }
/^END:VEVENT/ && inside { print_data(start, dur, end, summary, cmd, collection); exit } /^END:VEVENT/ && inside { print_data(start, dur, end, summary); exit }
/^DTSTART/ && inside { start = parse( dt) } /^DTSTART/ && inside { start = parse() }
/^DTEND/ && inside { end = parse( dt) } /^DTEND/ && inside { end = parse() }
/^DURATION/ && inside { end = parse_duration( dt, dta, i, n, a, seps); dur = 1 } /^DURATION/ && inside { end = parse_duration($NF); dur = 1 }
/^STATUS/ && inside { status = $NF }
/^[^ ]/ && rs { rs = 0 } /^[^ ]/ && rs { rs = 0 }
/^ / && rs { summary = summary substr($0, 2); } /^ / && rs { summary = summary substr($0, 2) }
/^SUMMARY/ && inside { rs = 1; summary = $0; } /^SUMMARY/ && inside { rs = 1; summary = $0 }
/^BEGIN:VEVENT/ { inside = 1 } /^BEGIN:VEVENT/ { inside = 1 }

42
src/awk/set.awk Normal file
View File

@@ -0,0 +1,42 @@
## 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.
# 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
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 { next }
/^[^ ]/ && con { con = 0 }
/^SEQUENCE/ && inside { seq = $2; next } # store sequence number and skip
/^LAST-MODIFIED/ && inside { next }
{ print }

View File

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

View File

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

View File

@@ -1,23 +1,34 @@
## src/awk/weekview.awk
## Print view of all appointments of the current week.
##
## @assign startofweek: Date of first day in the week
# Functions
# Compose line that will display a day in the week.
#
# @return: Single-line string
function c() { function c() {
return CYAN substr($0, index($0, ">") + 1) OFF " " RED "/" OFF return CYAN substr($0, index($0, ">") + 1) OFF " " RED "/" OFF
} }
# AWK program
BEGIN { BEGIN {
GREEN = "\033[1;32m"; GREEN = "\033[1;32m"
RED = "\033[1;31m"; RED = "\033[1;31m"
WHITE = "\033[1;97m"; CYAN = "\033[1;36m"
CYAN = "\033[1;36m"; OFF = "\033[m"
FAINT = "\033[2m";
OFF = "\033[m";
OFS = "|" OFS = "|"
} }
/^[0-7] 00:00 -- 00:00/ { dayline = dayline " " c(); next } /^[0-7] 00:00 -- 00:00/ { dayline = dayline " " c(); next }
/^[0-7] 00:00 -- / { dayline = dayline " <--" $4 " " c(); next } /^[0-7] 00:00 -- / { dayline = dayline " <--" $4 " " c(); next }
/^[0-7] [0-9]{2}:[0-9]{2} -- 00:00/ { dayline = dayline " " $2 "--> " c(); next } /^[0-7] [0-9]{2}:[0-9]{2} -- 00:00/ { dayline = dayline " " $2 "" c(); next }
/^[0-7] [0-9]{2}:[0-9]{2} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next } /^[0-7] [0-9]{2}:[0-9]{2} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next }
/^[0-7]$/ && dayline { print "+", startofweek " +" $0-1 " days", "", dayline; } /^[0-7]$/ && dayline { print "+", startofweek " +" $0-1 " days", "", dayline }
/^[0-7]$/ { /^[0-7]$/ {
cmd = "date -d '" startofweek " +" $0 " days' +\"%a %e %b %Y\""; cmd = "date -d '" startofweek " +" $0 " days' +\"%a %e %b %Y\""
cmd | getline dayline; cmd | getline dayline
close(cmd); close(cmd)
dayline = GREEN dayline ": " OFF dayline = GREEN dayline ": " OFF
} }

File diff suppressed because it is too large Load Diff