tty-markdown-meinac 0.7.2
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 +7 -0
- data/CHANGELOG.md +157 -0
- data/LICENSE.txt +21 -0
- data/README.md +414 -0
- data/lib/tty/markdown/color.rb +110 -0
- data/lib/tty/markdown/converter.rb +1243 -0
- data/lib/tty/markdown/decorator.rb +82 -0
- data/lib/tty/markdown/error.rb +11 -0
- data/lib/tty/markdown/formatter.rb +39 -0
- data/lib/tty/markdown/highlighter.rb +82 -0
- data/lib/tty/markdown/parser.rb +66 -0
- data/lib/tty/markdown/symbols.rb +304 -0
- data/lib/tty/markdown/theme.rb +159 -0
- data/lib/tty/markdown/version.rb +7 -0
- data/lib/tty/markdown.rb +165 -0
- data/lib/tty-markdown.rb +1 -0
- metadata +191 -0
|
@@ -0,0 +1,1243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "kramdown/converter/base"
|
|
4
|
+
require "pastel"
|
|
5
|
+
require "strings"
|
|
6
|
+
|
|
7
|
+
require_relative "decorator"
|
|
8
|
+
require_relative "highlighter"
|
|
9
|
+
|
|
10
|
+
module TTY
|
|
11
|
+
class Markdown
|
|
12
|
+
# Responsible for converting a Markdown document into terminal output
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
class Converter < ::Kramdown::Converter::Base
|
|
16
|
+
# The alt attribute name
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
21
|
+
ALT_ATTRIBUTE = "alt"
|
|
22
|
+
private_constant :ALT_ATTRIBUTE
|
|
23
|
+
|
|
24
|
+
# The HTML comment delimiters pattern
|
|
25
|
+
#
|
|
26
|
+
# @return [Regexp]
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
COMMENT_DELIMITERS_PATTERN = /^<!-{2,}\s*|-{2,}>$/.freeze
|
|
30
|
+
private_constant :COMMENT_DELIMITERS_PATTERN
|
|
31
|
+
|
|
32
|
+
# The converted HTML elements
|
|
33
|
+
#
|
|
34
|
+
# @return [Array<String>]
|
|
35
|
+
#
|
|
36
|
+
# @api private
|
|
37
|
+
CONVERTED_HTML_ELEMENTS = %w[a b br del em i img strong].freeze
|
|
38
|
+
private_constant :CONVERTED_HTML_ELEMENTS
|
|
39
|
+
|
|
40
|
+
# The empty string
|
|
41
|
+
#
|
|
42
|
+
# @return [String]
|
|
43
|
+
#
|
|
44
|
+
# @api private
|
|
45
|
+
EMPTY = ""
|
|
46
|
+
private_constant :EMPTY
|
|
47
|
+
|
|
48
|
+
# The href attribute name
|
|
49
|
+
#
|
|
50
|
+
# @return [String]
|
|
51
|
+
#
|
|
52
|
+
# @api private
|
|
53
|
+
HREF_ATTRIBUTE = "href"
|
|
54
|
+
private_constant :HREF_ATTRIBUTE
|
|
55
|
+
|
|
56
|
+
# The mailto scheme pattern
|
|
57
|
+
#
|
|
58
|
+
# @return [Regexp]
|
|
59
|
+
#
|
|
60
|
+
# @api private
|
|
61
|
+
MAILTO_SCHEME_PATTERN = /^mailto:/.freeze
|
|
62
|
+
private_constant :MAILTO_SCHEME_PATTERN
|
|
63
|
+
|
|
64
|
+
# The newline character
|
|
65
|
+
#
|
|
66
|
+
# @return [String]
|
|
67
|
+
#
|
|
68
|
+
# @api private
|
|
69
|
+
NEWLINE = "\n"
|
|
70
|
+
private_constant :NEWLINE
|
|
71
|
+
|
|
72
|
+
# The space character
|
|
73
|
+
#
|
|
74
|
+
# @return [String]
|
|
75
|
+
#
|
|
76
|
+
# @api private
|
|
77
|
+
SPACE = " "
|
|
78
|
+
private_constant :SPACE
|
|
79
|
+
|
|
80
|
+
# The src attribute name
|
|
81
|
+
#
|
|
82
|
+
# @return [String]
|
|
83
|
+
#
|
|
84
|
+
# @api private
|
|
85
|
+
SRC_ATTRIBUTE = "src"
|
|
86
|
+
private_constant :SRC_ATTRIBUTE
|
|
87
|
+
|
|
88
|
+
# The title attribute name
|
|
89
|
+
#
|
|
90
|
+
# @return [String]
|
|
91
|
+
#
|
|
92
|
+
# @api private
|
|
93
|
+
TITLE_ATTRIBUTE = "title"
|
|
94
|
+
private_constant :TITLE_ATTRIBUTE
|
|
95
|
+
|
|
96
|
+
# The UTF-8 characters directive
|
|
97
|
+
#
|
|
98
|
+
# @return [String]
|
|
99
|
+
#
|
|
100
|
+
# @api private
|
|
101
|
+
UTF8_CHARACTERS_DIRECTIVE = "U*"
|
|
102
|
+
private_constant :UTF8_CHARACTERS_DIRECTIVE
|
|
103
|
+
|
|
104
|
+
# Create a {TTY::Markdown::Converter} instance
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# converter = TTY::Markdown::Converter.new(document)
|
|
108
|
+
#
|
|
109
|
+
# @param [Kramdown::Element] root
|
|
110
|
+
# the root element
|
|
111
|
+
# @param [Hash] options
|
|
112
|
+
# the root element options
|
|
113
|
+
#
|
|
114
|
+
# @api public
|
|
115
|
+
def initialize(root, options = {})
|
|
116
|
+
super
|
|
117
|
+
pastel = Pastel.new(enabled: options[:enabled])
|
|
118
|
+
@decorator = Decorator.new(pastel, options[:theme])
|
|
119
|
+
@highlighter = Highlighter.new(@decorator, mode: options[:mode])
|
|
120
|
+
@current_indent = 0
|
|
121
|
+
@footnote_number = 1
|
|
122
|
+
@footnotes = {}
|
|
123
|
+
@indent = options[:indent]
|
|
124
|
+
@symbols = options[:symbols]
|
|
125
|
+
@width = options[:width]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Convert an element
|
|
129
|
+
#
|
|
130
|
+
# @example
|
|
131
|
+
# converter.convert(root)
|
|
132
|
+
#
|
|
133
|
+
# @param [Kramdown::Element] element
|
|
134
|
+
# the root element
|
|
135
|
+
# @param [Hash] options
|
|
136
|
+
# the root element options
|
|
137
|
+
#
|
|
138
|
+
# @return [String]
|
|
139
|
+
#
|
|
140
|
+
# @api public
|
|
141
|
+
def convert(element, options = {indent: 0})
|
|
142
|
+
send(:"convert_#{element.type}", element, options)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# The available width without the current indentation
|
|
148
|
+
#
|
|
149
|
+
# @return [Integer]
|
|
150
|
+
#
|
|
151
|
+
# @api private
|
|
152
|
+
def available_width
|
|
153
|
+
@width - @current_indent
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Indent content by the indentation level
|
|
157
|
+
#
|
|
158
|
+
# @param [Integer] indentation_level
|
|
159
|
+
# the indentation level
|
|
160
|
+
#
|
|
161
|
+
# @return [void]
|
|
162
|
+
#
|
|
163
|
+
# @api private
|
|
164
|
+
def indent_by(indentation_level)
|
|
165
|
+
@current_indent = indentation_level * @indent
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# The current space indentation
|
|
169
|
+
#
|
|
170
|
+
# @return [String]
|
|
171
|
+
#
|
|
172
|
+
# @api private
|
|
173
|
+
def indentation
|
|
174
|
+
SPACE * @current_indent
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Invoke a block with indentation
|
|
178
|
+
#
|
|
179
|
+
# @param [Boolean] add_indentation
|
|
180
|
+
# whether to add indentation
|
|
181
|
+
#
|
|
182
|
+
# @return [Object]
|
|
183
|
+
#
|
|
184
|
+
# @api private
|
|
185
|
+
def with_indentation(add_indentation: true)
|
|
186
|
+
@current_indent += @indent if add_indentation
|
|
187
|
+
yield.tap do
|
|
188
|
+
@current_indent -= @indent if add_indentation
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Transform an element children
|
|
193
|
+
#
|
|
194
|
+
# @param [Kramdown::Element] element
|
|
195
|
+
# the element with child elements
|
|
196
|
+
# @param [Hash] options
|
|
197
|
+
# the element options
|
|
198
|
+
#
|
|
199
|
+
# @return [Array<String>]
|
|
200
|
+
#
|
|
201
|
+
# @api private
|
|
202
|
+
def transform_children(element, options)
|
|
203
|
+
element.children.map.with_index do |child_element, child_index|
|
|
204
|
+
child_options = build_child_options(element, child_index)
|
|
205
|
+
convert(child_element, options.merge(child_options))
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Build a child element options
|
|
210
|
+
#
|
|
211
|
+
# @param [Kramdown::Element] element
|
|
212
|
+
# the element with child elements
|
|
213
|
+
# @param [Integer] child_index
|
|
214
|
+
# the child element index
|
|
215
|
+
#
|
|
216
|
+
# @return [Hash]
|
|
217
|
+
#
|
|
218
|
+
# @api private
|
|
219
|
+
def build_child_options(element, child_index)
|
|
220
|
+
{
|
|
221
|
+
index: child_index,
|
|
222
|
+
next: element.children[child_index + 1],
|
|
223
|
+
parent: element,
|
|
224
|
+
prev: child_index > 0 ? element.children[child_index - 1] : nil
|
|
225
|
+
}
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Convert a root element
|
|
229
|
+
#
|
|
230
|
+
# @param [Kramdown::Element] element
|
|
231
|
+
# the root element
|
|
232
|
+
# @param [Hash] options
|
|
233
|
+
# the root element options
|
|
234
|
+
#
|
|
235
|
+
# @return [String]
|
|
236
|
+
#
|
|
237
|
+
# @api private
|
|
238
|
+
def convert_root(element, options)
|
|
239
|
+
content = transform_children(element, options)
|
|
240
|
+
return content.join if @footnotes.empty?
|
|
241
|
+
|
|
242
|
+
content.join + build_footnotes_list(root, options)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Build an ordered list of footnotes
|
|
246
|
+
#
|
|
247
|
+
# @param [Kramdown::Element] root
|
|
248
|
+
# the root element
|
|
249
|
+
# @param [Hash] options
|
|
250
|
+
# the root element options
|
|
251
|
+
#
|
|
252
|
+
# @return [String]
|
|
253
|
+
#
|
|
254
|
+
# @api private
|
|
255
|
+
def build_footnotes_list(root, options)
|
|
256
|
+
ol = Kramdown::Element.new(:ol)
|
|
257
|
+
@footnotes.each_value do |footnote|
|
|
258
|
+
value, index = *footnote
|
|
259
|
+
li_options = {index: index, parent: ol}.merge(options)
|
|
260
|
+
li = Kramdown::Element.new(:li, nil, {}, li_options)
|
|
261
|
+
li.children = Marshal.load(Marshal.dump(value.children))
|
|
262
|
+
ol.children << li
|
|
263
|
+
end
|
|
264
|
+
convert_ol(ol, {parent: root}.merge(options))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Convert a header element
|
|
268
|
+
#
|
|
269
|
+
# @param [Kramdown::Element] element
|
|
270
|
+
# the header element
|
|
271
|
+
# @param [Hash] options
|
|
272
|
+
# the header element options
|
|
273
|
+
#
|
|
274
|
+
# @return [Array<String>]
|
|
275
|
+
#
|
|
276
|
+
# @api private
|
|
277
|
+
def convert_header(element, options)
|
|
278
|
+
level = element.options[:level]
|
|
279
|
+
indent_content = options[:parent].type == :root
|
|
280
|
+
indent_by(level - 1) if indent_content
|
|
281
|
+
theme = :"h#{level}"
|
|
282
|
+
content = transform_children(element, options)
|
|
283
|
+
content.join.lines.map do |line|
|
|
284
|
+
"#{indentation}#{@decorator.decorate(line.chomp, theme)}#{NEWLINE}"
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Convert a paragraph element
|
|
289
|
+
#
|
|
290
|
+
# @param [Kramdown::Element] element
|
|
291
|
+
# the p element
|
|
292
|
+
# @param [Hash] options
|
|
293
|
+
# the p element options
|
|
294
|
+
#
|
|
295
|
+
# @return [Array<String>]
|
|
296
|
+
#
|
|
297
|
+
# @api private
|
|
298
|
+
def convert_p(element, options)
|
|
299
|
+
parent_type = options[:parent].type
|
|
300
|
+
blockquote_parent = parent_type == :blockquote
|
|
301
|
+
li_parent = parent_type == :li
|
|
302
|
+
content = transform_children(element, options).join
|
|
303
|
+
return "#{content}#{NEWLINE}" if blockquote_parent
|
|
304
|
+
|
|
305
|
+
content.lines.map.with_index do |line, line_index|
|
|
306
|
+
"#{indentation unless line_index.zero? && li_parent}#{line}"
|
|
307
|
+
end.join + NEWLINE
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Convert a text element
|
|
311
|
+
#
|
|
312
|
+
# @param [Kramdown::Element] element
|
|
313
|
+
# the text element
|
|
314
|
+
# @param [Hash] options
|
|
315
|
+
# the text element options
|
|
316
|
+
#
|
|
317
|
+
# @return [String]
|
|
318
|
+
#
|
|
319
|
+
# @api private
|
|
320
|
+
def convert_text(element, options)
|
|
321
|
+
text = Strings.wrap(element.value, available_width)
|
|
322
|
+
options[:strip] ? text.chomp : text
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Convert a deleted element
|
|
326
|
+
#
|
|
327
|
+
# @param [Kramdown::Element] element
|
|
328
|
+
# the html element
|
|
329
|
+
# @param [Hash] options
|
|
330
|
+
# the html element options
|
|
331
|
+
#
|
|
332
|
+
# @return [Array<String>]
|
|
333
|
+
#
|
|
334
|
+
# @api private
|
|
335
|
+
def convert_del(element, options)
|
|
336
|
+
content = transform_children(element, options).join
|
|
337
|
+
@decorator.decorate_each_line(content, :delete)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Convert a strong element
|
|
341
|
+
#
|
|
342
|
+
# @param [Kramdown::Element] element
|
|
343
|
+
# the strong element
|
|
344
|
+
# @param [Hash] options
|
|
345
|
+
# the strong element options
|
|
346
|
+
#
|
|
347
|
+
# @return [String]
|
|
348
|
+
#
|
|
349
|
+
# @api private
|
|
350
|
+
def convert_strong(element, options)
|
|
351
|
+
content = transform_children(element, options).join
|
|
352
|
+
@decorator.decorate_each_line(content, :strong)
|
|
353
|
+
end
|
|
354
|
+
alias convert_b convert_strong
|
|
355
|
+
|
|
356
|
+
# Convert an emphasis element
|
|
357
|
+
#
|
|
358
|
+
# @param [Kramdown::Element] element
|
|
359
|
+
# the em element
|
|
360
|
+
# @param [Hash] options
|
|
361
|
+
# the em element options
|
|
362
|
+
#
|
|
363
|
+
# @return [String]
|
|
364
|
+
#
|
|
365
|
+
# @api private
|
|
366
|
+
def convert_em(element, options)
|
|
367
|
+
content = transform_children(element, options).join
|
|
368
|
+
@decorator.decorate_each_line(content, :em)
|
|
369
|
+
end
|
|
370
|
+
alias convert_i convert_em
|
|
371
|
+
|
|
372
|
+
# Convert a blank element
|
|
373
|
+
#
|
|
374
|
+
# @return [String]
|
|
375
|
+
#
|
|
376
|
+
# @api private
|
|
377
|
+
def convert_blank(*)
|
|
378
|
+
NEWLINE
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Convert a smart quote element
|
|
382
|
+
#
|
|
383
|
+
# @param [Kramdown::Element] element
|
|
384
|
+
# the smart quote element
|
|
385
|
+
# @param [Hash] options
|
|
386
|
+
# the smart quote element options
|
|
387
|
+
#
|
|
388
|
+
# @return [String]
|
|
389
|
+
#
|
|
390
|
+
# @api private
|
|
391
|
+
def convert_smart_quote(element, options)
|
|
392
|
+
@symbols[element.value]
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Convert a codespan element
|
|
396
|
+
#
|
|
397
|
+
# @param [Kramdown::Element] element
|
|
398
|
+
# the codespan element
|
|
399
|
+
# @param [Hash] options
|
|
400
|
+
# the codespan element options
|
|
401
|
+
#
|
|
402
|
+
# @return [String]
|
|
403
|
+
#
|
|
404
|
+
# @api private
|
|
405
|
+
def convert_codespan(element, options)
|
|
406
|
+
code = Strings.wrap(element.value, available_width)
|
|
407
|
+
language = element.options[:lang]
|
|
408
|
+
highlighted = @highlighter.highlight(code, language)
|
|
409
|
+
highlighted.lines.map.with_index do |line, line_index|
|
|
410
|
+
"#{indentation unless line_index.zero?}#{line.chomp}"
|
|
411
|
+
end.join(NEWLINE)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Convert a codeblock element
|
|
415
|
+
#
|
|
416
|
+
# @param [Kramdown::Element] element
|
|
417
|
+
# the codeblock element
|
|
418
|
+
# @param [Hash] options
|
|
419
|
+
# the codeblock element options
|
|
420
|
+
#
|
|
421
|
+
# @return [String]
|
|
422
|
+
#
|
|
423
|
+
# @api private
|
|
424
|
+
def convert_codeblock(element, options)
|
|
425
|
+
"#{indentation}#{convert_codespan(element, options)}#{NEWLINE}"
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Convert a blockquote element
|
|
429
|
+
#
|
|
430
|
+
# @param [Kramdown::Element] element
|
|
431
|
+
# the blockquote element
|
|
432
|
+
# @param [Hash] options
|
|
433
|
+
# the blockquote element options
|
|
434
|
+
#
|
|
435
|
+
# @return [Array<String>]
|
|
436
|
+
#
|
|
437
|
+
# @api private
|
|
438
|
+
def convert_blockquote(element, options)
|
|
439
|
+
bar = @decorator.decorate(@symbols[:bar], :quote)
|
|
440
|
+
prefix = "#{indentation}#{bar} "
|
|
441
|
+
content = transform_children(element, options)
|
|
442
|
+
content.join.lines.map do |line|
|
|
443
|
+
"#{prefix}#{line}"
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Convert a description, ordered or unordered list element
|
|
448
|
+
#
|
|
449
|
+
# @param [Kramdown::Element] element
|
|
450
|
+
# the dl, ol or ul element
|
|
451
|
+
# @param [Hash] options
|
|
452
|
+
# the dl, ol or ul element options
|
|
453
|
+
#
|
|
454
|
+
# @return [String]
|
|
455
|
+
#
|
|
456
|
+
# @api private
|
|
457
|
+
def convert_ul(element, options)
|
|
458
|
+
indent_content = options[:parent].type != :root
|
|
459
|
+
content = with_indentation(add_indentation: indent_content) do
|
|
460
|
+
transform_children(element, options)
|
|
461
|
+
end
|
|
462
|
+
content.join
|
|
463
|
+
end
|
|
464
|
+
alias convert_ol convert_ul
|
|
465
|
+
alias convert_dl convert_ul
|
|
466
|
+
|
|
467
|
+
# Convert a list item element
|
|
468
|
+
#
|
|
469
|
+
# @param [Kramdown::Element] element
|
|
470
|
+
# the li element
|
|
471
|
+
# @param [Hash] options
|
|
472
|
+
# the li element options
|
|
473
|
+
#
|
|
474
|
+
# @return [String]
|
|
475
|
+
#
|
|
476
|
+
# @api private
|
|
477
|
+
def convert_li(element, options)
|
|
478
|
+
index = options[:index] + 1
|
|
479
|
+
parent_type = options[:parent].type
|
|
480
|
+
prefix_type = parent_type == :ol ? "#{index}." : @symbols[:bullet]
|
|
481
|
+
prefix = "#{@decorator.decorate(prefix_type, :list)} "
|
|
482
|
+
options[:strip] = true
|
|
483
|
+
content = transform_children(element, options)
|
|
484
|
+
"#{indentation}#{prefix}#{content.join}"
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Convert a description term element
|
|
488
|
+
#
|
|
489
|
+
# @param [Kramdown::Element] element
|
|
490
|
+
# the dt element
|
|
491
|
+
# @param [Hash] options
|
|
492
|
+
# the dt element options
|
|
493
|
+
#
|
|
494
|
+
# @return [String]
|
|
495
|
+
#
|
|
496
|
+
# @api private
|
|
497
|
+
def convert_dt(element, options)
|
|
498
|
+
content = transform_children(element, options)
|
|
499
|
+
"#{indentation}#{content.join}#{NEWLINE}"
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# Convert a description details element
|
|
503
|
+
#
|
|
504
|
+
# @param [Kramdown::Element] element
|
|
505
|
+
# the dd element
|
|
506
|
+
# @param [Hash] options
|
|
507
|
+
# the dd element options
|
|
508
|
+
#
|
|
509
|
+
# @return [Array<String>]
|
|
510
|
+
#
|
|
511
|
+
# @api private
|
|
512
|
+
def convert_dd(element, options)
|
|
513
|
+
next_type = options[:next] && options[:next].type
|
|
514
|
+
suffix = next_type == :dt ? NEWLINE : EMPTY
|
|
515
|
+
content = with_indentation do
|
|
516
|
+
transform_children(element, options)
|
|
517
|
+
end
|
|
518
|
+
"#{content.join}#{suffix}"
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Convert a table element
|
|
522
|
+
#
|
|
523
|
+
# @param [Kramdown::Element] element
|
|
524
|
+
# the table element
|
|
525
|
+
# @param [Hash] options
|
|
526
|
+
# the table element options
|
|
527
|
+
#
|
|
528
|
+
# @return [String]
|
|
529
|
+
#
|
|
530
|
+
# @api private
|
|
531
|
+
def convert_table(element, options)
|
|
532
|
+
initialize_table
|
|
533
|
+
column_alignments = element.options[:alignment]
|
|
534
|
+
table_data = extract_table_data(element, options)
|
|
535
|
+
table_options = build_table_options(table_data, column_alignments)
|
|
536
|
+
transform_children(element, options.merge(table_options)).join
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Initialise a table
|
|
540
|
+
#
|
|
541
|
+
# @return [void]
|
|
542
|
+
#
|
|
543
|
+
# @api private
|
|
544
|
+
def initialize_table
|
|
545
|
+
@column = 0
|
|
546
|
+
@row = 0
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Extract the table data
|
|
550
|
+
#
|
|
551
|
+
# @param [Kramdown::Element] element
|
|
552
|
+
# the table element
|
|
553
|
+
# @param [Hash] options
|
|
554
|
+
# the table element options
|
|
555
|
+
#
|
|
556
|
+
# @return [Array<Array<String>>]
|
|
557
|
+
#
|
|
558
|
+
# @api private
|
|
559
|
+
def extract_table_data(element, options)
|
|
560
|
+
element.children.each_with_object([]) do |child_element, data|
|
|
561
|
+
child_element.children.each do |row|
|
|
562
|
+
data << row.children.map do |cell|
|
|
563
|
+
transform_children(cell, options)
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Build a table element options
|
|
570
|
+
#
|
|
571
|
+
# @param [Array<Array<String>>] table_data
|
|
572
|
+
# the table data
|
|
573
|
+
# @param [Array<Symbol>] column_alignments
|
|
574
|
+
# the table column alignments
|
|
575
|
+
#
|
|
576
|
+
# @return [Hash]
|
|
577
|
+
#
|
|
578
|
+
# @api private
|
|
579
|
+
def build_table_options(table_data, column_alignments)
|
|
580
|
+
max_column_widths = calculate_max_column_widths(table_data)
|
|
581
|
+
column_widths = distribute_column_widths(max_column_widths)
|
|
582
|
+
row_heights = calculate_max_row_heights(table_data, column_widths)
|
|
583
|
+
{
|
|
584
|
+
column_alignments: column_alignments,
|
|
585
|
+
column_widths: column_widths,
|
|
586
|
+
row_heights: row_heights,
|
|
587
|
+
table_data: table_data
|
|
588
|
+
}
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Distribute column widths within the total width
|
|
592
|
+
#
|
|
593
|
+
# @param [Array<Integer>] column_widths
|
|
594
|
+
# the table column widths
|
|
595
|
+
#
|
|
596
|
+
# @return [Array<Integer>]
|
|
597
|
+
#
|
|
598
|
+
# @api private
|
|
599
|
+
def distribute_column_widths(column_widths)
|
|
600
|
+
borders_width = (column_widths.size + 1)
|
|
601
|
+
indentation_width = (indentation.length + 1) * 2
|
|
602
|
+
screen_width = @width - borders_width - indentation_width
|
|
603
|
+
total_width = column_widths.reduce(&:+)
|
|
604
|
+
return column_widths if total_width <= screen_width
|
|
605
|
+
|
|
606
|
+
reduce_column_widths(column_widths, total_width, screen_width)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# Reduce column widths to the screen width
|
|
610
|
+
#
|
|
611
|
+
# @param [Array<Integer>] column_widths
|
|
612
|
+
# the table column widths
|
|
613
|
+
# @param [Integer] total_width
|
|
614
|
+
# the total width of the table columns
|
|
615
|
+
# @param [Integer] screen_width
|
|
616
|
+
# the screen width
|
|
617
|
+
#
|
|
618
|
+
# @return [Array<Integer>]
|
|
619
|
+
#
|
|
620
|
+
# @api private
|
|
621
|
+
def reduce_column_widths(column_widths, total_width, screen_width)
|
|
622
|
+
extra_width = total_width - screen_width
|
|
623
|
+
column_widths.map do |column_width|
|
|
624
|
+
ratio = column_width / total_width.to_f
|
|
625
|
+
column_width - (extra_width * ratio).floor
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Calculate maximum widths for every column
|
|
630
|
+
#
|
|
631
|
+
# @param [Array<Array<String>>] table_data
|
|
632
|
+
# the table data
|
|
633
|
+
#
|
|
634
|
+
# @return [Array<Integer>]
|
|
635
|
+
#
|
|
636
|
+
# @api private
|
|
637
|
+
def calculate_max_column_widths(table_data)
|
|
638
|
+
table_data.first.map.with_index do |_, column_index|
|
|
639
|
+
calculate_max_column_width(table_data, column_index)
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Calculate the maximum table cell width for a given column index
|
|
644
|
+
#
|
|
645
|
+
# @param [Array<Array<String>>] table_data
|
|
646
|
+
# the table data
|
|
647
|
+
# @param [Integer] column_index
|
|
648
|
+
# the table column index
|
|
649
|
+
#
|
|
650
|
+
# @return [Integer]
|
|
651
|
+
#
|
|
652
|
+
# @api private
|
|
653
|
+
def calculate_max_column_width(table_data, column_index)
|
|
654
|
+
table_data.map do |row|
|
|
655
|
+
Strings.sanitize(row[column_index].join).lines.map(&:length).max || 0
|
|
656
|
+
end.max
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# Calculate maximum heights for every row
|
|
660
|
+
#
|
|
661
|
+
# @param [Array<Array<String>>] table_data
|
|
662
|
+
# the table data
|
|
663
|
+
# @param [Array<Integer>] column_widths
|
|
664
|
+
# the table column widths
|
|
665
|
+
#
|
|
666
|
+
# @return [Array<Integer>]
|
|
667
|
+
#
|
|
668
|
+
# @api private
|
|
669
|
+
def calculate_max_row_heights(table_data, column_widths)
|
|
670
|
+
table_data.map do |row|
|
|
671
|
+
calculate_max_row_height(row, column_widths)
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# Calculate the maximum table cell height for a given row
|
|
676
|
+
#
|
|
677
|
+
# @param [Array<Array<String>>] row
|
|
678
|
+
# the table row
|
|
679
|
+
# @param [Array<Integer>] column_widths
|
|
680
|
+
# the table column widths
|
|
681
|
+
#
|
|
682
|
+
# @return [Integer]
|
|
683
|
+
#
|
|
684
|
+
# @api private
|
|
685
|
+
def calculate_max_row_height(row, column_widths)
|
|
686
|
+
row.map.with_index do |cell, column_index|
|
|
687
|
+
Strings.wrap(cell.join, column_widths[column_index]).lines.size
|
|
688
|
+
end.max
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# Convert a table head element
|
|
692
|
+
#
|
|
693
|
+
# @param [Kramdown::Element] element
|
|
694
|
+
# the thead element
|
|
695
|
+
# @param [Hash] options
|
|
696
|
+
# the thead element options
|
|
697
|
+
#
|
|
698
|
+
# @return [String]
|
|
699
|
+
#
|
|
700
|
+
# @api private
|
|
701
|
+
def convert_thead(element, options)
|
|
702
|
+
top_border = build_border(:top, options[:column_widths])
|
|
703
|
+
content = transform_children(element, options)
|
|
704
|
+
"#{indentation}#{top_border}#{NEWLINE}#{content.join}"
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# Build a horizontal border line
|
|
708
|
+
#
|
|
709
|
+
# @param [Symbol] location
|
|
710
|
+
# the location out of :bottom, :mid or :top
|
|
711
|
+
# @param [Array<Integer>] column_widths
|
|
712
|
+
# the table column widths
|
|
713
|
+
#
|
|
714
|
+
# @return [String]
|
|
715
|
+
#
|
|
716
|
+
# @api private
|
|
717
|
+
def build_border(location, column_widths)
|
|
718
|
+
border = [@symbols[:"#{location}_left"]]
|
|
719
|
+
column_widths.each.with_index do |column_width, column_index|
|
|
720
|
+
border << @symbols[:"#{location}_center"] unless column_index.zero?
|
|
721
|
+
border << (@symbols[:line] * (column_width + 2))
|
|
722
|
+
end
|
|
723
|
+
border << @symbols[:"#{location}_right"]
|
|
724
|
+
@decorator.decorate(border.join, :table)
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
# Convert a table body element
|
|
728
|
+
#
|
|
729
|
+
# @param [Kramdown::Element] element
|
|
730
|
+
# the tbody element
|
|
731
|
+
# @param [Hash] options
|
|
732
|
+
# the tbody element options
|
|
733
|
+
#
|
|
734
|
+
# @return [String]
|
|
735
|
+
#
|
|
736
|
+
# @api private
|
|
737
|
+
def convert_tbody(element, options)
|
|
738
|
+
column_widths = options[:column_widths]
|
|
739
|
+
next_type = options[:next] && options[:next].type
|
|
740
|
+
prev_type = options[:prev] && options[:prev].type
|
|
741
|
+
top_border_type = prev_type == :thead ? :mid : :top
|
|
742
|
+
top_border = build_border(top_border_type, column_widths)
|
|
743
|
+
bottom_border_type = next_type == :tfoot ? :mid : :bottom
|
|
744
|
+
bottom_border = build_border(bottom_border_type, column_widths)
|
|
745
|
+
content = transform_children(element, options)
|
|
746
|
+
"#{indentation}#{top_border}#{NEWLINE}#{content.join}" \
|
|
747
|
+
"#{indentation}#{bottom_border}#{NEWLINE}"
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# Convert a table foot element
|
|
751
|
+
#
|
|
752
|
+
# @param [Kramdown::Element] element
|
|
753
|
+
# the tfoot element
|
|
754
|
+
# @param [Hash] options
|
|
755
|
+
# the tfoot element options
|
|
756
|
+
#
|
|
757
|
+
# @return [String]
|
|
758
|
+
#
|
|
759
|
+
# @api private
|
|
760
|
+
def convert_tfoot(element, options)
|
|
761
|
+
bottom_border = build_border(:bottom, options[:column_widths])
|
|
762
|
+
content = transform_children(element, options)
|
|
763
|
+
"#{content.join}#{indentation}#{bottom_border}#{NEWLINE}"
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Convert a table row element
|
|
767
|
+
#
|
|
768
|
+
# @param [Kramdown::Element] element
|
|
769
|
+
# the tr element
|
|
770
|
+
# @param [Hash] options
|
|
771
|
+
# the tr element options
|
|
772
|
+
#
|
|
773
|
+
# @return [String]
|
|
774
|
+
#
|
|
775
|
+
# @api private
|
|
776
|
+
def convert_tr(element, options)
|
|
777
|
+
add_border = options[:prev] && options[:prev].type == :tr
|
|
778
|
+
border = add_border ? build_row_border(options[:column_widths]) : EMPTY
|
|
779
|
+
content = transform_children(element, options)
|
|
780
|
+
move_to_next_row
|
|
781
|
+
"#{border}#{format_table_row(content)}"
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
# Build a table row border
|
|
785
|
+
#
|
|
786
|
+
# @param [Array<Integer>] column_widths
|
|
787
|
+
# the table column widths
|
|
788
|
+
#
|
|
789
|
+
# @return [String]
|
|
790
|
+
#
|
|
791
|
+
# @api private
|
|
792
|
+
def build_row_border(column_widths)
|
|
793
|
+
"#{indentation}#{build_border(:mid, column_widths)}#{NEWLINE}"
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
# Move to the next table row
|
|
797
|
+
#
|
|
798
|
+
# @return [void]
|
|
799
|
+
#
|
|
800
|
+
# @api private
|
|
801
|
+
def move_to_next_row
|
|
802
|
+
@row += 1
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
# Format a table row
|
|
806
|
+
#
|
|
807
|
+
# @param [String] content
|
|
808
|
+
# the content to format
|
|
809
|
+
#
|
|
810
|
+
# @return [String]
|
|
811
|
+
#
|
|
812
|
+
# @api private
|
|
813
|
+
def format_table_row(content)
|
|
814
|
+
number_of_columns = content.size
|
|
815
|
+
last_column_index = number_of_columns - 1
|
|
816
|
+
content.each_with_object([]).with_index do |(cell, row), column_index|
|
|
817
|
+
append_newline = column_index == last_column_index
|
|
818
|
+
insert_cell_into_row(cell, row, append_newline)
|
|
819
|
+
end.join
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
# Insert a cell into a table row
|
|
823
|
+
#
|
|
824
|
+
# @param [Array<String>] cell
|
|
825
|
+
# the cell to insert
|
|
826
|
+
# @param [Array] row
|
|
827
|
+
# the row to insert into
|
|
828
|
+
# @param [Boolean] append_newline
|
|
829
|
+
# whether to append a newline
|
|
830
|
+
#
|
|
831
|
+
# @return [void]
|
|
832
|
+
#
|
|
833
|
+
# @api private
|
|
834
|
+
def insert_cell_into_row(cell, row, append_newline)
|
|
835
|
+
cell.each_with_index do |cell_line, cell_line_index|
|
|
836
|
+
(row[cell_line_index] ||= []) << cell_line.chomp
|
|
837
|
+
row[cell_line_index] << NEWLINE if append_newline
|
|
838
|
+
end
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
# Convert a table data element
|
|
842
|
+
#
|
|
843
|
+
# @param [Kramdown::Element] element
|
|
844
|
+
# the td element
|
|
845
|
+
# @param [Hash] options
|
|
846
|
+
# the td element options
|
|
847
|
+
#
|
|
848
|
+
# @return [Array<String>]
|
|
849
|
+
#
|
|
850
|
+
# @api private
|
|
851
|
+
def convert_td(element, options)
|
|
852
|
+
add_indentation = @column.zero?
|
|
853
|
+
cell_content = transform_children(element, options).join
|
|
854
|
+
formatted_cell = format_table_cell(cell_content, options)
|
|
855
|
+
number_of_columns = options[:column_widths].size
|
|
856
|
+
cycle_to_next_column(number_of_columns)
|
|
857
|
+
decorate_table_cell(formatted_cell, add_indentation)
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
# Cycle to the next table column
|
|
861
|
+
#
|
|
862
|
+
# @param [Integer] number_of_columns
|
|
863
|
+
# the number of table columns
|
|
864
|
+
#
|
|
865
|
+
# @return [void]
|
|
866
|
+
#
|
|
867
|
+
# @api private
|
|
868
|
+
def cycle_to_next_column(number_of_columns)
|
|
869
|
+
@column = (@column + 1) % number_of_columns
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# Format a table cell
|
|
873
|
+
#
|
|
874
|
+
# @param [String] content
|
|
875
|
+
# the content to format
|
|
876
|
+
# @param [Hash] options
|
|
877
|
+
# the element options
|
|
878
|
+
#
|
|
879
|
+
# @return [String]
|
|
880
|
+
#
|
|
881
|
+
# @api private
|
|
882
|
+
def format_table_cell(content, options)
|
|
883
|
+
alignment = options[:column_alignments][@column]
|
|
884
|
+
align_options = alignment == :default ? {} : {direction: alignment}
|
|
885
|
+
cell_height = options[:row_heights][@row]
|
|
886
|
+
cell_width = options[:column_widths][@column]
|
|
887
|
+
wrapped = Strings.wrap(content, cell_width)
|
|
888
|
+
aligned = Strings.align(wrapped, cell_width, **align_options)
|
|
889
|
+
return aligned if aligned.lines.size == cell_height
|
|
890
|
+
|
|
891
|
+
Strings.pad(aligned, [0, 0, cell_height - aligned.lines.size, 0])
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
# Decorate a table cell
|
|
895
|
+
#
|
|
896
|
+
# @param [String] content
|
|
897
|
+
# the content to decorate
|
|
898
|
+
# @param [Boolean] add_indentation
|
|
899
|
+
# whether to add indentation
|
|
900
|
+
#
|
|
901
|
+
# @return [Array<String>]
|
|
902
|
+
#
|
|
903
|
+
# @api private
|
|
904
|
+
def decorate_table_cell(content, add_indentation)
|
|
905
|
+
pipe = @decorator.decorate(@symbols[:pipe], :table)
|
|
906
|
+
prefix = add_indentation ? "#{indentation}#{pipe} " : EMPTY
|
|
907
|
+
suffix = " #{pipe} "
|
|
908
|
+
content.lines.map do |line|
|
|
909
|
+
suffix_insert_index = line.end_with?(NEWLINE) ? -2 : -1
|
|
910
|
+
"#{prefix}#{line.insert(suffix_insert_index, suffix)}"
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
# Convert a line break element
|
|
915
|
+
#
|
|
916
|
+
# @param [Kramdown::Element] element
|
|
917
|
+
# the br element
|
|
918
|
+
# @param [Hash] options
|
|
919
|
+
# the br element options
|
|
920
|
+
#
|
|
921
|
+
# @return [String]
|
|
922
|
+
#
|
|
923
|
+
# @api private
|
|
924
|
+
def convert_br(element, options)
|
|
925
|
+
NEWLINE
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
# Convert a horizontal rule element
|
|
929
|
+
#
|
|
930
|
+
# @param [Kramdown::Element] element
|
|
931
|
+
# the hr element
|
|
932
|
+
# @param [Hash] options
|
|
933
|
+
# the hr element options
|
|
934
|
+
#
|
|
935
|
+
# @return [String]
|
|
936
|
+
#
|
|
937
|
+
# @api private
|
|
938
|
+
def convert_hr(element, options)
|
|
939
|
+
inner_line_width = @width - (@symbols[:diamond].length * 2)
|
|
940
|
+
inner_line = @symbols[:line] * inner_line_width
|
|
941
|
+
line = "#{@symbols[:diamond]}#{inner_line}#{@symbols[:diamond]}"
|
|
942
|
+
"#{@decorator.decorate(line, :hr)}#{NEWLINE}"
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
# Convert an anchor element
|
|
946
|
+
#
|
|
947
|
+
# @param [Kramdown::Element] element
|
|
948
|
+
# the a element
|
|
949
|
+
# @param [Hash] options
|
|
950
|
+
# the a element options
|
|
951
|
+
#
|
|
952
|
+
# @return [Array<String>]
|
|
953
|
+
#
|
|
954
|
+
# @api private
|
|
955
|
+
def convert_a(element, options)
|
|
956
|
+
content = transform_children(element, options).join
|
|
957
|
+
return [] if content.strip.empty?
|
|
958
|
+
|
|
959
|
+
href = strip_mailto_scheme(element.attr[HREF_ATTRIBUTE])
|
|
960
|
+
title = element.attr[TITLE_ATTRIBUTE].to_s
|
|
961
|
+
build_link(content, href, title)
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
# Strip the mailto scheme from the href attribute
|
|
965
|
+
#
|
|
966
|
+
# @param [String] href
|
|
967
|
+
# the href attribute
|
|
968
|
+
#
|
|
969
|
+
# @return [String]
|
|
970
|
+
#
|
|
971
|
+
# @api private
|
|
972
|
+
def strip_mailto_scheme(href)
|
|
973
|
+
href.sub(MAILTO_SCHEME_PATTERN, EMPTY)
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
# Build a link
|
|
977
|
+
#
|
|
978
|
+
# @param [String] content
|
|
979
|
+
# the link content
|
|
980
|
+
# @param [String] href
|
|
981
|
+
# the link href attribute
|
|
982
|
+
# @param [String] title
|
|
983
|
+
# the link title attribute
|
|
984
|
+
#
|
|
985
|
+
# @return [Array<String>]
|
|
986
|
+
#
|
|
987
|
+
# @api private
|
|
988
|
+
def build_link(content, href, title)
|
|
989
|
+
link = []
|
|
990
|
+
link << "#{content} #{@symbols[:arrow]} " if content != href
|
|
991
|
+
link << "(#{title}) " unless title.strip.empty?
|
|
992
|
+
link << @decorator.decorate(href, :link)
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
# Convert a math element
|
|
996
|
+
#
|
|
997
|
+
# @param [Kramdown::Element] element
|
|
998
|
+
# the math element
|
|
999
|
+
# @param [Hash] options
|
|
1000
|
+
# the math element options
|
|
1001
|
+
#
|
|
1002
|
+
# @return [String]
|
|
1003
|
+
#
|
|
1004
|
+
# @api private
|
|
1005
|
+
def convert_math(element, options)
|
|
1006
|
+
if element.options[:category] == :block
|
|
1007
|
+
convert_codeblock(element, options)
|
|
1008
|
+
else
|
|
1009
|
+
convert_codespan(element, options)
|
|
1010
|
+
end
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
# Convert an abbreviation element
|
|
1014
|
+
#
|
|
1015
|
+
# @param [Kramdown::Element] element
|
|
1016
|
+
# the abbreviation element
|
|
1017
|
+
# @param [Hash] options
|
|
1018
|
+
# the abbreviation element options
|
|
1019
|
+
#
|
|
1020
|
+
# @return [String]
|
|
1021
|
+
#
|
|
1022
|
+
# @api private
|
|
1023
|
+
def convert_abbreviation(element, options)
|
|
1024
|
+
title = @root.options[:abbrev_defs][element.value]
|
|
1025
|
+
if title.to_s.empty?
|
|
1026
|
+
element.value
|
|
1027
|
+
else
|
|
1028
|
+
"#{element.value}(#{title})"
|
|
1029
|
+
end
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
# Convert a typographic symbol element
|
|
1033
|
+
#
|
|
1034
|
+
# @param [Kramdown::Element] element
|
|
1035
|
+
# the typographic sym element
|
|
1036
|
+
# @param [Hash] options
|
|
1037
|
+
# the typographic sym element options
|
|
1038
|
+
#
|
|
1039
|
+
# @return [String]
|
|
1040
|
+
#
|
|
1041
|
+
# @api private
|
|
1042
|
+
def convert_typographic_sym(element, options)
|
|
1043
|
+
@symbols[element.value]
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
# Convert an entity element
|
|
1047
|
+
#
|
|
1048
|
+
# @param [Kramdown::Element] element
|
|
1049
|
+
# the entity element
|
|
1050
|
+
# @param [Hash] options
|
|
1051
|
+
# the entity element options
|
|
1052
|
+
#
|
|
1053
|
+
# @return [String]
|
|
1054
|
+
#
|
|
1055
|
+
# @api private
|
|
1056
|
+
def convert_entity(element, options)
|
|
1057
|
+
transform_codepoint(element.value.code_point)
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
# Transform a codepoint into a UTF-8 character
|
|
1061
|
+
#
|
|
1062
|
+
# @param [Integer] codepoint
|
|
1063
|
+
# the codepoint to transform
|
|
1064
|
+
#
|
|
1065
|
+
# @return [String]
|
|
1066
|
+
#
|
|
1067
|
+
# @api private
|
|
1068
|
+
def transform_codepoint(codepoint)
|
|
1069
|
+
[codepoint].pack(UTF8_CHARACTERS_DIRECTIVE)
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
# Convert a footnote element
|
|
1073
|
+
#
|
|
1074
|
+
# @param [Kramdown::Element] element
|
|
1075
|
+
# the footnote element
|
|
1076
|
+
# @param [Hash] options
|
|
1077
|
+
# the footnote element options
|
|
1078
|
+
#
|
|
1079
|
+
# @return [String]
|
|
1080
|
+
#
|
|
1081
|
+
# @api private
|
|
1082
|
+
def convert_footnote(element, options)
|
|
1083
|
+
name = element.options[:name]
|
|
1084
|
+
content = element.value
|
|
1085
|
+
footnote = fetch_or_add_footnote(name, content)
|
|
1086
|
+
number = footnote.last
|
|
1087
|
+
@decorator.decorate(@symbols.wrap_in_brackets(number), :note)
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
# Fetch or add a footnote
|
|
1091
|
+
#
|
|
1092
|
+
# @param [String] name
|
|
1093
|
+
# the footnote name
|
|
1094
|
+
# @param [String] content
|
|
1095
|
+
# the footnote content
|
|
1096
|
+
#
|
|
1097
|
+
# @return [Array<Integer, String>]
|
|
1098
|
+
#
|
|
1099
|
+
# @api private
|
|
1100
|
+
def fetch_or_add_footnote(name, content)
|
|
1101
|
+
@footnotes.fetch(name) do
|
|
1102
|
+
add_footnote(name, content).tap do
|
|
1103
|
+
increment_footnote_number
|
|
1104
|
+
end
|
|
1105
|
+
end
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
# Add a footnote
|
|
1109
|
+
#
|
|
1110
|
+
# @param [String] name
|
|
1111
|
+
# the footnote name
|
|
1112
|
+
# @param [String] content
|
|
1113
|
+
# the footnote content
|
|
1114
|
+
#
|
|
1115
|
+
# @return [Array<Integer, String>]
|
|
1116
|
+
#
|
|
1117
|
+
# @api private
|
|
1118
|
+
def add_footnote(name, content)
|
|
1119
|
+
@footnotes[name] = [content, @footnote_number]
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
# Increment a footnote number
|
|
1123
|
+
#
|
|
1124
|
+
# @return [Integer]
|
|
1125
|
+
#
|
|
1126
|
+
# @api private
|
|
1127
|
+
def increment_footnote_number
|
|
1128
|
+
@footnote_number += 1
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
# Convert a raw element
|
|
1132
|
+
#
|
|
1133
|
+
# @return [String]
|
|
1134
|
+
#
|
|
1135
|
+
# @api private
|
|
1136
|
+
def convert_raw(*)
|
|
1137
|
+
warning("Raw content is not supported")
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1140
|
+
# Convert an image element
|
|
1141
|
+
#
|
|
1142
|
+
# @param [Kramdown::Element] element
|
|
1143
|
+
# the img element
|
|
1144
|
+
# @param [Hash] options
|
|
1145
|
+
# the img element options
|
|
1146
|
+
#
|
|
1147
|
+
# @return [String]
|
|
1148
|
+
#
|
|
1149
|
+
# @api private
|
|
1150
|
+
def convert_img(element, options)
|
|
1151
|
+
alt = element.attr[ALT_ATTRIBUTE].to_s
|
|
1152
|
+
src = element.attr[SRC_ATTRIBUTE].to_s
|
|
1153
|
+
image = build_image(alt, src)
|
|
1154
|
+
@decorator.decorate(@symbols.wrap_in_parentheses(image), :image)
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
# Build an image
|
|
1158
|
+
#
|
|
1159
|
+
# @param [String] alt
|
|
1160
|
+
# the image alt attribute
|
|
1161
|
+
# @param [String] src
|
|
1162
|
+
# the image src attribute
|
|
1163
|
+
#
|
|
1164
|
+
# @return [String]
|
|
1165
|
+
#
|
|
1166
|
+
# @api private
|
|
1167
|
+
def build_image(alt, src)
|
|
1168
|
+
return src if alt.empty?
|
|
1169
|
+
|
|
1170
|
+
"#{alt} #{@symbols[:ndash]} #{src}"
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
# Convert an HTML element
|
|
1174
|
+
#
|
|
1175
|
+
# @param [Kramdown::Element] element
|
|
1176
|
+
# the html element
|
|
1177
|
+
# @param [Hash] options
|
|
1178
|
+
# the html element options
|
|
1179
|
+
#
|
|
1180
|
+
# @return [Array<String>, String]
|
|
1181
|
+
#
|
|
1182
|
+
# @api private
|
|
1183
|
+
def convert_html_element(element, options)
|
|
1184
|
+
if CONVERTED_HTML_ELEMENTS.include?(element.value)
|
|
1185
|
+
send(:"convert_#{element.value}", element, options)
|
|
1186
|
+
elsif element.children.any?
|
|
1187
|
+
transform_children(element, options)
|
|
1188
|
+
else
|
|
1189
|
+
warning("HTML element '#{element.value.inspect}' not supported")
|
|
1190
|
+
EMPTY
|
|
1191
|
+
end
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1194
|
+
# Convert an XML comment element
|
|
1195
|
+
#
|
|
1196
|
+
# @param [Kramdown::Element] element
|
|
1197
|
+
# the xml comment element
|
|
1198
|
+
# @param [Hash] options
|
|
1199
|
+
# the xml comment element options
|
|
1200
|
+
#
|
|
1201
|
+
# @return [String]
|
|
1202
|
+
#
|
|
1203
|
+
# @api private
|
|
1204
|
+
def convert_xml_comment(element, options)
|
|
1205
|
+
inline_level = element.options[:category] == :span
|
|
1206
|
+
content = strip_comment_delimiters(element.value)
|
|
1207
|
+
comment = build_comment(content, inline_level)
|
|
1208
|
+
inline_level ? comment : "#{comment}#{NEWLINE}"
|
|
1209
|
+
end
|
|
1210
|
+
alias convert_comment convert_xml_comment
|
|
1211
|
+
|
|
1212
|
+
# Strip the delimiters from the HTML comment
|
|
1213
|
+
#
|
|
1214
|
+
# @param [String] comment
|
|
1215
|
+
# the HTML comment
|
|
1216
|
+
#
|
|
1217
|
+
# @return [String]
|
|
1218
|
+
#
|
|
1219
|
+
# @api private
|
|
1220
|
+
def strip_comment_delimiters(comment)
|
|
1221
|
+
comment.gsub(COMMENT_DELIMITERS_PATTERN, EMPTY)
|
|
1222
|
+
end
|
|
1223
|
+
|
|
1224
|
+
# Build a comment
|
|
1225
|
+
#
|
|
1226
|
+
# @param [String] content
|
|
1227
|
+
# the comment content
|
|
1228
|
+
# @param [Boolean] inline_level
|
|
1229
|
+
# whether the comment level is inline
|
|
1230
|
+
#
|
|
1231
|
+
# @return [String]
|
|
1232
|
+
#
|
|
1233
|
+
# @api private
|
|
1234
|
+
def build_comment(content, inline_level)
|
|
1235
|
+
comment_indentation = inline_level ? EMPTY : indentation
|
|
1236
|
+
content.lines.map do |line|
|
|
1237
|
+
comment_indentation +
|
|
1238
|
+
@decorator.decorate("#{@symbols[:hash]} #{line.chomp}", :comment)
|
|
1239
|
+
end.join(NEWLINE)
|
|
1240
|
+
end
|
|
1241
|
+
end # Converter
|
|
1242
|
+
end # Markdown
|
|
1243
|
+
end # TTY
|