replace the not-so-useful tcal format by a plain text output - ics2txt - conver… | |
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
--- | |
commit 742516775b1d9b12e4c8893114b7cc5a363884ad | |
parent 5a6d05cc7d0f248c84b7f22bd1262bd9fdc9e750 | |
Author: Josuah Demangeon <[email protected]> | |
Date: Sun, 20 Jun 2021 12:12:53 +0200 | |
replace the not-so-useful tcal format by a plain text output | |
The input format will be an email open by a text editor, spawned by | |
some script. | |
Diffstat: | |
M .gitignore | 1 + | |
M Makefile | 4 ++-- | |
M README | 6 ++---- | |
M base64.c | 3 --- | |
D bin/tcal2tsv | 85 -----------------------------… | |
D bin/tsv2tcal | 91 -----------------------------… | |
M ical.c | 3 --- | |
M ical.h | 8 +++----- | |
M ics2tree.c | 11 +++++++---- | |
M ics2tsv.c | 19 +++++++++++++------ | |
A strtonum.c | 66 +++++++++++++++++++++++++++++… | |
D tcal.5 | 61 -----------------------------… | |
A tsv2agenda.c | 193 +++++++++++++++++++++++++++++… | |
M util.c | 8 +++----- | |
M util.h | 3 ++- | |
15 files changed, 292 insertions(+), 270 deletions(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -1,4 +1,5 @@ | |
*.o | |
/ics2tsv | |
/ics2tree | |
+/tsv2agenda | |
/ics2txt-[0-9]* | |
diff --git a/Makefile b/Makefile | |
@@ -2,7 +2,7 @@ NAME = ics2txt | |
VERSION = 0.2 | |
W = -Wall -Wextra -std=c99 --pedantic | |
-D = -D_POSIX_C_SOURCE=200811L -DVERSION='"${VERSION}"' | |
+D = -D_POSIX_C_SOURCE=200811L -D_BSD_SOURCE -DVERSION='"${VERSION}"' | |
CFLAGS = $D $W -g | |
PREFIX = /usr/local | |
MANPREFIX = ${PREFIX}/man | |
@@ -10,7 +10,7 @@ MANPREFIX = ${PREFIX}/man | |
SRC = ical.c base64.c util.c | |
HDR = ical.h base64.h util.h | |
OBJ = ${SRC:.c=.o} | |
-BIN = ics2tree ics2tsv | |
+BIN = ics2tree ics2tsv tsv2agenda | |
MAN1 = ics2txt.1 ics2tsv.1 | |
MAN5 = tcal.5 | |
diff --git a/README b/README | |
@@ -7,11 +7,9 @@ The current implementation uses [awk](//josuah.net/wiki/awk/) … | |
rather complete implementation of iCalendar, without memory leak or crash, is | |
already there, and used for the `ics2tree` linting tool. | |
-Plans include to have an `ics2json` tool for a 1:1 mapping of iCalendar, and | |
-have `ics2tsv` a more general-purpose tool with user-chosen column fields. | |
+`ics2tsv` converts the iCalendar data to an easier-to-parse TSV format. | |
-So far, Awk-based parsing have been tested with the following input formats | |
-(sample account created for testing): | |
+So far, Awk-based parsing have been tested with the following inputs: | |
* Zoom meetings generated events | |
* FOSDEM events, like <https://fosdem.org/2020/schedule/ical> | |
diff --git a/base64.c b/base64.c | |
@@ -1,12 +1,9 @@ | |
#include "base64.h" | |
- | |
#include <assert.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <string.h> | |
-#include <stdio.h> | |
- | |
static char encode_map[64] = | |
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
diff --git a/bin/tcal2tsv b/bin/tcal2tsv | |
@@ -1,85 +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 maketime(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 text_to_epoch(str, tz, | |
- tm) | |
-{ | |
- tm["year"] = substr(str, 1, 4) | |
- tm["mon"] = substr(str, 6, 2) | |
- tm["mday"] = substr(str, 9, 2) | |
- tm["hour"] = substr(str, 12, 2) | |
- tm["min"] = substr(str, 15, 2) | |
- return maketime(tm) - tz | |
-} | |
- | |
-BEGIN { | |
- FIELDS = "beg end cat loc sum des" | |
- split(FIELDS, fields, " ") | |
- | |
- for (i = 1; i in fields; i++) { | |
- pos[fields[i]] = i | |
- printf("%s%s", (i > 1 ? "\t" : ""), fields[i]) | |
- } | |
- printf("\n") | |
-} | |
- | |
-{ | |
- gsub(/\t/, " ") | |
-} | |
- | |
-/^TZ[+-]/ { | |
- TZ = substr($1, 3, 1) substr($0, 4, 2)*3600 + substr($0, 6, 2)*60 | |
- while (getline && $0 ~ /^$/) | |
- continue | |
-} | |
- | |
-/^[0-9]+-[0-9]+-[0-9]+/ { | |
- if ("beg" in ev) | |
- ev["end"] = text_to_epoch($0, TZ) | |
- else | |
- ev["beg"] = text_to_epoch($0, TZ) | |
- next | |
-} | |
- | |
-/^ / { | |
- tag = $1 | |
- sub("^ *[^ :]+: *", "") | |
- sub(":$", "", tag) | |
- ev[tag] = $0 | |
- next | |
-} | |
- | |
-/^$/ { | |
- for (i = 1; i in fields; i++) | |
- printf("%s%s", (i > 1 ? "\t" : ""), ev[fields[i]]) | |
- printf("\n") | |
- delete ev | |
-} | |
diff --git a/bin/tsv2tcal b/bin/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 { | |
- "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/ical.c b/ical.c | |
@@ -1,5 +1,4 @@ | |
#include "ical.h" | |
- | |
#include <assert.h> | |
#include <ctype.h> | |
#include <errno.h> | |
@@ -7,7 +6,6 @@ | |
#include <stdlib.h> | |
#include <string.h> | |
#include <strings.h> | |
- | |
#include "util.h" | |
#include "base64.h" | |
@@ -329,7 +327,6 @@ ical_parse(IcalParser *p, FILE *fp) | |
} while (l > 0 && (err = ical_parse_contentline(p, contentline)… | |
free(contentline); | |
- free(line); | |
if (err == 0 && p->current != p->stack) | |
return ical_err(p, "more BEGIN: than END:"); | |
diff --git a/ical.h b/ical.h | |
@@ -6,9 +6,6 @@ | |
#define ICAL_STACK_SIZE 10 | |
-typedef struct IcalParser IcalParser; | |
-typedef struct IcalStack IcalStack; | |
- | |
typedef enum { | |
ICAL_BLOCK_VEVENT, | |
ICAL_BLOCK_VTODO, | |
@@ -18,11 +15,12 @@ typedef enum { | |
ICAL_BLOCK_OTHER, | |
} IcalBlock; | |
-struct IcalStack { | |
+typedef struct { | |
char name[32]; | |
char tzid[32]; | |
-}; | |
+} IcalStack; | |
+typedef struct IcalParser IcalParser; | |
struct IcalParser { | |
/* function called while parsing in this order */ | |
int (*fn_field_name)(IcalParser *, char *); | |
diff --git a/ics2tree.c b/ics2tree.c | |
@@ -2,10 +2,13 @@ | |
#include <stdlib.h> | |
#include <string.h> | |
#include <strings.h> | |
- | |
#include "ical.h" | |
#include "util.h" | |
+#ifndef __OpenBSD__ | |
+#define pledge(...) 0 | |
+#endif | |
+ | |
static void | |
print_ruler(int level) | |
{ | |
@@ -76,7 +79,7 @@ main(int argc, char **argv) | |
if (*argv == NULL) { | |
if (ical_parse(&p, stdin) < 0) | |
- err("parsing stdin:%d: %s", p.linenum, p.errmsg); | |
+ err(1, "parsing stdin:%d: %s", p.linenum, p.errmsg); | |
} | |
for (; *argv != NULL; argv++, argc--) { | |
@@ -84,9 +87,9 @@ main(int argc, char **argv) | |
debug("converting \"%s\"", *argv); | |
if ((fp = fopen(*argv, "r")) == NULL) | |
- err("opening %s", *argv); | |
+ err(1, "opening %s", *argv); | |
if (ical_parse(&p, fp) < 0) | |
- err("parsing %s:%d: %s", *argv, p.linenum, p.errmsg); | |
+ err(1, "parsing %s:%d: %s", *argv, p.linenum, p.errmsg… | |
fclose(fp); | |
} | |
return 0; | |
diff --git a/ics2tsv.c b/ics2tsv.c | |
@@ -5,10 +5,13 @@ | |
#include <strings.h> | |
#include <time.h> | |
#include <unistd.h> | |
- | |
#include "ical.h" | |
#include "util.h" | |
+#ifndef __OpenBSD__ | |
+#define pledge(...) 0 | |
+#endif | |
+ | |
#define FIELDS_MAX 128 | |
typedef struct Field Field; | |
@@ -155,6 +158,9 @@ main(int argc, char **argv) | |
arg0 = *argv; | |
+ if (pledge("stdio rpath", "") < 0) | |
+ err(1, "pledge: %s", strerror(errno)); | |
+ | |
p.fn_field_name = fn_field_name; | |
p.fn_block_begin = fn_block_begin; | |
p.fn_block_end = fn_block_end; | |
@@ -186,12 +192,12 @@ main(int argc, char **argv) | |
i = 0; | |
do { | |
if (i >= sizeof fields / sizeof *fields - 1) | |
- err("too many fields specified with -o flag"); | |
+ err(1, "too many fields specified with -o flag"); | |
} while ((fields[i++] = strsep(&flag_f, ",")) != NULL); | |
fields[i] = NULL; | |
if (flag_1) { | |
- printf("%s\t%s\t%s", "TYPE", "BEG", "END"); | |
+ printf("%s\t%s\t%s\t%s", "TYPE", "BEG", "END", "RECUR"); | |
for (i = 0; fields[i] != NULL; i++) | |
printf("\t%s", fields[i]); | |
fputc('\n', stdout); | |
@@ -200,16 +206,17 @@ main(int argc, char **argv) | |
if (*argv == NULL || strcmp(*argv, "-") == 0) { | |
debug("converting *stdin*"); | |
if (ical_parse(&p, stdin) < 0) | |
- err("parsing *stdin*:%d: %s", p.linenum, p.errmsg); | |
+ err(1, "parsing *stdin*:%d: %s", p.linenum, p.errmsg); | |
} | |
for (; *argv != NULL; argv++, argc--) { | |
FILE *fp; | |
debug("converting \"%s\"", *argv); | |
if ((fp = fopen(*argv, "r")) == NULL) | |
- err("opening %s: %s", *argv, strerror(errno)); | |
+ err(1, "opening %s: %s", *argv, strerror(errno)); | |
if (ical_parse(&p, fp) < 0) | |
- err("parsing %s:%d: %s", *argv, p.linenum, p.errmsg); | |
+ err(1, "parsing %s:%d: %s", *argv, p.linenum, p.errmsg… | |
fclose(fp); | |
} | |
+ | |
return 0; | |
} | |
diff --git a/strtonum.c b/strtonum.c | |
@@ -0,0 +1,66 @@ | |
+/* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ … | |
+ | |
+/* | |
+ * Copyright (c) 2004 Ted Unangst and Todd Miller | |
+ * All rights reserved. | |
+ * | |
+ * Permission to use, copy, modify, and distribute this software for any | |
+ * purpose with or without fee is hereby granted, provided that the above | |
+ * copyright notice and this permission notice appear in all copies. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
+ */ | |
+ | |
+#include <errno.h> | |
+#include <limits.h> | |
+#include <stdlib.h> | |
+ | |
+#define INVALID 1 | |
+#define TOOSMALL 2 | |
+#define TOOLARGE 3 | |
+ | |
+long long | |
+strtonum(const char *numstr, long long minval, long long maxval, | |
+ const char **errstrp) | |
+{ | |
+ long long ll = 0; | |
+ int error = 0; | |
+ char *ep; | |
+ struct errval { | |
+ const char *errstr; | |
+ int err; | |
+ } ev[4] = { | |
+ { NULL, 0 }, | |
+ { "invalid", EINVAL }, | |
+ { "too small", ERANGE }, | |
+ { "too large", ERANGE }, | |
+ }; | |
+ | |
+ ev[0].err = errno; | |
+ errno = 0; | |
+ if (minval > maxval) { | |
+ error = INVALID; | |
+ } else { | |
+ ll = strtoll(numstr, &ep, 10); | |
+ if (numstr == ep || *ep != '\0') | |
+ error = INVALID; | |
+ else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) | |
+ error = TOOSMALL; | |
+ else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) | |
+ error = TOOLARGE; | |
+ } | |
+ if (errstrp != NULL) | |
+ *errstrp = ev[error].errstr; | |
+ errno = ev[error].err; | |
+ if (error) | |
+ ll = 0; | |
+ | |
+ return (ll); | |
+} | |
+DEF_WEAK(strtonum); | |
diff --git a/tcal.5 b/tcal.5 | |
@@ -1,61 +0,0 @@ | |
-.Dd $Mdocdate: March 05 2020$ | |
-.Dt TCAL 5 | |
-.Os | |
-. | |
-. | |
-.Sh NAME | |
-. | |
-.Nm tcal | |
-.Nd plaintext calendar for editing by hand on the go | |
-. | |
-. | |
-.Sh DESCRIPTION | |
-. | |
-The first line contain | |
-.Dq TZ+HHMM | |
-with | |
-.Dq +HHMM | |
-as returned by | |
-.D1 $ date +%z . | |
-. | |
-.Pp | |
-Then empty line delimited event entries follow, with for each: | |
-One line with the start date, one line with the end date, | |
-formatted like: | |
-.Dq %Y-%m-%d %H:%M | |
-. | |
-.Pp | |
-Then one line per attribute, each formatted with: | |
-optional space, attribute name, colon, | |
-optional space, and attribute content, | |
-end of line. | |
-. | |
-. | |
-.Sh EXAMPLES | |
-. | |
-.Bd -literal | |
-TZ+0200 | |
- | |
-2021-06-28 00:00 | |
-2021-06-05 00:00 | |
- loc: 950-0994, Chuo Ward, Niigata, Japan | |
- sum: summer holidays | |
- | |
-2021-06-29 13:30 | |
-2021-06-29 15:00 | |
- loc: online, irc.bitreich.org, #bitreich-en | |
- sum: bitreich irc invitation | |
- des: at this moment like all other moment, everyone invited on IRC | |
-.Ed | |
-. | |
-. | |
-.Sh SEE ALSO | |
-. | |
-.Xr cal 1 , | |
-.Xr calendar 1 | |
-. | |
-. | |
-.Sh AUTHORS | |
-. | |
-.An Josuah Demangeon | |
-.Aq Mt [email protected] | |
diff --git a/tsv2agenda.c b/tsv2agenda.c | |
@@ -0,0 +1,193 @@ | |
+#include <assert.h> | |
+#include <errno.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <stdint.h> | |
+#include <string.h> | |
+#include <unistd.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, | |
+}; | |
+ | |
+typedef struct { | |
+ struct tm beg, end; | |
+} AgendaCtx; | |
+ | |
+static size_t field_categories = 0; | |
+static size_t field_location = 0; | |
+static size_t field_summary = 0; | |
+ | |
+void | |
+print_date(struct tm *tm) | |
+{ | |
+ if (tm == NULL) { | |
+ fprintf(stdout, "%11s", ""); | |
+ } else { | |
+ char buf[128]; | |
+ if (strftime(buf, sizeof buf, "%Y-%m-%d", tm) == 0) | |
+ err(1, "strftime: %s", strerror(errno)); | |
+ fprintf(stdout, "%s ", buf); | |
+ } | |
+} | |
+ | |
+void | |
+print_time(struct tm *tm) | |
+{ | |
+ if (tm == NULL) { | |
+ fprintf(stdout, "%5s ", ""); | |
+ } else { | |
+ char buf[128]; | |
+ if (strftime(buf, sizeof buf, "%H:%M", tm) == 0) | |
+ err(1, "strftime: %s", strerror(errno)); | |
+ fprintf(stdout, "%5s ", buf); | |
+ } | |
+} | |
+ | |
+void | |
+print(AgendaCtx *ctx, char **fields, size_t n) | |
+{ | |
+ struct tm beg = {0}, end = {0}; | |
+ time_t t; | |
+ char const *e; | |
+ int rows, samedate; | |
+ | |
+ t = strtonum(fields[FIELD_BEG], 0, UINT32_MAX, &e); | |
+ if (e != NULL) | |
+ err(1, "start time %s is %s", fields[FIELD_BEG], e); | |
+ localtime_r(&t, &beg); | |
+ | |
+ t = strtonum(fields[FIELD_END], 0, UINT32_MAX, &e); | |
+ if (e != NULL) | |
+ err(1, "end time %s is %s", fields[FIELD_END], e); | |
+ 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]); | |
+ } | |
+ | |
+ ctx->beg = beg; | |
+ ctx->end = end; | |
+} | |
+ | |
+void | |
+set_fields_num(char **fields, size_t n) | |
+{ | |
+ 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"); | |
+} | |
+ | |
+ssize_t | |
+tsv_getline(char **fields, size_t max, char **line, size_t *sz, FILE *fp) | |
+{ | |
+ char *s; | |
+ size_t n = 0; | |
+ | |
+ if (getline(line, sz, fp) <= 0) | |
+ return ferror(fp) ? -1 : 0; | |
+ s = *line; | |
+ strchomp(s); | |
+ | |
+ do { | |
+ if (n >= max) | |
+ return errno=E2BIG, -1; | |
+ } while ((fields[n++] = strsep(&s, "\t")) != NULL); | |
+ | |
+ return n - 1; | |
+} | |
+ | |
+int | |
+main(int argc, char **argv) | |
+{ | |
+ AgendaCtx ctx = {0}; | |
+ ssize_t nfield, n; | |
+ size_t sz = 0; | |
+ char *line = NULL, *fields[FIELDS_MAX]; | |
+ | |
+ arg0 = *argv; | |
+ | |
+ if (pledge("stdio", "") < 0) | |
+ err(1, "pledge: %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) | |
+ break; | |
+ if (n != nfield) | |
+ err(1, "line %zd: had %lld columns, wanted %lld", | |
+ num, n, nfield); | |
+ | |
+ print(&ctx, fields, n); | |
+ } | |
+ fputc('\n', stdout); | |
+ | |
+ free(line); | |
+ | |
+ return 0; | |
+} | |
diff --git a/util.c b/util.c | |
@@ -1,5 +1,4 @@ | |
#include "util.h" | |
- | |
#include <errno.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
@@ -22,13 +21,13 @@ _log(char const *fmt, va_list va) | |
} | |
void | |
-err(char const *fmt, ...) | |
+err(int e, char const *fmt, ...) | |
{ | |
va_list va; | |
va_start(va, fmt); | |
_log( fmt, va); | |
- exit(1); | |
+ exit(e); | |
} | |
void | |
@@ -87,8 +86,7 @@ strsep(char **sp, char const *sep) | |
if (*sp == NULL) | |
return NULL; | |
prev = *sp; | |
- for (s = *sp; strchr(sep, *s) == NULL; s++) | |
- continue; | |
+ for (s = *sp; strchr(sep, *s) == NULL; s++); | |
if (*s == '\0') { | |
*sp = NULL; | |
} else { | |
diff --git a/util.h b/util.h | |
@@ -7,7 +7,7 @@ | |
/** logging **/ | |
extern char *arg0; | |
-void err(char const *fmt, ...); | |
+void err(int, char const *fmt, ...); | |
void warn(char const *fmt, ...); | |
void debug(char const *fmt, ...); | |
@@ -17,6 +17,7 @@ char *strsep(char **, char const *); | |
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 **); | |
/** memory **/ | |
void *reallocarray(void *, size_t, size_t); |