markly 0.16.0 → 0.17.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.
@@ -13,14 +13,17 @@ require_relative "headings"
13
13
 
14
14
  require "cgi/escape"
15
15
 
16
- # Compatibility for older Ruby versions where escape_html alias doesn't exist:
17
- unless CGI.respond_to?(:escape_html)
18
- require "cgi"
19
- end
20
-
21
16
  module Markly
22
17
  module Renderer
18
+ # Renders Markdown node trees as HTML.
23
19
  class HTML < Generic
20
+ # Initializes an HTML renderer.
21
+ #
22
+ # @parameter ids [Boolean] Whether to wrap headings in anchored sections.
23
+ # @parameter headings [Markly::Renderer::Headings | Nil] A heading tracker to reuse.
24
+ # @parameter tight [Boolean] Whether to render paragraphs tightly.
25
+ # @option :flags [Integer] The enabled rendering flags.
26
+ # @option :extensions [Array(Symbol)] The enabled extensions.
24
27
  def initialize(ids: false, headings: nil, tight: false, **options)
25
28
  super(**options)
26
29
 
@@ -33,6 +36,9 @@ module Markly
33
36
  @footnotes = {}
34
37
  end
35
38
 
39
+ # Renders a complete document and closes any generated sections.
40
+ #
41
+ # @parameter _ [Markly::Node] The document node.
36
42
  def document(_)
37
43
  @section = false
38
44
  super
@@ -40,13 +46,22 @@ module Markly
40
46
  out("</section>") if @section
41
47
  end
42
48
 
49
+ # Returns an escaped HTML id attribute for a heading node.
50
+ #
51
+ # @parameter node [Markly::Node] The heading node.
52
+ # @returns [String | Nil] The id attribute when heading IDs are enabled.
43
53
  def id_for(node)
44
54
  if @headings
45
55
  anchor = @headings.anchor_for(node)
46
- return " id=\"#{CGI.escape_html anchor}\""
56
+ # `CGI.escape_html` is not exposed by `cgi/escape` on Ruby 3.4:
57
+ return " id=\"#{CGI.escapeHTML anchor}\""
47
58
  end
48
59
  end
49
60
 
61
+ # Generates a normalized anchor from a node's plain-text content.
62
+ #
63
+ # @parameter node [Markly::Node] The node to convert.
64
+ # @returns [String] The normalized anchor.
50
65
  def self.anchor_for(node)
51
66
  # Convert to plaintext, strip trailing whitespace, convert to lowercase:
52
67
  text = node.to_plaintext.chomp.downcase
@@ -57,10 +72,17 @@ module Markly
57
72
  return text
58
73
  end
59
74
 
75
+ # Generates a normalized anchor from a node's plain-text content.
76
+ #
77
+ # @parameter node [Markly::Node] The node to convert.
78
+ # @returns [String] The normalized anchor.
60
79
  def anchor_for(node)
61
80
  self.class.anchor_for(node)
62
81
  end
63
82
 
83
+ # Renders a heading node, optionally wrapped in an anchored section.
84
+ #
85
+ # @parameter node [Markly::Node] The heading node.
64
86
  def header(node)
65
87
  block do
66
88
  if @headings
@@ -73,6 +95,9 @@ module Markly
73
95
  end
74
96
  end
75
97
 
98
+ # Renders a paragraph node.
99
+ #
100
+ # @parameter node [Markly::Node] The paragraph node.
76
101
  def paragraph(node)
77
102
  if @tight && node.parent.type != :blockquote
78
103
  out(:children)
@@ -89,6 +114,9 @@ module Markly
89
114
  end
90
115
  end
91
116
 
117
+ # Renders an ordered or unordered list node.
118
+ #
119
+ # @parameter node [Markly::Node] The list node.
92
120
  def list(node)
93
121
  old_tight = @tight
94
122
  @tight = node.list_tight
@@ -113,6 +141,9 @@ module Markly
113
141
  @tight = old_tight
114
142
  end
115
143
 
