diff options
-rwxr-xr-x[-rw-r--r--] | commonmark.rb | 147 |
1 files changed, 135 insertions, 12 deletions
diff --git a/commonmark.rb b/commonmark.rb index 32a4a72..8767c04 100644..100755 --- 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| |