cmark

My personal build of CMark ✏️

Commit
f28ef91f1199f483492438533bc23b8960047445
Parent
8f97a33837ce2a836036e60fc05385d172435500
Author
John MacFarlane <jgm@berkeley.edu>
Date

commonmark.rb - support remaining elements in HTML renderer.

Halt on finding unsupported method.

Diffstat

1 file changed, 135 insertions, 12 deletions

Status File Name N° Changes Insertions Deletions
Modified commonmark.rb 147 135 12
diff --git a/commonmark.rb b/commonmark.rb
@@ -1,7 +1,9 @@
+#!/usr/bin/env ruby
 require 'ffi'
 require 'stringio'
 require 'cgi'
 require 'set'
+require 'uri'
 
 module CMark
   extend FFI::Library
@@ -12,6 +14,7 @@ module CMark
                     :atx_header, :setext_header, :hrule, :reference_def,
                     :str, :softbreak, :linebreak, :code, :inline_html,
                     :emph, :strong, :link, :image]
+  enum :list_type, [:no_list, :bullet_list, :ordered_list]
 
   attach_function :cmark_free_nodes, [:node], :void
   attach_function :cmark_node_unlink, [:node], :void
@@ -24,11 +27,17 @@ module CMark
   attach_function :cmark_node_previous, [:node], :node
   attach_function :cmark_node_get_type, [:node], :node_type
   attach_function :cmark_node_get_string_content, [:node], :string
+  attach_function :cmark_node_get_url, [:node], :string
+  attach_function :cmark_node_get_title, [:node], :string
   attach_function :cmark_node_get_header_level, [:node], :int
+  attach_function :cmark_node_get_list_type, [:node], :list_type
+  attach_function :cmark_node_get_list_start, [:node], :int
+  attach_function :cmark_node_get_list_tight, [:node], :bool
 end
 
 class Node
-  attr_accessor :type, :children, :string_content, :header_level
+  attr_accessor :type, :children, :string_content, :header_level,
+                :list_type, :list_start, :list_tight, :url, :title
   def initialize(pointer)
     if pointer.null?
       return nil
@@ -43,7 +52,20 @@ class Node
       b = CMark::cmark_node_next(b)
     end
     @string_content = CMark::cmark_node_get_string_content(pointer)
-    @header_level = CMark::cmark_node_get_header_level(pointer)
+    if @type == :atx_header || @type == :setext_header
+      @header_level = CMark::cmark_node_get_header_level(pointer)
+    end
+    if @type == :list
+      @list_type = CMark::cmark_node_get_list_type(pointer)
+      @list_start = CMark::cmark_node_get_list_start(pointer)
+      @list_tight = CMark::cmark_node_get_list_tight(pointer)
+    end
+    if @type == :link || @type == :image
+      @url = CMark::cmark_node_get_url(pointer)
+      if !@url then @url = "" end
+      @title = CMark::cmark_node_get_title(pointer)
+      if !@title then @title = "" end
+    end
     if @type == :document
       self.free
     end
@@ -64,7 +86,7 @@ class Node
 end
 
 class Renderer
-  attr_reader :warnings
+  attr_accessor :in_tight, :warnings, :in_plain
   def initialize(stream = nil)
     if stream
       @stream = stream
@@ -75,6 +97,8 @@ class Renderer
     end
     @need_blocksep = false
     @warnings = Set.new []
+    @in_tight = false
+    @in_plain = false
   end
 
   def outf(format, *args)
@@ -103,12 +127,17 @@ class Renderer
       if @stringwriter
         return @stream.string
       end
+    elsif self.in_plain && node.type != :str && node.type != :softbreak
+      # pass through looking for str, softbreak
+      node.children.each do |child|
+        render(child)
+      end
     else
       begin
         self.send(node.type, node)
-      rescue NoMethodError
+      rescue NoMethodError => e
         @warnings.add("WARNING:  " + node.type.to_s + " not implemented.")
-        self.out(node.children)
+        raise e
       end
     end
   end
@@ -140,43 +169,114 @@ class Renderer
     self.out("\n\n")
   end
 
-  def asblock(&blk)
+  def containersep
+    if !self.in_tight
+      self.out("\n")
+    end
+  end
+
+  def block(&blk)
     if @need_blocksep
       self.blocksep
     end
     blk.call
     @need_blocksep = true
   end
+
+  def container(starter, ender, &blk)
+    self.out(starter)
+    self.containersep
+    @need_blocksep = false
+    blk.call
+    self.containersep
+    self.out(ender)
+  end
+
+  def plain(&blk)
+    old_in_plain = @in_plain
+    @in_plain = true
+    blk.call
+    @in_plain = old_in_plain
+  end
 end
 
 class HtmlRenderer < Renderer
   def header(node)
-    asblock do
+    block do
       self.out("<h", node.header_level, ">", node.children,
              "</h", node.header_level, ">")
     end
   end
 
   def paragraph(node)
-    asblock do
-      self.out("<p>", node.children, "</p>")
+    block do
+      if self.in_tight
+        self.out(node.children)
+      else
+        self.out("<p>", node.children, "</p>")
+      end
+    end
+  end
+
+  def list(node)
+    old_in_tight = self.in_tight
+    self.in_tight = node.list_tight
+    block do
+      if node.list_type == :bullet_list
+        container("<ul>", "</ul>") do
+          self.out(node.children)
+        end
+      else
+        start = node.list_start == 1 ? '' :
+                (' start="' + node.list_start.to_s + '"')
+        container(start, "</ol>") do
+          self.out(node.children)
+        end
+      end
+    end
+    self.in_tight = old_in_tight
+  end
+
+  def list_item(node)
+    block do
+      container("<li>", "</li>") do
+        self.out(node.children)
+      end
+    end
+  end
+
+  def blockquote(node)
+    block do
+      container("<blockquote>", "</blockquote>") do
+        self.out(node.children)
+      end
     end
   end
 
   def hrule(node)
-    asblock do
+    block do
       self.out("<hr />")
     end
   end
 
   def code_block(node)
-    asblock do
+    block do
       self.out("<pre><code>")
       self.out(CGI.escapeHTML(node.string_content))
       self.out("</code></pre>")
     end
   end
 
+  def html(node)
+    block do
+      self.out(node.string_content)
+    end
+  end
+
+  def inline_html(node)
+    self.out(node.string_content)
+  end
+
   def emph(node)
     self.out("<em>", node.children, "</em>")
   end
@@ -185,6 +285,24 @@ class HtmlRenderer < Renderer
     self.out("<strong>", node.children, "</strong>")
   end
 
+  def link(node)
+    self.out('<a href="', URI.escape(node.url), '"')
+    if node.title
+      self.out(' title="', CGI.escapeHTML(node.title), '"')
+    end
+    self.out('>', node.children, '</a>')
+  end
+
+  def image(node)
+    self.out('<img src="', URI.escape(node.url), '"')
+    if node.title
+      self.out(' title="', CGI.escapeHTML(node.title), '"')
+    end
+    plain do
+      self.out(' alt="', node.children, '" />')
+    end
+  end
+
   def str(node)
     self.out(CGI.escapeHTML(node.string_content))
   end
@@ -195,12 +313,17 @@ class HtmlRenderer < Renderer
     self.out("</code>")
   end
 
+  def linebreak(node)
+    self.out("<br/>")
+    self.softbreak(node)
+  end
+
   def softbreak(node)
     self.out("\n")
   end
 end
 
-doc = Node.parse_file(STDIN)
+doc = Node.parse_file(ARGF)
 renderer = HtmlRenderer.new(STDOUT)
 renderer.render(doc)
 renderer.warnings.each do |w|