2 * Copyright (c) 2021, 2022 Omar Polo <op@omarpolo.com>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 * A streaming gemtext parser.
33 #include "telescope.h"
37 static int gemtext_parse_line(struct buffer *, const char *, size_t);
38 static int gemtext_free(struct buffer *);
39 static int gemtext_serialize(struct buffer *, FILE *);
41 static int parse_link(struct buffer *, const char*, size_t);
42 static int parse_title(struct buffer *, const char*, size_t);
43 static void search_title(struct buffer *, enum line_type);
45 const struct parser gemtext_parser = {
46 .name = "text/gemini",
47 .parseline = &gemtext_parse_line,
48 .free = &gemtext_free,
49 .serialize = &gemtext_serialize,
53 emit_line(struct buffer *b, enum line_type type, char *line, char *alt)
57 l = xcalloc(1, sizeof(*l));
68 if (l->type == LINE_PRE_END &&
69 hide_pre_closing_line)
72 case LINE_PRE_CONTENT:
78 !emojied_line(line, (const char **)&l->data))
85 if (dont_apply_styling)
86 l->flags &= ~L_HIDDEN;
88 TAILQ_INSERT_TAIL(&b->head, l, lines);
94 parse_link(struct buffer *b, const char *line, size_t len)
100 return emit_line(b, LINE_TEXT, NULL, NULL);
103 while (len > 0 && isspace((unsigned char)line[0]))
107 return emit_line(b, LINE_TEXT, NULL, NULL);
110 while (len > 0 && !isspace((unsigned char)line[0]))
113 url = xstrndup(start, line - start);
115 while (len > 0 && isspace(line[0]))
119 label = xstrdup(url);
121 label = xstrndup(line, len);
124 return emit_line(b, LINE_LINK, label, url);
128 parse_title(struct buffer *b, const char *line, size_t len)
130 enum line_type t = LINE_TITLE_1;
134 while (len > 0 && *line == '#') {
137 if (t == LINE_TITLE_3)
141 while (len > 0 && isspace((unsigned char)*line))
145 return emit_line(b, t, NULL, NULL);
147 if (t == LINE_TITLE_1 && *b->title == '\0')
148 strncpy(b->title, line, MIN(sizeof(b->title)-1, len));
150 l = xstrndup(line, len);
151 return emit_line(b, t, l, NULL);
155 gemtext_parse_line(struct buffer *b, const char *line, size_t len)
159 if (b->parser_flags & PARSER_IN_PRE) {
160 if (len >= 3 && !strncmp(line, "```", 3)) {
161 b->parser_flags ^= PARSER_IN_PRE;
162 return emit_line(b, LINE_PRE_END, NULL, NULL);
166 return emit_line(b, LINE_PRE_CONTENT, NULL, NULL);
167 l = xstrndup(line, len);
168 return emit_line(b, LINE_PRE_CONTENT, l, NULL);
172 return emit_line(b, LINE_TEXT, NULL, NULL);
176 if (len < 1 || line[1] != ' ')
180 while (len > 0 && isspace((unsigned char)*line))
183 return emit_line(b, LINE_ITEM, NULL, NULL);
184 l = xstrndup(line, len);
185 return emit_line(b, LINE_ITEM, l, NULL);
189 while (len > 0 && isspace((unsigned char)*line))
192 return emit_line(b, LINE_QUOTE, NULL, NULL);
193 l = xstrndup(line, len);
194 return emit_line(b, LINE_QUOTE, l, NULL);
197 if (len > 1 && line[1] == '>')
198 return parse_link(b, line, len);
202 return parse_title(b, line, len);
205 if (len < 3 || strncmp(line, "```", 3) != 0)
208 b->parser_flags |= PARSER_IN_PRE;
210 while (len > 0 && isspace((unsigned char)*line))
213 return emit_line(b, LINE_PRE_START,
215 l = xstrndup(line, len);
216 return emit_line(b, LINE_PRE_START, l, NULL);
219 l = xstrndup(line, len);
220 return emit_line(b, LINE_TEXT, l, NULL);
224 gemtext_free(struct buffer *b)
226 /* flush the buffer */
228 if (!gemtext_parse_line(b, b->buf, b->len))
230 if ((b->parser_flags & PARSER_IN_PRE) &&
231 !emit_line(b, LINE_PRE_END, NULL, NULL))
236 * use the first level 2 or 3 header as page title if none
239 if (*b->title == '\0')
240 search_title(b, LINE_TITLE_2);
241 if (*b->title == '\0')
242 search_title(b, LINE_TITLE_3);
248 search_title(struct buffer *b, enum line_type level)
252 TAILQ_FOREACH(l, &b->head, lines) {
253 if (l->type == level) {
256 strlcpy(b->title, l->line, sizeof(b->title));
262 static const char *gemtext_prefixes[] = {
264 [LINE_TITLE_1] = "# ",
265 [LINE_TITLE_2] = "## ",
266 [LINE_TITLE_3] = "### ",
269 [LINE_PRE_START] = "``` ",
270 [LINE_PRE_CONTENT] = "",
271 [LINE_PRE_END] = "```",
275 gemtext_serialize(struct buffer *b, FILE *fp)
282 TAILQ_FOREACH(line, &b->head, lines) {
283 if ((text = line->line) == NULL)
286 if ((alt = line->alt) == NULL)
289 switch (line->type) {
297 case LINE_PRE_CONTENT:
299 r = fprintf(fp, "%s%s\n", gemtext_prefixes[line->type],
304 r = fprintf(fp, "=> %s %s\n", alt, text);