middleman-commonmarker 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2843575c04f2b516147d5aaaa62789482ead5e3e65950ac268b824759883c1bf
4
- data.tar.gz: 9e90c64fba5895575d957c0d9ad174254f7242d06e648405f7a84b01e9e7703c
3
+ metadata.gz: a84e082e8ec76cfb63d7acbde0e2fdd5ab6a2f4355e7fe322924b74ecaa81d16
4
+ data.tar.gz: 1d2ef217f0fe335f01c98d5e28292dfb98f3c56eca4e25c25fdf7d04e6e57022
5
5
  SHA512:
6
- metadata.gz: 18e343279365e8a11a2ace792f8441cf5f4f8753c40d4a8772389117937346428d8461474c77888cca55dc117e95277f62e64d54dc0bca77aaa735bf999cf49f
7
- data.tar.gz: 60024dfa0cc18d349f5b93f8d2a1ddaa2b60e6cba42fa83c53b710196d6268db4629ef24360d71d2ff511f876804c1e80f12b4066a026e90bd1ba0f9e4600672
6
+ metadata.gz: babd88f6b95ea0b671f00d824bb3a1e5e3a16ebbd101b95de6ebac79740f8a1365c4b063fe5720ea4bf3a9ebe2a5ce2819f3fbeb52ae4826d56229f015e67573
7
+ data.tar.gz: 81b518c5818560624547091fab0da203dbf772c775af1079540f416ee82280f49ef8ef7d4ffb77b255bae13ed70c5bbea662ccc563ef27c6605a5e77fba1234a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-10-25
4
+
5
+ - Add `plugins` option to pass commonmarker plugins (e.g. syntax highlighter theme) ([#18](https://github.com/unasuke/middleman-commonmarker/pull/18))
6
+
3
7
  ## [0.1.0] - 2026-05-27
4
8
 
5
9
  - Initial release
data/README.md CHANGED
@@ -46,6 +46,29 @@ Note that flat keys on `activate` (e.g. `activate :commonmarker, table: true`) a
46
46
 
47
47
  These flat options are mapped onto commonmarker's `parse` / `render` / `extension` option groups (and `smartypants` maps to `parse.smart`).
48
48
 
49
+ ### Syntax highlighting
50
+
51
+ By default, commonmarker highlights fenced code blocks with the `base16-ocean.dark` theme, writing the colors as inline `style` attributes.
52
+
53
+ Use the `plugins` option to change this. Its value is passed as-is to commonmarker's `plugins:` argument; see [commonmarker's README](https://github.com/gjtorikian/commonmarker#plugins) for the available values.
54
+
55
+ ```ruby
56
+ # Use another built-in theme
57
+ set :markdown, plugins: {syntax_highlighter: {theme: "InspiredGitHub"}}
58
+
59
+ # Load syntax_themes/my-theme.tmTheme
60
+ set :markdown, plugins: {
61
+ syntax_highlighter: {theme: "my-theme", path: File.expand_path("syntax_themes", __dir__)}
62
+ }
63
+
64
+ # Disable syntax highlighting
65
+ set :markdown, plugins: {syntax_highlighter: nil}
66
+ ```
67
+
68
+ Give `path` as an absolute path; a relative path is resolved against the current working directory.
69
+
70
+ `plugins` can also be given through `activate :commonmarker, options: { plugins: ... }`. When both are set, the `activate` value replaces the `set :markdown` value entirely (no deep merge).
71
+
49
72
  ### Raw HTML is enabled during rendering
50
73
 
51
74
  To emit the `<img>` HTML produced by `image_tag`, the renderer forces commonmarker's `unsafe: true` during transformation.
@@ -53,7 +76,7 @@ As a result, raw HTML in your Markdown is passed through (consistent with Redcar
53
76
 
54
77
  ## Scope
55
78
 
56
- This extension covers helper integration (`image_tag` / `url_for`), `automatic_image_sizes`, and commonmarker's native parse/render/extension options (`table`, `strikethrough`, `autolink`, `smartypants`, `tasklist`, …).
79
+ This extension covers helper integration (`image_tag` / `url_for`), `automatic_image_sizes`, commonmarker's native parse/render/extension options (`table`, `strikethrough`, `autolink`, `smartypants`, `tasklist`, …), and commonmarker plugins such as the syntax highlighter (`plugins`).
57
80
  Redcarpet-specific options such as `no_images`, `no_links`, `link_attributes`, and `filter_html` are out of scope.
58
81
 
59
82
  ## Development
@@ -75,6 +75,27 @@ Feature: Markdown (Commonmarker) support
75
75
  When I go to "/tables.html"
76
76
  Then I should see "<table>"
77
77
 
78
+ Scenario: Commonmarker passes plugins through set :markdown
79
+ Given a fixture app "markdown-app"
80
+ And a file named "config.rb" with:
81
+ """
82
+ activate :commonmarker
83
+ set :markdown, plugins: {syntax_highlighter: {theme: "InspiredGitHub"}}
84
+ """
85
+ Given the Server is running at "markdown-app"
86
+ When I go to "/fenced_code_blocks.html"
87
+ Then I should see "background-color:#ffffff"
88
+
89
+ Scenario: Commonmarker passes plugins through activate
90
+ Given a fixture app "markdown-app"
91
+ And a file named "config.rb" with:
92
+ """
93
+ activate :commonmarker, options: {plugins: {syntax_highlighter: {theme: "InspiredGitHub"}}}
94
+ """
95
+ Given the Server is running at "markdown-app"
96
+ When I go to "/fenced_code_blocks.html"
97
+ Then I should see "background-color:#ffffff"
98
+
78
99
  Scenario: Commonmarker uses our link_to and image_tag helpers
79
100
  Given a fixture app "markdown-app"
80
101
  And a file named "config.rb" with:
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "commonmarker"
4
4
  require "active_support/core_ext/module/attribute_accessors"
5
+ require "active_support/core_ext/hash/keys"
5
6
 
6
7
  module Middleman
7
8
  module Renderers
@@ -21,6 +22,7 @@ module Middleman
21
22
 
22
23
  def prepare
23
24
  @context = options.delete(:context)
25
+ @commonmarker_plugins = build_commonmarker_plugins
24
26
  @commonmarker_options = build_commonmarker_options
25
27
  end
26
28
 
@@ -36,10 +38,10 @@ module Middleman
36
38
  end
37
39
  end
38
40
  # Force unsafe so the injected raw <img> HTML is emitted.
39
- return doc.to_html(options: render_options_with_unsafe)
41
+ return render_html(doc, render_options_with_unsafe)
40
42
  end
41
43
 
42
- doc.to_html(options: @commonmarker_options)
44
+ render_html(doc, @commonmarker_options)
43
45
  end
44
46
 
45
47
  private
@@ -77,12 +79,32 @@ module Middleman
77
79
  nil
78
80
  end
79
81
 
82
+ # Render with the user's `plugins` when given; otherwise let commonmarker
83
+ # apply its default plugins.
84
+ def render_html(doc, commonmarker_options)
85
+ doc.to_html(options: commonmarker_options, **@commonmarker_plugins)
86
+ end
87
+
80
88
  def render_options_with_unsafe
81
89
  @commonmarker_options.merge(
82
90
  render: @commonmarker_options[:render].merge(unsafe: true)
83
91
  )
84
92
  end
85
93
 
94
+ # Take `plugins` out of the options as keyword arguments for to_html: {}
95
+ # when unset, {plugins: value} when set (a nil value disables the syntax
96
+ # highlighter). When both :plugins and "plugins" are present, the later
97
+ # one wins, as with the flat options, so `activate` overrides
98
+ # `set :markdown`.
99
+ def build_commonmarker_plugins
100
+ keys = options.keys.select { |key| key.to_s == "plugins" }
101
+ return {} if keys.empty?
102
+
103
+ value = keys.map { |key| options.delete(key) }.last
104
+ value = value.deep_symbolize_keys if value.is_a?(Hash)
105
+ {plugins: value}
106
+ end
107
+
86
108
  # Distribute the flat `set :markdown, ...` options into the
87
109
  # parse/render/extension nested structure, mapping `smartypants` to
88
110
  # parse.smart.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Middleman
4
4
  module Commonmarker
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -16,7 +16,9 @@ module Middleman
16
16
  def transform_link_node: (untyped node) -> void
17
17
  def extract_text: (untyped node) -> String
18
18
  def html_node_from: (String html) -> untyped
19
+ def render_html: (untyped doc, Hash[Symbol, untyped] commonmarker_options) -> String
19
20
  def render_options_with_unsafe: () -> Hash[Symbol, untyped]
21
+ def build_commonmarker_plugins: () -> Hash[Symbol, untyped]
20
22
  def build_commonmarker_options: () -> Hash[Symbol, untyped]
21
23
  end
22
24
  end
@@ -1,18 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "test_helper"
4
+ require "pathname"
4
5
 
5
6
  class CommonmarkerTemplateTest < Minitest::Test
6
- # Build a template instance; prepare runs on init and computes
7
- # @commonmarker_options from the given options.
8
- def build(opts = {})
9
- Middleman::Renderers::CommonmarkerTemplate.new(nil, 1, opts) { "" }
7
+ CODE_BLOCK = "```ruby\nputs 1\n```\n"
8
+ THEMES_DIR = File.expand_path("fixtures", __dir__)
9
+
10
+ # Build a template instance whose source is `body`; prepare runs on init
11
+ # and computes @commonmarker_plugins and @commonmarker_options from the
12
+ # given options.
13
+ def build(opts = {}, body = "")
14
+ Middleman::Renderers::CommonmarkerTemplate.new(nil, 1, opts) { body }
10
15
  end
11
16
 
12
17
  def options_for(opts)
13
18
  build(opts).instance_variable_get(:@commonmarker_options)
14
19
  end
15
20
 
21
+ # Render CODE_BLOCK with the given options. Tilt passes Object.new as the
22
+ # scope, so this goes through the scope branch of evaluate.
23
+ def html_for(opts)
24
+ build(opts, CODE_BLOCK).render
25
+ end
26
+
16
27
  def image_node(markdown)
17
28
  node = Commonmarker.parse(markdown)
18
29
  found = nil
@@ -53,4 +64,88 @@ class CommonmarkerTemplateTest < Minitest::Test
53
64
  def test_html_node_from_returns_nil_when_no_html_present
54
65
  assert_nil build.send(:html_node_from, "just plain text")
55
66
  end
67
+
68
+ def test_plugins_unset_matches_commonmarker_default
69
+ template = build({}, CODE_BLOCK)
70
+ opts = template.send(:render_options_with_unsafe)
71
+ expected = Commonmarker.parse(CODE_BLOCK, options: opts).to_html(options: opts)
72
+ assert_equal expected, template.render
73
+ end
74
+
75
+ def test_plugins_with_builtin_theme
76
+ html = html_for(plugins: {syntax_highlighter: {theme: "InspiredGitHub"}})
77
+ assert_includes html, "background-color:#ffffff"
78
+ end
79
+
80
+ def test_plugins_with_theme_from_path
81
+ html = html_for(plugins: {syntax_highlighter: {theme: "test-theme", path: THEMES_DIR}})
82
+ assert_includes html, "background-color:#123456"
83
+ end
84
+
85
+ def test_plugins_with_path_only_keeps_default_theme
86
+ assert_equal html_for({}), html_for(plugins: {syntax_highlighter: {path: THEMES_DIR}})
87
+ end
88
+
89
+ def test_plugins_with_empty_theme_outputs_classes
90
+ html = html_for(plugins: {syntax_highlighter: {theme: ""}})
91
+ assert_includes html, %(<pre class="syntax-highlighting">)
92
+ refute_includes html, "style="
93
+ end
94
+
95
+ def test_plugins_disabling_highlighter
96
+ [{syntax_highlighter: nil}, {}, nil].each do |plugins|
97
+ refute_includes html_for(plugins:), "style=", "plugins: #{plugins.inspect}"
98
+ end
99
+ end
100
+
101
+ def test_plugins_with_misspelled_key_disables_highlighter
102
+ refute_includes html_for(plugins: {syntax_highlight: {theme: "InspiredGitHub"}}), "style="
103
+ end
104
+
105
+ def test_plugins_with_string_keys
106
+ assert_equal html_for(plugins: {syntax_highlighter: {theme: "InspiredGitHub"}}),
107
+ html_for(plugins: {"syntax_highlighter" => {"theme" => "InspiredGitHub"}})
108
+ end
109
+
110
+ def test_later_plugins_key_takes_precedence
111
+ inspired = {syntax_highlighter: {theme: "InspiredGitHub"}}
112
+ assert_equal html_for(plugins: inspired), html_for({:plugins => nil, "plugins" => inspired})
113
+ assert_equal html_for(plugins: nil), html_for({"plugins" => inspired, :plugins => nil})
114
+ end
115
+
116
+ def test_plugins_with_unknown_theme_raises
117
+ assert_raises(ArgumentError) { html_for(plugins: {syntax_highlighter: {theme: "no-such-theme"}}) }
118
+ end
119
+
120
+ def test_plugins_with_empty_highlighter_raises
121
+ assert_raises(TypeError) { html_for(plugins: {syntax_highlighter: {}}) }
122
+ end
123
+
124
+ def test_plugins_with_nil_theme_raises
125
+ assert_raises(TypeError) { html_for(plugins: {syntax_highlighter: {theme: nil}}) }
126
+ end
127
+
128
+ def test_plugins_with_non_hash_value_raises
129
+ {"x" => NoMethodError, [1] => TypeError}.each do |plugins, error|
130
+ assert_raises(error, "plugins: #{plugins.inspect}") { html_for(plugins:) }
131
+ end
132
+ end
133
+
134
+ def test_plugins_with_pathname_path_raises
135
+ assert_raises(TypeError) do
136
+ html_for(plugins: {syntax_highlighter: {theme: "test-theme", path: Pathname(THEMES_DIR)}})
137
+ end
138
+ end
139
+
140
+ def test_plugins_are_not_mixed_into_commonmarker_options
141
+ opts = options_for(plugins: {syntax_highlighter: {theme: "InspiredGitHub"}})
142
+ refute opts[:parse].key?(:plugins)
143
+ refute opts[:render].key?(:plugins)
144
+ refute opts[:extension].key?(:plugins)
145
+ end
146
+
147
+ def test_plugins_apply_without_scope
148
+ template = build({plugins: {syntax_highlighter: {theme: "InspiredGitHub"}}}, CODE_BLOCK)
149
+ assert_includes template.send(:evaluate, nil, {}), "background-color:#ffffff"
150
+ end
56
151
  end
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>name</key>
6
+ <string>test-theme</string>
7
+ <key>settings</key>
8
+ <array>
9
+ <dict>
10
+ <key>settings</key>
11
+ <dict>
12
+ <key>background</key>
13
+ <string>#123456</string>
14
+ <key>foreground</key>
15
+ <string>#abcdef</string>
16
+ </dict>
17
+ </dict>
18
+ </array>
19
+ </dict>
20
+ </plist>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: middleman-commonmarker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Nakamura
@@ -85,6 +85,7 @@ files:
85
85
  - sig/middleman-commonmarker/extension.rbs
86
86
  - sig/middleman-commonmarker/version.rbs
87
87
  - test/commonmarker_template_test.rb
88
+ - test/fixtures/test-theme.tmTheme
88
89
  - test/test_helper.rb
89
90
  homepage: https://github.com/unasuke/middleman-commonmarker
90
91
  licenses:
@@ -109,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  - !ruby/object:Gem::Version
110
111
  version: '0'
111
112
  requirements: []
112
- rubygems_version: 4.0.10
113
+ rubygems_version: 4.0.20
113
114
  specification_version: 4
114
115
  summary: Commonmarker (CommonMark) markdown engine extension for Middleman
115
116
  test_files: []