remove unused utilities and flatten the source some more - ics2txt - convert ic… | |
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
--- | |
commit 24ae7d2759496b7907cce29f0c26697950453ff5 | |
parent 742516775b1d9b12e4c8893114b7cc5a363884ad | |
Author: Josuah Demangeon <[email protected]> | |
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(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -1,5 +1,6 @@ | |
*.o | |
/ics2tsv | |
/ics2tree | |
+/tsv2ics | |
/tsv2agenda | |
/ics2txt-[0-9]* | |
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 [email protected] $@ | |
+ chmod +x $@ | |
+ | |
${OBJ}: ${HDR} | |
${BIN}: ${OBJ} ${BIN:=.o} | |
${CC} ${LDFLAGS} -o $@ [email protected] ${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} | |
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 | |
-} | |
diff --git a/bin/ics2txt b/bin/ics2txt | |
@@ -1,2 +0,0 @@ | |
-#!/bin/sh -e | |
-exec ics2tsv "$@" | exec tsv2tcal | |
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" | |
-} | |
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) == NUL… | |
+ if (strappend(&block.fields[i], flag_sep) == N… | |
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); | |
} | |
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.… | |
- 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(er… | |
- 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; | |
} | |
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/stde… | |
+ 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" | |
+} | |
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 * | |
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); |