|
|
tsv2agenda.c - ics2txt - convert icalendar .ics file to plain text |
|
|
 |
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt (git://bitreich.org) |
|
|
 |
Log |
|
|
 |
Files |
|
|
 |
Refs |
|
|
 |
Tags |
|
|
 |
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 } |
|