tty-markdown 0.3.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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