Home
tsv2agenda.c - 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 --- 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 }