-- SPDX-FileCopyrightText: © 2022—2026 Vladimir Zorin -- SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later -- Licensed under OWL v1.0+. See LICENSE. --[==[ Streaming markdown parser and renderer with support for CommonMark, GFM extensions (tables, strikethrough, autolinks, task lists), and djot features (fenced divs, inline attributes). Provides static terminal rendering via TSS, streaming terminal rendering for LLM output, and HTML5 output. ]==] local parser = require("markdown.parser") local renderer_registry = require("markdown.renderer") local from_html_mod = require("markdown.from_html") local DEFAULT_CONTENT_WIDTH = 80 -- default rendering width in columns ---! Create a streaming parser instance for incremental parsing ---@ stream(`options`{.tbl .opt}) -> `parser`{.tbl} --[===[ Returns a parser instance with `feed(chunk)`, `finish()`, `reset()`, and `set_event_callback(fn)` methods. Options: - `on_event`: callback `function(event)` invoked for each parser event - `inline`: parse inline elements (default `true`) - `streaming_inline`: emit inline events incrementally (default `true`) ]===] local stream = function(options) return parser.new(options) end -- Shared render pipeline: parse input through a renderer and return its output local render_with = function(renderer, input) local p = parser.new({ on_event = function(e) renderer:render_event(e) end, inline = true, streaming_inline = false, }) p:feed(input or "") p:finish() return renderer:finish() end ---! Render markdown to styled terminal output ---@ render(`input`{.str}, `options`{.tbl .opt}) -> `result`{.tbl .or_nil}, `err`{.str .err} --[===[ Parses and renders markdown through the named renderer. Options: - `renderer`: renderer name (`"static"` default) - `width`: content width (default `80`) - `rss`: custom RSS table to merge with defaults - `indent`: global indentation (default `0`) - `hide_link_urls`: hide URLs in rendered output (default `false`) - `supports_ts`: terminal supports OSC 66 text sizing (default `true`) ]===] local render = function(input, options) options = options or {} local renderer_name = options.renderer or "static" local r, err = renderer_registry.create(renderer_name, { width = options.width or DEFAULT_CONTENT_WIDTH, rss = options.rss, indent = options.indent or 0, hide_link_urls = options.hide_link_urls or false, supports_ts = options.supports_ts, base_path = options.base_path, image_resolver = options.image_resolver, hide_images = options.hide_images or false, }) if not r then return nil, err end return render_with(r, input) end ---! Render markdown to semantic HTML5 ---@ render_html(`input`{.str}, `options`{.tbl .opt}) -> `html`{.str .or_nil}, `err`{.str .err} local render_html = function(input, options) options = options or {} local r, err = renderer_registry.create("html", options) if not r then return nil, err end return render_with(r, input) end ---! Extract a markdown-friendly rendering from raw HTML ---@ from_html(`html`{.str}) -> `markdown`{.str} --[===[ Picks the article root from messy real-world HTML, strips noise tags, converts structural tags to markdown, and decodes entities. Output is intended for a markdown renderer or pager — see `markdown.from_html` submodule for `decode_entities` if needed separately. ]===] local from_html = from_html_mod.extract ---! Parse markdown and collect events into an array ---@ parse(`input`{.str}, `options`{.tbl .opt}) -> `events`{.tbl} --[===[ Parses input and returns an array of event tables. Options: - `on_event`: custom callback (default: collect into array) - `inline`: parse inline elements (default `true`) - `streaming_inline`: emit inline events incrementally (default `true`) ]===] local parse = function(input, options) options = options or {} local collected = {} local p = parser.new({ on_event = options.on_event or function(e) collected[#collected + 1] = e end, inline = options.inline, streaming_inline = options.streaming_inline, }) p:feed(input) p:finish() return collected end return { stream = stream, render = render, render_html = render_html, from_html = from_html, parse = parse, }