144
+ # Renders a list-item node, including task-list attributes when present.
145
+ #
146
+ # @parameter node [Markly::Node] The list-item node.
116
147
  def list_item(node)
117
148
  block do
118
149
  tasklist_data = tasklist(node)
@@ -122,6 +153,10 @@ module Markly
122
153
  end
123
154
  end
124
155
 
156
+ # Returns the HTML fragment required for a task-list item.
157
+ #
158
+ # @parameter node [Markly::Node] The list-item node.
159
+ # @returns [String] The task-list fragment, or an empty string.
125
160
  def tasklist(node)
126
161
  return "" unless tasklist?(node)
127
162
 
@@ -133,6 +168,9 @@ module Markly
133
168
  "><input type=\"checkbox\" #{state} /"
134
169
  end
135
170
 
171
+ # Renders a blockquote node.
172
+ #
173
+ # @parameter node [Markly::Node] The blockquote node.
136
174
  def blockquote(node)
137
175
  block do
138
176
  container("<blockquote#{source_position(node)}>\n", "</blockquote>") do
@@ -141,22 +179,30 @@ module Markly
141
179
  end
142
180
  end
143
181
 
182
+ # Renders a thematic-break node.
183
+ #
184
+ # @parameter node [Markly::Node] The thematic-break node.
144
185
  def hrule(node)
145
186
  block do
146
187
  out("<hr#{source_position(node)} />")
147
188
  end
148
189
  end
149
190
 
191
+ # Renders a code block and its optional language metadata.
192
+ #
193
+ # @parameter node [Markly::Node] The code block node.
150
194
  def code_block(node)
151
195
  block do
196
+ language = node.code_language
197
+
152
198
  if flag_enabled?(GITHUB_PRE_LANG)
153
199
  out("<pre#{source_position(node)}")
154
- out(' lang="', node.fence_info.split(/\s+/)[0], '"') if node.fence_info && !node.fence_info.empty?
200
+ out(' lang="', language, '"') if language
155
201
  out("><code>")
156
202
  else
157
203
  out("<pre#{source_position(node)}><code")
158
- if node.fence_info && !node.fence_info.empty?
159
- out(' class="language-', node.fence_info.split(/\s+/)[0], '">')
204
+ if language
205
+ out(' class="language-', language, '">')
160
206
  else
161
207
  out(">")
162
208
  end
@@ -166,6 +212,9 @@ module Markly
166
212
  end
167
213
  end
168
214
 
215
+ # Renders or omits a raw block-level HTML node according to the flags.
216
+ #
217
+ # @parameter node [Markly::Node] The raw HTML node.
169
218
  def html(node)
170
219
  block do
171
220
  if flag_enabled?(UNSAFE)
@@ -176,6 +225,9 @@ module Markly
176
225
  end
177
226
  end
178
227
 
228
+ # Renders or omits a raw inline HTML node according to the flags.
229
+ #
230
+ # @parameter node [Markly::Node] The raw inline HTML node.
179
231
  def inline_html(node)
180
232
  if flag_enabled?(UNSAFE)
181
233
  out(tagfilter(node.string_content))
@@ -184,10 +236,16 @@ module Markly
184
236
  end
185
237
  end
186
238
 
239
+ # Renders an emphasized inline node.
240
+ #
241
+ # @parameter node [Markly::Node] The emphasized node.
187
242
  def emph(node)
188
243
  out("<em>", :children, "</em>")
189
244
  end
190
245
 
246
+ # Renders a strongly emphasized inline node.
247
+ #
248
+ # @parameter node [Markly::Node] The strong node.
191
249
  def strong(node)
192
250
  if node.parent.nil? || node.parent.type == node.type
193
251
  out(:children)
@@ -196,12 +254,18 @@ module Markly
196
254
  end
197
255
  end
198
256
 
257
+ # Renders a link node with escaped destination and title attributes.
258
+ #
259
+ # @parameter node [Markly::Node] The link node.
199
260
  def link(node)
200
261
  out('<a href="', node.url.nil? ? "" : escape_href(node.url), '"')
