ics2tsv.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
---
ics2tsv.c (4611B)
---
1 #include <errno.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <strings.h>
6 #include <time.h>
7 #include <unistd.h>
8 #include "ical.h"
9 #include "util.h"
10
11 #ifndef __OpenBSD__
12 #define pledge(...) 0
13 #endif
14
15 #define FIELDS_MAX 128
16
17 typedef struct Field Field;
18 typedef struct Block Block;
19
20 struct Field {
21 char *key;
22 char *value;
23 };
24
25 struct Block {
26 time_t beg, end;
27 char *fields[FIELDS_MAX];
28 };
29
30 static int flag_header = 1;
31 static char default_fields[] = "SUMMARY,DESCRIPTION,CATEGORIES,LOCATION";
32 static char *flag_sep = ",";
33 static char *flag_timefmt = NULL;
34 static char *flag_fields = default_fields;
35 static char *fields[FIELDS_MAX];
36 static Block block;
37
38 static int
39 fn_field_name(IcalParser *p, char *name)
40 {
41 (void)p;
42 (void)name;
43
44 return 0;
45 }
46
47 static int
48 fn_block_begin(IcalParser *p, char *name)
49 {
50 (void)p;
51 (void)name;
52
53 if (p->blocktype == ICAL_BLOCK_OTHER)
54 return 0;
55
56 memset(&block, 0, sizeof block);
57 return 0;
58 }
59
60 static int
61 fn_block_end(IcalParser *p, char *name)
62 {
63 (void)name;
64
65 if (p->blocktype == ICAL_BLOCK_OTHER)
66 return 0;
67 fputs(p->current->name, stdout);
68
69 /* printing dates with %s is much much slower than %lld */
70 if (flag_timefmt == NULL) {
71 printf("\t%lld\t%lld", block.beg, block.end);
72 } else {
73 char buf[128];
74 struct tm tm = {0};
75
76 localtime_r(&block.beg, &tm);
77 strftime(buf, sizeof buf, flag_timefmt, &tm);
78 printf("\t%s", buf);
79
80 localtime_r(&block.end, &tm);
81 strftime(buf, sizeof buf, flag_timefmt, &tm);
82 printf("\t%s", buf);
83 }
84
85 /* reserved for recurring events */
86 printf("\t%s", "(null)");
87
88 for (int i = 0; fields[i] != NULL; i++) {
89 fputc('\t', stdout);
90 if (block.fields[i] != NULL)
91 fputs(block.fields[i], stdout);
92 }
93 printf("\n");
94 return 0;
95 }
96
97 static int
98 fn_param_value(IcalParser *p, char *name, char *value)
99 {
100 (void)p;
101 (void)name;
102 (void)value;
103
104 return 0;
105 }
106
107 static int
108 fn_field_value(IcalParser *p, char *name, char *value)
109 {
110 static char *map[][2] = {
111 [ICAL_BLOCK_VEVENT] = { "DTSTART", "DTEND" },
112 [ICAL_BLOCK_VTODO] = { NULL, "DUE" },
113 [ICAL_BLOCK_VJOURNAL] = { "DTSTAMP", NULL },
114 [ICAL_BLOCK_VFREEBUSY] = { "DTSTART", "DTEND" },
115 [ICAL_BLOCK_VALARM] = { "DTSTART", NULL },
116 [ICAL_BLOCK_OTHER] = { NULL, NULL },
117 };
118 char *beg, *end;
119
120 /* fill the date fields */
121 beg = map[p->blocktype][0];
122 if (beg != NULL && strcasecmp(name, beg) == 0)
123 if (ical_get_time(p, value, &block.beg) != 0)
124 return -1;
125 end = map[p->blocktype][1];
126 if (end != NULL && strcasecmp(name, end) == 0)
127 if (ical_get_time(p, value, &block.end) != 0)
128 return -1;
129
130 /* fill text fields as requested with -o F1,F2... */
131 for (int i = 0; fields[i] != NULL; i++) {
132 if (strcasecmp(name, fields[i]) == 0) {
133 if (block.fields[i] == NULL) {
134 if ((block.fields[i] = strdup(value)) == NULL)
135 return ical_err(p, strerror(errno));
136 } else {
137 if (strappend(&block.fields[i], flag_sep) == NULL ||
138 strappend(&block.fields[i], value) == NULL)
139 return ical_err(p, strerror(errno));
140 }
141 }
142 }
143
144 return 0;
145 }
146
147 static void
148 usage(void)
149 {
150 fprintf(stderr,"usage: %s [-1] [-f fields] [-s separator] [-t timefmt]"
151 " [file...]\n", arg0);
152 exit(1);
153 }
154
155 int
156 main(int argc, char **argv)
157 {
158 IcalParser p = {0};
159 int c;
160
161 arg0 = *argv;
162
163 if (pledge("stdio rpath", "") < 0)
164 err(1, "pledge: %s", strerror(errno));
165
166 p.fn_field_name = fn_field_name;
167 p.fn_block_begin = fn_block_begin;
168 p.fn_block_end = fn_block_end;
169 p.fn_param_value = fn_param_value;
170 p.fn_field_value = fn_field_value;
171
172 while ((c = getopt(argc, argv, "01f:s:t:")) != -1) {
173 switch (c) {
174 case '0':
175 flag_header = 0;
176 break;
177 case '1':
178 flag_header = 1;
179 break;
180 case 'f':
181 flag_fields = optarg;
182 break;
183 case 's':
184 flag_sep = optarg;
185 break;
186 case 't':
187 flag_timefmt = optarg;
188 break;
189 case '?':
190 usage();
191 break;
192 }
193 }
194 argv += optind;
195 argc -= optind;
196
197 if (strsplit(flag_fields, fields, LEN(fields), ",") < 0)
198 err(1, "too many fields specified with -f flag");
199
200 if (flag_header) {
201 printf("%s\t%s\t%s\t%s", "TYPE", "START", "END", "RECUR");
202 for (size_t i = 0; fields[i] != NULL; i++)
203 printf("\t%s", fields[i]);
204 fputc('\n', stdout);
205 }
206
207 if (*argv == NULL || strcmp(*argv, "-") == 0) {
208 debug("converting *stdin*");
209 if (ical_parse(&p, stdin) < 0)
210 err(1, "parsing *stdin*:%d: %s", p.linenum, p.errmsg);
211 }
212 for (; *argv != NULL; argv++, argc--) {
213 FILE *fp;
214 debug("converting \"%s\"", *argv);
215 if ((fp = fopen(*argv, "r")) == NULL)
216 err(1, "opening %s: %s", *argv, strerror(errno));
217 if (ical_parse(&p, fp) < 0)
218 err(1, "parsing %s:%d: %s", *argv, p.linenum, p.errmsg);
219 fclose(fp);
220 }
221
222 return 0;
223 }