Class: MaquinaStream::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/maquina_stream/renderer.rb,
lib/maquina_stream/renderer/fence.rb,
lib/maquina_stream/renderer/post_pass.rb,
lib/maquina_stream/renderer/tag_blocks.rb,
lib/maquina_stream/renderer/view_context.rb

Overview

Markdown in, sanitized HTML out.

MaquinaStream::Renderer.call(markdown, mode: :streaming) # => SafeBuffer

Pure: no request, no controller, no stubbing. The same function serves the live stream, a page reload, a replay and an export, and mode changes nothing but whether the reveal attributes are emitted. If this ever starts needing request context, that is a design error rather than a plumbing one.

The pipeline

  1. MaquinaRemend.call repairs the unterminated markdown a half-written buffer always ends in — an open bold run, an unclosed fence.
  2. Renderer::TagBlocks puts blank lines around the opening and closing tags of a registered tag, so a block-level <thinking> is its own HTML block rather than a closing tag stranded inside a paragraph.
  3. Commonmarker parses it, with source positions.
  4. Renderer::PostPass rewrites elements: fences, custom tags, registered element overrides, text direction, block indices.
  5. Sanitizer runs, unconditionally, last.

Renderer returns one HTML string for the whole buffer. It does not split it into blocks or stamp ids and digests on them — that is Document, and without it a page cannot be repaired at all. Host code almost always wants MaquinaStream.render or Document rather than this.

Defined Under Namespace

Classes: Fence, PostPass, TagBlocks, ViewContext

Constant Summary collapse

MODES =

The two render modes. Both produce the same document byte for byte; the mode is carried so the post-pass knows whether the message is still being written.

%i[streaming static].freeze
COMMONMARKER_OPTIONS =

unsafe: true lets registered custom tags through the parser. It is not a relaxation: the sanitizer is the gate, it runs unconditionally, and it runs last. Model output is assumed hostile at every step before it.

{
  parse: {sourcepos_chars: true},
  render: {sourcepos: true, unsafe: true, github_pre_lang: false},
  extension: {
    table: true,
    strikethrough: true,
    autolink: true,
    tasklist: true,
    footnotes: true
  }
}.freeze
COMMONMARKER_PLUGINS =

commonmarker highlights with syntect by default, which would ship a second highlighter's inline styles into a pipeline that already owns highlighting (Rouge, at fence close only) and forbids inline colour. Confirmed against commonmarker 2.10.0 in test/sourcepos_test.rb.

{syntax_highlighter: nil}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mode: :streaming, config: MaquinaStream.config) ⇒ Renderer

Builds a reusable renderer. Raises ArgumentError unless mode: is one of MODES.

Raises:

  • (ArgumentError)


98
99
100
101
102
103
# File 'lib/maquina_stream/renderer.rb', line 98

def initialize(mode: :streaming, config: MaquinaStream.config)
  raise ArgumentError, "mode must be one of #{MODES.join(", ")}" unless MODES.include?(mode)

  @mode = mode
  @config = config
end

Instance Attribute Details

#config ⇒ Object (readonly)

The Configuration this renderer reads.



72
73
74
# File 'lib/maquina_stream/renderer.rb', line 72

def config
  @config
end

#mode ⇒ Object (readonly)

The mode this renderer was built with, one of MODES.



69
70
71
# File 'lib/maquina_stream/renderer.rb', line 69

def mode
  @mode
end

Class Method Details

.call(markdown, mode: :streaming, config: MaquinaStream.config) ⇒ Object

Renders markdown in one call. The usual entry point.

mode: is :streaming or :static; anything else raises ArgumentError. config: defaults to the global MaquinaStream.config, and is a keyword rather than a lookup so the renderer stays callable from a plain Ruby process with no Rails around it.



80
81
82
# File 'lib/maquina_stream/renderer.rb', line 80

def self.call(markdown, mode: :streaming, config: MaquinaStream.config)
  new(mode: mode, config: config).call(markdown)
end

.prepare(markdown) ⇒ Object

The buffer as commonmarker will see it: repaired, then normalised so a registered block-level tag stands on its own. Document parses this rather than the raw buffer, so its source positions are the ones the rendered document was built from — two parses of two different strings do not line up, and the block ids are derived from the alignment.

Byte-identical to MaquinaRemend.call(markdown) when no tag is registered.



92
93
94
# File 'lib/maquina_stream/renderer.rb', line 92

def self.prepare(markdown)
  TagBlocks.call(MaquinaRemend.call(markdown))
end

Instance Method Details

#call(markdown) ⇒ Object

Renders one markdown string to sanitized HTML.

Returns an html_safe String — a plain String when ActiveSupport is not loaded — and an empty one for nil or whitespace-only input.



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/maquina_stream/renderer.rb', line 109

def call(markdown)
  return safe("") if markdown.nil? || markdown.strip.empty?

  prepared = self.class.prepare(markdown)
  parsed = Commonmarker.to_html(prepared, options: COMMONMARKER_OPTIONS, plugins: COMMONMARKER_PLUGINS)
  fragment = Nokogiri::HTML5.fragment(parsed)

  PostPass.new(fragment, markdown: prepared, mode: mode, config: config).call

  safe(Sanitizer.call(fragment.to_html, config: config))
end