Introduction
Introduction Statistics Contact Development Disclaimer Help
initial C implementation - ics2txt - convert icalendar .ics file to plain text
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws…
Log
Files
Refs
Tags
README
---
commit 94bccd0b9ea7049ebeec4fcf2416f6f0b7d221b5
parent 78e0184b4deb29669bfde9a66fc945968845ced8
Author: Josuah Demangeon <[email protected]>
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(-)
---
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+*.o
+ics2tsv
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 $@ [email protected] ${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
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
+}
diff --git a/ics2txt b/bin/ics2txt
diff --git a/tcal2tsv b/bin/tcal2tsv
diff --git a/tsv2ics b/bin/tsv2ics
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("")
+}
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
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
-}
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;
+}
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);
+}
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
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);
+}
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
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);
+}
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
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);
+}
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
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("")
-}
You are viewing proxied material from bitreich.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.