201
262
  out(' title="', escape_html(node.title), '"') if node.title && !node.title.empty?
202
263
  out(">", :children, "</a>")
203
264
  end
204
265
 
266
+ # Renders an image node with plain-text alternative content.
267
+ #
268
+ # @parameter node [Markly::Node] The image node.
205
269
  def image(node)
206
270
  out('<img src="', escape_href(node.url), '"')
207
271
  plain do
@@ -211,20 +275,35 @@ module Markly
211
275
  out(" />")
212
276
  end
213
277
 
278
+ # Renders an escaped text node.
279
+ #
280
+ # @parameter node [Markly::Node] The text node.
214
281
  def text(node)
215
282
  out(escape_html(node.string_content))
216
283
  end
217
284
 
285
+ # Renders an inline code node and its optional language metadata.
286
+ #
287
+ # @parameter node [Markly::Node] The inline code node.
218
288
  def code(node)
219
- out("<code>")
289
+ language = node.code_language
290
+ out("<code")
291
+ out(' class="language-', language, '"') if language
292
+ out(">")
220
293
  out(escape_html(node.string_content))
221
294
  out("</code>")
222
295
  end
223
296
 
297
+ # Renders a hard line break.
298
+ #
299
+ # @parameter _node [Markly::Node] The line-break node.
224
300
  def linebreak(_node)
225
301
  out("<br />\n")
226
302
  end
227
303
 
304
+ # Renders a soft line break according to the configured flags.
305
+ #
306
+ # @parameter _ [Markly::Node] The soft-break node.
228
307
  def softbreak(_)
229
308
  if flag_enabled?(HARD_BREAKS)
230
309
  out("<br />\n")
@@ -235,6 +314,9 @@ module Markly
235
314
  end
236
315
  end
237
316
 
317
+ # Renders a table node and initializes its column alignments.
318
+ #
319
+ # @parameter node [Markly::Node] The table node.
238
320
  def table(node)
239
321
  @alignments = node.table_alignments
240
322
  @needs_close_tbody = false
@@ -243,6 +325,9 @@ module Markly
243
325
  out("</table>\n")
244
326
  end
245
327
 
328
+ # Renders a table-header row.
329
+ #
330
+ # @parameter node [Markly::Node] The table-header node.
246
331
  def table_header(node)
247
332
  @column_index = 0
248
333
 
@@ -251,6 +336,9 @@ module Markly
251
336
  @in_header = false
252
337
  end
253
338
 
339
+ # Renders a table row, opening the table body when necessary.
340
+ #
341
+ # @parameter node [Markly::Node] The table-row node.
254
342
  def table_row(node)
255
343
  @column_index = 0
256
344
  if !@in_header && !@needs_close_tbody
@@ -260,22 +348,32 @@ module Markly
260
348
  out("<tr#{source_position(node)}>\n", :children, "</tr>\n")
261
349
  end
262
350
 
351
+ # @constant [Hash(Symbol, String)] HTML attributes for table-cell alignments.
263
352
  TABLE_CELL_ALIGNMENT = {
264
353
  left: ' align="left"',
265
354
  right: ' align="right"',
266
355
  center: ' align="center"'
267
356
  }.freeze
268
357
 
358
+ # Renders a table cell using the current column alignment.
359
+ #
360
+ # @parameter node [Markly::Node] The table-cell node.
269
361
  def table_cell(node)
270
362
  align = TABLE_CELL_ALIGNMENT.fetch(@alignments[@column_index], "")
271
363
  out(@in_header ? "<th#{align}#{source_position(node)}>" : "<td#{align}#{source_position(node)}>", :children, @in_header ? "</th>\n" : "</td>\n")
272
364
  @column_index += 1
273
365
  end
274
366
 
367
+ # Renders a strikethrough node.
368
+ #
369
+ # @parameter _ [Markly::Node] The strikethrough node.
275
370
  def strikethrough(_)
276
371
  out("<del>", :children, "</del>")
277
372
  end
