#include "markdown.h" #include "arena.h" #include "str.h" #include "strstream.h" #include "file.h" #include "ini.h" #include "tracelog.h" #ifndef MD_LIST_MAX_DEPTH #define MD_LIST_MAX_DEPTH 8 #endif typedef struct { struct { int indent; int count; bool list_is_ordered[MD_LIST_MAX_DEPTH]; } list; struct { bool is_in_block; strview_t lang; } code; bool is_bold; bool is_italic; bool is_in_paragraph; strview_t raw_line; md_options_t *options; md_parser_t *curparser; } markdown_ctx_t; static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out); static int markdown__count_chars(strview_t *line, char c); static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start); static strview_t markdown__parse_header(markdown_ctx_t *md, strview_t line, outstream_t *out); static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out); static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out); static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out); static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text); static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out); static void markdown__close_list(markdown_ctx_t *md, outstream_t *out); static void markdown__escape(strview_t view, outstream_t *out); str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options) { str_t text = fileReadWholeStr(&scratch, filename); return markdownStr(arena, strv(text), options); } str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options) { instream_t in = istrInitLen(markdown_str.buf, markdown_str.len); markdown__parse_config(arena, &in, options ? options->out_config : NULL); outstream_t out = ostrInit(arena); markdown_ctx_t md = { .list = { .indent = -1, }, .options = options, }; while (!istrIsFinished(in)) { md.raw_line = istrGetLine(&in); markdown__parse_line(&md, strvTrimLeft(md.raw_line), &out, true, true); } markdown__empty_line(&md, &out); return ostrAsStr(&out); } // == PRIVATE FUNCTIONS ================================================== static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out) { strview_t first_line = strvTrim(istrGetLine(in)); if (!strvEquals(first_line, strv("---"))) { return; } strview_t ini_data = strvInitLen(in->cur, 0); usize data_beg = istrTell(*in); while (!istrIsFinished(*in)) { strview_t line = istrGetViewEither(in, strv("\r\n")); if (strvEquals(strvTrim(line), strv("---"))) { break; } istrSkipWhitespace(in); } usize data_end = istrTell(*in); ini_data.len = data_end - data_beg - 3; if (out) { // allocate the string as ini_t only as a copy str_t ini_str = str(arena, ini_data); *out = iniParseStr(arena, strv(ini_str), NULL); } } static int markdown__count_chars(strview_t *line, char c) { strview_t temp = *line; int n = 0; while (strvFront(temp) == c) { n++; temp = strvRemovePrefix(temp, 1); } *line = temp; return n; } static strview_t markdown__parse_header(markdown_ctx_t* md, strview_t line, outstream_t *out) { int n = markdown__count_chars(&line, '#'); line = strvTrimLeft(line); ostrPrintf(out, "", n); markdown__parse_line(md, line, out, false, false); ostrPrintf(out, "", n); return STRV_EMPTY; } static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out) { // check if there is anything before this character, if there is // it means we're in the middle of a line and we should ignore strview_t prev = strvSub(md->raw_line, 0, line.buf - md->raw_line.buf); int space_count; for (space_count = 0; space_count < prev.len; ++space_count) { if (prev.buf[space_count] != ' ') break; } if (space_count < prev.len) { return line; } // if its only * or -, this is a list if (line.len > 1 && line.buf[1] == ' ') { strview_t raw_line = md->raw_line; int cur_indent = markdown__count_chars(&raw_line, ' '); // start of list if (md->list.indent < cur_indent) { if (md->list.count >= MD_LIST_MAX_DEPTH) { fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH); } md->list.list_is_ordered[md->list.count++] = false; ostrPuts(out, strv("\n")); } } md->list.indent = -1; // close paragraph if (md->is_in_paragraph) { ostrPuts(out, strv("

\n")); } md->is_in_paragraph = false; } static void markdown__close_list(markdown_ctx_t *md, outstream_t *out) { if (md->list.count > 0) { if (md->list.list_is_ordered[--md->list.count]) { ostrPuts(out, strv("\n")); } else { ostrPuts(out, strv("\n")); } } } static void markdown__escape(strview_t view, outstream_t *out) { for (usize i = 0; i < view.len; ++i) { switch (view.buf[i]){ case '&': ostrPuts(out, strv("&")); break; case '<': ostrPuts(out, strv("<")); break; case '>': ostrPuts(out, strv(">")); break; default: ostrPutc(out, view.buf[i]); break; } } }