tty-markdown 0.6.0 → 0.7.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.
@@ -0,0 +1,821 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "kramdown/converter"
4
+ require "kramdown/element"
5
+ require "pastel"
6
+ require "strings"
7
+ require "uri"
8
+
9
+ require_relative "syntax_highlighter"
10
+
11
+ module TTY
12
+ module Markdown
13
+ # Converts a Kramdown::Document tree to a terminal friendly output
14
+ class Converter < ::Kramdown::Converter::Base
15
+ NEWLINE = "\n"
16
+ SPACE = " "
17
+
18
+ def initialize(root, options = {})
19
+ super
20
+ @current_indent = 0
21
+ @indent = options[:indent]
22
+ @pastel = Pastel.new(enabled: options[:enabled])
23
+ @color_opts = { mode: options[:mode],
24
+ color: @pastel.yellow.detach,
25
+ enabled: options[:enabled] }
26
+ @width = options[:width]
27
+ @theme = options[:theme].each_with_object({}) do |(key, val), acc|
28
+ acc[key] = Array(val)
29
+ end
30
+ @symbols = options[:symbols]
31
+ @footnote_no = 1
32
+ @footnotes = {}
33
+ end
34
+
35
+ # Invoke an element conversion
36
+ #
37
+ # @api public
38
+ def convert(el, opts = { indent: 0 })
39
+ send("convert_#{el.type}", el, opts)
40
+ end
41
+
42
+ private
43
+
44
+ # Process children of this element
45
+ #
46
+ # @param [Kramdown::Element] el
47
+ # the element with child elements
48
+ #
49
+ # @api private
50
+ def inner(el, opts)
51
+ result = []
52
+ el.children.each_with_index do |inner_el, i|
53
+ options = opts.dup
54
+ options[:parent] = el
55
+ options[:prev] = (i.zero? ? nil : el.children[i - 1])
56
+ options[:next] = (i == el.children.length - 1 ? nil : el.children[i + 1])
57
+ options[:index] = i
58
+ result << convert(inner_el, options)
59
+ end
60
+ result
61
+ end
62
+
63
+ # Convert root element
64
+ #
65
+ # @param [Kramdown::Element] el
66
+ # the `kd:root` element
67
+ # @param [Hash] opts
68
+ # the element options
69
+ #
70
+ # @api private
71
+ def convert_root(el, opts)
72
+ content = inner(el, opts)
73
+ return content.join if @footnotes.empty?
74
+
75
+ content.join + footnotes_list(root, opts)
76
+ end
77
+
78
+ # Create an ordered list of footnotes
79
+ #
80
+ # @param [Kramdown::Element] root
81
+ # the `kd:root` element
82
+ # @param [Hash] opts
83
+ # the root element options
84
+ #
85
+ # @api private
86
+ def footnotes_list(root, opts)
87
+ ol = Kramdown::Element.new(:ol)
88
+ @footnotes.values.each do |footnote|
89
+ value, index = *footnote
90
+ options = { index: index, parent: ol }
91
+ li = Kramdown::Element.new(:li, nil, {}, options.merge(opts))
92
+ li.children = Marshal.load(Marshal.dump(value.children))
93
+ ol.children << li
94
+ end
95
+ convert_ol(ol, { parent: root }.merge(opts))
96
+ end
97
+
98
+ # Convert header element
99
+ #
100
+ # @param [Kramdown::Element] el
101
+ # the `kd:header` element
102
+ # @param [Hash] opts
103
+ # the element options
104
+ #
105
+ # @api private
106
+ def convert_header(el, opts)
107
+ level = el.options[:level]
108
+ if opts[:parent] && opts[:parent].type == :root
109
+ # Header determines indentation only at top level
110
+ @current_indent = (level - 1) * @indent
111
+ indent = SPACE * (level - 1) * @indent
112
+ else
113
+ indent = SPACE * @current_indent
114
+ end
115
+ styles = @theme[:header].dup
116
+ styles << :underline if level == 1
117
+
118
+ content = inner(el, opts)
119
+
120
+ content.join.lines.map do |line|
121
+ indent + @pastel.decorate(line.chomp, *styles) + NEWLINE
122
+ end
123
+ end
124
+
125
+ # Convert paragraph element
126
+ #
127
+ # @param [Kramdown::Element] el
128
+ # the `kd:p` element
129
+ # @param [Hash] opts
130
+ # the element options
131
+ #
132
+ # @api private
133
+ def convert_p(el, opts)
134
+ indent = SPACE * @current_indent
135
+ result = []
136
+
137
+ if ![:blockquote, :li].include?(opts[:parent].type)
138
+ result << indent
139
+ end
140
+
141
+ opts[:indent] = @current_indent
142
+ if opts[:parent].type == :blockquote
143
+ opts[:indent] = 0
144
+ end
145
+
146
+ content = inner(el, opts)
147
+
148
+ result << content.join
149
+ unless result.last.to_s.end_with?(NEWLINE)
150
+ result << NEWLINE
151
+ end
152
+ result
153
+ end
154
+
155
+ # Convert text element
156
+ #
157
+ # @param [Kramdown::Element] element
158
+ # the `kd:text` element
159
+ # @param [Hash] opts
160
+ # the element options
161
+ #
162
+ # @api private
163
+ def convert_text(el, opts)
164
+ text = Strings.wrap(el.value, @width - @current_indent)
165
+ text = text.chomp if opts[:strip]
166
+ indent = SPACE * opts[:indent]
167
+ text.gsub(/\n/, "#{NEWLINE}#{indent}")
168
+ end
169
+
170
+ # Convert strong element
171
+ #
172
+ # @param [Kramdown::Element] element
173
+ # the `kd:strong` element
174
+ # @param [Hash] opts
175
+ # the element options
176
+ #
177
+ # @api private
178
+ def convert_strong(el, opts)
179
+ content = inner(el, opts)
180
+
181
+ content.join.lines.map do |line|
182
+ @pastel.decorate(line.chomp, *@theme[:strong])
183
+ end.join(NEWLINE)
184
+ end
185
+
186
+ # Convert em element
187
+ #
188
+ # @param [Kramdown::Element] el
189
+ # the `kd:em` element
190
+ # @param [Hash] opts
191
+ # the element options
192
+ #
193
+ # @api private
194
+ def convert_em(el, opts)
195
+ content = inner(el, opts)
196
+
197
+ content.join.lines.map do |line|
198
+ @pastel.decorate(line.chomp, *@theme[:em])
199
+ end.join(NEWLINE)
200
+ end
201
+
202
+ # Convert new line element
203
+ #
204
+ # @param [Kramdown::Element] el
205
+ # the `kd:blank` element
206
+ # @param [Hash] opts
207
+ # the element options
208
+ #
209
+ # @api private
210
+ def convert_blank(*)
211
+ NEWLINE
212
+ end
213
+
214
+ # Convert smart quote element
215
+ #
216
+ # @param [Kramdown::Element] el
217
+ # the `kd:smart_quote` element
218
+ # @param [Hash] opts
219
+ # the element options
220
+ #
221
+ # @api private
222
+ def convert_smart_quote(el, opts)
223
+ @symbols[el.value]
224
+ end
225
+
226
+ # Convert codespan element
227
+ #
228
+ # @param [Kramdown::Element] el
229
+ # the `kd:codespan` element
230
+ # @param [Hash] opts
231
+ # the element options
232
+ #
233
+ # @api private
234
+ def convert_codespan(el, opts)
235
+ indent = SPACE * @current_indent
236
+ syntax_opts = @color_opts.merge(lang: el.options[:lang])
237
+ raw_code = Strings.wrap(el.value, @width - @current_indent)
238
+ highlighted = SyntaxHighliter.highlight(raw_code, **syntax_opts)
239
+
240
+ highlighted.lines.map.with_index do |line, i|
241
+ i.zero? ? line.chomp : indent + line.chomp
242
+ end.join(NEWLINE)
243
+ end
244
+
245
+ # Convert codeblock element
246
+ #
247
+ # @param [Kramdown::Element] el
248
+ # the `kd:codeblock` element
249
+ # @param [Hash] opts
250
+ # the element options
251
+ #
252
+ # @api private
253
+ def convert_codeblock(el, opts)
254
+ indent = SPACE * @current_indent
255
+ indent + convert_codespan(el, opts)
256
+ end
257
+
258
+ # Convert blockquote element
259
+ #
260
+ # @param [Kramdown::Element] el
261
+ # the `kd:blockquote` element
262
+ # @param [Hash] opts
263
+ # the element options
264
+ #
265
+ # @api private
266
+ def convert_blockquote(el, opts)
267
+ indent = SPACE * @current_indent
268
+ bar_symbol = @symbols[:bar]
269
+ prefix = "#{indent}#{@pastel.decorate(bar_symbol, *@theme[:quote])} "
270
+
271
+ content = inner(el, opts)
272
+
273
+ content.join.lines.map do |line|
274
+ prefix + line
275
+ end
276
+ end
277
+
278
+ # Convert ordered and unordered list element
279
+ #
280
+ # @param [Kramdown::Element] el
281
+ # the `kd:ul` or `kd:ol` element
282
+ # @param [Hash] opts
283
+ # the element options
284
+ #
285
+ # @api private
286
+ def convert_ul(el, opts)
287
+ @current_indent += @indent unless opts[:parent].type == :root
288
+ content = inner(el, opts)
289
+ @current_indent -= @indent unless opts[:parent].type == :root
290
+ content.join
291
+ end
292
+ alias convert_ol convert_ul
293
+ alias convert_dl convert_ul
294
+
295
+ # Convert list element
296
+ #
297
+ # @param [Kramdown::Element] el
298
+ # the `kd:li` element
299
+ # @param [Hash] opts
300
+ # the element options
301
+ #
302
+ # @api private
303
+ def convert_li(el, opts)
304
+ index = opts[:index] + 1
305
+ indent = SPACE * @current_indent
306
+ prefix_type = opts[:parent].type == :ol ? "#{index}." : @symbols[:bullet]
307
+ prefix = @pastel.decorate(prefix_type, *@theme[:list]) + SPACE
308
+ opts[:strip] = true
309
+
310
+ content = inner(el, opts)
311
+
312
+ indent + prefix + content.join
313
+ end
314
+
315
+ # Convert dt element
316
+ #
317
+ # @param [Kramdown::Element] el
318
+ # the `kd:dt` element
319
+ # @param [Hash] opts
320
+ # the element options
321
+ #
322
+ # @api private
323
+ def convert_dt(el, opts)
324
+ indent = SPACE * @current_indent
325
+ content = inner(el, opts)
326
+ indent + content.join + NEWLINE
327
+ end
328
+
329
+ # Convert dd element
330
+ #
331
+ # @param [Kramdown::Element] el
332
+ # the `kd:dd` element
333
+ # @param [Hash] opts
334
+ # the element options
335
+ #
336
+ # @api private
337
+ def convert_dd(el, opts)
338
+ result = []
339
+ @current_indent += @indent unless opts[:parent].type == :root
340
+ content = inner(el, opts)
341
+ @current_indent -= @indent unless opts[:parent].type == :root
342
+ result << content.join
343
+ result << NEWLINE if opts[:next] && opts[:next].type == :dt
344
+ result
345
+ end
346
+
347
+ # Convert table element
348
+ #
349
+ # @param [Kramdown::Element] el
350
+ # the `kd:table` element
351
+ # @param [Hash] opts
352
+ # the element options
353
+ #
354
+ # @api private
355
+ def convert_table(el, opts)
356
+ @row = 0
357
+ @column = 0
358
+ opts[:alignment] = el.options[:alignment]
359
+ opts[:table_data] = extract_table_data(el, opts)
360
+ opts[:column_widths] = distribute_widths(max_widths(opts[:table_data]))
361
+ opts[:row_heights] = max_row_heights(opts[:table_data], opts[:column_widths])
362
+
363
+ inner(el, opts).join
364
+ end
365
+
366
+ # Extract table data
367
+ #
368
+ # @param [Kramdown::Element] el
369
+ # the `kd:table` element
370
+ #
371
+ # @api private
372
+ def extract_table_data(el, opts)
373
+ el.children.each_with_object([]) do |container, data|
374
+ container.children.each do |row|
375
+ data_row = []
376
+ row.children.each do |cell|
377
+ data_row << inner(cell, opts)
378
+ end
379
+ data << data_row
380
+ end
381
+ end
382
+ end
383
+
384
+ # Distribute column widths inside total width
385
+ #
386
+ # @return [Array<Integer>]
387
+ #
388
+ # @api private
389
+ def distribute_widths(widths)
390
+ indent = SPACE * @current_indent
391
+ total_width = widths.reduce(&:+)
392
+ screen_width = @width - (indent.length + 1) * 2 - (widths.size + 1)
393
+ return widths if total_width <= screen_width
394
+
395
+ extra_width = total_width - screen_width
396
+
397
+ widths.map do |w|
398
+ ratio = w / total_width.to_f
399
+ w - (extra_width * ratio).floor
400
+ end
401
+ end
402
+
403
+ # Calculate maximum widths for each column
404
+ #
405
+ # @return [Array<Integer>]
406
+ #
407
+ # @api private
408
+ def max_widths(table_data)
409
+ table_data.first.each_with_index.reduce([]) do |acc, (*, col)|
410
+ acc << max_width(table_data, col)
411
+ end
412
+ end
413
+
414
+ # Calculate maximum cell width for a given column
415
+ #
416
+ # @return [Integer]
417
+ #
418
+ # @api private
419
+ def max_width(table_data, col)
420
+ table_data.map do |row|
421
+ Strings.sanitize(row[col].join).lines.map(&:length).max || 0
422
+ end.max
423
+ end
424
+
425
+ # Calculate maximum heights for each row
426
+ #
427
+ # @return [Array<Integer>]
428
+ #
429
+ # @api private
430
+ def max_row_heights(table_data, column_widths)
431
+ table_data.reduce([]) do |acc, row|
432
+ acc << max_row_height(row, column_widths)
433
+ end
434
+ end
435
+
436
+ # Calculate maximum cell height for a given row
437
+ #
438
+ # @return [Integer]
439
+ #
440
+ # @api private
441
+ def max_row_height(row, column_widths)
442
+ row.map.with_index do |column, col_index|
443
+ Strings.wrap(column.join, column_widths[col_index]).lines.size
444
+ end.max
445
+ end
446
+
447
+ # Convert thead element
448
+ #
449
+ # @param [Kramdown::Element] el
450
+ # the `kd:thead` element
451
+ # @param [Hash] opts
452
+ # the element options
453
+ #
454
+ # @api private
455
+ def convert_thead(el, opts)
456
+ indent = SPACE * @current_indent
457
+ result = []
458
+
459
+ result << indent
460
+ result << border(opts[:column_widths], :top)
461
+ result << NEWLINE
462
+
463
+ content = inner(el, opts)
464
+
465
+ result << content.join
466
+ result.join
467
+ end
468
+
469
+ # Render horizontal border line
470
+ #
471
+ # @param [Array<Integer>] column_widths
472
+ # the table column widths
473
+ # @param [Symbol] location
474
+ # location out of :top, :mid, :bottom
475
+ #
476
+ # @return [String]
477
+ #
478
+ # @api private
479
+ def border(column_widths, location)
480
+ result = []
481
+ result << @symbols[:"#{location}_left"]
482
+ column_widths.each.with_index do |width, i|
483
+ result << @symbols[:"#{location}_center"] if i != 0
484
+ result << (@symbols[:line] * (width + 2))
485
+ end
486
+ result << @symbols[:"#{location}_right"]
487
+ @pastel.decorate(result.join, *@theme[:table])
488
+ end
489
+
490
+ # Convert tbody element
491
+ #
492
+ # @param [Kramdown::Element] el
493
+ # the `kd:tbody` element
494
+ # @param [Hash] opts
495
+ # the element options
496
+ #
497
+ # @api private
498
+ def convert_tbody(el, opts)
499
+ indent = SPACE * @current_indent
500
+ result = []
501
+
502
+ result << indent
503
+ if opts[:prev] && opts[:prev].type == :thead
504
+ result << border(opts[:column_widths], :mid)
505
+ else
506
+ result << border(opts[:column_widths], :top)
507
+ end
508
+ result << "\n"
509
+
510
+ content = inner(el, opts)
511
+
512
+ result << content.join
513
+ result << indent
514
+ if opts[:next] && opts[:next].type == :tfoot
515
+ result << border(opts[:column_widths], :mid)
516
+ else
517
+ result << border(opts[:column_widths], :bottom)
518
+ end
519
+ result << NEWLINE
520
+ result.join
521
+ end
522
+
523
+ # Convert tfoot element
524
+ #
525
+ # @param [Kramdown::Element] el
526
+ # the `kd:tfoot` element
527
+ # @param [Hash] opts
528
+ # the element options
529
+ #
530
+ # @api private
531
+ def convert_tfoot(el, opts)
532
+ indent = SPACE * @current_indent
533
+
534
+ inner(el, opts).join + indent +
535
+ border(opts[:column_widths], :bottom) +
536
+ NEWLINE
537
+ end
538
+
539
+ # Convert td element
540
+ #
541
+ # @param [Kramdown::Element] el
542
+ # the `kd:td` element
543
+ # @param [Hash] opts
544
+ # the element options
545
+ #
546
+ # @api private
547
+ def convert_tr(el, opts)
548
+ indent = SPACE * @current_indent
549
+ result = []
550
+
551
+ if opts[:prev] && opts[:prev].type == :tr
552
+ result << indent
553
+ result << border(opts[:column_widths], :mid)
554
+ result << NEWLINE
555
+ end
556
+
557
+ content = inner(el, opts)
558
+
559
+ columns = content.count
560
+
561
+ row = content.each_with_index.reduce([]) do |acc, (cell, i)|
562
+ if cell.size > 1 # multiline
563
+ cell.each_with_index do |c, j| # zip columns
564
+ acc[j] = [] if acc[j].nil?
565
+ acc[j] << c.chomp
566
+ acc[j] << "\n" if i == (columns - 1)
567
+ end
568
+ else
569
+ acc << cell
570
+ acc << "\n" if i == (columns - 1)
571
+ end
572
+ acc
573
+ end.join
574
+
575
+ result << row
576
+ @row += 1
577
+ result.join
578
+ end
579
+
580
+ # Convert td element
581
+ #
582
+ # @param [Kramdown::Element] el
583
+ # the `kd:td` element
584
+ # @param [Hash] opts
585
+ # the element options
586
+ #
587
+ # @api private
588
+ def convert_td(el, opts)
589
+ indent = SPACE * @current_indent
590
+ pipe_char = @symbols[:pipe]
591
+ pipe = @pastel.decorate(pipe_char, *@theme[:table])
592
+ suffix = " #{pipe} "
593
+
594
+ cell_content = inner(el, opts)
595
+ cell_width = opts[:column_widths][@column]
596
+ cell_height = opts[:row_heights][@row]
597
+ alignment = opts[:alignment][@column]
598
+ align_opts = alignment == :default ? {} : { direction: alignment }
599
+
600
+ wrapped = Strings.wrap(cell_content.join, cell_width)
601
+ aligned = Strings.align(wrapped, cell_width, **align_opts)
602
+ padded = if aligned.lines.size < cell_height
603
+ Strings.pad(aligned, [0, 0, cell_height - aligned.lines.size, 0])
604
+ else
605
+ aligned.dup
606
+ end
607
+
608
+ content = padded.lines.map do |line|
609
+ # add pipe to first column
610
+ (@column.zero? ? "#{indent}#{pipe} " : "") +
611
+ (line.end_with?("\n") ? line.insert(-2, suffix) : line << suffix)
612
+ end
613
+ @column = (@column + 1) % opts[:column_widths].size
614
+ content
615
+ end
616
+
617
+ def convert_br(el, opts)
618
+ NEWLINE
619
+ end
620
+
621
+ # Convert hr element
622
+ #
623
+ # @param [Kramdown::Element] el
624
+ # the `kd:hr` element
625
+ # @param [Hash] opts
626
+ # the element options
627
+ #
628
+ # @api private
629
+ def convert_hr(el, opts)
630
+ width = @width - @symbols[:diamond].length * 2
631
+ line = @symbols[:diamond] + @symbols[:line] * width + @symbols[:diamond]
632
+ @pastel.decorate(line, *@theme[:hr]) + NEWLINE
633
+ end
634
+
635
+ # Convert a element
636
+ #
637
+ # @param [Kramdown::Element] el
638
+ # the `kd:a` element
639
+ # @param [Hash] opts
640
+ # the element options
641
+ #
642
+ # @api private
643
+ def convert_a(el, opts)
644
+ result = []
645
+
646
+ if URI.parse(el.attr["href"]).class == URI::MailTo
647
+ el.attr["href"] = URI.parse(el.attr["href"]).to
648
+ end
649
+
650
+ if el.children.size == 1 && el.children[0].type == :text &&
651
+ el.children[0].value == el.attr["href"]
652
+
653
+ if !el.attr["title"].nil? && !el.attr["title"].strip.empty?
654
+ result << "(#{el.attr["title"]}) "
655
+ end
656
+ result << @pastel.decorate(el.attr["href"], *@theme[:link])
657
+
658
+ elsif el.children.size > 0 &&
659
+ (el.children[0].type != :text || !el.children[0].value.strip.empty?)
660
+
661
+ content = inner(el, opts)
662
+
663
+ result << content.join
664
+ result << " #{@symbols[:arrow]} "
665
+ if el.attr["title"]
666
+ result << "(#{el.attr["title"]}) "
667
+ end
668
+ result << @pastel.decorate(el.attr["href"], *@theme[:link])
669
+ end
670
+ result
671
+ end
672
+
673
+ # Convert math element
674
+ #
675
+ # @param [Kramdown::Element] el
676
+ # the `kd:math` element
677
+ # @param [Hash] opts
678
+ # the element options
679
+ #
680
+ # @api private
681
+ def convert_math(el, opts)
682
+ if el.options[:category] == :block
683
+ convert_codeblock(el, opts) + NEWLINE
684
+ else
685
+ convert_codespan(el, opts)
686
+ end
687
+ end
688
+
689
+ # Convert abbreviation element
690
+ #
691
+ # @param [Kramdown::Element] el
692
+ # the `kd:abbreviation` element
693
+ # @param [Hash] opts
694
+ # the element options
695
+ #
696
+ # @api private
697
+ def convert_abbreviation(el, opts)
698
+ title = @root.options[:abbrev_defs][el.value]
699
+ if title.to_s.empty?
700
+ el.value
701
+ else
702
+ "#{el.value}(#{title})"
703
+ end
704
+ end
705
+
706
+ def convert_typographic_sym(el, opts)
707
+ @symbols[el.value]
708
+ end
709
+
710
+ def convert_entity(el, opts)
711
+ unicode_char(el.value.code_point)
712
+ end
713
+
714
+ # Convert codepoint to UTF-8 representation
715
+ def unicode_char(codepoint)
716
+ [codepoint].pack("U*")
717
+ end
718
+
719
+ # Convert image element
720
+ #
721
+ # @param [Kramdown::Element] element
722
+ # the `kd:footnote` element
723
+ # @param [Hash] opts
724
+ # the element options
725
+ #
726
+ # @api private
727
+ def convert_footnote(el, opts)
728
+ name = el.options[:name]
729
+ if footnote = @footnotes[name]
730
+ number = footnote.last
731
+ else
732
+ number = @footnote_no
733
+ @footnote_no += 1
734
+ @footnotes[name] = [el.value, number]
735
+ end
736
+
737
+ content = "#{@symbols[:bracket_left]}#{number}#{@symbols[:bracket_right]}"
738
+ @pastel.decorate(content, *@theme[:note])
739
+ end
740
+
741
+ def convert_raw(*)
742
+ warning("Raw content is not supported")
743
+ end
744
+
745
+ # Convert image element
746
+ #
747
+ # @param [Kramdown::Element] element
748
+ # the `kd:img` element
749
+ # @param [Hash] opts
750
+ # the element options
751
+ #
752
+ # @api private
753
+ def convert_img(el, opts)
754
+ src = el.attr["src"]
755
+ alt = el.attr["alt"]
756
+ link = [@symbols[:paren_left]]
757
+ unless alt.to_s.empty?
758
+ link << "#{alt} #{@symbols[:ndash]} "
759
+ end
760
+ link << "#{src}#{@symbols[:paren_right]}"
761
+ @pastel.decorate(link.join, *@theme[:image])
762
+ end
763
+
764
+ # Convert html element
765
+ #
766
+ # @param [Kramdown::Element] element
767
+ # the `kd:html_element` element
768
+ # @param [Hash] opts
769
+ # the element options
770
+ #
771
+ # @api private
772
+ def convert_html_element(el, opts)
773
+ if el.value == "div"
774
+ inner(el, opts)
775
+ elsif %w[i em].include?(el.value)
776
+ convert_em(el, opts)
777
+ elsif %w[b strong].include?(el.value)
778
+ convert_strong(el, opts)
779
+ elsif el.value == "img"
780
+ convert_img(el, opts)
781
+ elsif el.value == "a"
782
+ convert_a(el, opts)
783
+ elsif el.value == "del"
784
+ inner(el, opts).join.chars.to_a.map do |char|
785
+ char + @symbols[:delete]
786
+ end
787
+ elsif el.value == "br"
788
+ NEWLINE
789
+ elsif !el.children.empty?
790
+ inner(el, opts)
791
+ else
792
+ warning("HTML element '#{el.value.inspect}' not supported")
793
+ ""
794
+ end
795
+ end
796
+
797
+ # Convert xml comment element
798
+ #
799
+ # @param [Kramdown::Element] element
800
+ # the `kd:xml_comment` element
801
+ # @param [Hash] opts
802
+ # the element options
803
+ #
804
+ # @api private
805
+ def convert_xml_comment(el, opts)
806
+ block = el.options[:category] == :block
807
+ indent = SPACE * @current_indent
808
+ content = el.value
809
+ content.gsub!(/^<!-{2,}\s*/, "") if content.start_with?("<!--")
810
+ content.gsub!(/-{2,}>$/, "") if content.end_with?("-->")
811
+ result = content.lines.map.with_index do |line, i|
812
+ (i.zero? && !block ? "" : indent) +
813
+ @pastel.decorate("#{@symbols[:hash]} " + line.chomp,
814
+ *@theme[:comment])
815
+ end.join(NEWLINE)
816
+ block ? result + NEWLINE : result
817
+ end
818
+ alias convert_comment convert_xml_comment
819
+ end # Parser
820
+ end # Markdown
821
+ end # TTY