aboutsummaryrefslogtreecommitdiff
path: root/commonmark.rb
blob: 4546845951b67790899d7adacbeb184c2acbecff (plain)
  1. require 'ffi'
  2. require 'stringio'
  3. require 'cgi'
  4. module CMark
  5. extend FFI::Library
  6. ffi_lib ['libcmark', 'cmark']
  7. typedef :pointer, :node
  8. enum :node_type, [:document, :blockquote, :list, :list_item,
  9. :fenced_code, :indented_code, :html, :paragraph,
  10. :atx_header, :setext_header, :hrule, :reference_def,
  11. :str, :softbreak, :linebreak, :code, :inline_html,
  12. :emph, :strong, :link, :image]
  13. attach_function :cmark_free_nodes, [:node], :void
  14. attach_function :cmark_node_unlink, [:node], :void
  15. attach_function :cmark_markdown_to_html, [:string, :int], :string
  16. attach_function :cmark_parse_document, [:string, :int], :node
  17. attach_function :cmark_node_first_child, [:node], :node
  18. attach_function :cmark_node_parent, [:node], :node
  19. attach_function :cmark_node_next, [:node], :node
  20. attach_function :cmark_node_previous, [:node], :node
  21. attach_function :cmark_node_get_type, [:node], :node_type
  22. attach_function :cmark_node_get_string_content, [:node], :string
  23. attach_function :cmark_node_get_header_level, [:node], :int
  24. end
  25. class Node
  26. attr_accessor :type, :children, :string_content, :header_level
  27. def initialize(pointer)
  28. if pointer.null?
  29. return nil
  30. end
  31. @pointer = pointer
  32. @type = CMark::cmark_node_get_type(pointer)
  33. @children = []
  34. first_child = CMark::cmark_node_first_child(pointer)
  35. b = first_child
  36. while !b.null?
  37. @children << Node.new(b)
  38. b = CMark::cmark_node_next(b)
  39. end
  40. @string_content = CMark::cmark_node_get_string_content(pointer)
  41. @header_level = CMark::cmark_node_get_header_level(pointer)
  42. if @type == :document
  43. self.free
  44. end
  45. end
  46. def self.from_markdown(s)
  47. len = s.bytes.length
  48. Node.new(CMark::cmark_parse_document(s, len))
  49. end
  50. def free
  51. CMark::cmark_free_nodes(@pointer)
  52. end
  53. end
  54. class Renderer
  55. def initialize(stream = nil)
  56. if stream
  57. @stream = stream
  58. @stringwriter = false
  59. else
  60. @stringwriter = true
  61. @stream = StringIO.new
  62. end
  63. end
  64. def outf(format, *args)
  65. @stream.printf(format, *args)
  66. end
  67. def out(arg)
  68. @stream.write(arg)
  69. end
  70. def render(node)
  71. case node.type
  72. when :document
  73. self.document(node.children)
  74. if @stringwriter
  75. @stream.string
  76. end
  77. when :paragraph
  78. self.paragraph(node.children)
  79. when :setext_header, :atx_header
  80. self.header(node.header_level, node.children)
  81. when :str
  82. self.str(node.string_content)
  83. else
  84. # raise "unimplemented " + node.type.to_s
  85. end
  86. end
  87. def document(children)
  88. children.each { |x| render(x) }
  89. end
  90. def header(level, children)
  91. children.each { |x| render(x) }
  92. end
  93. def paragraph(children)
  94. children.each { |x| render(x) }
  95. end
  96. def str(content)
  97. self.out(content)
  98. end
  99. end
  100. class HtmlRenderer < Renderer
  101. def header(level, children)
  102. self.outf("<h%d>", level)
  103. children.each { |x| render(x) }
  104. self.outf("</h%d>\n", level)
  105. end
  106. def paragraph(children)
  107. self.out("<p>")
  108. children.each { |x| render(x) }
  109. self.out("</p>\n")
  110. end
  111. def str(content)
  112. self.out(CGI.escapeHTML(content))
  113. end
  114. end
  115. doc = Node.from_markdown(STDIN.read())
  116. renderer = HtmlRenderer.new(STDOUT)
  117. renderer.render(doc)
  118. # def markdown_to_html(s)
  119. # len = s.bytes.length
  120. # CMark::cmark_markdown_to_html(s, len)
  121. # end
  122. # print markdown_to_html(STDIN.read())