Skip to content

Commit

Permalink
Merge pull request #104 from joyofrails/feat/commonmarker
Browse files Browse the repository at this point in the history
Adopt commonmarker for markdown parsing
  • Loading branch information
rossta authored May 5, 2024
2 parents b6428d8 + 2d96f9f commit 453d905
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ gem "inline_svg" # Embed SVGs in Rails views and style them with CSS [https://gi
gem "rouge", group: [:default, :wasm] # Pure Ruby syntaix highlighter [https://github.com/rouge-ruby/rouge
gem "sitepress-rails", group: [:default, :wasm] # Static site generator for Rails [https://sitepress.cc/getting-started/rails]
gem "phlex-rails", group: [:default, :wasm] # An object-oriented alternative to ActionView for Ruby on Rails. [https://github.com/phlex-ruby/phlex-rails]
gem "markly"
gem "commonmarker", require: false

gem "bootsnap", require: false # Reduces boot times through caching; required in config/boot.rb [https://github.com/Shopify/bootsnap]

Expand Down
5 changes: 3 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ GEM
childprocess (5.0.0)
code_analyzer (0.5.5)
sexp_processor
commonmarker (1.1.2-arm64-darwin)
commonmarker (1.1.2-x86_64-linux)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crack (1.0.0)
Expand Down Expand Up @@ -264,7 +266,6 @@ GEM
mail_interceptor (0.0.7)
activesupport
marcel (1.0.4)
markly (0.10.0)
matrix (0.4.2)
mime-types (3.5.2)
mime-types-data (~> 3.2015)
Expand Down Expand Up @@ -517,6 +518,7 @@ DEPENDENCIES
brakeman
bundle-audit
capybara
commonmarker
cuprite!
debug
device_detector
Expand All @@ -534,7 +536,6 @@ DEPENDENCIES
letter_opener
litestream
mail_interceptor
markly
mission_control-jobs
phlex-rails
puma (>= 5.0)
Expand Down
34 changes: 17 additions & 17 deletions app/views/components/markdown/application.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
class Markdown::Application < Markdown::Base
class Handler
class << self
def call(template, content)
Markdown::Application.new(content).call
end
end
# Options for CommonMarker
def default_commonmarker_options
{
render: {
unsafe: true
}
}
end

def visit(node)
return if node.nil?

case node.type
in :header
header(node.header_level) do
visit_children(node)
end
in :link
link(node.url, node.title) { visit_children(node) }
in :inline_html
unsafe_raw(node.string_content)
in :html
unsafe_raw(node.string_content)
in :html_block
unsafe_raw(node.to_html(options: @options))
else
super
end
Expand All @@ -40,7 +33,6 @@ def header(header_level, &)

def code_block(source, metadata = "", **attributes)
language, json_attributes = parse_code_block_metadata(metadata)
Rails.logger.debug("CODE_BLOCK: #{json_attributes.inspect}")
render CodeBlock.new(source, language: language, **json_attributes, **attributes)
end

Expand Down Expand Up @@ -90,4 +82,12 @@ def anchor_svg
</svg>
SVG
end

class Handler
class << self
def call(template, content)
Markdown::Application.new(content).call
end
end
end
end
39 changes: 26 additions & 13 deletions app/views/components/markdown/base.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
# frozen_string_literal: true

require "phlex"
require "markly"
require "commonmarker"

class Markdown::Base < Phlex::HTML
def initialize(content, flags: Markly::DEFAULT)
def initialize(content, **options)
@content = content
@flags = flags
@options = default_commonmarker_options.merge(options)
end

def template
def view_template
visit(doc)
end

private
protected

def doc
Markly.parse(@content, flags: @flags)
Commonmarker.parse(@content, options: @options)
end

# Options for CommonMarker
def default_commonmarker_options
{}
end

