Class: MarkdownPPConverter
- Inherits:
-
Asciidoctor::Converter::Base
- Object
- Asciidoctor::Converter::Base
- MarkdownPPConverter
- Defined in:
- lib/asciidoctor/converter/mdpp.rb
Instance Method Summary collapse
- #convert(node, transform = node.node_name) ⇒ Object
-
#convert_admonition(node) ⇒ Object
Render an admonition block as a Markdown++ styled block Emit a style comment and a blockquote for each content line.
-
#convert_document(doc) ⇒ Object
Render the document: title and top-level blocks (preamble, sections, etc.).
-
#convert_example(node) ⇒ Object
Render an example block (==== delimited) as nested blockquote lines.
-
#convert_header(header) ⇒ Object
Render the document title (from = Title) as a Markdown++ setext header.
-
#convert_image(node) ⇒ Object
(also: #convert_inline_image)
Render a block-level or inline image as a Markdown image with optional dimensions.
-
#convert_inline_anchor(node) ⇒ Object
Render inline anchor macros: xrefs and explicit anchors.
-
#convert_inline_quoted(node) ⇒ Object
Render inline quoted text (e.g., text) as Markdown++ strong syntax.
-
#convert_list_item(node) ⇒ Object
Render a list item: include its text and any nested blocks.
-
#convert_listing(node) ⇒ Object
Render a listing block (---- delimited) or source blocks as Markdown++ code fences.
-
#convert_literal(node) ⇒ Object
Render a literal block (.... delimited) as a code fence.
-
#convert_olist(olist) ⇒ Object
Render an ordered list with proper indentation for nested levels.
-
#convert_page_break(node) ⇒ Object
Render a page break directive (<<<) as blank space (skip into next page).
-
#convert_paragraph(par) ⇒ Object
Render a paragraph.
-
#convert_preamble(preamble) ⇒ Object
Render the document preamble (blocks before the first section).
-
#convert_section(sec) ⇒ Object
Render a section header, and include an explicit anchor comment if an id was set via a block anchor.
-
#convert_table(node) ⇒ Object
Render a table as a Markdown++ table, handling both simple and multiline cases.
-
#convert_thematic_break(node) ⇒ Object
Render a thematic break (''' delimited) as a horizontal rule.
-
#convert_ulist(ulist) ⇒ Object
Render an unordered list with proper indentation for nested levels.
-
#convert_video(node) ⇒ Object
Render a video macro as a YouTube iframe embed.
Instance Method Details
#convert(node, transform = node.node_name) ⇒ Object
22 23 24 25 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 22 def convert(node, transform = node.node_name) method = "convert_#{transform}" respond_to?(method) ? send(method, node) : "<!-- TODO: #{transform} -->" end |
#convert_admonition(node) ⇒ Object
Render an admonition block as a Markdown++ styled block Emit a style comment and a blockquote for each content line
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 336 def convert_admonition(node) # Build style tag (e.g., AdmonitionNote, AdmonitionTip) style = "Admonition#{node.caption}" # Gather content lines: use nested blocks if present, else raw lines if node.blocks.any? # convert each child block and accumulate its lines content = node.blocks.map { |b| convert(b) }.join("\n") lines = content.lines.map(&:chomp) suffix = '' else # fallback to raw source lines for short-form admonition lines = node.lines.map(&:chomp) suffix = "\n" end # Quote each line quoted = lines.map { |line| "> #{line}" }.join("\n") # Prepend style comment and content, with optional suffix "<!-- style:#{style} -->\n" + quoted + suffix end |
#convert_document(doc) ⇒ Object
Render the document: title and top-level blocks (preamble, sections, etc.)
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 113 def convert_document(doc) # Prepare document title or fallback to first section title parts = [] # Copy blocks to avoid mutating original blocks = doc.blocks.dup if doc.header && doc.header.title && !doc.header.title.empty? # Use document title parts << convert(doc.header, 'header') rest_blocks = blocks elsif blocks.first && blocks.first.node_name == 'section' # Promote first section title as document title first_sec = blocks.shift title = first_sec.title parts << "#{title}\n#{'=' * title.length}" # Render child blocks of that first section, then any remaining top-level blocks rest_blocks = first_sec.blocks + blocks else rest_blocks = blocks end # Render each remaining block rest_blocks.each do |blk| parts << convert(blk) end # Join parts with blank lines result = parts.compact.join("\n\n") # Append trailing newline if last document block is a standalone image macro paragraph if doc.blocks.any? && doc.blocks.last.node_name == 'paragraph' && doc.blocks.last.lines.size == 1 && doc.blocks.last.lines.first.strip.start_with?('image:') result << "\n" end result end |
#convert_example(node) ⇒ Object
Render an example block (==== delimited) as nested blockquote lines
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 357 def convert_example(node) # indent level for example block indent_str = '>' prefix_str = indent_str + ' ' lines = [] node.blocks.each_with_index do |b, idx| if b.node_name == 'listing' # nested listing block: increase indent content = convert_listing(b) nested_indent = '>' * 2 nested_prefix = nested_indent + ' ' content.split("\n", -1).each do |line| if line.empty? lines << nested_prefix else lines << nested_prefix + line end end else content = convert(b) content.split("\n", -1).each do |line| if line.empty? lines << indent_str else lines << prefix_str + line end end end # separator blank line between blocks if idx < node.blocks.size - 1 # first separator without space, subsequent with space if idx == 0 lines << indent_str else lines << prefix_str end end end lines.join("\n") end |
#convert_header(header) ⇒ Object
Render the document title (from = Title) as a Markdown++ setext header
28 29 30 31 32 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 28 def convert_header(header) title = header.title underline = '=' * title.length "#{title}\n#{underline}" end |
#convert_image(node) ⇒ Object Also known as: convert_inline_image
Render a block-level or inline image as a Markdown image with optional dimensions
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 300 def convert_image(node) # Determine alternate text: use explicit first positional attribute if provided; otherwise, no alt text alt = node.attributes.key?(1) ? node.attributes[1].to_s : '' # Determine source URL or path src = node.inline? ? node.target : node.attr('target') # Extract width and height (may include percentage units) width = node.attr('width') || node.attributes[2] height = node.attr('height') || node.attributes[3] # Build style name: handle numeric and percentage dimensions style_parts = [] if width w = width.to_s style_parts << (w.end_with?('%') ? "w#{w.chomp('%')}percent" : "w#{w}") end if height h = height.to_s style_parts << (h.end_with?('%') ? "h#{h.chomp('%')}percent" : "h#{h}") end style = style_parts.join # Assemble Markdown image img = "" # Prepend style comment only if dimensions were specified style.empty? ? img : "<!-- style:#{style} -->#{img}" end |
#convert_inline_anchor(node) ⇒ Object
Render inline anchor macros: xrefs and explicit anchors
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 254 def convert_inline_anchor(node) case node.type when :xref # Render cross-reference as a Markdown++ link text = node.text || node.target # node.target may include a leading '#', so do not add an extra "[#{text}](#{node.target})" when :ref # Render an explicit anchor id as a comment "<!-- ##{node.id} -->" else # Unknown inline anchor, omit "" end end |
#convert_inline_quoted(node) ⇒ Object
Render inline quoted text (e.g., text) as Markdown++ strong syntax
329 330 331 332 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 329 def convert_inline_quoted(node) # Always use double asterisks to denote quoted text "**#{node.text}**" end |
#convert_list_item(node) ⇒ Object
Render a list item: include its text and any nested blocks
245 246 247 248 249 250 251 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 245 def convert_list_item(node) parts = [node.text] node.blocks.each do |b| parts << convert(b) end parts.join("\n") end |
#convert_listing(node) ⇒ Object
Render a listing block (---- delimited) or source blocks as Markdown++ code fences
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 406 def convert_listing(node) # Simple listing blocks outside of example context: no language code fence if node.style == 'listing' && node.parent.node_name != 'example' return "```\n" + node.lines.join("\n") + "\n```" end # Named source code fence outside of example context: include language if node.style == 'source' && node.parent.node_name != 'example' lang = node.attr('language') || node.attributes[2] return "```#{lang}\n" + node.lines.join("\n") + "\n```" end # Fallback: convert content groups into paragraphs or headings lines = node.lines groups = [] current = [] lines.each do |line| if line.strip.empty? groups << current unless current.empty? current = [] else current << line end end groups << current unless current.empty? parts = groups.map do |group| if group.size == 1 && (m = group[0].match(/^(=+)\s*(.*)/)) level = m[1].length "#{'#' * level} #{m[2]}" else group.join(' ') end end parts.join("\n\n") end |
#convert_literal(node) ⇒ Object
Render a literal block (.... delimited) as a code fence
399 400 401 402 403 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 399 def convert_literal(node) lines = node.lines # Wrap literal block in Markdown++ code fence "```\n" + lines.join("\n") + "\n```" end |
#convert_olist(olist) ⇒ Object
Render an ordered list with proper indentation for nested levels
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 35 def convert_olist(olist) # indent nested ordered lists (two spaces per nested level) only when under another list item if olist.parent.respond_to?(:node_name) && olist.parent.node_name == 'list_item' indent = ' ' * (olist.level - 1) else indent = '' end olist.items.each_with_index.map do |li, idx| index = idx + 1 prefix = "#{indent}#{index}. " # Attempt to recover inline breaks from raw source when AST loses continuation raw_conv = nil if li.respond_to?(:source_location) && (loc = li.source_location) # Determine source file path (absolute if available, otherwise relative) raw_path = loc.respond_to?(:file) && loc.file ? loc.file : loc.path ln = loc.lineno raw_lines = nil if raw_path if File.exist?(raw_path) raw_lines = File.readlines(raw_path) else if (docfile = li.document.attr('docfile')) base = File.dirname(docfile) candidate = File.join(base, raw_path) raw_lines = File.readlines(candidate) if File.exist?(candidate) end end end if raw_lines # Check for trailing '+' on the first list-item line first_line = raw_lines[ln - 1].chomp("\n") if first_line.rstrip.end_with?('+') # Collect all lines belonging to this list item continuation i = ln - 1 segments = [] while i < raw_lines.size line = raw_lines[i].chomp("\n") # Stop at next list-item marker or blank line break if line.lstrip =~ /^(?:\d+\.\s|\.\s)/ || line.strip.empty? # Remove trailing '+' if present seg = line.rstrip seg = seg.chomp('+').rstrip if seg.end_with?('+') segments << seg i += 1 end if segments.any? # Determine marker prefix from the first segment if (m = segments[0].match(/^\s*(?:\d+\.\s|\.\s)/)) marker = m[0] else marker = prefix end # Build recovered lines: first with marker, continuations indented lines_out = [] # first segment text after marker text0 = segments[0][marker.length..-1].lstrip lines_out << "#{marker}#{text0}" # subsequent segments segments[1..-1].to_a.each do |seg| lines_out << (' ' * marker.length) + seg.lstrip end raw_conv = lines_out.join("\n") end end end end # Use recovered content if available, otherwise default conversion if raw_conv raw_conv else converted = convert(li, 'list_item') body = converted.gsub(/\n/, "\n" + ' ' * prefix.length) "#{prefix}#{body}" end end.join("\n") end |
#convert_page_break(node) ⇒ Object
Render a page break directive (<<<) as blank space (skip into next page)
271 272 273 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 271 def convert_page_break(node) '' end |
#convert_paragraph(par) ⇒ Object
Render a paragraph. Handle inline image macros specially, otherwise process inline macros.
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 173 def convert_paragraph(par) # If this paragraph is a Markdown++ include tag, emit it raw if par.lines.size == 1 && par.lines.first.strip =~ /<!--\s*include:[^>]+-->/ return par.lines.first.strip end # Process raw lines to handle trailing '+' hard breaks lines = par.lines.map do |line| l = line.chomp("\n") if l.rstrip.end_with?('+') l.chomp('+') else l end end text = lines.join("\n") # Convert any inline image macros in this paragraph if text.include?('image:') text = text.gsub(/image:(\S+?)\[([^\]]*)\]/) do src = Regexp.last_match(1) positional = [] named = {} Regexp.last_match(2).split(',').map(&:strip).each do |param| next if param.empty? if param.include?('=') k, v = param.split('=', 2) v = v.gsub(/^"|"$|^'|'$/, '') named[k] = v else positional << param end end alt = positional[0] || '' width = named['width'] || positional[1] height = named['height'] || positional[2] style_parts = [] if width && !width.empty? w = width.to_s style_parts << (w.end_with?('%') ? "w#{w.chomp('%')}percent" : "w#{w}") end if height && !height.empty? h = height.to_s style_parts << (h.end_with?('%') ? "h#{h.chomp('%')}percent" : "h#{h}") end style = style_parts.join img = "" style.empty? ? img : "<!-- style:#{style} -->#{img}" end end # Convert inline xref anchors: <<id, text>> to Markdown++ links text = text.gsub(/<<([^,>]+),\s*([^>]+?)>>/) { "[#{$2}](##{$1})" } # Convert inline quoted text: *text* to bold **text** text = text.gsub(/\*(.+?)\*/) { "**#{$1}**" } text end |
#convert_preamble(preamble) ⇒ Object
Render the document preamble (blocks before the first section)
146 147 148 149 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 146 def convert_preamble(preamble) # Convert each child block in the preamble preamble.blocks.map { |blk| convert(blk) }.compact.join("\n\n") end |
#convert_section(sec) ⇒ Object
Render a section header, and include an explicit anchor comment if an id was set via a block anchor
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 152 def convert_section(sec) # Build the Markdown header line header_line = '#' * sec.level + ' ' + sec.title # Start with optional anchor comment for explicit anchors output = '' # Include comment only when section id was explicitly set (does not begin with auto-generated prefix) id = sec.id.to_s # Default id prefix as defined by document (defaults to '_') prefix = sec.document.attributes['idprefix'] || '_' if !id.empty? && !id.start_with?(prefix) output << "<!-- ##{id} -->\n" end # Add the header output << header_line # Append child blocks, separated by a blank line sec.blocks.each do |b| output << "\n\n" << convert(b) end output end |
#convert_table(node) ⇒ Object
Render a table as a Markdown++ table, handling both simple and multiline cases
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 441 def convert_table(node) # Determine Markdown++ style tag style = node.attr('role') # Fallback: simple table conversion via AST for tables with more than 2 columns ast_rows = node.rows if (ast_rows.respond_to?(:head) && ast_rows.respond_to?(:body) ? (ast_rows.head.first || []).size > 2 : (node.attr('cols') || '').split(',').size > 2) # AST-based simple table: pad columns to equal width header_cells = ast_rows.respond_to?(:head) ? (ast_rows.head.first || []) : [] body_ast = ast_rows.respond_to?(:body) ? ast_rows.body : (begin arr = []; node.rows.each { |r| arr << r.cells }; arr.drop(1) end) # Extract texts header_texts = header_cells.map(&:text) body_texts = body_ast.map { |cells| cells.map(&:text) } # Compute max length per column, defaulting missing entries to 0 max_lens = header_texts.map(&:length) body_texts.each do |row| row.each_with_index do |text, idx| current = max_lens[idx] || 0 max_lens[idx] = text.length if text.length > current end end # Column widths with padding widths = max_lens.map { |l| l + 2 } # Build header line hdr_cells = header_texts.each_with_index.map { |h, i| h.ljust(widths[i] - 2) } header_line = "| " + hdr_cells.join(' | ') + " |" # Build alignment line align_line = '|' + widths.map { |w| '-' * w }.join('|') + '|' # Build body lines body_lines = body_texts.map do |row| cells = row.each_with_index.map { |text, i| text.ljust(widths[i] - 2) } "| " + cells.join(' | ') + " |" end # Style comment comment = style ? "<!-- style:#{style} -->" : '' return [comment, header_line, align_line, *body_lines].reject(&:empty?).join("\n") end # Locate raw source file and read lines; if file unreadable, fallback to simple AST conversion src_file = node.document.attr('docfile') begin src_lines = File.readlines(src_file) rescue # Fallback to simple AST-based table conversion header_cells = ast_rows.respond_to?(:head) ? (ast_rows.head.first || []) : [] body_ast = ast_rows.respond_to?(:body) ? ast_rows.body : (begin arr = []; node.rows.each { |r| arr << r.cells }; arr.drop(1) end) header_texts = header_cells.map(&:text) body_texts = body_ast.map { |cells| cells.map(&:text) } max_lens = header_texts.map(&:length) body_texts.each do |row| row.each_with_index { |text, idx| max_lens[idx] = text.length if text.length > max_lens[idx] } end widths = max_lens.map { |l| l + 2 } hdr_cells = header_texts.each_with_index.map { |h, i| h.ljust(widths[i] - 2) } header_line = "| " + hdr_cells.join(' | ') + " |" align_line = '|' + widths.map { |w| '-' * w }.join('|') + '|' body_lines = body_texts.map do |row| cells = row.each_with_index.map { |text, i| text.ljust(widths[i] - 2) } "| " + cells.join(' | ') + " |" end comment = style ? "<!-- style:#{style} -->" : '' return [comment, header_line, align_line, *body_lines].reject(&:empty?).join("\n") end # If table is not fenced with grid markers, fallback to simple AST conversion unless src_lines.any? { |l| l.strip == '|===' } header_cells = ast_rows.respond_to?(:head) ? (ast_rows.head.first || []) : [] body_ast = ast_rows.respond_to?(:body) ? ast_rows.body : (begin arr = []; node.rows.each { |r| arr << r.cells }; arr.drop(1) end) header_texts = header_cells.map(&:text) body_texts = body_ast.map { |cells| cells.map(&:text) } # Safe AST-based fallback: compute column widths max_lens = header_texts.map(&:length) body_texts.each do |row| row.each_with_index do |text, idx| current = max_lens[idx] || 0 max_lens[idx] = text.length if text.length > current end end widths = max_lens.map { |l| (l || 0) + 2 } hdr_cells = header_texts.each_with_index.map { |h, i| h.ljust(widths[i] - 2) } header_line = "| " + hdr_cells.join(' | ') + " |" align_line = '|' + widths.map { |w| '-' * w }.join('|') + '|' body_lines = body_texts.map do |row| cells = row.each_with_index.map { |text, i| text.ljust(widths[i] - 2) } "| " + cells.join(' | ') + " |" end comment = style ? "<!-- style:#{style} -->" : '' return [comment, header_line, align_line, *body_lines].reject(&:empty?).join("\n") end # Identify table boundaries (fenced with |===) start_idx = src_lines.index { |l| l.strip == '|===' } end_rel = src_lines[(start_idx + 1)..-1].index { |l| l.strip == '|===' } end_idx = start_idx + 1 + (end_rel || 0) raw = src_lines[(start_idx + 1)...end_idx] # Parse header row hdr_line = raw.find { |l| l.strip.start_with?('|') } hdr_cols = hdr_line.strip.sub(/^\|/, '').split('|').map(&:strip) # Parse body rows: each '| ' marks a new row; details follow until next '|' or end rows = [] body = raw.drop_while { |l| l != hdr_line }[1..] || [] i = 0 while i < body.size line = body[i] if line.strip.start_with?('|') # New row header header_text = line.strip.sub(/^\|/, '').chomp('+').strip # Collect detail lines until next row or table end details = [] i += 1 while i < body.size && !body[i].strip.start_with?('|') txt = body[i].rstrip details << txt unless txt.strip.empty? i += 1 end rows << { header: header_text, details: details } else i += 1 end end # Post-process details for code and admonition rows rows.each do |row| details = row[:details] # Code block detection if details.first =~ /^\[source,.*\]$/ # Extract lines between '----' fences start_idx = details.index('----') if start_idx end_idx = details[(start_idx + 1)..].index('----') code_lines = if end_idx details[(start_idx + 1)...(start_idx + 1 + end_idx)] else details[(start_idx + 1)..] end else code_lines = details.drop(1) end row[:details] = ['```'] + code_lines + ['```'] elsif details.first == '====' # Admonition block detection detail = details[1] || '' cap = detail.split(':', 2).first style_name = cap.downcase.capitalize row[:details] = ["<!-- style:Admonition#{style_name} -->", "> #{detail}"] end end # Compute column widths: column1 based on headers and row headers; column2 based on header and details col1_max = ([hdr_cols[0].length] + rows.map { |r| r[:header].length }).max col2_max = ([hdr_cols[1].length] + rows.flat_map { |r| r[:details].map(&:length) }).max widths = [col1_max + 2, col2_max + 2] # Build style comment: add 'multiline' if any detail contains more than one line = [] << "style:#{style}" if style << 'multiline' if rows.any? { |r| r[:details].size > 1 } comment = .empty? ? '' : "<!-- #{tags.join('; ')} -->" # Build header and alignment rows header_md = "| " + hdr_cols.each_with_index.map { |h, j| h.ljust(widths[j] - 2) }.join(' | ') + " |" # Alignment line: adjust second column width for multiline tables align_parts = widths.map.with_index do |w, idx| '-' * (idx.zero? ? w : w + 1) end align_md = '|' + align_parts.join('|') + '|' # Assemble table lines md = [] md << comment md << header_md md << align_md rows.each_with_index do |row, idx| # First line: header and first detail (or blank) md << '|' + ' ' + row[:header].ljust(widths[0] - 2) + ' | ' + (row[:details][0] || '').ljust(widths[1] - 2) + ' |' # Additional detail lines row[:details][1..].to_a.each do |det| md << '|' + ' ' * widths[0] + '|' + ' ' + det.ljust(widths[1] - 2) + ' |' end # Separator blank row between logical row groups unless idx == rows.size - 1 # Blank separator row: adjust second column width as alignment line blank_parts = widths.map.with_index do |w, idx| ' ' * (idx.zero? ? w : w + 1) end md << '|' + blank_parts.join('|') + '|' end end md.join("\n") end |
#convert_thematic_break(node) ⇒ Object
Render a thematic break (''' delimited) as a horizontal rule
276 277 278 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 276 def convert_thematic_break(node) '---' end |
#convert_ulist(ulist) ⇒ Object
Render an unordered list with proper indentation for nested levels
229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 229 def convert_ulist(ulist) # indent nested unordered lists (two spaces per nested level) only when under another list item if ulist.parent.respond_to?(:node_name) && ulist.parent.node_name == 'list_item' indent = ' ' * (ulist.level - 1) else indent = '' end # Render unordered list items, indenting nested lines, and ensure trailing newline ulist.items.map do |li| # render item and indent subsequent lines body = convert(li, 'list_item').gsub(/\n/, "\n#{indent} ") "#{indent}- #{body}" end.join("\n") end |
#convert_video(node) ⇒ Object
Render a video macro as a YouTube iframe embed
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/asciidoctor/converter/mdpp.rb', line 281 def convert_video(node) # Extract video id and dimensions id = node.attr('target') width = node.attr('width') height = node.attr('height') # Build iframe embed lines = [] lines << '<iframe ' lines << " width=\"#{width}\"" lines << " height=\"#{height}\"" lines << " src=\"https://www.youtube.com/embed/#{id}\"" lines << ' frameborder="0"' lines << ' allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"' lines << ' allowfullscreen>' lines << '</iframe>' lines.join("\n") end |