split the role of parsing and formatting through a simple TSV intermediate format - ics2txt - convert icalendar .ics file to plain text HTML 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 HTML 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