def visit(node)
Expand All @@ -30,7 +35,7 @@ def visit(node)
visit_children(node)
in :text
plain(node.string_content)
in :header
in :heading
case node.header_level
in 1 then h1 { visit_children(node) }
in 2 then h2 { visit_children(node) }
Expand All @@ -48,7 +53,7 @@ def visit(node)
p { visit_children(node) }
end
in :link
a(href: node.url, title: node.title) { visit_children(node) }
link(node.url, node.title) { visit_children(node) }
in :image
img(
src: node.url,
Expand All @@ -61,10 +66,10 @@ def visit(node)
strong { visit_children(node) }
in :list
case node.list_type
in :ordered_list then ol { visit_children(node) }
in :bullet_list then ul { visit_children(node) }
in :ordered then ol { visit_children(node) }
in :bullet then ul { visit_children(node) }
end
in :list_item
in :item
li { visit_children(node) }
in :code
inline_code do |**attributes|
Expand All @@ -78,10 +83,12 @@ def visit(node)
end
end
end
in :hrule
in :thematic_break
hr
in :blockquote
in :block_quote
blockquote { visit_children(node) }
in :html_block
# This is a raw HTML block, so we skip here in safe mode
end
end

Expand All @@ -93,6 +100,12 @@ def code_block(code, language, **attributes)
yield(**attributes)
end

def link(url, title, **attrs, &)
a(href: url, title: title, &)
end

private

def visit_children(node)
node.each { |c| visit(c) }
end
Expand Down
26 changes: 13 additions & 13 deletions app/views/components/markdown/erb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,6 @@
# you trust the source of your markdown and that its not user input.

class Markdown::Erb < Markdown::Application
class Handler
class << self
def call(template, content)
content = Markdown::Erb.new(content, flags: Markly::UNSAFE).call
erb.call(template, content)
end

def erb
@erb ||= ActionView::Template.registered_template_handler(:erb)
end
end
end

ERB_TAGS = %r{s*<%.*?%>}
ERB_TAGS_START = %r{\A<%.*?%>}

Expand Down Expand Up @@ -42,4 +29,17 @@ def visit(node)
super
end
end

class Handler
class << self
def call(template, content)
content = Markdown::Erb.new(content).call
erb.call(template, content)
end

def erb
@erb ||= ActionView::Template.registered_template_handler(:erb)
end
end
end
end
2 changes: 1 addition & 1 deletion app/views/components/markdown/toc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def visit(node)
case node.type

# collect header nodes in a 2-level hierarchy
in :header
in :heading
if @tree.empty? || node.header_level <= 2
@tree << [node, []]
elsif node.header_level > 2
Expand Down
26 changes: 18 additions & 8 deletions spec/views/components/markdown/erb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,21 @@ def render(content, &block)
end

it "handles multiline fenced with multiple erbs" do
html = <<~HTML
md = <<~MD
```
<%= 1 + 1 %>
<%= 2 + 2 %>
```
HTML
processed_html = <<~HTML
MD
html = <<~HTML
&lt;%= 1 + 1 %&gt;
&lt;%= 2 + 2 %&gt;
HTML
expect(render(html)).to eq(processed_html)
expect(render(md)).to eq(html)
end

it "handles multiple fences and unfenced areas" do
given_html = <<~HTML
md = <<~MD
<%= 1 + 1 %>
```
<%= 2 + 2 %>
Expand All @@ -77,13 +77,23 @@ def render(content, &block)
<%= 4 + 4 %>
```
<%= 5 + 5 %>
HTML
processed_html = <<~HTML.strip
MD
html = <<~HTML.strip
<%= 1 + 1 %>&lt;%= 2 + 2 %&gt;
<%= 3 + 3 %>&lt;%= 4 + 4 %&gt;
<%= 5 + 5 %>
HTML
expect(render(given_html)).to eq(processed_html)
expect(render(md)).to eq(html)
end

it "renders arbitrary html" do
md = <<~MD
<div>Hello</div>
MD
html = <<~HTML
<div>Hello</div>
HTML
expect(render(md)).to eq(html)
end
end
end

0 comments on commit 453d905

Please sign in to comment.