import cmark/native as cmark except Node, Parser # the builtin re library would probably be better for this - it can directly take cstrings (so better perf when dealing with the cstrings from cmark) and may be faster # unfortunately it does not expose a findAll thing which returns the *positions* of everything for some weird reason cmark_gfm_core_extensions_ensure_registered() type Node = object raw: NodePtr BorrowedNode = object raw: NodePtr Parser = object raw: ParserPtr proc `=copy`(dest: var Node, source: Node) {.error.} proc `=destroy`(x: var Node) = cmark_node_free(x.raw) proc `=destroy`(x: var BorrowedNode) = discard proc `=destroy`(x: var Parser) = cmark_parser_free(x.raw) proc borrow(n: Node): BorrowedNode = BorrowedNode(raw: n.raw) proc newParser(options: int64, extensions: seq[string]): Parser = let parser: ParserPtr = cmark_parser_new(options.cint) if parser == nil: raise newException(CatchableError, "failed to initialize parser") # load and enable desired syntax extensions # these are freed with the parser (probably) for ext in extensions: let e: cstring = ext let eptr = cmark_find_syntax_extension(e) if eptr == nil: cmark_parser_free(parser) raise newException(LibraryError, "failed to find extension " & ext) if cmark_parser_attach_syntax_extension(parser, eptr) == 0: cmark_parser_free(parser) raise newException(CatchableError, "failed to attach extension " & ext) Parser(raw: parser) proc parse(p: Parser, document: string): Node = let str: cstring = document length = len(document).csize_t cmark_parser_feed(p.raw, str, length) let ast = cmark_parser_finish(p.raw) if ast == nil: raise newException(CatchableError, "parsing failed - should not occur") Node(raw: ast) proc nodeType(n: BorrowedNode): NodeType = cmark_node_get_type(n.raw) proc nodeContent(n: BorrowedNode): string = $cmark_node_get_literal(n.raw) proc newNode(ty: NodeType, content: string): Node = let raw = cmark_node_new(ty) if raw == nil: raise newException(CatchableError, "node creation failed") if cmark_node_set_literal(raw, content) != 1: cmark_node_free(raw) raise newException(CatchableError, "node content setting failed") Node(raw: raw) proc parentNode(parentOf: BorrowedNode): BorrowedNode = BorrowedNode(raw: cmark_node_parent(parentOf.raw)) proc pushNodeAfter(after: BorrowedNode, node: sink Node) {.nodestroy.} = assert cmark_node_insert_before(after.raw, node.raw) == 1 proc unlinkNode(node: sink BorrowedNode): Node {.nodestroy.} = cmark_node_unlink(node.raw) Node(raw: node.raw) proc render(ast: Node, options: int64, parser: Parser): string = let html: cstring = cmark_render_html(ast.raw, options.cint, cmark_parser_get_syntax_extensions(parser.raw)) defer: free(html) result = $html iterator cmarkTree(root: BorrowedNode): (EventType, BorrowedNode) {.inline.} = var iter = cmark_iter_new(root.raw) if iter == nil: raise newException(CatchableError, "iterator initialization failed") defer: cmark_iter_free(iter) while true: let ev = cmark_iter_next(iter) if ev == etDone: break let node: NodePtr = cmark_iter_get_node(iter) yield (ev, BorrowedNode(raw: node)) proc renderToHtml*(input: string): string = let opt = CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE or CMARK_OPT_TABLE_PREFER_STYLE_ATTRIBUTES # initialize parser with the extensions in use, parse things let parser = newParser(opt, @["table", "strikethrough"]) let doc = parse(parser, input) render(doc, opt, parser)