278
373
 
374
+ # Renders a footnote reference linking to its definition.
375
+ #
376
+ # @parameter node [Markly::Node] The footnote-reference node.
279
377
  def footnote_reference(node)
280
378
  label = node.parent_footnote_def.string_content
281
379
 
@@ -283,6 +381,9 @@ module Markly
283
381
  # out(node.to_html)
284
382
  end
285
383
 
384
+ # Renders a footnote definition and records its backlink target.
385
+ #
386
+ # @parameter node [Markly::Node] The footnote-definition node.
286
387
  def footnote_definition(node)
287
388
  unless @footnote_ix
288
389
  out("<section class=\"footnotes\" data-footnotes>\n<ol>\n")
@@ -6,6 +6,8 @@
6
6
  # Copyright, 2017-2018, by Ashe Connor.
7
7
  # Copyright, 2020-2026, by Samuel Williams.
8
8
 
9
+ # @namespace
9
10
  module Markly
10
- VERSION = "0.16.0"
11
+ # @constant [String] The version of the Markly gem.
12
+ VERSION = "0.17.0"
11
13
  end
data/lib/markly.rb CHANGED
@@ -6,7 +6,7 @@
6
6
  # Copyright, 2015-2019, by Garen Torikian.
7
7
  # Copyright, 2015, by Nick Wellnhofer.
8
8
  # Copyright, 2016-2017, by Yuki Izumi.
9
- # Copyright, 2020-2025, by Samuel Williams.
9
+ # Copyright, 2020-2026, by Samuel Williams.
10
10
 
11
11
  require "markly/markly"
12
12
 
@@ -16,14 +16,14 @@ require_relative "markly/renderer/html"
16
16
 
17
17
  require_relative "markly/version"
18
18
 
19
+ # Parses and renders CommonMark-compatible Markdown documents.
19
20
  module Markly
20
- # Public: Parses a Markdown string into a `document` node.
21
+ # Parse a Markdown string into a document node.
21
22
  #
22
- # string - {String} to be parsed
23
- # option - A {Symbol} or {Array of Symbol}s indicating the parse options
24
- # extensions - An {Array of Symbol}s indicating the extensions to use
25
- #
26
- # Returns the `parser` node.
23
+ # @parameter text [String] The Markdown text to parse.
24
+ # @parameter flags [Integer] The enabled parsing flags.
25
+ # @parameter extensions [Array(Symbol) | Nil] The extensions to enable.
26
+ # @returns [Markly::Node] The parsed document node.
27
27
  def self.parse(text, flags: DEFAULT, extensions: nil)
28
28
  parser = Parser.new(flags)
29
29
 
@@ -34,13 +34,14 @@ module Markly
34
34
  return parser.parse(text.encode(Encoding::UTF_8))
35
35
  end
36
36
 
37
- # Public: Parses a Markdown string into an HTML string.
38
- #
39
- # text - A {String} of text
40
- # option - Either a {Symbol} or {Array of Symbol}s indicating the render options
41
- # extensions - An {Array of Symbol}s indicating the extensions to use
37
+ # Parse a Markdown string and render it as HTML.
42
38
  #
43
- # Returns a {String} of converted HTML.
39
+ # @parameter text [String] The Markdown text to render.
40
+ # @parameter flags [Integer] The default parsing and rendering flags.
41
+ # @parameter parse_flags [Integer] The enabled parsing flags.
42
+ # @parameter render_flags [Integer] The enabled rendering flags.
43
+ # @parameter extensions [Array(Symbol)] The extensions to enable.
44
+ # @returns [String] The rendered HTML.
44
45
  def self.render_html(text, flags: DEFAULT, parse_flags: flags, render_flags: flags, extensions: [])
45
46
  root = self.parse(text, flags: parse_flags, extensions: extensions)
46
47
 
