Skip to content

Commit

Permalink
Let Convert/Node filters to use context at calltime
Browse files Browse the repository at this point in the history
  • Loading branch information
gjtorikian committed Apr 26, 2024
1 parent 5b16b24 commit 6f51e52
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 8 deletions.
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,15 @@ results tothe next filter. A pipeline has several kinds of filters available to

You can assemble each sequence into a single pipeline, or choose to call each filter individually.

As an example, suppose we want to transform Commonmark source text into Markdown HTML. With the content, we also want to:
As an example, suppose we want to transform Commonmark source text into Markdown HTML:

- change every instance of `$NAME` to "`Johnny"
```
Hey there, @gjtorikian
```

With the content, we also want to:

- change every instance of `Hey` to `Hello`
- strip undesired HTML
- linkify @mention

Expand All @@ -73,7 +79,7 @@ require 'html_pipeline'

class HelloJohnnyFilter < HTMLPipelineFilter
def call
text.gsub("$NAME", "Johnny")
text.gsub("Hey", "Hello")
end
end

Expand Down Expand Up @@ -104,11 +110,21 @@ used to pass around arguments and metadata between filters in a pipeline. For
example, if you want to disable footnotes in the `MarkdownFilter`, you can pass an option in the context hash:

```ruby
context = { markdown: { extensions: { footnotes: false } } }
context = { markdown: { extensions: { footnotes: false } } }
filter = HTMLPipeline::ConvertFilter::MarkdownFilter.new(context: context)
filter.call("Hi **world**!")
```

Alternatively, you can construct a pipeline, and pass in a context during the call:

```ruby
pipeline = HTMLPipeline.new(
convert_filter: HTMLPipeline::ConvertFilter::MarkdownFilter.new,
node_filters: [HTMLPipeline::NodeFilter::MentionFilter.new]
)
pipeline.call(user_supplied_text, context: { markdown: { extensions: { footnotes: false } } })
```

Please refer to the documentation for each filter to understand what configuration options are available.

### More Examples
Expand Down
3 changes: 2 additions & 1 deletion lib/html_pipeline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,13 @@ def call(text, context: {}, result: {})
text
else
instrument("call_convert_filter.html_pipeline", payload) do
html = @convert_filter.call(text)
html = @convert_filter.call(text, context: context)
end
end

unless @node_filters.empty?
instrument("call_node_filters.html_pipeline", payload) do
@node_filters.each { |filter| filter.context = (filter.context || {}).merge(context) }
result[:output] = Selma::Rewriter.new(sanitizer: @sanitization_config, handlers: @node_filters).rewrite(html)
html = result[:output]
payload = default_payload({
Expand Down
4 changes: 2 additions & 2 deletions lib/html_pipeline/convert_filter/markdown_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def initialize(context: {}, result: {})
end

# Convert Commonmark to HTML using the best available implementation.
def call(text)
options = @context.fetch(:markdown, {})
def call(text, context: @context)
options = context.fetch(:markdown, {})
plugins = options.fetch(:plugins, {})
Commonmarker.to_html(text, options: options, plugins: plugins).rstrip!
end
Expand Down
2 changes: 2 additions & 0 deletions lib/html_pipeline/node_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

class HTMLPipeline
class NodeFilter < Filter
attr_accessor :context

def initialize(context: {}, result: {})
super(context: context, result: {})
send(:after_initialize) if respond_to?(:after_initialize)
Expand Down
30 changes: 30 additions & 0 deletions test/html_pipeline_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,34 @@ def test_kitchen_sink

assert_equal("<p>!&gt;eeuqram/eeuqram&lt; ees ot evoL .yllib@ ,ereht <strong>yeH</strong></p>", result)
end

def test_context_is_carried_over_in_call
text = "yeH! I _think_ <marquee>@gjtorikian is ~great~</marquee>!"

pipeline = HTMLPipeline.new(
text_filters: [YehBolderFilter.new],
convert_filter: HTMLPipeline::ConvertFilter::MarkdownFilter.new,
node_filters: [HTMLPipeline::NodeFilter::MentionFilter.new],
)
result = pipeline.call(text)[:output]

# note:
# - yeH is bolded
# - strikethroughs are rendered
# - mentions are not linked
assert_equal("<p><strong>yeH</strong>! I <em>think</em> <a href=\"/gjtorikian\">@gjtorikian</a> is <del>great</del>!</p>", result)

context = {
no_bolding: false,
markdown: { extension: { strikethrough: false } },
base_url: "http://your-domain.com",
}
result_with_context = pipeline.call(text, context: context)[:output]

# note:
# - yeH is not bolded
# - strikethroughs are not rendered
# - mentions are linked
assert_equal("<p>yeH! I <em>think</em> <a href=\"http://your-domain.com/gjtorikian\">@gjtorikian</a> is ~great~!</p>", result_with_context)
end
end
26 changes: 26 additions & 0 deletions test/sanitization_filter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,5 +263,31 @@ def test_sanitization_pipeline_can_be_removed

assert_equal(result[:output].to_s, expected.chomp)
end

def test_sanitization_pipeline_does_not_need_node_filters
config = {
elements: ["p", "pre", "code"],
}

pipeline = HTMLPipeline.new(
convert_filter:
HTMLPipeline::ConvertFilter::MarkdownFilter.new,
sanitization_config: config,
)

result = pipeline.call(<<~CODE)
This is *great*, @birdcar:
some_code(:first)
CODE

expected = <<~HTML
<p>This is great, @birdcar:</p>
<pre><code>some_code(:first)
</code></pre>
HTML

assert_equal(result[:output].to_s, expected.chomp)
end
end
end
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ def call(input, context: {}, result: {})
# bolds any instance of the word yeH
class YehBolderFilter < HTMLPipeline::TextFilter
def call(input, context: {}, result: {})
input.gsub("yeH", "**yeH**")
input.gsub("yeH", "**yeH**") unless context[:no_bolding] == false
end
end

0 comments on commit 6f51e52

Please sign in to comment.