tty-markdown 0.3.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.
data/Gemfile DELETED
@@ -1,16 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
- gemspec
6
-
7
- group :test do
8
- gem 'benchmark-ips', '~> 2.7.2'
9
- gem 'simplecov', '~> 0.14.1'
10
- gem 'coveralls', '~> 0.8.21'
11
- end
12
-
13
- group :metrics do
14
- gem 'yard', '~> 0.9.12'
15
- gem 'yardstick', '~> 0.9.9'
16
- end
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
3
- FileList['tasks/**/*.rake'].each(&method(:import))
4
-
5
- desc 'Run all specs'
6
- task ci: %w[ spec ]
7
-
8
- task default: :spec
@@ -1,23 +0,0 @@
1
- ---
2
- install:
3
- - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
4
- - ruby --version
5
- - gem --version
6
- - bundle install
7
- build: off
8
- test_script:
9
- - bundle exec rake ci
10
- environment:
11
- matrix:
12
- - ruby_version: "200"
13
- - ruby_version: "200-x64"
14
- - ruby_version: "21"
15
- - ruby_version: "21-x64"
16
- - ruby_version: "22"
17
- - ruby_version: "22-x64"
18
- - ruby_version: "23"
19
- - ruby_version: "23-x64"
20
- - ruby_version: "24"
21
- - ruby_version: "24-x64"
22
- - ruby_version: "25"
23
- - ruby_version: "25-x64"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "tty/markdown"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,49 +0,0 @@
1
-
2
- TTY::Markdown
3
- =============
4
-
5
- **tty-markdown** converts markdown document into a terminal friendly output.
6
-
7
- ## Examples
8
-
9
- ### Nested list items
10
-
11
- - Item 1
12
- - Item 2
13
- - Item 3
14
- - Item 4
15
- - Item 5
16
-
17
- ### Quote
18
-
19
- > Blockquotes are very handy in email to emulate reply text.
20
- > This line is part of the same quote.
21
- > *Oh*, you can put **Markdown** into a blockquote.
22
-
23
- ### Codeblock
24
-
25
- ```ruby
26
- class Greeter
27
- def hello(name)
28
- puts "Hello #{name}"
29
- end
30
- end
31
- ```
32
-
33
- ### Table
34
-
35
- | Tables | Are | Cool |
36
- |----------|:-------------:|------:|
37
- | col 1 is | left-aligned | $1600 |
38
- | col 2 is | centered | $12 |
39
- | col 3 is | right-aligned | $1 |
40
-
41
- ### Horizontal line
42
-
43
- ***
44
-
45
- ### Link
46
-
47
- [I'm an inline-style link](https://www.google.com)
48
-
49
- [I'm an inline-style link with title](https://www.google.com "Google's Homepage")
@@ -1,17 +0,0 @@
1
- # name(1) - description
2
-
3
- ## SYNOPSIS
4
-
5
- `name` \[`OPTIONS`\] *arguments*...
6
-
7
- ### DESCRIPTION
8
-
9
- Paragraph describing what the tool does.
10
-
11
- ### OPTIONS
12
-
13
- * A
14
- * list
15
- * of
16
- * available
17
- * options
@@ -1,6 +0,0 @@
1
- require_relative '../lib/tty-markdown'
2
-
3
- path = File.join(__dir__, 'man.md')
4
- out = TTY::Markdown.parse_file(path, colors: 256)
5
-
6
- puts out
@@ -1,6 +0,0 @@
1
- require_relative '../lib/tty-markdown'
2
-
3
- path = File.join(__dir__, 'example.md')
4
- out = TTY::Markdown.parse_file(path, colors: 256, width: 80)
5
-
6
- puts out
@@ -1,467 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'kramdown/converter'
4
- require 'pastel'
5
- require 'strings'
6
- require 'tty-screen'
7
-
8
- require_relative 'syntax_highlighter'
9
-
10
- module TTY
11
- module Markdown
12
- # Converts a Kramdown::Document tree to a terminal friendly output
13
- class Parser < Kramdown::Converter::Base
14
-
15
- def initialize(root, **options)
16
- super
17
- @stack = []
18
- @current_indent = 0
19
- @indent = options.fetch(:indent, 2)
20
- @pastel = Pastel.new
21
- @color_opts = { mode: options[:colors] }
22
- @width = options.fetch(:width) { TTY::Screen.width }
23
- @theme = options.fetch(:theme) { TTY::Markdown::THEME }
24
- end
25
-
26
- # Invoke an element conversion
27
- #
28
- # @api public
29
- def convert(el, opts = { indent: 0, result: [] })
30
- send("convert_#{el.type}", el, opts)
31
- end
32
-
33
- private
34
-
35
- # Process children of this element
36
- def inner(el, opts)
37
- @stack << [el, opts]
38
- el.children.each_with_index do |inner_el, i|
39
- options = opts.dup
40
- options[:parent] = el
41
- options[:prev] = (i == 0 ? nil : el.children[i - 1])
42
- options[:index] = i
43
- convert(inner_el, options)
44
- end
45
- @stack.pop
46
- end
47
-
48
- def convert_root(el, opts)
49
- inner(el, opts)
50
- opts[:result]
51
- end
52
-
53
- def convert_header(el, opts)
54
- level = el.options[:level]
55
- @current_indent = (level - 1) * @indent # Header determines indentation
56
- indent = ' ' * (level - 1) * @indent
57
- styles = Array(@theme[:header]).dup
58
- styles << :underline if level == 1
59
- opts[:result] << indent + @pastel.lookup(*styles)
60
- inner(el, opts)
61
- opts[:result] << @pastel.lookup(:reset) + "\n"
62
- end
63
-
64
- def convert_p(el, opts)
65
- result_before = @stack.last[1][:result].dup
66
- indent = ' ' * @current_indent
67
-
68
- if opts[:parent].type != :blockquote
69
- opts[:result] << indent
70
- end
71
-
72
- case opts[:parent].type
73
- when :li
74
- bullet = TTY::Markdown.symbols[:bullet]
75
- index = @stack.last[1][:index] + 1
76
- symbol = opts[:ordered] ? "#{index}." : bullet
77
- styles = Array(@theme[:list])
78
- opts[:result] << @pastel.decorate(symbol, *styles) + ' '
79
- end
80
-
81
- inner(el, opts)
82
-
83
- if opts[:parent].type == :blockquote
84
- format_blockquote(result_before, opts[:result])
85
- end
86
-
87
- unless opts[:result].last.end_with?("\n")
88
- opts[:result] << "\n"
89
- end
90
- end
91
-
92
- # Format current element by inserting prefix for each
93
- # quoted line within the allowed screen size.
94
- #
95
- # @param [Array[String]] result_before
96
- # @param [Array[String]] result
97
- #
98
- # @return [nil]
99
- #
100
- # @api private
101
- def format_blockquote(result_before, result)
102
- indent = ' ' * @current_indent
103
- start_index = result_before.size
104
- max_index = result.size - 1
105
- bar_symbol = TTY::Markdown.symbols[:bar]
106
- styles = Array(@theme[:quote])
107
- prefix = "#{indent}#{@pastel.decorate(bar_symbol, *styles)} "
108
-
109
- result.map!.with_index do |str, i|
110
- if i == start_index
111
- str.insert(0, prefix)
112
- end
113
-
114
- # only modify blockquote element
115
- if i >= start_index && str.to_s.include?("\n") # multiline string found
116
- str.lines.map! do |line|
117
- if (line != str.lines.last || i < max_index)
118
- line.insert(-1, line.end_with?("\n") ? prefix : "\n" + prefix)
119
- else
120
- line
121
- end
122
- end.join
123
- else
124
- str
125
- end
126
- end
127
- end
128
-
129
- def convert_text(el, opts)
130
- text = el.value
131
- opts[:result] << Strings.wrap(text, @width)
132
- end
133
-
134
- def convert_strong(el, opts)
135
- styles = Array(@theme[:strong])
136
- opts[:result] << @pastel.lookup(*styles)
137
- inner(el, opts)
138
- opts[:result] << @pastel.lookup(:reset)
139
- end
140
-
141
- def convert_em(el, opts)
142
- styles = Array(@theme[:em])
143
- opts[:result] << @pastel.lookup(*styles)
144
- inner(el, opts)
145
- opts[:result] << @pastel.lookup(:reset)
146
- end
147
-
148
- def convert_blank(el, opts)
149
- opts[:result] << "\n"
150
- end
151
-
152
- def convert_smart_quote(el, opts)
153
- opts[:result] << TTY::Markdown.symbols[el.value]
154
- end
155
-
156
- def convert_codespan(el, opts)
157
- raw_code = Strings.wrap(el.value, @width)
158
- highlighted = SyntaxHighliter.highlight(raw_code, @color_opts.merge(opts))
159
- code = highlighted.split("\n").map.with_index do |line, i|
160
- if i.zero? # first line
161
- line
162
- else
163
- line.insert(0, ' ' * @current_indent)
164
- end
165
- end
166
- opts[:result] << code.join("\n")
167
- end
168
-
169
- def convert_codeblock(el, opts)
170
- opts[:fenced] = false
171
- convert_codespan(el, opts)
172
- end
173
-
174
- def convert_blockquote(el, opts)
175
- inner(el, opts)
176
- end
177
-
178
- def convert_ul(el, opts)
179
- @current_indent += @indent unless opts[:parent].type == :root
180
- inner(el, opts)
181
- @current_indent -= @indent unless opts[:parent].type == :root
182
- end
183
- alias convert_ol convert_ul
184
-
185
- def convert_li(el, opts)
186
- if opts[:parent].type == :ol
187
- opts[:ordered] = true
188
- end
189
- inner(el, opts)
190
- end
191
-
192
- def convert_table(el, opts)
193
- opts[:alignment] = el.options[:alignment]
194
-
195
- result = opts[:result]
196
- opts[:result] = []
197
- data = []
198
-
199
- el.children.each do |container|
200
- container.children.each do |row|
201
- data_row = []
202
- data << data_row
203
- row.children.each do |cell|
204
- opts[:result] = []
205
- cell_data = inner(cell, opts)
206
- data_row << cell_data[1][:result]
207
- end
208
- end
209
- end
210
-
211
- opts[:result] = result
212
- opts[:table_data] = data
213
-
214
- inner(el, opts)
215
- end
216
-
217
- def convert_thead(el, opts)
218
- indent = ' ' * @current_indent
219
- table_data = opts[:table_data]
220
-
221
- opts[:result] << indent
222
- opts[:result] << border(table_data, :top)
223
- opts[:result] << "\n"
224
- inner(el, opts)
225
- end
226
-
227
- # Render horizontal border line
228
- #
229
- # @param [Array[Array[String]]] table_data
230
- # table rows and cells
231
- # @param [Symbol] location
232
- # location out of :top, :mid, :bottom
233
- #
234
- # @return [String]
235
- #
236
- # @api private
237
- def border(table_data, location)
238
- symbols = TTY::Markdown.symbols
239
- result = []
240
- result << symbols[:"#{location}_left"]
241
- distribute_widths(max_widths(table_data)).each.with_index do |width, i|
242
- result << symbols[:"#{location}_center"] if i != 0
243
- result << (symbols[:line] * (width + 2))
244
- end
245
- result << symbols[:"#{location}_right"]
246
- styles = Array(@theme[:table])
247
- @pastel.decorate(result.join, *styles)
248
- end
249
-
250
- def convert_tbody(el, opts)
251
- indent = ' ' * @current_indent
252
- table_data = opts[:table_data]
253
-
254
- opts[:result] << indent
255
- if opts[:prev] && opts[:prev].type == :thead
256
- opts[:result] << border(table_data, :mid)
257
- else
258
- opts[:result] << border(table_data, :top)
259
- end
260
- opts[:result] << "\n"
261
-
262
- inner(el, opts)
263
-
264
- opts[:result] << indent
265
- opts[:result] << border(table_data, :bottom)
266
- opts[:result] << "\n"
267
- end
268
-
269
- def convert_tfoot(el, opts)
270
- inner(el, opts)
271
- end
272
-
273
- def convert_tr(el, opts)
274
- indent = ' ' * @current_indent
275
- table_data = opts[:table_data]
276
-
277
- if opts[:prev] && opts[:prev].type == :tr
278
- opts[:result] << indent
279
- opts[:result] << border(table_data, :mid)
280
- opts[:result] << "\n"
281
- end
282
-
283
- opts[:cells] = []
284
-
285
- inner(el, opts)
286
-
287
- columns = table_data.first.count
288
-
289
- row = opts[:cells].each_with_index.reduce([]) do |acc, (cell, i)|
290
- if cell.size > 1 # multiline
291
- cell.each_with_index do |c, j| # zip columns
292
- acc[j] = [] if acc[j].nil?
293
- acc[j] << c.chomp
294
- acc[j] << "\n" if i == (columns - 1)
295
- end
296
- else
297
- acc << cell
298
- acc << "\n" if i == (columns - 1)
299
- end
300
- acc
301
- end.join
302
-
303
- opts[:result] << row
304
- end
305
-
306
- def convert_td(el, opts)
307
- indent = ' ' * @current_indent
308
- pipe = TTY::Markdown.symbols[:pipe]
309
- styles = Array(@theme[:table])
310
- table_data = opts[:table_data]
311
- result = opts[:cells]
312
- suffix = " #{@pastel.decorate(pipe, *styles)} "
313
- opts[:result] = []
314
-
315
- inner(el, opts)
316
-
317
- row, column = *find_row_column(table_data, opts[:result])
318
- cell_widths = distribute_widths(max_widths(table_data))
319
- cell_width = cell_widths[column]
320
- cell_height = max_height(table_data, row, cell_widths)
321
- alignment = opts[:alignment][column]
322
- align_opts = alignment == :default ? {} : { direction: alignment }
323
-
324
- wrapped = Strings.wrap(opts[:result].join, cell_width)
325
- aligned = Strings.align(wrapped, cell_width, align_opts)
326
- padded = if aligned.lines.size < cell_height
327
- Strings.pad(aligned, [0, 0, cell_height - aligned.lines.size, 0])
328
- else
329
- aligned.dup
330
- end
331
-
332
- result << padded.lines.map do |line|
333
- # add pipe to first column
334
- (column.zero? ? indent + @pastel.decorate("#{pipe} ", *styles) : '') +
335
- (line.end_with?("\n") ? line.insert(-2, suffix) : line << suffix)
336
- end
337
- end
338
-
339
- # Find row and column indexes
340
- #
341
- # @return [Array[Integer, Integer]]
342
- #
343
- # @api private
344
- def find_row_column(table_data, cell)
345
- table_data.each_with_index do |row, row_no|
346
- row.size.times do |col|
347
- return [row_no, col] if row[col] == cell
348
- end
349
- end
350
- end
351
-
352
- # Calculate maximum cell width for a given column
353
- #
354
- # @return [Integer]
355
- #
356
- # @api private
357
- def max_width(table_data, col)
358
- table_data.map do |row|
359
- Strings.sanitize(row[col].join).lines.map(&:length).max
360
- end.max
361
- end
362
-
363
- # Calculate maximum cell height for a given row
364
- #
365
- # @return [Integer]
366
- #
367
- # @api private
368
- def max_height(table_data, row, cell_widths)
369
- table_data[row].map.with_index do |col, i|
370
- Strings.wrap(col.join, cell_widths[i]).lines.size
371
- end.max
372
- end
373
-
374
- def max_widths(table_data)
375
- table_data.first.each_with_index.reduce([]) do |acc, (*, col)|
376
- acc << max_width(table_data, col)
377
- acc
378
- end
379
- end
380
-
381
- def distribute_widths(widths)
382
- indent = ' ' * @current_indent
383
- total_width = widths.reduce(&:+)
384
- screen_width = @width - (indent.length + 1) * 2 - (widths.size + 1)
385
- return widths if total_width <= screen_width
386
-
387
- extra_width = total_width - screen_width
388
-
389
- widths.map do |w|
390
- ratio = w / total_width.to_f
391
- w - (extra_width * ratio).floor
392
- end
393
- end
394
-
395
- def convert_hr(el, opts)
396
- indent = ' ' * @current_indent
397
- symbols = TTY::Markdown.symbols
398
- width = @width - (indent.length + 1) * 2
399
- styles = Array(@theme[:hr])
400
- line = symbols[:diamond] + symbols[:line] * width + symbols[:diamond]
401
-
402
- opts[:result] << indent
403
- opts[:result] << @pastel.decorate(line, *styles)
404
- opts[:result] << "\n"
405
- end
406
-
407
- def convert_a(el, opts)
408
- symbols = TTY::Markdown.symbols
409
- styles = Array(@theme[:link])
410
- if el.children.size == 1 && el.children[0].type == :text
411
- opts[:result] << @pastel.decorate(el.attr['href'], *styles)
412
- else
413
- if el.attr['title']
414
- opts[:result] << el.attr['title']
415
- else
416
- inner(el, opts)
417
- end
418
- opts[:result] << " #{symbols[:arrow]} "
419
- opts[:result] << @pastel.decorate(el.attr['href'], *styles)
420
- opts[:result] << "\n"
421
- end
422
- end
423
-
424
- def convert_math(el, opts)
425
- if opts[:prev] && opts[:prev].type == :blank
426
- indent = ' ' * @current_indent
427
- opts[:result] << indent
428
- end
429
- convert_codespan(el, opts)
430
- opts[:result] << "\n"
431
- end
432
-
433
- def convert_abbreviation(el, opts)
434
- opts[:result] << el.value
435
- end
436
-
437
- def convert_typographic_sym(el, opts)
438
- opts[:result] << TTY::Markdown.symbols[el.value]
439
- end
440
-
441
- def convert_entity(el, opts)
442
- opts[:result] << unicode_char(el.value.code_point)
443
- end
444
-
445
- # Convert codepoint to UTF-8 representation
446
- def unicode_char(codepoint)
447
- [codepoint].pack('U*')
448
- end
449
-
450
- def convert_footnote(*)
451
- warning("Footnotes are not supported")
452
- end
453
-
454
- def convert_raw(*)
455
- warning("Raw content is not supported")
456
- end
457
-
458
- def convert_img(*)
459
- warning("Images are not supported")
460
- end
461
-
462
- def convert_html_element(*)
463
- warning("HTML elements are not supported")
464
- end
465
- end # Parser
466
- end # Markdown
467
- end # TTY