data/readme.md CHANGED
@@ -24,9 +24,19 @@ Please see the [project documentation](https://socketry.github.io/markly/) for m
24
24
 
25
25
  Please see the [project releases](https://socketry.github.io/markly/releases/index) for all releases.
26
26
 
27
+ ### v0.17.0
28
+
29
+ - Add opt-in language prefixes for inline code spans with `Markly::INLINE_CODE_INFO`, expose code metadata through `Node#code_info`, and provide `Node#code_language` as a convenient language accessor.
30
+ - Expose fenced code-block metadata through `Node#fence` and `Node::Fence`.
31
+ - Add `Node#next_header`, retain `Node#next_heading` as an alias, and correctly recognize header nodes.
32
+ - Raise `NotImplementedError` from the base renderer's unimplemented code-block callback instead of recursing indefinitely.
33
+ - Remove unused legacy separator helpers from the Ruby renderer.
34
+
27
35
  ### v0.16.0
28
36
 
29
- - Update `cmark-gfm` from upstream, including a denial-of-service fix for tables with a large number of autocompleted cells, corrected `end_line` source positions for single-line and multi-line HTML blocks, and a fix for trailing newlines when rendering inline nodes.
37
+ - Update `cmark-gfm` from upstream, including two table-rendering protections: avoid repeatedly scanning preceding cells, and limit the number of automatically completed cells to prevent denial of service.
38
+ - Correct `end_line` source positions for single-line and multi-line HTML blocks.
39
+ - Fix trailing newlines when rendering inline nodes.
30
40
  - Add support for front matter (`CMARK_OPT_FRONT_MATTER`): a `---` delimited block at the start of a document is captured as a `CMARK_NODE_FRONT_MATTER` node. The raw content is available via `node.string_content` and an optional format hint (e.g. `"yaml"`, `"toml"`) via `node.fence_info`.
31
41
  - Allow `:` in HTML tag names to support XML namespace prefixes (e.g. `<svg:circle>`, `<xhtml:div>`).
32
42
 
data/releases.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # Releases
2
2
 
3
+ ## v0.17.0
4
+
5
+ - Add opt-in language prefixes for inline code spans with `Markly::INLINE_CODE_INFO`, expose code metadata through `Node#code_info`, and provide `Node#code_language` as a convenient language accessor.
6
+ - Expose fenced code-block metadata through `Node#fence` and `Node::Fence`.
7
+ - Add `Node#next_header`, retain `Node#next_heading` as an alias, and correctly recognize header nodes.
8
+ - Raise `NotImplementedError` from the base renderer's unimplemented code-block callback instead of recursing indefinitely.
9
+ - Remove unused legacy separator helpers from the Ruby renderer.
10
+
3
11
  ## v0.16.0
4
12
 
5
- - Update `cmark-gfm` from upstream, including a denial-of-service fix for tables with a large number of autocompleted cells, corrected `end_line` source positions for single-line and multi-line HTML blocks, and a fix for trailing newlines when rendering inline nodes.
13
+ - Update `cmark-gfm` from upstream, including two table-rendering protections: avoid repeatedly scanning preceding cells, and limit the number of automatically completed cells to prevent denial of service.
14
+ - Correct `end_line` source positions for single-line and multi-line HTML blocks.
15
+ - Fix trailing newlines when rendering inline nodes.
6
16
  - Add support for front matter (`CMARK_OPT_FRONT_MATTER`): a `---` delimited block at the start of a document is captured as a `CMARK_NODE_FRONT_MATTER` node. The raw content is available via `node.string_content` and an optional format hint (e.g. `"yaml"`, `"toml"`) via `node.fence_info`.
7
17
  - Allow `:` in HTML tag names to support XML namespace prefixes (e.g. `<svg:circle>`, `<xhtml:div>`).
8
18
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garen Torikian
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
173
  - !ruby/object:Gem::Version
174
174
  version: '0'
175
175
  requirements: []
176
- rubygems_version: 4.0.6
176
+ rubygems_version: 4.0.10
177
177
  specification_version: 4
178
178
  summary: CommonMark parser and renderer. Written in C, wrapped in Ruby.
179
179
  test_files: []
metadata.gz.sig CHANGED
Binary file