Commit Diff


commit - 590c00b776ba5daed0a5dcb4b65f0d2faefac615
commit + 70d237dfca1766a56254470d2b97611d02c79010
blob - f4dece444925d5a3c688f7fb4eb0bf38af9e03a7
blob + 9ecb75426404cb073667ece1e5eb73282f7eff9a
--- Makefile.am
+++ Makefile.am
@@ -12,7 +12,6 @@ telescope_SOURCES =	cmd.c		\
 			defaults.h	\
 			emoji-matcher.c	\
 			fs.c		\
-			gemtext.c	\
 			gen-emoji-matcher.sh \
 			gencmd.awk	\
 			help.c		\
@@ -27,10 +26,11 @@ telescope_SOURCES =	cmd.c		\
 			parse.y		\
 			parser.c	\
 			parser.h	\
+			parser_gemtext.c	\
+			parser_textplain.c	\
 			sandbox.c	\
 			telescope.c	\
 			telescope.h	\
-			textplain.c	\
 			tofu.c		\
 			ui.c		\
 			ui.h		\
blob - 239ffd9fc68e010cb523a0e38b1080674449e8ae (mode 644)
blob + /dev/null
--- gemtext.c
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-/*
- * A streaming gemtext parser.
- *
- * TODO:
- *  - handle NULs
- *  - UTF8
- */
-
-#include "compat.h"
-
-#include <ctype.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "defaults.h"
-#include "parser.h"
-#include "utf8.h"
-
-static int	gemtext_parse(struct parser*, const char*, size_t);
-static int	gemtext_foreach_line(struct parser*, const char*, size_t);
-static int	gemtext_free(struct parser*);
-
-static int	parse_text(struct parser*, enum line_type, const char*, size_t);
-static int	parse_link(struct parser*, enum line_type, const char*, size_t);
-static int	parse_title(struct parser*, enum line_type, const char*, size_t);
-static int	parse_item(struct parser*, enum line_type, const char*, size_t);
-static int	parse_quote(struct parser*, enum line_type, const char*, size_t);
-static int	parse_pre_start(struct parser*, enum line_type, const char*, size_t);
-static int	parse_pre_cnt(struct parser*, enum line_type, const char*, size_t);
-static int	parse_pre_end(struct parser*, enum line_type, const char*, size_t);
-static void	search_title(struct parser*, enum line_type);
-
-typedef int (parselinefn)(struct parser*, enum line_type, const char*, size_t);
-
-static parselinefn *parsers[] = {
-	[LINE_TEXT]		= parse_text,
-	[LINE_LINK]		= parse_link,
-	[LINE_TITLE_1]		= parse_title,
-	[LINE_TITLE_2]		= parse_title,
-	[LINE_TITLE_3]		= parse_title,
-	[LINE_ITEM]		= parse_item,
-	[LINE_QUOTE]		= parse_quote,
-	[LINE_PRE_START]	= parse_pre_start,
-	[LINE_PRE_CONTENT]	= parse_pre_cnt,
-	[LINE_PRE_END]		= parse_pre_end,
-};
-
-void
-gemtext_initparser(struct parser *p)
-{
-	memset(p, 0, sizeof(*p));
-
-	p->name = "text/gemini";
-	p->parse = &gemtext_parse;
-	p->free  = &gemtext_free;
-}
-
-static inline int
-emit_line(struct parser *p, enum line_type type, char *line, char *alt)
-{
-	struct line *l;
-
-	if ((l = calloc(1, sizeof(*l))) == NULL)
-		return 0;
-
-	l->type = type;
-	l->line = line;
-	l->alt = alt;
-
-	switch (l->type) {
-	case LINE_PRE_START:
-	case LINE_PRE_END:
-		if (hide_pre_context)
-			l->flags = L_HIDDEN;
-		if (l->type == LINE_PRE_END &&
-		    hide_pre_closing_line)
-			l->flags = L_HIDDEN;
-		break;
-	case LINE_PRE_CONTENT:
-		if (hide_pre_blocks)
-			l->flags = L_HIDDEN;
-		break;
-	case LINE_LINK:
-		if (emojify_link &&
-		    !emojied_line(line, (const char **)&l->data))
-			l->data = NULL;
-		break;
-	default:
-		break;
-	}
-
-	if (TAILQ_EMPTY(&p->head))
-		TAILQ_INSERT_HEAD(&p->head, l, lines);
-	else
-		TAILQ_INSERT_TAIL(&p->head, l, lines);
-
-	return 1;
-}
-
-static int
-parse_text(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	char *l;
-
-	if ((l = calloc(1, len+1)) == NULL)
-		return 0;
-	memcpy(l, buf, len);
-	return emit_line(p, t, l, NULL);
-}
-
-static int
-parse_link(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	char *l, *u;
-	const char *url_start;
-
-	if (len <= 2)
-		return emit_line(p, t, NULL, NULL);
-	buf += 2;
-	len -= 2;
-
-	while (len > 0 && isspace(buf[0])) {
-		buf++;
-		len--;
-	}
-
-	if (len == 0)
-		return emit_line(p, t, NULL, NULL);
-
-	url_start = buf;
-	while (len > 0 && !isspace(buf[0])) {
-		buf++;
-		len--;
-	}
-
-	if ((u = calloc(1, buf - url_start + 1)) == NULL)
-		return 0;
-	memcpy(u, url_start, buf - url_start);
-
-	if (len == 0)
-		goto nolabel;
-
-	while (len > 0 && isspace(buf[0])) {
-		buf++;
-		len--;
-	}
-
-	if (len == 0)
-		goto nolabel;
-
-	if ((l = calloc(1, len + 1)) == NULL)
-		return 0;
-
-	memcpy(l, buf, len);
-	return emit_line(p, t, l, u);
-
-nolabel:
-	if ((l = strdup(u)) == NULL)
-		return 0;
-	return emit_line(p, t, l, u);
-}
-
-static int
-parse_title(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	char *l;
-
-	switch (t) {
-	case LINE_TITLE_1:
-		if (len <= 1)
-			return emit_line(p, t, NULL, NULL);
-		buf++;
-		len--;
-		break;
-	case LINE_TITLE_2:
-		if (len <= 2)
-			return emit_line(p, t, NULL, NULL);
-		buf += 2;
-		len -= 2;
-		break;
-	case LINE_TITLE_3:
-		if (len <= 3)
-			return emit_line(p, t, NULL, NULL);
-		buf += 3;
-		len -= 3;
-		break;
-	default:
-		/* unreachable */
-		abort();
-	}
-
-	while (len > 0 && isspace(buf[0])) {
-		buf++;
-		len--;
-	}
-
-	if (len == 0)
-		return emit_line(p, t, NULL, NULL);
-
-	if (t == LINE_TITLE_1 && *p->title == '\0')
-		strncpy(p->title, buf, MIN(sizeof(p->title)-1, len));
-
-	if ((l = calloc(1, len+1)) == NULL)
-		return 0;
-	memcpy(l, buf, len);
-	return emit_line(p, t, l, NULL);
-}
-
-static int
-parse_item(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	char *l;
-
-	if (len == 1)
-		return emit_line(p, t, NULL, NULL);
-
-	buf++;
-	len--;
-
-	while (len > 0 && isspace(buf[0])) {
-		buf++;
-		len--;
-	}
-
-	if (len == 0)
-		return emit_line(p, t, NULL, NULL);
-
-	if ((l = calloc(1, len+1)) == NULL)
-		return 0;
-	memcpy(l, buf, len);
-	return emit_line(p, t, l, NULL);
-}
-
-static int
-parse_quote(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	char *l;
-
-	if (len == 1)
-		return emit_line(p, t, NULL, NULL);
-
-	buf++;
-	len--;
-
-	while (len > 0 && isspace(buf[0])) {
-		buf++;
-		len--;
-	}
-
-	if (len == 0)
-		return emit_line(p, t, NULL, NULL);
-
-	if ((l = calloc(1, len+1)) == NULL)
-		return 0;
-	memcpy(l, buf, len);
-	return emit_line(p, t, l, NULL);
-}
-
-static int
-parse_pre_start(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	char *l;
-
-	if (len <= 3)
-		return emit_line(p, t, NULL, NULL);
-
-	buf += 3;
-	len -= 3;
-
-	while (len > 0 && isspace(buf[0])) {
-		buf++;
-		len--;
-	}
-
-	if (len == 0)
-		return emit_line(p, t, NULL, NULL);
-
-	if ((l = calloc(1, len+1)) == NULL)
-		return 0;
-
-	memcpy(l, buf, len);
-	return emit_line(p, t, l, NULL);
-}
-
-static int
-parse_pre_cnt(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	char *l;
-
-	if (len == 0)
-		return emit_line(p, t, NULL, NULL);
-
-	if ((l = calloc(1, len+1)) == NULL)
-		return 0;
-	memcpy(l, buf, len);
-	return emit_line(p, t, l, NULL);
-}
-
-static int
-parse_pre_end(struct parser *p, enum line_type t, const char *buf, size_t len)
-{
-	return emit_line(p, t, NULL, NULL);
-}
-
-static inline enum line_type
-detect_line_type(const char *buf, size_t len, int in_pre)
-{
-	if (in_pre) {
-		if (len >= 3 &&
-		    buf[0] == '`' && buf[1] == '`' && buf[2] == '`')
-			return LINE_PRE_END;
-		else
-			return LINE_PRE_CONTENT;
-	}
-
-	if (len == 0)
-		return LINE_TEXT;
-
-	switch (*buf) {
-	case '*': return LINE_ITEM;
-	case '>': return LINE_QUOTE;
-	case '=':
-		if (len >= 1 && buf[1] == '>')
-			return LINE_LINK;
-		break;
-	case '#':
-		if (len == 1)
-			return LINE_TEXT;
-		if (buf[1] != '#')
-			return LINE_TITLE_1;
-		if (len == 2)
-			return LINE_TEXT;
-		if (buf[2] != '#')
-			return LINE_TITLE_2;
-		if (len == 3)
-			return LINE_TEXT;
-		return LINE_TITLE_3;
-	case '`':
-		if (len < 3)
-			return LINE_TEXT;
-		if (buf[0] == '`' && buf[1] == '`' && buf[2] == '`')
-			return LINE_PRE_START;
-		break;
-	}
-
-	return LINE_TEXT;
-}
-
-static int
-gemtext_parse(struct parser *p, const char *buf, size_t size)
-{
-	return parser_foreach_line(p, buf, size, gemtext_foreach_line);
-}
-
-static int
-gemtext_foreach_line(struct parser *p, const char *line, size_t linelen)
-{
-	enum line_type t;
-
-	t = detect_line_type(line, linelen, p->flags & PARSER_IN_PRE);
-	if (t == LINE_PRE_START)
-		p->flags ^= PARSER_IN_PRE;
-	if (t == LINE_PRE_END)
-		p->flags ^= PARSER_IN_PRE;
-	return parsers[t](p, t, line, linelen);
-}
-
-static int
-gemtext_free(struct parser *p)
-{
-	enum line_type	t;
-
-	/* flush the buffer */
-	if (p->len != 0) {
-		t = detect_line_type(p->buf, p->len, p->flags & PARSER_IN_PRE);
-		if (!parsers[t](p, t, p->buf, p->len))
-			return 0;
-		if ((p->flags & PARSER_IN_PRE) &&
-		    !emit_line(p, LINE_PRE_END, NULL, NULL))
-			return 0;
-	}
-
-	free(p->buf);
-
-	/*
-	 * use the first level 2 or 3 header as page title if none
-	 * found yet.
-	 */
-	if (*p->title == '\0')
-		search_title(p, LINE_TITLE_2);
-	if (*p->title == '\0')
-		search_title(p, LINE_TITLE_3);
-
-	return 1;
-}
-
-static void
-search_title(struct parser *p, enum line_type level)
-{
-	struct line *l;
-
-	TAILQ_FOREACH(l, &p->head, lines) {
-		if (l->type == level) {
-			if (l->line == NULL)
-				continue;
-			strlcpy(p->title, l->line, sizeof(p->title));
-			break;
-		}
-	}
-}
blob - /dev/null
blob + 239ffd9fc68e010cb523a0e38b1080674449e8ae (mode 644)
--- /dev/null
+++ parser_gemtext.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * A streaming gemtext parser.
+ *
+ * TODO:
+ *  - handle NULs
+ *  - UTF8
+ */
+
+#include "compat.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "defaults.h"
+#include "parser.h"
+#include "utf8.h"
+
+static int	gemtext_parse(struct parser*, const char*, size_t);
+static int	gemtext_foreach_line(struct parser*, const char*, size_t);
+static int	gemtext_free(struct parser*);
+
+static int	parse_text(struct parser*, enum line_type, const char*, size_t);
+static int	parse_link(struct parser*, enum line_type, const char*, size_t);
+static int	parse_title(struct parser*, enum line_type, const char*, size_t);
+static int	parse_item(struct parser*, enum line_type, const char*, size_t);
+static int	parse_quote(struct parser*, enum line_type, const char*, size_t);
+static int	parse_pre_start(struct parser*, enum line_type, const char*, size_t);
+static int	parse_pre_cnt(struct parser*, enum line_type, const char*, size_t);
+static int	parse_pre_end(struct parser*, enum line_type, const char*, size_t);
+static void	search_title(struct parser*, enum line_type);
+
+typedef int (parselinefn)(struct parser*, enum line_type, const char*, size_t);
+
+static parselinefn *parsers[] = {
+	[LINE_TEXT]		= parse_text,
+	[LINE_LINK]		= parse_link,
+	[LINE_TITLE_1]		= parse_title,
+	[LINE_TITLE_2]		= parse_title,
+	[LINE_TITLE_3]		= parse_title,
+	[LINE_ITEM]		= parse_item,
+	[LINE_QUOTE]		= parse_quote,
+	[LINE_PRE_START]	= parse_pre_start,
+	[LINE_PRE_CONTENT]	= parse_pre_cnt,
+	[LINE_PRE_END]		= parse_pre_end,
+};
+
+void
+gemtext_initparser(struct parser *p)
+{
+	memset(p, 0, sizeof(*p));
+
+	p->name = "text/gemini";
+	p->parse = &gemtext_parse;
+	p->free  = &gemtext_free;
+}
+
+static inline int
+emit_line(struct parser *p, enum line_type type, char *line, char *alt)
+{
+	struct line *l;
+
+	if ((l = calloc(1, sizeof(*l))) == NULL)
+		return 0;
+
+	l->type = type;
+	l->line = line;
+	l->alt = alt;
+
+	switch (l->type) {
+	case LINE_PRE_START:
+	case LINE_PRE_END:
+		if (hide_pre_context)
+			l->flags = L_HIDDEN;
+		if (l->type == LINE_PRE_END &&
+		    hide_pre_closing_line)
+			l->flags = L_HIDDEN;
+		break;
+	case LINE_PRE_CONTENT:
+		if (hide_pre_blocks)
+			l->flags = L_HIDDEN;
+		break;
+	case LINE_LINK:
+		if (emojify_link &&
+		    !emojied_line(line, (const char **)&l->data))
+			l->data = NULL;
+		break;
+	default:
+		break;
+	}
+
+	if (TAILQ_EMPTY(&p->head))
+		TAILQ_INSERT_HEAD(&p->head, l, lines);
+	else
+		TAILQ_INSERT_TAIL(&p->head, l, lines);
+
+	return 1;
+}
+
+static int
+parse_text(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	char *l;
+
+	if ((l = calloc(1, len+1)) == NULL)
+		return 0;
+	memcpy(l, buf, len);
+	return emit_line(p, t, l, NULL);
+}
+
+static int
+parse_link(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	char *l, *u;
+	const char *url_start;
+
+	if (len <= 2)
+		return emit_line(p, t, NULL, NULL);
+	buf += 2;
+	len -= 2;
+
+	while (len > 0 && isspace(buf[0])) {
+		buf++;
+		len--;
+	}
+
+	if (len == 0)
+		return emit_line(p, t, NULL, NULL);
+
+	url_start = buf;
+	while (len > 0 && !isspace(buf[0])) {
+		buf++;
+		len--;
+	}
+
+	if ((u = calloc(1, buf - url_start + 1)) == NULL)
+		return 0;
+	memcpy(u, url_start, buf - url_start);
+
+	if (len == 0)
+		goto nolabel;
+
+	while (len > 0 && isspace(buf[0])) {
+		buf++;
+		len--;
+	}
+
+	if (len == 0)
+		goto nolabel;
+
+	if ((l = calloc(1, len + 1)) == NULL)
+		return 0;
+
+	memcpy(l, buf, len);
+	return emit_line(p, t, l, u);
+
+nolabel:
+	if ((l = strdup(u)) == NULL)
+		return 0;
+	return emit_line(p, t, l, u);
+}
+
+static int
+parse_title(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	char *l;
+
+	switch (t) {
+	case LINE_TITLE_1:
+		if (len <= 1)
+			return emit_line(p, t, NULL, NULL);
+		buf++;
+		len--;
+		break;
+	case LINE_TITLE_2:
+		if (len <= 2)
+			return emit_line(p, t, NULL, NULL);
+		buf += 2;
+		len -= 2;
+		break;
+	case LINE_TITLE_3:
+		if (len <= 3)
+			return emit_line(p, t, NULL, NULL);
+		buf += 3;
+		len -= 3;
+		break;
+	default:
+		/* unreachable */
+		abort();
+	}
+
+	while (len > 0 && isspace(buf[0])) {
+		buf++;
+		len--;
+	}
+
+	if (len == 0)
+		return emit_line(p, t, NULL, NULL);
+
+	if (t == LINE_TITLE_1 && *p->title == '\0')
+		strncpy(p->title, buf, MIN(sizeof(p->title)-1, len));
+
+	if ((l = calloc(1, len+1)) == NULL)
+		return 0;
+	memcpy(l, buf, len);
+	return emit_line(p, t, l, NULL);
+}
+
+static int
+parse_item(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	char *l;
+
+	if (len == 1)
+		return emit_line(p, t, NULL, NULL);
+
+	buf++;
+	len--;
+
+	while (len > 0 && isspace(buf[0])) {
+		buf++;
+		len--;
+	}
+
+	if (len == 0)
+		return emit_line(p, t, NULL, NULL);
+
+	if ((l = calloc(1, len+1)) == NULL)
+		return 0;
+	memcpy(l, buf, len);
+	return emit_line(p, t, l, NULL);
+}
+
+static int
+parse_quote(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	char *l;
+
+	if (len == 1)
+		return emit_line(p, t, NULL, NULL);
+
+	buf++;
+	len--;
+
+	while (len > 0 && isspace(buf[0])) {
+		buf++;
+		len--;
+	}
+
+	if (len == 0)
+		return emit_line(p, t, NULL, NULL);
+
+	if ((l = calloc(1, len+1)) == NULL)
+		return 0;
+	memcpy(l, buf, len);
+	return emit_line(p, t, l, NULL);
+}
+
+static int
+parse_pre_start(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	char *l;
+
+	if (len <= 3)
+		return emit_line(p, t, NULL, NULL);
+
+	buf += 3;
+	len -= 3;
+
+	while (len > 0 && isspace(buf[0])) {
+		buf++;
+		len--;
+	}
+
+	if (len == 0)
+		return emit_line(p, t, NULL, NULL);
+
+	if ((l = calloc(1, len+1)) == NULL)
+		return 0;
+
+	memcpy(l, buf, len);
+	return emit_line(p, t, l, NULL);
+}
+
+static int
+parse_pre_cnt(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	char *l;
+
+	if (len == 0)
+		return emit_line(p, t, NULL, NULL);
+
+	if ((l = calloc(1, len+1)) == NULL)
+		return 0;
+	memcpy(l, buf, len);
+	return emit_line(p, t, l, NULL);
+}
+
+static int
+parse_pre_end(struct parser *p, enum line_type t, const char *buf, size_t len)
+{
+	return emit_line(p, t, NULL, NULL);
+}
+
+static inline enum line_type
+detect_line_type(const char *buf, size_t len, int in_pre)
+{
+	if (in_pre) {
+		if (len >= 3 &&
+		    buf[0] == '`' && buf[1] == '`' && buf[2] == '`')
+			return LINE_PRE_END;
+		else
+			return LINE_PRE_CONTENT;
+	}
+
+	if (len == 0)
+		return LINE_TEXT;
+
+	switch (*buf) {
+	case '*': return LINE_ITEM;
+	case '>': return LINE_QUOTE;
+	case '=':
+		if (len >= 1 && buf[1] == '>')
+			return LINE_LINK;
+		break;
+	case '#':
+		if (len == 1)
+			return LINE_TEXT;
+		if (buf[1] != '#')
+			return LINE_TITLE_1;
+		if (len == 2)
+			return LINE_TEXT;
+		if (buf[2] != '#')
+			return LINE_TITLE_2;
+		if (len == 3)
+			return LINE_TEXT;
+		return LINE_TITLE_3;
+	case '`':
+		if (len < 3)
+			return LINE_TEXT;
+		if (buf[0] == '`' && buf[1] == '`' && buf[2] == '`')
+			return LINE_PRE_START;
+		break;
+	}
+
+	return LINE_TEXT;
+}
+
+static int
+gemtext_parse(struct parser *p, const char *buf, size_t size)
+{
+	return parser_foreach_line(p, buf, size, gemtext_foreach_line);
+}
+
+static int
+gemtext_foreach_line(struct parser *p, const char *line, size_t linelen)
+{
+	enum line_type t;
+
+	t = detect_line_type(line, linelen, p->flags & PARSER_IN_PRE);
+	if (t == LINE_PRE_START)
+		p->flags ^= PARSER_IN_PRE;
+	if (t == LINE_PRE_END)
+		p->flags ^= PARSER_IN_PRE;
+	return parsers[t](p, t, line, linelen);
+}
+
+static int
+gemtext_free(struct parser *p)
+{
+	enum line_type	t;
+
+	/* flush the buffer */
+	if (p->len != 0) {
+		t = detect_line_type(p->buf, p->len, p->flags & PARSER_IN_PRE);
+		if (!parsers[t](p, t, p->buf, p->len))
+			return 0;
+		if ((p->flags & PARSER_IN_PRE) &&
+		    !emit_line(p, LINE_PRE_END, NULL, NULL))
+			return 0;
+	}
+
+	free(p->buf);
+
+	/*
+	 * use the first level 2 or 3 header as page title if none
+	 * found yet.
+	 */
+	if (*p->title == '\0')
+		search_title(p, LINE_TITLE_2);
+	if (*p->title == '\0')
+		search_title(p, LINE_TITLE_3);
+
+	return 1;
+}
+
+static void
+search_title(struct parser *p, enum line_type level)
+{
+	struct line *l;
+
+	TAILQ_FOREACH(l, &p->head, lines) {
+		if (l->type == level) {
+			if (l->line == NULL)
+				continue;
+			strlcpy(p->title, l->line, sizeof(p->title));
+			break;
+		}
+	}
+}
blob - /dev/null
blob + 46ef180b412797d8b711d722ee03998dcf4903b2 (mode 644)
--- /dev/null
+++ parser_textplain.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * A streaming text/plain "parser."
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "telescope.h"
+#include "parser.h"
+
+static int	textplain_parse(struct parser*, const char*, size_t);
+static int	textplain_foreach_line(struct parser*, const char*, size_t);
+static int	textplain_free(struct parser*);
+
+static inline int
+emit_line(struct parser *p, enum line_type type, const char *line, size_t len)
+{
+	struct line *l;
+
+	if ((l = calloc(1, sizeof(*l))) == NULL)
+		return 0;
+
+	l->type = type;
+
+	if (len != 0) {
+		if ((l->line = calloc(1, len+1)) == NULL) {
+			free(l);
+			return 0;
+		}
+
+		memcpy(l->line, line, len);
+	}
+
+	if (TAILQ_EMPTY(&p->head))
+		TAILQ_INSERT_HEAD(&p->head, l, lines);
+	else
+		TAILQ_INSERT_TAIL(&p->head, l, lines);
+
+	return 1;
+}
+
+void
+textplain_initparser(struct parser *p)
+{
+	memset(p, 0, sizeof(*p));
+
+	p->name = "text/plain";
+	p->parse = &textplain_parse;
+	p->free = &textplain_free;
+}
+
+static int
+textplain_parse(struct parser *p, const char *buf, size_t size)
+{
+	return parser_foreach_line(p, buf, size, textplain_foreach_line);
+}
+
+static int
+textplain_foreach_line(struct parser *p, const char *line, size_t linelen)
+{
+	return emit_line(p, LINE_PRE_CONTENT, line, linelen);
+}
+
+static int
+textplain_free(struct parser *p)
+{
+	if (p->len != 0)
+		return emit_line(p, LINE_PRE_CONTENT, p->buf, p->len);
+	return 1;
+}
blob - 46ef180b412797d8b711d722ee03998dcf4903b2 (mode 644)
blob + /dev/null
--- textplain.c
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-/*
- * A streaming text/plain "parser."
- */
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "telescope.h"
-#include "parser.h"
-
-static int	textplain_parse(struct parser*, const char*, size_t);
-static int	textplain_foreach_line(struct parser*, const char*, size_t);
-static int	textplain_free(struct parser*);
-
-static inline int
-emit_line(struct parser *p, enum line_type type, const char *line, size_t len)
-{
-	struct line *l;
-
-	if ((l = calloc(1, sizeof(*l))) == NULL)
-		return 0;
-
-	l->type = type;
-
-	if (len != 0) {
-		if ((l->line = calloc(1, len+1)) == NULL) {
-			free(l);
-			return 0;
-		}
-
-		memcpy(l->line, line, len);
-	}
-
-	if (TAILQ_EMPTY(&p->head))
-		TAILQ_INSERT_HEAD(&p->head, l, lines);
-	else
-		TAILQ_INSERT_TAIL(&p->head, l, lines);
-
-	return 1;
-}
-
-void
-textplain_initparser(struct parser *p)
-{
-	memset(p, 0, sizeof(*p));
-
-	p->name = "text/plain";
-	p->parse = &textplain_parse;
-	p->free = &textplain_free;
-}
-
-static int
-textplain_parse(struct parser *p, const char *buf, size_t size)
-{
-	return parser_foreach_line(p, buf, size, textplain_foreach_line);
-}
-
-static int
-textplain_foreach_line(struct parser *p, const char *line, size_t linelen)
-{
-	return emit_line(p, LINE_PRE_CONTENT, line, linelen);
-}
-
-static int
-textplain_free(struct parser *p)
-{
-	if (p->len != 0)
-		return emit_line(p, LINE_PRE_CONTENT, p->buf, p->len);
-	return 1;
-}