remove unused utilities and flatten the source some more - 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 24ae7d2759496b7907cce29f0c26697950453ff5 DIR parent 742516775b1d9b12e4c8893114b7cc5a363884ad HTML Author: Josuah Demangeon <me@josuah.net> Date: Sun, 20 Jun 2021 18:37:15 +0200 remove unused utilities and flatten the source some more Diffstat: M .gitignore | 1 + M Makefile | 19 ++++++++++++------- D bin/ics2tsv | 141 ------------------------------- D bin/ics2txt | 2 -- D bin/tsv2ics | 104 ------------------------------- M ics2tsv.c | 57 ++++++++++++++++--------------- M tsv2agenda.c | 249 ++++++++++++++++++------------- A tsv2ics.awk | 106 ++++++++++++++++++++++++++++++ M util.c | 14 ++++++++++++++ M util.h | 4 ++++ 10 files changed, 310 insertions(+), 387 deletions(-) --- DIR diff --git a/.gitignore b/.gitignore @@ -1,5 +1,6 @@ *.o /ics2tsv /ics2tree +/tsv2ics /tsv2agenda /ics2txt-[0-9]* DIR diff --git a/Makefile b/Makefile @@ -10,31 +10,36 @@ MANPREFIX = ${PREFIX}/man SRC = ical.c base64.c util.c HDR = ical.h base64.h util.h OBJ = ${SRC:.c=.o} +AWK = tsv2ics.awk BIN = ics2tree ics2tsv tsv2agenda MAN1 = ics2txt.1 ics2tsv.1 -MAN5 = tcal.5 all: ${BIN} .c.o: ${CC} -c ${CFLAGS} -o $@ $< +${AWK:.awk=}: + cp $@.awk $@ + chmod +x $@ + ${OBJ}: ${HDR} ${BIN}: ${OBJ} ${BIN:=.o} ${CC} ${LDFLAGS} -o $@ $@.o ${OBJ} clean: - rm -rf *.o ${BIN} ${NAME}-${VERSION} *.gz + rm -rf *.o ${BIN} ${AWK:.awk} ${NAME}-${VERSION} *.gz -install: +install: ${BIN} ${AWK:.awk=} mkdir -p ${DESTDIR}$(PREFIX)/bin - cp bin/* $(BIN) ${DESTDIR}$(PREFIX)/bin + cp $(BIN) ${AWK:.awk=} ${DESTDIR}$(PREFIX)/bin mkdir -p ${DESTDIR}$(MANPREFIX)/man1 cp ${MAN1} ${DESTDIR}$(MANPREFIX)/man1 - mkdir -p ${DESTDIR}$(MANPREFIX)/man5 - cp ${MAN5} ${DESTDIR}$(MANPREFIX)/man5 dist: clean mkdir -p ${NAME}-${VERSION} - cp -r README Makefile bin ${MAN1} ${MAN5} ${SRC} ${NAME}-${VERSION} + cp -r README Makefile ${AWK} ${MAN1} ${SRC} ${NAME}-${VERSION} tar -cf - ${NAME}-${VERSION} | gzip -c >${NAME}-${VERSION}.tar.gz + +.SUFFIXES: .awk +.PHONY: ${AWK} DIR diff --git a/bin/ics2tsv b/bin/ics2tsv @@ -1,141 +0,0 @@ -#!/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(tm, - sec, mon, day) -{ - sec = tm["sec"] + tm["min"] * 60 + tm["hour"] * 3600 - - day = tm["mday"] - 1 - - for (mon = tm["mon"] - 1; mon > 0; mon--) - day = day + mdays(mon, tm["year"]) - - # constants: x * 365 + x / 400 - x / 100 + x / 4 - day = day + int(tm["year"] / 400) * 146097 - day = day + int(tm["year"] % 400 / 100) * 36524 - day = day + int(tm["year"] % 100 / 4) * 1461 - day = day + int(tm["year"] % 4 / 1) * 365 - - return sec + (day - 719527) * 86400 -} - -function print_vevent(ev, fields, - i) -{ - for (i = 1; i in fields; i++) - printf("%s%s", (i > 1 ? "\t" : ""), ev[fields[i]]) - printf("\n") -} - -function ical_parse_line(str, content, params, - i, eq) -{ - if ((i = index(str, ":")) == 0) - return -1 - content["value"] = substr(str, i + 1) - str = substr(str, 1, i - 1) - - if ((i = index(str, ";")) == 0) { - content["name"] = str - return 0 - } - content["name"] = substr(str, 1, i - 1) - str = substr(str, i + 1) - - while ((i = index(str, ";")) > 0) { - if ((eq = index(str, "=")) == 0) - return -1 - param[substr(str, 1, eq - 1)] = substr(str, eq + 1, i - 1) - str = substr(str, eq + 1) - } - if ((eq = index(str, "=")) == 0) - return -1 - params[substr(str, 1, eq - 1)] = substr(str, eq + 1) - return 0 -} - -function ical_set_tz(tzid) -{ - gsub("'", "", tzid) - cmd = "TZ='" tzid "' exec date +%z" - cmd | getline tzid - close(cmd) - TZ = substr(tzid, 1, 1) substr(tzid, 2, 2)*3600 + substr(tzid, 4, 2)*60 -} - -function ical_to_epoch(content, param, - tz, cmd) -{ - if (param["TZID"]) - ical_set_tz(param["TZID"]) - - tm["year"] = substr(content["value"], 1, 4) - tm["mon"] = substr(content["value"], 5, 2) - tm["mday"] = substr(content["value"], 7, 2) - tm["hour"] = substr(content["value"], 10, 2) - tm["min"] = substr(content["value"], 12, 2) - tm["sec"] = substr(content["value"], 14, 2) - - return timegm(tm) + TZ -} - -BEGIN { - split("DTSTART DTEND CATEGORIES LOCATION SUMMARY DESCRIPTION URL", - FIELDS, " ") - DT["DTSTART"] = DT["DTEND"] = DT["DUE"] = 1 - - # by default: "CATEGORIES" -> "cat", "LOCATION" -> "loc"... - translate["DTSTART"] = "beg" - translate["DTEND"] = "end" - - for (i = 1; i in FIELDS; i++) { - if (!(s = translate[FIELDS[i]])) - s = tolower(substr(FIELDS[i], 1, 3)) - printf("%s%s", (i > 1 ? "\t" : ""), s) - } - printf("\n") - - FS = "[:;]" -} - -{ - gsub("\r", "") - gsub("\t", "\\\\t") -} - -sub("^ ", "") { - content["value"] = content["value"] $0 - next -} - -{ - delete content - delete param - - if (ical_parse_line($0, content, params) < 0) - next - - if (content["name"] == "TZID") { - ical_set_tz(content["value"]) - } else if (DT[content["name"]]) { - vevent[content["name"]] = ical_to_epoch(content, params) - } else { - vevent[content["name"]] = content["value"] - } -} - -/^END:VEVENT/ { - print_vevent(vevent, FIELDS) - delete vevent - next -} DIR diff --git a/bin/ics2txt b/bin/ics2txt @@ -1,2 +0,0 @@ -#!/bin/sh -e -exec ics2tsv "$@" | exec tsv2tcal DIR diff --git a/bin/tsv2ics b/bin/tsv2ics @@ -1,104 +0,0 @@ -#!/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 { - FS = "\t" - - print "BEGIN:VCALENDAR" - print "VERSION:2.0" - print "CALSCALE:GREGORIAN" - print "METHOD:PUBLISH" -} - -NR == 1 { - for (i = 1; i <= NF; i++) - name[i] = $i - next -} - -{ - for (i = 1; i <= NF; i++) - ev[name[i]] = $i - - print "" - print "BEGIN:VEVENT" - - gmtime(ev["beg"] + offset, ev) - printf "DTSTART:%04d%02d%02dT%02d%02d00Z\n", - ev["year"], ev["mon"], ev["mday"], ev["hour"], ev["min"] - - gmtime(ev["end"] + offset, ev) - printf "DTEND:%04d%02d%02dT%02d%02d00Z\n", - ev["year"], ev["mon"], ev["mday"], ev["hour"], ev["min"] - - print "SUMMARY:" ev["sum"] - print "DESCRIPTION:" ev["des"] - print "CATEGORIES:" ev["cat"] - print "LOCATION:" ev["loc"] - print "END:VEVENT" - - delete ev -} - -END { - print "" - print "END:VCALENDAR" -} DIR diff --git a/ics2tsv.c b/ics2tsv.c @@ -27,11 +27,11 @@ struct Block { char *fields[FIELDS_MAX]; }; -static int flag_1 = 0; -static char default_fields[] = "CATEGORIES,LOCATION,SUMMARY,DESCRIPTION"; -static char *flag_s = ","; -static char *flag_t = NULL; -static char *flag_f = default_fields; +static int flag_header = 1; +static char default_fields[] = "SUMMARY,DESCRIPTION,CATEGORIES,LOCATION"; +static char *flag_sep = ","; +static char *flag_timefmt = NULL; +static char *flag_fields = default_fields; static char *fields[FIELDS_MAX]; static Block block; @@ -60,9 +60,6 @@ fn_block_begin(IcalParser *p, char *name) static int fn_block_end(IcalParser *p, char *name) { - char buf[128]; - struct tm tm = {0}; - (void)name; if (p->blocktype == ICAL_BLOCK_OTHER) @@ -70,12 +67,18 @@ fn_block_end(IcalParser *p, char *name) fputs(p->current->name, stdout); /* printing dates with %s is much much slower than %lld */ - if (flag_t == NULL) { + if (flag_timefmt == NULL) { printf("\t%lld\t%lld", block.beg, block.end); } else { - strftime(buf, sizeof buf, flag_t, localtime_r(&block.beg, &tm)); + char buf[128]; + struct tm tm = {0}; + + localtime_r(&block.beg, &tm); + strftime(buf, sizeof buf, flag_timefmt, &tm); printf("\t%s", buf); - strftime(buf, sizeof buf, flag_t, localtime_r(&block.end, &tm)); + + localtime_r(&block.end, &tm); + strftime(buf, sizeof buf, flag_timefmt, &tm); printf("\t%s", buf); } @@ -131,7 +134,7 @@ fn_field_value(IcalParser *p, char *name, char *value) if ((block.fields[i] = strdup(value)) == NULL) return ical_err(p, strerror(errno)); } else { - if (strappend(&block.fields[i], flag_s) == NULL || + if (strappend(&block.fields[i], flag_sep) == NULL || strappend(&block.fields[i], value) == NULL) return ical_err(p, strerror(errno)); } @@ -144,7 +147,7 @@ fn_field_value(IcalParser *p, char *name, char *value) static void usage(void) { - fprintf(stderr,"usage: %s [-1] [-f fields] [-s subsep] [-t timefmt]" + fprintf(stderr,"usage: %s [-1] [-f fields] [-s separator] [-t timefmt]" " [file...]\n", arg0); exit(1); } @@ -153,7 +156,6 @@ int main(int argc, char **argv) { IcalParser p = {0}; - size_t i; int c; arg0 = *argv; @@ -167,19 +169,22 @@ main(int argc, char **argv) p.fn_param_value = fn_param_value; p.fn_field_value = fn_field_value; - while ((c = getopt(argc, argv, "1f:s:t:")) != -1) { + while ((c = getopt(argc, argv, "01f:s:t:")) != -1) { switch (c) { + case '0': + flag_header = 0; + break; case '1': - flag_1 = 1; + flag_header = 1; break; case 'f': - flag_f = optarg; + flag_fields = optarg; break; case 's': - flag_s = optarg; + flag_sep = optarg; break; case 't': - flag_t = optarg; + flag_timefmt = optarg; break; case '?': usage(); @@ -189,16 +194,12 @@ main(int argc, char **argv) argv += optind; argc -= optind; - i = 0; - do { - if (i >= sizeof fields / sizeof *fields - 1) - err(1, "too many fields specified with -o flag"); - } while ((fields[i++] = strsep(&flag_f, ",")) != NULL); - fields[i] = NULL; + if (strsplit(flag_fields, fields, LEN(fields), ",") < 0) + err(1, "too many fields specified with -f flag"); - if (flag_1) { - printf("%s\t%s\t%s\t%s", "TYPE", "BEG", "END", "RECUR"); - for (i = 0; fields[i] != NULL; i++) + if (flag_header) { + printf("%s\t%s\t%s\t%s", "TYPE", "START", "END", "RECUR"); + for (size_t i = 0; fields[i] != NULL; i++) printf("\t%s", fields[i]); fputc('\n', stdout); } DIR diff --git a/tsv2agenda.c b/tsv2agenda.c @@ -1,35 +1,38 @@ #include <assert.h> +#include <ctype.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> +#include <time.h> #include "util.h" #ifndef __OpenBSD__ #define pledge(...) 0 #endif -#define FIELDS_MAX 128 - enum { FIELD_TYPE, FIELD_BEG, FIELD_END, FIELD_RECUR, FIELD_OTHER, + FIELD_MAX = 128, }; typedef struct { struct tm beg, end; + char *fieldnames[FIELD_MAX]; + size_t fieldnum; + size_t linenum; } AgendaCtx; -static size_t field_categories = 0; -static size_t field_location = 0; -static size_t field_summary = 0; +static time_t flag_from = INT64_MIN; +static time_t flag_to = INT64_MAX; -void +static void print_date(struct tm *tm) { if (tm == NULL) { @@ -42,7 +45,7 @@ print_date(struct tm *tm) } } -void +static void print_time(struct tm *tm) { if (tm == NULL) { @@ -55,139 +58,175 @@ print_time(struct tm *tm) } } -void -print(AgendaCtx *ctx, char **fields, size_t n) +static void +print_header1(struct tm *old, struct tm *new) +{ + int same; + + same = (old->tm_year == new->tm_year && old->tm_mon == new->tm_mon && + old->tm_mday == new->tm_mday); + print_date(same ? NULL : new); + print_time(new); +} + +static void +print_header2(struct tm *beg, struct tm *end) +{ + int same; + + same = (beg->tm_year == end->tm_year && beg->tm_mon == end->tm_mon && + beg->tm_mday == end->tm_mday); + print_date(same ? NULL : end); + + same = (beg->tm_hour == end->tm_hour && beg->tm_min == end->tm_min); + print_time(same ? NULL : end); +} + +static void +print_header3(void) +{ + print_date(NULL); + print_time(NULL); +} + +static void +print_row(AgendaCtx *ctx, char **fields, size_t i) +{ + if (i > ctx->fieldnum || *fields[i] == '\0') + return; + fprintf(stdout, "%s\n", fields[i]); +} + +static void +print(AgendaCtx *ctx, char **fields) { struct tm beg = {0}, end = {0}; time_t t; + size_t i = FIELD_OTHER; char const *e; - int rows, samedate; - t = strtonum(fields[FIELD_BEG], 0, UINT32_MAX, &e); + t = strtonum(fields[FIELD_BEG], INT64_MIN, INT64_MAX, &e); if (e != NULL) err(1, "start time %s is %s", fields[FIELD_BEG], e); + if (t > flag_to) + return; localtime_r(&t, &beg); - t = strtonum(fields[FIELD_END], 0, UINT32_MAX, &e); + t = strtonum(fields[FIELD_END], INT64_MIN, INT64_MAX, &e); if (e != NULL) err(1, "end time %s is %s", fields[FIELD_END], e); + if (t < flag_from) + return; localtime_r(&t, &end); - fputc('\n', stdout); - - samedate = (ctx->beg.tm_year != beg.tm_year || ctx->beg.tm_mon != beg.tm_mon || - ctx->beg.tm_mday != beg.tm_mday); - print_date(samedate ? &beg : NULL); - print_time(&beg); - - assert(field_summary < n); - assert(field_summary > FIELD_OTHER); - fprintf(stdout, "%s\n", fields[field_summary]); - - samedate = (beg.tm_year != end.tm_year || beg.tm_mon != end.tm_mon || - beg.tm_mday != end.tm_mday); - print_date(samedate ? &end : NULL); - print_time(&end); - - rows = 0; - - assert(field_location < n); - if (field_location > 0 && fields[field_location][0] != '\0') { - assert(field_summary > FIELD_OTHER); - fprintf(stdout, "%s\n", fields[field_location]); - rows++; - } - - assert(field_categories < n); - if (field_categories > 0 && fields[field_categories][0] != '\0') { - assert(field_summary > FIELD_OTHER); - if (rows > 0) { - print_date(NULL); - print_time(NULL); - } - fprintf(stdout, "%s\n", fields[field_categories]); + print_header1(&ctx->beg, &beg); + print_row(ctx, fields, i++); + print_header2(&beg, &end); + print_row(ctx, fields, i++); + while (i < ctx->fieldnum) { + print_header3(); + print_row(ctx, fields, i++); } ctx->beg = beg; ctx->end = end; } -void -set_fields_num(char **fields, size_t n) +static void +tsv_to_agenda(AgendaCtx *ctx, FILE *fp) { - struct { char *name; size_t *var; } map[] = { - { "CATEGORIES", &field_categories }, - { "LOCATION", &field_location }, - { "SUMMARY", &field_summary }, - { NULL, NULL } - }; - - debug("n=%zd", n); - for (size_t i1 = FIELD_OTHER; i1 < n; i1++) - for (size_t i2 = 0; map[i2].name != NULL; i2++) - if (strcasecmp(fields[i1], map[i2].name) == 0) - *map[i2].var = i1; - if (field_summary < FIELD_OTHER) - err(1, "missing column SUMMARY"); -} + char *ln1 = NULL, *ln2 = NULL; + size_t sz1 = 0, sz2 = 0; + + if (ctx->linenum == 0) { + char *fields[FIELD_MAX]; + + ctx->linenum++; + if (getline(&ln1, &sz1, fp) < 0) + err(1, "reading stdin: %s", strerror(errno)); + if (feof(fp)) + err(1, "empty input"); + + strchomp(ln1); + ctx->fieldnum = strsplit(ln1, fields, FIELD_MAX, "\t"); + if (ctx->fieldnum == FIELD_MAX) + err(1, "line 1: too many fields"); + if (ctx->fieldnum < FIELD_OTHER) + err(1, "line 1: not enough input columns"); + if (strcasecmp(fields[0], "TYPE") != 0) + err(1, "line 1: 1st column is not \"TYPE\""); + if (strcasecmp(fields[1], "START") != 0) + err(1, "line 1: 2nd column is not \"START\""); + if (strcasecmp(fields[2], "END") != 0) + err(1, "line 1: 3rd column is not \"END\""); + if (strcasecmp(fields[3], "RECUR") != 0) + err(1, "line 1: 4th column is not \"RECUR\""); + } -ssize_t -tsv_getline(char **fields, size_t max, char **line, size_t *sz, FILE *fp) -{ - char *s; - size_t n = 0; + for (;;) { + char *fields[FIELD_MAX]; + + ctx->linenum++; + if (getline(&ln2, &sz2, fp) < 0) + err(1, "reading stdin: %s", strerror(errno)); + if (feof(fp)) + break; + + strchomp(ln2); + if (strsplit(ln2, fields, FIELD_MAX, "\t") != ctx->fieldnum) + err(1, "line %zd: bad number of columns", + ctx->linenum, strerror(errno)); - if (getline(line, sz, fp) <= 0) - return ferror(fp) ? -1 : 0; - s = *line; - strchomp(s); + fputc('\n', stdout); + print(ctx, fields); + } + fputc('\n', stdout); - do { - if (n >= max) - return errno=E2BIG, -1; - } while ((fields[n++] = strsep(&s, "\t")) != NULL); + free(ln1); + free(ln2); +} - return n - 1; +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-f fromdate] [-t todate]\n", arg0); + exit(1); } int main(int argc, char **argv) { AgendaCtx ctx = {0}; - ssize_t nfield, n; - size_t sz = 0; - char *line = NULL, *fields[FIELDS_MAX]; + char c; - arg0 = *argv; - - if (pledge("stdio", "") < 0) - err(1, "pledge: %s", strerror(errno)); + if ((flag_from = time(NULL)) == (time_t)-1) + err(1, "time: %s", strerror(errno)); - nfield = tsv_getline(fields, FIELDS_MAX, &line, &sz, stdin); - if (nfield == -1) - err(1, "reading stdin: %s", strerror(errno)); - if (nfield == 0) - err(1, "empty input"); - if (nfield < FIELD_OTHER) - err(1, "not enough input columns"); - - set_fields_num(fields, nfield); - - for (size_t num = 1;; num++) { - n = tsv_getline(fields, FIELDS_MAX, &line, &sz, stdin); - if (n < 0) - err(1, "line %zd: reading stdin: %s", num, strerror(errno)); - if (n == 0) + arg0 = *argv; + while ((c = getopt(argc, argv, "f:t:")) > 0) { + char const *e; + + switch (c) { + case 'f': + flag_from = strtonum(optarg, INT64_MIN, INT64_MAX, &e); + if (e != NULL) + err(1, "fromdate value %s is %s", optarg, e); break; - if (n != nfield) - err(1, "line %zd: had %lld columns, wanted %lld", - num, n, nfield); - - print(&ctx, fields, n); + case 't': + flag_to = strtonum(optarg, INT64_MIN, INT64_MAX, &e); + if (e != NULL) + err(1, "todate value %s is %s", optarg, e); + break; + default: + usage(); + } } - fputc('\n', stdout); + argc -= optind; + argv += optind; - free(line); + if (pledge("stdio", "") < 0) + err(1, "pledge: %s", strerror(errno)); + tsv_to_agenda(&ctx, stdin); return 0; } DIR diff --git a/tsv2ics.awk b/tsv2ics.awk @@ -0,0 +1,106 @@ +#!/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 +} + +BEGIN { + FS = "\t" + + DTSTART["VEVENT"] = "DTSTART" + DTEND["VEVENT"] = "DTEND" + + DTEND["VTODO"] = "DUE" + + DTSTART["VJOURNAL"] = "DTSTAMP" + + DTSTART["VFREEBUSY"] = "DTSTART" + DTEND["VFREEBUSY"] = "DTEND" + + DTSTART["VALARM"] = "DTSTART" + + print "BEGIN:VCALENDAR" + print "VERSION:2.0" + print "CALSCALE:GREGORIAN" + print "METHOD:PUBLISH" +} + +NR == 1 { + if ($1 != "TYPE" || $2 != "START" || $3 != "END" || $4 != "RECUR") { + print "tsv2ics: invalid column names on first line" >/dev/stderr + exit(EXIT = 1) + } + for (i = 1; i <= NF; i++) { + FIELD[$i] = i + NAME[i] = $i + } + next +} + +{ + type = $FIELD["TYPE"] + print "BEGIN:"type + + if (type in DTSTART) { + gmtime($FIELD["START"] + offset, tm) + printf "%s:%04d%02d%02dT%02d%02d00Z\n", DTSTART[type], + tm["year"], tm["mon"], tm["mday"], tm["hour"], tm["min"] + } + + if (type in DTEND) { + gmtime($FIELD["END"] + offset, tm) + printf "%s:%04d%02d%02dT%02d%02d00Z\n", DTEND[type], + tm["year"], tm["mon"], tm["mday"], tm["hour"], tm["min"] + } + + for (i = 5; i in NAME; i++) + print$NAME[i]":"$i + + print "END:"type +} + +END { + if (EXIT) exit(EXIT) + print "" + print "END:VCALENDAR" +} DIR diff --git a/util.c b/util.c @@ -1,4 +1,5 @@ #include "util.h" +#include <assert.h> #include <errno.h> #include <stdint.h> #include <stdlib.h> @@ -123,6 +124,19 @@ strappend(char **dp, char const *s) return *dp; } +size_t +strsplit(char *s, char **array, size_t len, char const *sep) +{ + size_t i; + + assert(len > 0); + for (i = 0; i < len; i++) + if ((array[i] = strsep(&s, sep)) == NULL) + break; + array[len - 1] = NULL; + return i; +} + /** memory **/ void * DIR diff --git a/util.h b/util.h @@ -2,9 +2,12 @@ #define UTIL_H #include <stddef.h> +#include <stdio.h> #include <stdarg.h> #include <time.h> +#define LEN(x) (sizeof (x) / sizeof *(x)) + /** logging **/ extern char *arg0; void err(int, char const *fmt, ...); @@ -18,6 +21,7 @@ void strchomp(char *); char *strappend(char **, char const *); size_t strlcat(char *, char const *, size_t); long long strtonum(const char *, long long, long long, const char **); +size_t strsplit(char *, char **, size_t, char const *); /** memory **/ void *reallocarray(void *, size_t, size_t);