xenotime/src/md.nim

87 lines
3.6 KiB
Nim

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)