From a862bc82f921b065fbec452eb65ff54429a0d28a Mon Sep 17 00:00:00 2001 From: Linnnus Date: Wed, 14 Feb 2024 11:33:03 +0100 Subject: feat(creole): Support [[https://example.com|links]] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for freelinks. This patch also introduced hprint() – a function to print text while escaping HTML characters. This isn't very DRY since that's also implemented in do_replacements(), but the abstraction necessary to reuse this functionality introduced disproportional amount of mental overhead. --- src/creole.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) (limited to 'src/creole.c') diff --git a/src/creole.c b/src/creole.c index 7472a3b..79a767d 100644 --- a/src/creole.c +++ b/src/creole.c @@ -16,6 +16,24 @@ void process(const char *begin, const char *end, bool new_block, FILE *out); int do_headers(const char *begin, const char *end, bool new_block, FILE *out); int do_paragraph(const char *begin, const char *end, bool new_block, FILE *out); int do_replacements(const char *begin, const char *end, bool new_block, FILE *out); +int do_link(const char *begin, const char *end, bool new_block, FILE *out); + +// Prints string escaped. +void hprint(FILE *out, const char *begin, const char *end) { + for (const char *p = begin; p != end; p++) { + if (*p == '&') { + fputs("&", out); + } else if (*p == '"') { + fputs(""", out); + } else if (*p == '>') { + fputs(">", out); + } else if (*p == '<') { + fputs("<", out); + } else { + fputc(*p, out); + } + } +} // A parser takes a (sub)string and returns the number of characters consumed, if any. // @@ -23,7 +41,7 @@ int do_replacements(const char *begin, const char *end, bool new_block, FILE *ou // The sign of the return value determines whether a new block should begin, after the consumed text. typedef int (* parser_t)(const char *begin, const char *end, bool new_block, FILE *out); -static parser_t parsers[] = { do_headers, do_paragraph, do_replacements }; +static parser_t parsers[] = { do_headers, do_paragraph, do_link, do_replacements }; int do_headers(const char *begin, const char *end, bool new_block, FILE *out) { if (!new_block) { // Headers are block-level elements. @@ -93,8 +111,10 @@ static struct { const char *from, *to; } replacements[] = { // Escaped special characters - // TODO (e.g. "\\{" should become "{" so users can escape and void it being parsed as markup + {"~[[", "[["}, + {"~]]", "]]"}, // NOTE: This pattern is duplicated in do_link(). // Characters that have special meaning in HTML + // NOTE: These rules are duplicated in hprint(). {"<", "<"}, {">", ">"}, {"\"", """}, @@ -117,6 +137,49 @@ int do_replacements(const char *begin, const char *end, bool new_block, FILE *ou return 0; } +int do_link(const char *begin, const char *end, bool new_block, FILE *out) +{ + // Links start with "[[". + if (begin + 2 >= end || begin[0] != '[' || begin[1] != '[') { + return 0; + } + const char *start = begin + 2; + + // Find the matching, unescaped "]]". + // Poor man's for...else + const char *stop = start - 1; + do { + stop = strnstr(stop + 1, "]]", end - (stop + 1)); + } while (stop != NULL && stop[-1] == '~'); + if (stop == NULL) { + return 0; + } + + // FIXME: How do we handle WikiWord style links? Should we just append ".html" if is_wikiword()? + + const char *pipe = strnstr(start, "|", stop - start); + if (pipe != NULL) { + const char *link_address_start = start; + const char *link_address_stop = pipe; + fprintf(out, ""); + + const char *link_text_start = pipe + 1; + const char *link_text_stop = stop; + process(link_text_start, link_text_stop, false, out); + fprintf(out, ""); + } else { + fprintf(out, ""); + hprint(out, start, stop); // Don't parse markup when we know it's a link. + fprintf(out, ""); + } + + return stop - start + 4 /* [[]] */; +} + void process(const char *begin, const char *end, bool new_block, FILE *out) { const char *p = begin; while (p < end) { -- cgit v1.2.3