split the role of parsing and formatting through a simple TSV intermediate format - ics2txt - convert icalendar .ics file to plain text
(HTM) git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
---
(DIR) commit 37b4e99568c76d39376244f8f85fcf0dcfc95bd9
(DIR) parent 7ef52e239bfc8757d45f3d868920dba32dcb5b61
(HTM) Author: Josuah Demangeon <me@josuah.net>
Date: Mon, 2 Mar 2020 01:15:03 +0100
split the role of parsing and formatting through a simple TSV intermediate format
Diffstat:
M Makefile | 2 +-
M README | 84 ++-----------------------------
D ics2txt | 168 -------------------------------
A ics2txt-back | 81 ++++++++++++++++++++++++++++++
A ics2txt-ics | 91 +++++++++++++++++++++++++++++++
A ics2txt-tsv | 64 +++++++++++++++++++++++++++++++
A ics2txt-txt | 97 ++++++++++++++++++++++++++++++
M ics2txt.1 | 49 +++++++++++++++++++++++--------
8 files changed, 374 insertions(+), 262 deletions(-)
---
(DIR) diff --git a/Makefile b/Makefile
@@ -1,4 +1,4 @@
-BIN = ics2txt
+BIN = ics2txt-*
MAN1 = ics2txt.1
all:
(DIR) diff --git a/README b/README
@@ -1,83 +1,7 @@
-ics2txt
-=======
+sical
+=====
-*ics2txt* is an awk scripts to deal with iCal [1] format to publish,
-display and convert *.ics files.
+*sical* is set of awk scripts to deal with iCal [1] format to publish,
+display and convert *.ics files, though a simple central TSV format.
[1]: https://tools.ietf.org/rfc/rfc5545.txt
-
-Sample output:
-
-2019-02-02
-
-07:30 Welcome to FOSDEM 2019
-07:55 Janson
- FOSDEM welcome and opening talk.
-
-08:30 The State of Go
-09:00 UD2.120 (Chavanne)
- Go 1.12 is planned to be released in February 2019 and this talk
- covers what's coming up with it.We'll talk about Go Modules, the
- proposals for Go 2, and all of the new things you might have missed.
-
-09:30 HTTP/3
-10:30 UD2.208 (Decroly)
- HTTP/3 is the next coming HTTP version. This time TCP is replaced by
- the new transport protocol QUIC and things are different yet again!
-
-10:05 Minimalism matters
-10:25 K.4.201
- Minimalism matters in computing. To trust systems we need to be able
- to understand them completely. Openssl heartbleed disaster was caused
- by code no longer being minimalistic, even if it is free and open
- source software. Hardware manfucturers and proprietary closed source
- solutions make things even worse with expectations of intrusion to
- privacy and backdoors if we don't aim for free hardware, software and
- minimalism. In this talk I will discuss minimalism in a broad context
- and narrow down on what the free software community can aim for.
-
-2019-02-03
-
-07:55 Microkernel virtualization under one roof
-08:30 AW1.121
- Today's off-the-shell virtualization solution is ridden with
- complexity. Application of virtualization call for trustworthy
- solutions. Complexity defeats trust.Microkernels with virtualization
- extensions and user-level VMMs on top are a approach to mitigate
- complexity. Modern microkernels like seL4, the NOVA microhypervisor,
- Genode's -hw- kernel or Fiasco.OC are such promising candidates.
- Fortunately and unfortunately, the diversity come with fragmentation
- of the small microkernel community. There are several VMMs for each
- platform tight to a specific microkernel, rendering it unusable
- across various kernels.Genode supports several kernels already, so
- that unification of virtualization interfaces for VMMs across kernels
- seem to come into reach. Does it ? The talk will cover the venture
- and current state of harmonization hardware-assisted virtualization
- interfaces to fit into the Genode OS framework.
-
-14:40 FOSDEM infrastructure review
-14:55 H.2215 (Ferrer)
- Informational and fun.
-
-15:00 2019 - Fifty years of Unix and Linux advances
-15:50 Janson
- 2019 marks the fiftieth anniversary of Unix, but it is also the
- fiftieth anniversary of the ArpaNet/Internet, and people walking on
- the moon. It marks the 50th anniversary of Woodstock, the beginning
- of America's LGBTQ movement at the Stonewall Inn in New York City,
- and maddog wrote his first program fifty years ago. It was also in
- 1969 that he shaved for the last time.2019 marks the 30th year of the
- World Wide Web, the 25th anniversary of V1.0 of the Linux kernel, and
- of many GNU/Linux distributions starting. 2019 also marks the
- twentieth anniversary of the Linux Professional Institute.All of
- these years, and anniversaries.....but why has Unix (and its younger
- offspring Linux) lasted so long? What was different about Unix that
- caused it to survive and flourish? Why is it important today, and
- how can we take it further? How should we celebrate 2019? While
- maddog does not have all the answers, he tries to make the answers he
- does have interesting and fun to know.
-
-15:55 Closing FOSDEM 2019
-16:00 Janson
- Some closing words. Don't miss it!
-
(DIR) diff --git a/ics2txt b/ics2txt
@@ -1,168 +0,0 @@
-#!/usr/bin/awk -f
-
-# display iCal entries in plain text
-
-function leap(yrs)
-{
- return (yrs % 4 == 0) && (yrs % 100 != 0) || (yrs % 400 == 0)
-}
-
-function days_per_month(mth, yrs)
-{
- if (mth == 2)
- return 28 + leap(yrs)
- else
- return 30 + (mth - (mth > 7)) % 2
-}
-
-function to_sec(yrs, mth, day, hrs, min, sec)
-{
- while (--mth >= 1)
- day += days_per_month(mth, yrs)
- while (--yrs >= 1970)
- day += 365 + leap(yrs)
- return (((((day - 1) * 24) + hrs) * 60) + min) * 60 + sec
-}
-
-function to_date(fmt, sec)
-{
- for (yrs = 1970; sec >= (s = 3600 * 24 * (365 + leap(yrs))); yrs++)
- sec -= s
- for (mth = 1; sec >= (s = 3600 * 24 * days_per_month(mth, yrs)); mth++)
- sec -= s
- for (day = 1; sec >= (s = 3600 * 24); day++)
- sec -= s
- for (hrs = 0; sec >= 3600; hrs++)
- sec -= 3600
- for (min = 0; sec >= 60; min++)
- sec -= 60
- return sprintf(fmt, yrs, mth, day, hrs, min, sec)
-}
-
-function date_ical(str, offset) {
- yrs = substr(str, 1, 4)
- mth = substr(str, 5, 2)
- day = substr(str, 7, 2)
- hrs = substr(str, 10, 2)
- min = substr(str, 12, 2)
- if (substr(str, 16, 1) == "Z")
- return to_sec(yrs, mth, day, hrs, min, 0)
- else
- return to_sec(yrs, mth, day, hrs, min, 0) - offset
-}
-
-function date_iso8601(date, offset)
-{
- yrs = substr(date, 1, 4)
- mth = substr(date, 6, 2)
- day = substr(date, 9, 2)
- hrs = substr(date, 12, 2)
- min = substr(date, 15, 2)
- return to_sec(yrs, mth, day, hrs, min, 0) - offset
-}
-
-function swap(array, a, b)
-{
- tmp = array[a]
- array[a] = array[b]
- array[b] = tmp
-}
-
-function sort(array, beg, end)
-{
- if (beg >= end) # end recursion
- return
-
- a = beg + 1; # #1 is the pivot
- b = end
- while (a < b) {
- while (a < b && array[a] <= array[beg]) # beg: skip lesser
- a++
- while (a < b && array[b] > array[beg]) # end: skip greater
- b--
- swap(array, a, b); # found 2 misplaced
- }
-
- if (array[beg] > array[a]) # put the pivot back
- swap(array, beg, a)
-
- sort(array, beg, a - 1); # sort lower half
- sort(array, a, end); # sort higher half
-}
-
-function parse_ical(list, offset)
-{
- FS = "[:;]"
-
- while (getline) {
- gsub("\r", " "); gsub("\\\\[ntr]", " "); gsub("\\\\", "")
- gsub("^ *", ""); gsub(" *$", "")
- gsub(" *<[a-zA-Z0-9/]*>* *", "")
-
- if (match($0, "^ ")) {
- event[type] = event[type] substr($0, 2, length($0) - 1)
- } else {
- type = $1
- i = index($0, ":")
- event[type] = substr($0, i + 1, length($0) - i)
- }
-
- if ($0 ~ /END:VEVENT/)
- list[++n] = sprintf("%d\t%d\t%s\t%s\t%s\t%s",
- date_ical(event["DTSTART"], offset),
- date_ical(event["DTEND"], offset),
- event["SUMMARY"],
- event["LOCATION"],
- event["DESCRIPTION"])
- }
- sort(list, 1, n)
- return n
-}
-
-function print_fold(prefix, s, n)
-{
- while (s != "") {
- line = substr(s, 1, n)
- if (length(s) > n) sub(" +[^ \t\r\n]*$", "", line)
- print prefix line
- s = substr(s, length(line) + 2)
- }
-}
-
-function print_entry(beg, end, summary, location, description, offset)
-{
- b = to_date("%04d-%02d-%02d %02d:%02d", beg + offset)
- e = to_date("%04d-%02d-%02d %02d:%02d", end + offset)
- date = substr(b, 1, 10)
- hour_beg = substr(b, 12)
- hour_end = substr(e, 12)
-
- if (date != last_date) print "\n" date
- print "\n" hour_beg "\t" summary
- done = 0
- if (category) printf("%s\t%s\n", !done++ ? hour_end : "", category)
- if (location) printf("%s\t%s\n", !done++ ? hour_end : "", location)
- if (description) {
- printf("%s", !done++ ? hour_end : "")
- print_fold("\t", description, 70)
- }
-
- last_date = date
-}
-
-BEGIN {
- "date +%z" | getline offset_str
- close("date +%z")
-
- offset = substr(offset_str, 2, 2) * 3600
- offset += substr(offset_str, 4, 2) * 60
- if (substr(offset_str, 1, 1) == "-")
- offset *= -1
-
- n = parse_ical(list, offset)
- for (i = 1; i <= n; i++) {
- split(list[i], arr, "\t")
- print_entry(arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], offset)
- }
- print ""
-}
(DIR) diff --git a/ics2txt-back b/ics2txt-back
@@ -0,0 +1,81 @@
+#!/usr/bin/awk -f
+
+function isleap(year)
+{
+ return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)
+}
+
+function mdays(mon, year)
+{
+ return (mon == 2) ? (28 + isleap(year)) : (30 + (mon + (mon > 7)) % 2)
+}
+
+function timegm(year, mon, mday, hour, min, sec)
+{
+ while (--mon >= 1)
+ mday += mdays(mon, year)
+ while (--year >= 1970)
+ mday += 365 + isleap(year)
+ return (((((mday - 1) * 24) + hour) * 60) + min) * 60 + sec
+}
+
+function date_text(str, offset,
+ year, mon, mday, hour, min)
+{
+ year = substr(str, 1, 4)
+ mon = substr(str, 6, 2)
+ mday = substr(str, 9, 2)
+ hour = substr(str, 12, 2)
+ min = substr(str, 15, 2)
+ return timegm(year, mon, mday, hour, min, 0) - offset
+}
+
+{
+ gsub(/\t/, " ")
+}
+
+/^TZ[+-]/ {
+ hour = substr($0, 4, 2)
+ min = substr($0, 6, 2)
+ tzoffset = substr(zone, 3, 1) hour * 3600 + min * 60
+ next
+}
+
+/^[0-9]+-[0-9]+-[0-9]+ / {
+ time = date_text($1 " " $2, tzoffset)
+ row++
+}
+
+/^ / {
+ d = $0
+ sub(/^ */, "", d)
+ des = des " " d
+}
+
+/^$/ {
+ if (beg)
+ printf "%d\t%d\t%s\t%s\t%s\t%s\n", beg, end, cat, loc, sum, des
+ beg = end = cat = loc = sum = des = ""
+}
+
+row == 1 {
+ beg = time
+ sum = $0
+ sub(/^[^ ]+ +[^ ]+ +/, "", sum)
+}
+
+row == 2 {
+ end = time
+
+ line = $0
+ sub(/^[^ ]+ +[^ ]+ +/, "", line)
+
+ cat = line
+ sub(/\].*/, "", cat)
+ sub(/^\[/, "", cat)
+
+ loc = line
+ sub(/[^]]*\] */, "", loc)
+
+ row = 0
+}
(DIR) diff --git a/ics2txt-ics b/ics2txt-ics
@@ -0,0 +1,91 @@
+#!/usr/bin/awk -f
+
+function isleap(year)
+{
+ return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)
+}
+
+function mdays(mon, year)
+{
+ return (mon == 2) ? (28 + isleap(year)) : (30 + (mon + (mon > 7)) % 2)
+}
+
+# Split the time in seconds since epoch into a table, with fields
+# named as with gmtime(3): tm["year"], tm["mon"], tm["mday"],
+# tm["hour"], tm["min"], tm["sec"]
+function gmtime(sec, tm,
+ s)
+{
+ tm["year"] = 1970
+ while (sec >= (s = 86400 * (365 + isleap(tm["year"])))) {
+ tm["year"]++
+ sec -= s
+ }
+
+ tm["mon"] = 1
+ while (sec >= (s = 86400 * mdays(tm["mon"], tm["year"]))) {
+ tm["mon"]++
+ sec -= s
+ }
+
+ tm["mday"] = 1
+ while (sec >= (s = 86400)) {
+ tm["mday"]++
+ sec -= s
+ }
+
+ tm["hour"] = 0
+ while (sec >= 3600) {
+ tm["hour"]++
+ sec -= 3600
+ }
+
+ tm["min"] = 0
+ while (sec >= 60) {
+ tm["min"]++
+ sec -= 60
+ }
+
+ tm["sec"] = sec
+}
+
+function print_fold(prefix, s, n)
+{
+ while (s != "") {
+ line = substr(s, 1, n)
+ if (length(s) > n) sub(" +[^ \t\r\n]*$", "", line)
+ print prefix line
+ s = substr(s, length(line) + 2)
+ }
+}
+
+BEGIN {
+ print "BEGIN:VCALENDAR"
+ print "VERSION:2.0"
+ print "CALSCALE:GREGORIAN"
+ print "METHOD:PUBLISH"
+}
+
+{
+ split($0, a, "\t")
+ gmtime(a[1] + offset, beg)
+ gmtime(a[2] + offset, end)
+ cat = a[3]; loc = a[4]; sum = a[5]; des = a[6]
+
+ print ""
+ print "BEGIN:VEVENT"
+ printf "DTSTART:%04d%02d%02dT%02d%02d00Z\n",
+ beg["year"], beg["mon"], beg["mday"], beg["hour"], beg["min"]
+ printf "DTEND:%04d%02d%02dT%02d%02d00Z\n",
+ end["year"], end["mon"], end["mday"], end["hour"], end["min"]
+ print "SUMMARY:" sum
+ print "DESCRIPTION:" des
+ print "CATEGORIES:" cat
+ print "LOCATION:" loc
+ print "END:VEVENT"
+}
+
+END {
+ print ""
+ print "END:VCALENDAR"
+}
(DIR) diff --git a/ics2txt-tsv b/ics2txt-tsv
@@ -0,0 +1,64 @@
+#!/usr/bin/awk -f
+
+function isleap(year)
+{
+ return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)
+}
+
+function mdays(mon, year)
+{
+ return (mon == 2) ? (28 + isleap(year)) : (30 + (mon + (mon > 7)) % 2)
+}
+
+function timegm(year, mon, mday, hour, min, sec)
+{
+ while (--mon >= 1)
+ mday += mdays(mon, year)
+ while (--year >= 1970)
+ mday += 365 + isleap(year)
+ return (((((mday - 1) * 24) + hour) * 60) + min) * 60 + sec
+}
+
+function date_ical(str, offset,
+ year, mon, mday, hour, min)
+{
+ year = substr(str, 1, 4)
+ mon = substr(str, 5, 2)
+ mday = substr(str, 7, 2)
+ hour = substr(str, 10, 2)
+ min = substr(str, 12, 2)
+ offset = (substr(str, 16, 1) == "Z" ? 0 : offset)
+ return timegm(year, mon, mday, hour, min, 0) - offset
+}
+
+BEGIN {
+ "date +%z" | getline offset_str
+ close("date +%z")
+ hour = substr($0, 4, 2)
+ min = substr($0, 6, 2)
+ tzoffset = substr(zone, 3, 1) hour * 3600 + min * 60
+
+ FS = "[:;]"
+}
+
+{
+ gsub("\r", ""); gsub("\t", "\\\\t")
+ gsub("^ *", ""); gsub(" *$", "")
+
+ if (match($0, "^ ")) {
+ event[type] = event[type] substr($0, 2, length($0) - 1)
+ } else {
+ type = $1
+ i = index($0, ":")
+ event[type] = substr($0, i + 1, length($0) - i)
+ }
+
+ if ($0 ~ /^END:VEVENT/)
+ printf("%d\t%d\t%s\t%s\t%s\t%s\n",
+ date_ical(event["DTSTART"], offset),
+ date_ical(event["DTEND"], offset),
+ event["CATEGORIES"],
+ event["LOCATION"],
+ event["SUMMARY"],
+ event["DESCRIPTION"])
+}
(DIR) diff --git a/ics2txt-txt b/ics2txt-txt
@@ -0,0 +1,97 @@
+#!/usr/bin/awk -f
+
+function isleap(year)
+{
+ return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)
+}
+
+function mdays(mon, year)
+{
+ return (mon == 2) ? (28 + isleap(year)) : (30 + (mon + (mon > 7)) % 2)
+}
+
+# Split the time in seconds since epoch into a table, with fields
+# named as with gmtime(3): tm["year"], tm["mon"], tm["mday"],
+# tm["hour"], tm["min"], tm["sec"]
+function gmtime(sec, tm,
+ s)
+{
+ tm["year"] = 1970
+ while (sec >= (s = 86400 * (365 + isleap(tm["year"])))) {
+ tm["year"]++
+ sec -= s
+ }
+
+ tm["mon"] = 1
+ while (sec >= (s = 86400 * mdays(tm["mon"], tm["year"]))) {
+ tm["mon"]++
+ sec -= s
+ }
+
+ tm["mday"] = 1
+ while (sec >= (s = 86400)) {
+ tm["mday"]++
+ sec -= s
+ }
+
+ tm["hour"] = 0
+ while (sec >= 3600) {
+ tm["hour"]++
+ sec -= 3600
+ }
+
+ tm["min"] = 0
+ while (sec >= 60) {
+ tm["min"]++
+ sec -= 60
+ }
+
+ tm["sec"] = sec
+}
+
+function print_fold(prefix, s, n)
+{
+ while (s != "") {
+ line = substr(s, 1, n)
+ if (length(s) > n) sub(" +[^ \t\r\n]*$", "", line)
+ print prefix line
+ s = substr(s, length(line) + 2)
+ }
+}
+
+BEGIN {
+ cmd = "date +%z"
+ cmd | getline zone
+ close(cmd)
+
+ hour = substr(zone, 2, 2)
+ min = substr(zone, 4, 2)
+
+ offset = (substr(zone, 1, 1) "1") * (hour * 3600 + min * 60)
+ print "TZ" zone
+}
+
+{
+ split($0, a, "\t")
+ gmtime(a[1] + offset, beg)
+ gmtime(a[2] + offset, end)
+ cat = a[3]; loc = a[4]; sum = a[5]; des = a[6]
+
+ print ""
+ printf "%04d-%02d-%02d %02d:%02d ",
+ beg["year"], beg["mon"], beg["mday"], beg["hour"], beg["min"]
+ print sum
+
+ printf "%04d-%02d-%02d %02d:%02d ",
+ end["year"], end["mon"], end["mday"], end["hour"], end["min"]
+ print "[" cat "] " loc
+
+ sub("^ *", "", des)
+ sub(" *$", "", des)
+ if (des)
+ print_fold(" ", des, 80)
+}
+
+END {
+ print ""
+}
(DIR) diff --git a/ics2txt.1 b/ics2txt.1
@@ -1,4 +1,4 @@
-.Dd $Mdocdate: May 21 2018$
+.Dd $Mdocdate: Mar 1 2020$
.Dt ICS2TXT 1
.Os
.
@@ -6,32 +6,54 @@
.Sh NAME
.
.Nm ics2txt
-.Nd convert ics file to a simple plain text format
+.Nd convert ics file to simpler tsv or txt formats
.
.
.Sh SYNOPSIS
.
-.Nm Ar ics-file...
-.
+.Nm ics2txt-tsv Ar <file.ics >file.tsv
+.Nm ics2txt-txt Ar <file.tsv >file.txt
+.Nm ics2txt-ics Ar <file.tsv >file.ics
+.Nm ics2txt-back Ar <file.txt >file.tsv
.
.Sh DESCRIPTION
.
.Nm
-displays iCalendar
-.Pq ical, Pa .ics
-.Ar file
-or stdin if not specified in the format described by the command:
+convert iCalendar
+.Pq ical
+.Ar file.ics
+or stdin if not specified to a tab separated value format, with one
+line per entry, and one column per field:
+.
+.Bl -offset 1n -width 1n -enum -compact
+.
+.It
+Begining (epoch)
.
+.It
+End (epoch)
.
-.Sh ENVIRONMENT
+.It
+Category
.
-.Bl -tag -width 6n
+.It
+Location
.
-.It Ev TZ
-Timezone to use for printing the dates.
+.It
+Summary
+.
+.It description
+description
.
.El
.
+.Pp
+The
+.Sq \en
+and
+.Sq \et
+charaters may represent newlines and tabs.
+.
.
.Sh SEE ALSO
.
@@ -39,6 +61,7 @@ Timezone to use for printing the dates.
.Xr calendar 1 ,
.Xr date 1
.
+.
.Sh STANDARDS
.
.Rs
@@ -51,4 +74,4 @@ Timezone to use for printing the dates.
.
.Sh AUTHORS
.
-.An Josuah Demangeon Aq Mt mail@josuah.net
+.An Josuah Demangeon Aq Mt me@josuah.net