initial C implementation - 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 94bccd0b9ea7049ebeec4fcf2416f6f0b7d221b5 DIR parent 78e0184b4deb29669bfde9a66fc945968845ced8 HTML Author: Josuah Demangeon <me@josuah.net> Date: Sat, 27 Jun 2020 20:31:09 +0200 initial C implementation Diffstat: A .gitignore | 2 ++ M Makefile | 24 +++++++++++++++++++----- A bin/ics2tsv | 141 +++++++++++++++++++++++++++++++ R ics2txt -> bin/ics2txt | 0 R tcal2tsv -> bin/tcal2tsv | 0 R tsv2ics -> bin/tsv2ics | 0 A bin/tsv2tcal | 91 +++++++++++++++++++++++++++++++ A doc/index.md | 11 +++++++++++ D ics2tsv | 138 ------------------------------ A ics2tsv.c | 62 +++++++++++++++++++++++++++++++ A src/ical.c | 108 +++++++++++++++++++++++++++++++ A src/ical.h | 25 +++++++++++++++++++++++++ A src/log.c | 89 +++++++++++++++++++++++++++++++ A src/log.h | 15 +++++++++++++++ A src/map.c | 102 +++++++++++++++++++++++++++++++ A src/map.h | 23 +++++++++++++++++++++++ A src/util.c | 74 +++++++++++++++++++++++++++++++ A src/util.h | 13 +++++++++++++ D tsv2tcal | 91 ------------------------------- 19 files changed, 775 insertions(+), 234 deletions(-) --- DIR diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +*.o +ics2tsv DIR diff --git a/Makefile b/Makefile @@ -1,23 +1,37 @@ NAME = ics2txt VERSION = 0.1 -BIN = ics2tsv tsv2tcal tcal2tsv tsv2ics ics2txt - +W = -Wall -Wextra -std=c99 --pedantic +I = -Isrc +D = -D_POSIX_C_SOURCE=200811L -DVERSION='"${VERSION}"' +CFLAGS = $I $D $W -g PREFIX = /usr/local MANPREFIX = ${PREFIX}/man +SRC = src/ical.c src/map.c src/util.c src/log.c +HDR = src/ical.h src/map.h src/util.h src/log.h +OBJ = ${SRC:.c=.o} +BIN = ics2tsv + all: ${BIN} +.c.o: + ${CC} -c ${CFLAGS} -o $@ $< + +${OBJ}: ${HDR} +${BIN}: ${OBJ} ${BIN:=.o} + ${CC} ${LDFLAGS} -o $@ $@.o ${OBJ} + clean: - rm -rf ${NAME}-${VERSION} *.gz + rm -rf *.o */*.o ${BIN} ${NAME}-${VERSION} *.gz install: mkdir -p ${DESTDIR}$(PREFIX)/bin - cp $(BIN) ${DESTDIR}$(PREFIX)/bin + cp bin/* $(BIN) ${DESTDIR}$(PREFIX)/bin mkdir -p ${DESTDIR}$(MANPREFIX)/man1 cp doc/*.1 ${DESTDIR}$(MANPREFIX)/man1 dist: clean mkdir -p ${NAME}-${VERSION} - cp -r README Makefile doc ${BIN} ${NAME}-${VERSION} + cp -r README Makefile doc bin ${SRC} ${NAME}-${VERSION} tar -cf - ${NAME}-${VERSION} | gzip -c >${NAME}-${VERSION}.tar.gz DIR diff --git a/bin/ics2tsv b/bin/ics2tsv @@ -0,0 +1,141 @@ +#!/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_tzid(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/ics2txt b/bin/ics2txt DIR diff --git a/tcal2tsv b/bin/tcal2tsv DIR diff --git a/tsv2ics b/bin/tsv2ics DIR diff --git a/bin/tsv2tcal b/bin/tsv2tcal @@ -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) +} + +function gmtime(sec, tm) +{ + 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 localtime(sec, tm, + tz, h, m) +{ + return gmtime(sec + TZ, tm) +} + +BEGIN { + "exec date +%z" | getline tz + close("exec date +%z") + TZ = substr(tz, 1, 1) substr(tz, 2, 2)*3600 + substr(tz, 4, 2)*60 + + print("TZ" tz) + + FS = "\t" +} + +NR == 1 { + for (i = 1; i <= NF; i++) + name[i] = $i + next +} + +{ + for (i = 1; i <= NF; i++) + ev[name[i]] = $i + + print("") + + localtime(ev["beg"] + offset, tm) + printf("%04d-%02d-%02d %02d:%02d\n", + tm["year"], tm["mon"], tm["mday"], tm["hour"], tm["min"]) + delete ev["beg"] + + localtime(ev["end"] + offset, tm) + printf("%04d-%02d-%02d %02d:%02d\n", + tm["year"], tm["mon"], tm["mday"], tm["hour"], tm["min"]) + delete ev["end"] + + for (i = 1; i <= NF; i++) { + if (name[i] in ev && ev[name[i]]) + printf(" %s: %s\n", name[i], ev[name[i]]) + } + + delete ev +} + +END { + print("") +} DIR diff --git a/doc/index.md b/doc/index.md @@ -0,0 +1,11 @@ +ics2txt +======= +Set of tools to work with the popular iCalendar format and converting to even +simpler TSV and text forms. + +Parsing have been tested with the following input formats (sample account +created for testing): + +* Zoom meetings generated events +* FOSDEM events, like <https://fosdem.org/2020/schedule/ical> +* Google Calendar DIR diff --git a/ics2tsv b/ics2tsv @@ -1,138 +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_to_epoch(content, param, - tz, cmd) -{ - tz = (param["TZID"] ? param["TZID"] : vcalendar["TZID"]) - gsub("'", "", tz) - - cmd = "TZ='"tz"' date +%z" - cmd | getline tz - close(cmd) - - tz = substr(tz, 1, 1) substr(tz, 2, 2)*3600 + substr(tz, 4, 2)*60 - - tm["year"] = substr(content["value"], 1, 4) - tm["mon"] = substr(content["value"], 5, 2) - tm["mday"] = substr(content["value"], 8, 2) - tm["hour"] = substr(content["value"], 12, 2) - tm["min"] = substr(content["value"], 15, 2) - tm["sec"] = substr(content["value"], 18, 2) - - return timegm(tm) + tz -} - -BEGIN { - split("DTSTART DTEND CATEGORIES LOCATION SUMMARY DESCRIPTION", - 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") { - vcalendar[content["name"]] = 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/ics2tsv.c b/ics2tsv.c @@ -0,0 +1,62 @@ +#include <stdio.h> + +#include "ical.h" +#include "log.h" +#include "util.h" + +int +print_ical_to_tsv(FILE *fp) +{ + struct ical_contentline contentline; + char *line = NULL; + size_t sz = 0; + ssize_t r; + + ical_init_contentline(&contentline); + + while ((r = ical_read_line(&line, &sz, fp)) > 0) { + debug("readling line \"%s\"", line); + if (ical_parse_contentline(&contentline, line) < 0) + die("parsing line \"%s\"", line); + } + return r; +} + +void +print_header(void) +{ + char *fields[] = { "", NULL }; + + printf("%s\t%s", "beg", "end"); + + for (char **f = fields; *f != NULL; f++) { + fprintf(stdout, "\t%s", *f); + } + fprintf(stdout, "\n"); +} + +int +main(int argc, char **argv) +{ + print_header(); + + log_arg0 = *argv++; + + if (*argv == NULL) { + if (print_ical_to_tsv(stdin) < 0) + die("converting stdin"); + } + + for (; *argv != NULL; argv++, argc--) { + FILE *fp; + + info("converting \"%s\"", *argv); + if ((fp = fopen(*argv, "r")) == NULL) + die("opening %s", *argv); + if (print_ical_to_tsv(fp) < 0) + die("converting %s", *argv); + fclose(fp); + } + + return 0; +} DIR diff --git a/src/ical.c b/src/ical.c @@ -0,0 +1,108 @@ +#include "ical.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +int +ical_read_line(char **line, size_t *sz, FILE *fp) +{ + ssize_t r; + char *tail = NULL; + size_t tail_sz = 0; + int c, ret = -1; + + if ((r = getline(line, sz, fp)) <= 0) + return r; + strchomp(*line); + + for (;;) { + if ((c = fgetc(fp)) == EOF) { + ret = ferror(fp) ? -1 : 0; + goto end; + } + if (c != ' ') + break; + if ((r = getline(&tail, &tail_sz, fp)) <= 0) { + ret = r; + goto end; + } + strchomp(tail); + if (strappend(line, tail) < 0) + goto end; + } + + ret = 1; +end: + free(tail); + ungetc(c, fp); + return ret; +} + +int +ical_parse_contentline(struct ical_contentline *contentline, char *line) +{ + char *column, *equal, *param, *cp; + size_t sz; + + debug("0"); + + if ((column = strchr(line, ':')) == NULL) + return -1; + *column = '\0'; + + { + size_t len; + + debug("1.1"); + len = strlen(column + 1); + debug("1.2"); + } + + + if ((contentline->value = strdup(column + 1)) == NULL) + return -1; + + debug("2"); + + cp = strchr(line, ';'); + cp = (cp == NULL) ? (NULL) : (cp + 1); + + debug("3"); + + while ((param = strsep(&cp, ";")) != NULL) { + if ((equal = strchr(param, '=')) == NULL) + return -1; + *equal = '\0'; + + if (map_set(&contentline->param, param, equal + 1) < 0) + return -1; + } + + debug("4"); + + sz = sizeof(contentline->name); + if (strlcpy(contentline->name, line, sz) >= sz) + return errno=EMSGSIZE, -1; + + debug("5"); + + return 0; +} + +void +ical_init_contentline(struct ical_contentline *contentline) +{ + memset(contentline, 0, sizeof(*contentline)); +} + + +void +ical_free_contentline(struct ical_contentline *contentline) +{ + map_free(&contentline->param); + free(contentline->value); +} DIR diff --git a/src/ical.h b/src/ical.h @@ -0,0 +1,25 @@ +#ifndef ICAL_H +#define ICAL_H + +#include <stdio.h> +#include <time.h> + +#include "map.h" + +struct ical_vevent { + time_t beg, end; + struct map map; +}; + +struct ical_contentline { + char name[32], *value; + struct map param; +}; + +/** src/ical.c **/ +int ical_read_line(char **line, size_t *sz, FILE *fp); +int ical_parse_contentline(struct ical_contentline *contentline, char *line); +void ical_init_contentline(struct ical_contentline *contentline); +void ical_free_contentline(struct ical_contentline *contentline); + +#endif DIR diff --git a/src/log.c b/src/log.c @@ -0,0 +1,89 @@ +#include "log.h" + +#include <assert.h> +#include <string.h> + +/* + * log.c - log to standard error according to the log level + * + * Instead of logging to syslog, delegate logging to a separate + * tool, such as FreeBSD's daemon(8), POSIX's logger(1). + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#define LOG_DEFAULT 3 /* info */ + +int log_level = -1; +char *log_arg0 = NULL; + +void +vlogf(int level, char const *flag, char const *fmt, va_list va) +{ + char *env; + int e = errno; + + if (log_level < 0) { + env = getenv("LOG"); + log_level = (env == NULL ? 0 : atoi(env)); + log_level = (log_level > 0 ? log_level : LOG_DEFAULT); + } + + if (log_level < level) + return; + + if (log_arg0 != NULL) + fprintf(stderr, "%s: ", log_arg0); + + fprintf(stderr, "%s: ", flag); + vfprintf(stderr, fmt, va); + + if (e != 0) + fprintf(stderr, ": %s", strerror(e)); + + fprintf(stderr, "\n"); + fflush(stderr); +} + +void +die(char const *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + vlogf(1, "error", fmt, va); + va_end(va); + exit(1); +} + +void +warn(char const *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + vlogf(2, "warn", fmt, va); + va_end(va); +} + +void +info(char const *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + vlogf(3, "info", fmt, va); + va_end(va); +} + +void +debug(char const *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + vlogf(4, "debug", fmt, va); + va_end(va); +} DIR diff --git a/src/log.h b/src/log.h @@ -0,0 +1,15 @@ +#ifndef LOG_H +#define LOG_H + +#include <stdarg.h> + +/** src/log.c **/ +int log_level; +char *log_arg0; +void vlogf(int level, char const *flag, char const *fmt, va_list va); +void die(char const *fmt, ...); +void warn(char const *fmt, ...); +void info(char const *fmt, ...); +void debug(char const *fmt, ...); + +#endif DIR diff --git a/src/map.c b/src/map.c @@ -0,0 +1,102 @@ +#include "map.h" + +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static int +map_cmp(void const *v1, void const *v2) +{ + struct map_entry const *e1 = v1, *e2 = v2; + + return strcmp(e1->key, e2->key); +} + +void * +map_get(struct map *map, char *key) +{ + struct map_entry *entry, k = { .key = key }; + size_t sz; + + sz = sizeof(*map->entry); + if ((entry = bsearch(&k, map->entry, map->len, sz, map_cmp)) == NULL) + return NULL; + return entry->value; +} + +int +map_set(struct map *map, char *key, void *value) +{ + struct map_entry *insert, *e; + size_t i, sz; + void *v; + + debug("%s: key=%s len=%zd", __func__, key, map->len); + + for (i = 0; i < map->len; i++) { + int cmp = strcmp(key, map->entry[i].key); + debug("cmp(%s,%s)=%d", key, map->entry[i].key, cmp); + + if (cmp == 0) { + map->entry[i].value = value; + return 0; + } + if (cmp < 0) + break; + } + + sz = sizeof(*map->entry); + if ((v = reallocarray(map->entry, map->len + 1, sz)) == NULL) + return -1; + map->entry = v; + map->len++; + + insert = map->entry + i; + e = map->entry + map->len - 1 - 1; + for (; e >= insert; e--) + e[1].key = e[0].key; + + if ((insert->key = strdup(key)) == NULL) + return -1; + insert->value = value; + + return 0; +} + +int +map_del(struct map *map, char *key) +{ + size_t i; + + for (i = 0; i < map->len; i++) { + int cmp = strcmp(key, map->entry[i].key); + + if (cmp == 0) + break; + if (cmp < 0) + return -1; + } + if (i == map->len) + return -1; + + map->len--; + for (; i < map->len; i++) + map->entry[i] = map->entry[i + 1]; + return 0; +} + +void +map_free_values(struct map *map) +{ + for (size_t i = 0; i < map->len; i++) + free(map->entry[map->len - 1].value); +} + +void +map_free(struct map *map) +{ + for (size_t i = 0; i < map->len; i++) + free(map->entry[map->len - 1].key); + free(map->entry); +} DIR diff --git a/src/map.h b/src/map.h @@ -0,0 +1,23 @@ +#ifndef MAP_H +#define MAP_H + +#include <stddef.h> + +struct map_entry { + char *key; + void *value; +}; + +struct map { + struct map_entry *entry; + size_t len; +}; + +/** src/map.c **/ +void * map_get(struct map *map, char *key); +int map_set(struct map *map, char *key, void *value); +int map_del(struct map *map, char *key); +void map_free_values(struct map *map); +void map_free(struct map *map); + +#endif DIR diff --git a/src/util.c b/src/util.c @@ -0,0 +1,74 @@ +#include "util.h" + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +size_t +strlcpy(char *buf, char const *str, size_t sz) +{ + size_t len, cpy; + + cpy = ((len = strlen(str)) > sz) ? (sz) : (len); + memcpy(buf, str, cpy + 1); + buf[sz - 1] = '\0'; + return len; +} + +char * +strsep(char **str_p, char const *sep) +{ + char *s, *prev; + + if (*str_p == NULL) + return NULL; + + for (s = prev = *str_p; strchr(sep, *s) == NULL; s++) + continue; + + if (*s == '\0') { + *str_p = NULL; + } else { + *s = '\0'; + *str_p = s + 1; + } + return prev; +} + +void +strchomp(char *line) +{ + size_t len; + + len = strlen(line); + if (len > 0 && line[len - 1] == '\n') + line[len-- - 1] = '\0'; + if (len > 0 && line[len - 1] == '\r') + line[len-- - 1] = '\0'; +} + +int +strappend(char **base_p, char const *s) +{ + size_t base_len, s_len; + void *v; + + base_len = strlen(*base_p); + s_len = strlen(s); + + if ((v = realloc(*base_p, base_len + s_len + 1)) == NULL) + return -1; + + *base_p = v; + memcpy(*base_p + base_len, s, s_len + 1); + return 0; +} + +void * +reallocarray(void *buf, size_t len, size_t sz) +{ + if (SIZE_MAX / len < sz) + return errno=ERANGE, NULL; + return realloc(buf, len * sz); +} DIR diff --git a/src/util.h b/src/util.h @@ -0,0 +1,13 @@ +#ifndef UTIL_H +#define UTIL_H + +#include <stddef.h> + +/** src/util.c **/ +size_t strlcpy(char *buf, char const *str, size_t sz); +char * strsep(char **str_p, char const *sep); +void strchomp(char *line); +int strappend(char **base_p, char const *s); +void * reallocarray(void *buf, size_t len, size_t sz); + +#endif DIR diff --git a/tsv2tcal b/tsv2tcal @@ -1,91 +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 gmtime(sec, tm) -{ - 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 localtime(sec, tm, - tz, h, m) -{ - return gmtime(sec + TZ, tm) -} - -BEGIN { - "date +%z" | getline tz - close("date +%z") - TZ = substr(tz, 1, 1) substr(tz, 2, 2)*3600 + substr(tz, 4, 2)*60 - - print("TZ" tz) - - FS = "\t" -} - -NR == 1 { - for (i = 1; i <= NF; i++) - name[i] = $i - next -} - -{ - for (i = 1; i <= NF; i++) - ev[name[i]] = $i - - print("") - - localtime(ev["beg"] + offset, tm) - printf("%04d-%02d-%02d %02d:%02d\n", - tm["year"], tm["mon"], tm["mday"], tm["hour"], tm["min"]) - delete ev["beg"] - - localtime(ev["end"] + offset, tm) - printf("%04d-%02d-%02d %02d:%02d\n", - tm["year"], tm["mon"], tm["mday"], tm["hour"], tm["min"]) - delete ev["end"] - - for (i = 1; i <= NF; i++) { - if (name[i] in ev && ev[name[i]]) - printf(" %s: %s\n", name[i], ev[name[i]]) - } - - delete ev -} - -END { - print("") -}