tsv2agenda.c - 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
---
tsv2agenda.c (5483B)
---
1 #include <assert.h>
2 #include <ctype.h>
3 #include <errno.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <stdint.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <time.h>
10 #include "util.h"
11
12 #ifndef __OpenBSD__
13 #define pledge(...) 0
14 #endif
15
16 enum {
17 FIELD_TYPE,
18 FIELD_BEG,
19 FIELD_END,
20 FIELD_RECUR,
21 FIELD_OTHER,
22 FIELD_MAX = 128,
23 };
24
25 typedef struct {
26 struct tm beg, end;
27 char *fieldnames[FIELD_MAX];
28 size_t fieldnum;
29 size_t linenum;
30 } AgendaCtx;
31
32 static time_t flag_from = INT64_MIN;
33 static time_t flag_to = INT64_MAX;
34
35 static void
36 print_date(struct tm *tm)
37 {
38 if (tm == NULL) {
39 fprintf(stdout, "%11s", "");
40 } else {
41 char buf[128];
42 if (strftime(buf, sizeof buf, "%Y-%m-%d", tm) == 0)
43 err(1, "strftime: %s", strerror(errno));
44 fprintf(stdout, "%s ", buf);
45 }
46 }
47
48 static void
49 print_time(struct tm *tm)
50 {
51 if (tm == NULL) {
52 fprintf(stdout, "%5s ", "");
53 } else {
54 char buf[128];
55 if (strftime(buf, sizeof buf, "%H:%M", tm) == 0)
56 err(1, "strftime: %s", strerror(errno));
57 fprintf(stdout, "%5s ", buf);
58 }
59 }
60
61 static void
62 print_header0(struct tm *old, struct tm *new)
63 {
64 int same;
65
66 same = (old->tm_year == new->tm_year && old->tm_mon == new->tm_mon &&
67 old->tm_mday == new->tm_mday);
68 print_date(same ? NULL : new);
69 print_time(new);
70 }
71
72 static void
73 print_header1(struct tm *beg, struct tm *end)
74 {
75 int same;
76
77 same = (beg->tm_year == end->tm_year && beg->tm_mon == end->tm_mon &&
78 beg->tm_mday == end->tm_mday);
79 print_date(same ? NULL : end);
80
81 same = (beg->tm_hour == end->tm_hour && beg->tm_min == end->tm_min);
82 print_time(same ? NULL : end);
83 }
84
85 static void
86 print_headerN(void)
87 {
88 print_date(NULL);
89 print_time(NULL);
90 }
91
92 static void
93 print_header(AgendaCtx *ctx, struct tm *beg, struct tm *end, size_t *num)
94 {
95 switch ((*num)++) {
96 case 0:
97 print_header0(&ctx->beg, beg);
98 break;
99 case 1:
100 print_header1(beg, end);
101 break;
102 default:
103 print_headerN();
104 break;
105 }
106 }
107
108 static void
109 unescape(char const *s, char *d)
110 {
111 for (; *s != '\0'; s++) {
112 if (*s == '\\') {
113 s++;
114 *d++ = (*s == 'n') ? '\n' : (*s == 't') ? ' ' : *s;
115 } else {
116 if (*s == '\\')
117 debug("s='%c'", *s);
118 *d++ = *s;
119 }
120 }
121 *d = '\0';
122 }
123
124 static void
125 print_row(AgendaCtx *ctx, char *s, struct tm *beg, struct tm *end, size_t *num)
126 {
127 unescape(s, s);
128
129 print_header(ctx, beg, end, num);
130 for (size_t i, n = 0; *s != '\0'; s++) {
131 switch (*s) {
132 case '\n':
133 newline:
134 fputc('\n', stdout);
135 print_header(ctx, beg, end, num);
136 fputs(": ", stdout);
137 n = 0;
138 break;
139 case ' ':
140 case '\t':
141 i = strcspn(s + 1, " \t\n");
142 if (n + i > 70)
143 goto newline;
144 fputc(' ', stdout);
145 n++;
146 break;
147 default:
148 fputc(*s, stdout);
149 n++;
150 }
151 }
152 fputc('\n', stdout);
153 }
154
155 static void
156 print(AgendaCtx *ctx, char **fields)
157 {
158 struct tm beg = {0}, end = {0};
159 time_t t;
160 char const *e;
161
162 t = strtonum(fields[FIELD_BEG], INT64_MIN, INT64_MAX, &e);
163 if (e != NULL)
164 err(1, "start time %s is %s", fields[FIELD_BEG], e);
165 if (t > flag_to)
166 return;
167 localtime_r(&t, &beg);
168
169 t = strtonum(fields[FIELD_END], INT64_MIN, INT64_MAX, &e);
170 if (e != NULL)
171 err(1, "end time %s is %s", fields[FIELD_END], e);
172 if (t < flag_from)
173 return;
174 localtime_r(&t, &end);
175
176 fputc('\n', stdout);
177 for (size_t i = FIELD_OTHER, row = 0; i < ctx->fieldnum; i++) {
178 if (fields[i][strspn(fields[i], " \\n")] == '\0')
179 continue;
180 print_row(ctx, fields[i], &beg, &end, &row);
181 }
182
183 ctx->beg = beg;
184 ctx->end = end;
185 }
186
187 static void
188 tsv2agenda(FILE *fp)
189 {
190 AgendaCtx ctx = {0};
191 char *line = NULL;
192 size_t sz1 = 0, sz2 = 0;
193
194 if (ctx.linenum == 0) {
195 char *fields[FIELD_MAX];
196
197 ctx.linenum++;
198 getline(&line, &sz1, fp);
199 if (ferror(fp))
200 err(1, "reading stdin: %s", strerror(errno));
201 if (feof(fp))
202 err(1, "empty input");
203 strchomp(line);
204 ctx.fieldnum = strsplit(line, fields, FIELD_MAX, "\t");
205 if (ctx.fieldnum == FIELD_MAX)
206 err(1, "line 1: too many fields");
207 if (ctx.fieldnum < FIELD_OTHER)
208 err(1, "line 1: not enough input columns");
209 if (strcasecmp(fields[0], "TYPE") != 0)
210 err(1, "line 1: 1st column is not \"TYPE\"");
211 if (strcasecmp(fields[1], "START") != 0)
212 err(1, "line 1: 2nd column is not \"START\"");
213 if (strcasecmp(fields[2], "END") != 0)
214 err(1, "line 1: 3rd column is not \"END\"");
215 if (strcasecmp(fields[3], "RECUR") != 0)
216 err(1, "line 1: 4th column is not \"RECUR\"");
217
218 free(line);
219 line = NULL;
220 }
221
222 for (;;) {
223 char *fields[FIELD_MAX];
224
225 ctx.linenum++;
226 getline(&line, &sz2, fp);
227 if (ferror(fp))
228 err(1, "reading stdin: %s", strerror(errno));
229 if (feof(fp))
230 break;
231
232 strchomp(line);
233
234 if (strsplit(line, fields, FIELD_MAX, "\t") != ctx.fieldnum)
235 err(1, "line %zd: bad number of columns",
236 ctx.linenum, strerror(errno));
237
238 print(&ctx, fields);
239 }
240 fputc('\n', stdout);
241
242 free(line);
243 line = NULL;
244 }
245
246 static void
247 usage(void)
248 {
249 fprintf(stderr, "usage: %s [-f fromdate] [-t todate]\n", arg0);
250 exit(1);
251 }
252
253 int
254 main(int argc, char **argv)
255 {
256 char c;
257
258 if (pledge("stdio", "") < 0)
259 err(1, "pledge: %s", strerror(errno));
260
261 arg0 = *argv;
262 while ((c = getopt(argc, argv, "f:t:")) > 0) {
263 char const *e;
264
265 switch (c) {
266 case 'f':
267 flag_from = strtonum(optarg, INT64_MIN, INT64_MAX, &e);
268 if (e != NULL)
269 err(1, "fromdate value %s is %s", optarg, e);
270 break;
271 case 't':
272 flag_to = strtonum(optarg, INT64_MIN, INT64_MAX, &e);
273 if (e != NULL)
274 err(1, "todate value %s is %s", optarg, e);
275 break;
276 default:
277 usage();
278 }
279 }
280 argc -= optind;
281 argv += optind;
282
283 tsv2agenda(stdin);
284 return 0;
285 }