aboutsummaryrefslogtreecommitdiff
path: root/commonmark.rb
blob: 32a4a72a2e76edb5de21720a13f9d5b8a84ba165 (plain)
  1. require 'ffi'
  2. require 'stringio'
  3. require 'cgi'
  4. require 'set'
  5. module CMark
  6. extend FFI::Library
  7. ffi_lib ['libcmark', 'cmark']
  8. typedef :pointer, :node
  9. enum :node_type, [:document, :blockquote, :list, :list_item,
  10. :fenced_code, :indented_code, :html, :paragraph,
  11. :atx_header, :setext_header, :hrule, :reference_def,
  12. :str, :softbreak, :linebreak, :code, :inline_html,
  13. :emph, :strong, :link, :image]
  14. attach_function :cmark_free_nodes, [:node], :void
  15. attach_function :cmark_node_unlink, [:node], :void
  16. attach_function :cmark_markdown_to_html, [:string, :int], :string
  17. attach_function :cmark_parse_document, [:string, :int], :node
  18. attach_function :cmark_node_first_child, [:node], :node
  19. attach_function :cmark_node_parent, [:node], :node
  20. attach_function :cmark_node_next, [:node], :node
  21. attach_function :cmark_node_previous, [:node], :node
  22. attach_function :cmark_node_get_type, [:node], :node_type
  23. attach_function :cmark_node_get_string_content, [:node], :string
  24. attach_function :cmark_node_get_header_level, [:node], :int
  25. end
  26. class Node
  27. attr_accessor :type, :children, :string_content, :header_level
  28. def initialize(pointer)
  29. if pointer.null?
  30. return nil
  31. end
  32. @pointer = pointer
  33. @type = CMark::cmark_node_get_type(pointer)
  34. @children = []
  35. first_child = CMark::cmark_node_first_child(pointer)
  36. b = first_child
  37. while !b.null?
  38. @children << Node.new(b)
  39. b = CMark::cmark_node_next(b)
  40. end
  41. @string_content = CMark::cmark_node_get_string_content(pointer)
  42. @header_level = CMark::cmark_node_get_header_level(pointer)
  43. if @type == :document
  44. self.free
  45. end
  46. end
  47. def self.parse_string(s)
  48. Node.new(CMark::cmark_parse_document(s, s.bytesize))
  49. end
  50. def self.parse_file(f)
  51. s = f.read()
  52. self.parse_string(s)
  53. end
  54. def free
  55. CMark::cmark_free_nodes(@pointer)
  56. end
  57. end
  58. class Renderer
  59. attr_reader :warnings
  60. def initialize(stream = nil)
  61. if stream
  62. @stream = stream
  63. @stringwriter = false
  64. else
  65. @stringwriter = true
  66. @stream = StringIO.new
  67. end
  68. @need_blocksep = false
  69. @warnings = Set.new []
  70. end
  71. def outf(format, *args)
  72. @stream.printf(format, *args)
  73. end
  74. def out(*args)
  75. args.each do |arg|
  76. if arg.kind_of?(String)
  77. @stream.write(arg)
  78. elsif arg.kind_of?(Node)
  79. self.render(arg)
  80. elsif arg.kind_of?(Array)
  81. arg.each { |x| self.out(x) }
  82. else
  83. @stream.write(arg)
  84. end
  85. end
  86. end
  87. def render(node)
  88. @node = node
  89. if node.type == :document
  90. self.document(node)
  91. self.out("\n")
  92. if @stringwriter
  93. return @stream.string
  94. end
  95. else
  96. begin
  97. self.send(node.type, node)
  98. rescue NoMethodError
  99. @warnings.add("WARNING: " + node.type.to_s + " not implemented.")
  100. self.out(node.children)
  101. end
  102. end
  103. end
  104. def document(node)
  105. self.out(node.children)
  106. end
  107. def indented_code(node)
  108. self.code_block(node)
  109. end
  110. def fenced_code(node)
  111. self.code_block(node)
  112. end
  113. def setext_header(node)
  114. self.header(node)
  115. end
  116. def atx_header(node)
  117. self.header(node)
  118. end
  119. def reference_def(node)
  120. end
  121. def blocksep
  122. self.out("\n\n")
  123. end
  124. def asblock(&blk)
  125. if @need_blocksep
  126. self.blocksep
  127. end
  128. blk.call
  129. @need_blocksep = true
  130. end
  131. end
  132. class HtmlRenderer < Renderer
  133. def header(node)
  134. asblock do
  135. self.out("<h", node.header_level, ">", node.children,
  136. "</h", node.header_level, ">")
  137. end
  138. end
  139. def paragraph(node)
  140. asblock do
  141. self.out("<p>", node.children, "</p>")
  142. end
  143. end
  144. def hrule(node)
  145. asblock do
  146. self.out("<hr />")
  147. end
  148. end
  149. def code_block(node)
  150. asblock do
  151. self.out("<pre><code>")
  152. self.out(CGI.escapeHTML(node.string_content))
  153. self.out("</code></pre>")
  154. end
  155. end
  156. def emph(node)
  157. self.out("<em>", node.children, "</em>")
  158. end
  159. def strong(node)
  160. self.out("<strong>", node.children, "</strong>")
  161. end
  162. def str(node)
  163. self.out(CGI.escapeHTML(node.string_content))
  164. end
  165. def code(node)
  166. self.out("<code>")
  167. self.out(CGI.escapeHTML(node.string_content))
  168. self.out("</code>")
  169. end
  170. def softbreak(node)
  171. self.out("\n")
  172. end
  173. end
  174. doc = Node.parse_file(STDIN)
  175. renderer = HtmlRenderer.new(STDOUT)
  176. renderer.render(doc)
  177. renderer.warnings.each do |w|
  178. STDERR.write(w)
  179. STDERR.write("\n")
  180. end
  181. # def markdown_to_html(s)
  182. # len = s.bytes.length
  183. # CMark::cmark_markdown_to_html(s, len)
  184. # end
  185. # print markdown_to_html(STDIN.read())