Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/cargo/comrak-0.30.0
Browse files Browse the repository at this point in the history
  • Loading branch information
gjtorikian committed Nov 25, 2024
2 parents 7137d17 + 3a76383 commit 00013ba
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 56 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ Ruby wrapper for Rust's [comrak](https://github.com/kivikakk/comrak) crate.

It passes all of the CommonMark test suite, and is therefore spec-complete. It also includes extensions to the CommonMark spec as documented in the [GitHub Flavored Markdown spec](http://github.github.com/gfm/), such as support for tables, strikethroughs, and autolinking.

For more information on available extensions, see [the documentation below](#extension-options).
> [!NOTE]
> By default, several extensions not in any spec have been enabled, for the sake of end user convenience when generating HTML.
>
> For more information on the available options and extensions, see [the documentation below](#options-and-plugins).
## Installation

Expand Down Expand Up @@ -147,16 +150,16 @@ Commonmarker.to_html('"Hi *there*"', options:{
})
```

Note that there is a distinction in comrak for "parse" options and "render" options, which are represented in the tables below.
Note that there is a distinction in comrak for "parse" options and "render" options, which are represented in the tables below. As well, if you wish to disable any-non boolean option, pass in `nil`.

### Parse options

| Name | Description | Default |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `smart` | Punctuation (quotes, full-stops and hyphens) are converted into 'smart' punctuation. | `false` |
| `default_info_string` | The default info string for fenced code blocks. | `""` |
| `relaxed_tasklist_matching` | Enables relaxing of the tasklist extension matching, allowing any non-space to be used for the "checked" state instead of only `x` and `X`. | `false` |
| `relaxed_autolinks` | Enable relaxing of the autolink extension parsing, allowing links to be recognized when in brackets, as well as permitting any url scheme. | `false` |
| Name | Description | Default |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `smart` | Punctuation (quotes, full-stops and hyphens) are converted into 'smart' punctuation. | `false` |
| `default_info_string` | The default info string for fenced code blocks. | `""` |
| `relaxed_tasklist_matching` | Enables relaxing of the tasklist extension matching, allowing any non-space to be used for the "checked" state instead of only `x` and `X`. | `false` |
| `relaxed_autolinks` | Enable relaxing of the autolink extension parsing, allowing links to be recognized when in brackets, as well as permitting any url scheme. | `false` |

### Render options

Expand Down Expand Up @@ -187,7 +190,7 @@ Commonmarker.to_html('"Hi *there*"', options: {
### Extension options

| Name | Description | Default |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------- |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------- |
| `strikethrough` | Enables the [strikethrough extension](https://github.github.com/gfm/#strikethrough-extension-) from the GFM spec. | `true` |
| `tagfilter` | Enables the [tagfilter extension](https://github.github.com/gfm/#disallowed-raw-html-extension-) from the GFM spec. | `true` |
| `table` | Enables the [table extension](https://github.github.com/gfm/#tables-extension-) from the GFM spec. | `true` |
Expand Down
37 changes: 13 additions & 24 deletions lib/commonmarker/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module Config
underline: false,
spoiler: false,
greentext: false,
},
}.freeze,
format: [:html].freeze,
}.freeze

Expand All @@ -59,10 +59,6 @@ module Config
class << self
include Commonmarker::Utils

def merged_with_defaults(options)
Commonmarker::Config::OPTIONS.merge(process_options(options))
end

def process_options(options)
{
parse: process_parse_options(options[:parse]),
Expand All @@ -79,37 +75,30 @@ def process_plugins(plugins)
end

[:parse, :render, :extension].each do |type|
define_singleton_method :"process_#{type}_options" do |option|
define_singleton_method :"process_#{type}_options" do |options|
Commonmarker::Config::OPTIONS[type].each_with_object({}) do |(key, value), hash|
if option.nil? # option not provided, go for the default
if options.nil? || !options.key?(key) # option not provided, use the default
hash[key] = value
next
end

# option explicitly not included, remove it
next if option[key].nil?
if options[key].nil? # # option explicitly not included, remove it
options.delete(key)
next
end

hash[key] = fetch_kv(option, key, value, type)
hash[key] = fetch_kv(options, key, value, type)
end
end
end

[:syntax_highlighter].each do |type|
define_singleton_method :"process_#{type}_plugin" do |plugin|
return if plugin.nil? # plugin explicitly nil, remove it
define_singleton_method :process_syntax_highlighter_plugin do |options|
return if options.nil? # plugin explicitly nil, remove it

Commonmarker::Config::PLUGINS[type].each_with_object({}) do |(key, value), hash|
if plugin.nil? # option not provided, go for the default
hash[key] = value
next
end
raise TypeError, "Expected a Hash for syntax_highlighter plugin, got #{options.class}" unless options.is_a?(Hash)
raise TypeError, "Expected a Hash for syntax_highlighter plugin, got nothing" if options.empty?

# option explicitly not included, remove it
next if plugin[key].nil?

hash[key] = fetch_kv(plugin, key, value, type)
end
end
Commonmarker::Config::PLUGINS[:syntax_highlighter].merge(options)
end
end
end
12 changes: 6 additions & 6 deletions lib/commonmarker/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ module Commonmarker
module Utils
include Commonmarker::Constants

def fetch_kv(option, key, value, type)
def fetch_kv(options, key, value, type)
value_klass = value.class

if Constants::BOOLS.include?(value) && BOOLS.include?(option[key])
option[key]
elsif option[key].is_a?(value_klass)
option[key]
if Constants::BOOLS.include?(value) && BOOLS.include?(options[key])
options[key]
elsif options[key].is_a?(value_klass)
options[key]
else
expected_type = Constants::BOOLS.include?(value) ? "Boolean" : value_klass.to_s
raise TypeError, "#{type} option `:#{key}` must be #{expected_type}; got #{option[key].class}"
raise TypeError, "#{type} option `:#{key}` must be #{expected_type}; got #{options[key].class}"
end
end
end
Expand Down
48 changes: 48 additions & 0 deletions test/config_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require "test_helper"

class ConfigTest < Minitest::Test
def test_process_options
user_config = {
parse: {
smart: true,
},
render: {
unsafe: false,
},
extension: {
autolink: false,
},
}
processed_config = Commonmarker::Config.process_options(user_config)

expected_config = [:parse, :render, :extension].each_with_object({}) do |type, hash|
hash[type] = Commonmarker::Config::OPTIONS[type].merge(user_config[type] || {})
end

assert_equal(expected_config, processed_config)
end

def test_process_plugins
user_config = {
syntax_highlighter: {
path: "./themes",
},
}
processed_config = Commonmarker::Config.process_plugins(user_config)
expected_config = [:syntax_highlighter].each_with_object({}) do |type, hash|
hash[type] = Commonmarker::Config::PLUGINS[type].merge(user_config[type])
end

assert_equal(expected_config, processed_config)
end

def test_config_merges_directly
# hardbreaks work, the `\n` in between is rendered
assert_equal("<p>aaaa<br />\nbbbb</p>\n", Commonmarker.to_html("aaaa\nbbbb"))

# hardbreaks still work
assert_equal("<p>aaaa<br />\nbbbb</p>\n", Commonmarker.to_html("aaaa\nbbbb", options: { render: { unsafe: false } }))
end
end
6 changes: 3 additions & 3 deletions test/extensions_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ def setup
end

def test_uses_specified_extensions
Commonmarker.to_html(@markdown, options: { extension: {} }).tap do |out|
Commonmarker.to_html(@markdown, options: { extension: { table: false, strikethrough: false } }).tap do |out|
assert_includes(out, "| a")
assert_includes(out, "| <strong>x</strong>")
assert_includes(out, "~~hi~~")
end

Commonmarker.to_html(@markdown, options: { extension: { table: true } }).tap do |out|
Commonmarker.to_html(@markdown, options: { extension: { table: true, strikethrough: false } }).tap do |out|
refute_includes(out, "| a")
["<table>", "<tr>", "<th>", "a", "</th>", "<td>", "c", "</td>", "<strong>x</strong>"].each { |html| assert_includes(out, html) }

assert_includes(out, "~~hi~~")
end

Commonmarker.to_html(@markdown, options: { extension: { strikethrough: true } }).tap do |out|
Commonmarker.to_html(@markdown, options: { extension: { table: false, strikethrough: true } }).tap do |out|
assert_includes(out, "| a")
refute_includes(out, "~~hi~~")
assert_includes(out, "<del>hi</del>")
Expand Down
2 changes: 0 additions & 2 deletions test/footnotes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
class FootnotesTest < Minitest::Test
def test_to_html
md = <<~MARKDOWN
# footnotes
Let's render some footnotes[^1]
[^1]: This is a footnote
MARKDOWN
expected = <<~HTML
<h1>footnotes</h1>
<p>Let's render some footnotes<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup></p>
<section class="footnotes" data-footnotes>
<ol>
Expand Down
4 changes: 2 additions & 2 deletions test/frontmatter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def test_frontmatter_does_not_interfere_with_codeblock
end

def test_frontmatter_custom_delimiter
md = "---\nyaml: true\nage: 42\n---\n# Title 1"
md = "---\nyaml: true\nage: 42\n---\nThis is some text"
expected = <<~HTML
<h1>Title 1</h1>
<p>This is some text</p>
HTML

assert_equal(expected, Commonmarker.to_html(md, options: { extension: { front_matter_delimiter: "---" } }))
Expand Down
1 change: 1 addition & 0 deletions test/smartpunct_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class SmartPunctTest < Minitest::Test
},
render: {
hardbreaks: false,
escaped_char_spans: false,
},
}

Expand Down
32 changes: 22 additions & 10 deletions test/spec_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,30 @@ class SpecTest < Minitest::Test

spec.each do |testcase|
define_method(:"test_to_html_example_#{testcase[:example]}") do
opts = {
render: {
unsafe: true,
gfm_quirks: true,
},
extension: testcase[:extensions].each_with_object({}) do |ext, hash|
hash[ext] = true
end,
render_options = Commonmarker::Config::OPTIONS[:render].each_with_object({}) do |(key, _value), hsh|
hsh[key] = nil
end
render_options[:unsafe] = true
render_options[:gfm_quirks] = true

string_extensions = [:front_matter_delimiter, :header_ids]
extensions_options = Commonmarker::Config::OPTIONS[:extension].each_with_object({}) do |(key, _value), hsh|
hsh[key] = if string_extensions.include?(key)
nil
else
false
end
end

testcase[:extensions].each do |ext, _value|
extensions_options[ext] = true
end

options = {
render: render_options,
extension: extensions_options,
}

options = Commonmarker::Config.merged_with_defaults(opts)
options[:extension].delete(:header_ids) # this interefers with the spec.txt extension-less capability
options[:extension][:tasklist] = true
actual = Commonmarker.to_html(testcase[:markdown], options: options, plugins: nil).rstrip

Expand Down

0 comments on commit 00013ba

Please sign in to comment.