remove unused utilities and flatten the source some more - 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 24ae7d2759496b7907cce29f0c26697950453ff5
(DIR) parent 742516775b1d9b12e4c8893114b7cc5a363884ad
(HTM) 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);