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|