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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -2
- data/README.md +117 -59
- data/lib/tty-markdown.rb +1 -1
- data/lib/tty/markdown.rb +127 -72
- data/lib/tty/markdown/converter.rb +821 -0
- data/lib/tty/markdown/kramdown_ext.rb +23 -0
- data/lib/tty/markdown/syntax_highlighter.rb +20 -16
- data/lib/tty/markdown/version.rb +1 -1
- metadata +38 -65
- data/Rakefile +0 -8
- data/assets/headers.png +0 -0
- data/assets/hr.png +0 -0
- data/assets/link.png +0 -0
- data/assets/list.png +0 -0
- data/assets/quote.png +0 -0
- data/assets/syntax_highlight.png +0 -0
- data/assets/table.png +0 -0
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/examples/man.rb +0 -6
- data/examples/marked.rb +0 -6
- data/lib/tty/markdown/parser.rb +0 -482
- data/spec/spec_helper.rb +0 -31
- data/spec/unit/parse/abbrev_spec.rb +0 -27
- data/spec/unit/parse/blockquote_spec.rb +0 -77
- data/spec/unit/parse/codeblock_spec.rb +0 -130
- data/spec/unit/parse/comment_spec.rb +0 -19
- data/spec/unit/parse/emphasis_spec.rb +0 -35
- data/spec/unit/parse/entity_spec.rb +0 -11
- data/spec/unit/parse/header_spec.rb +0 -35
- data/spec/unit/parse/hr_spec.rb +0 -25
- data/spec/unit/parse/link_spec.rb +0 -25
- data/spec/unit/parse/list_spec.rb +0 -103
- data/spec/unit/parse/math_spec.rb +0 -37
- data/spec/unit/parse/paragraph_spec.rb +0 -38
- data/spec/unit/parse/table_spec.rb +0 -164
- data/spec/unit/parse/typography_spec.rb +0 -20
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "kramdown/parser/kramdown"
|
4
|
+
|
5
|
+
module Kramdown
|
6
|
+
module Parser
|
7
|
+
class KramdownExt < Kramdown::Parser::Kramdown
|
8
|
+
def initialize(source, options)
|
9
|
+
super
|
10
|
+
|
11
|
+
{ codeblock_fenced: :codeblock_fenced_ext }.each do |current, replacement|
|
12
|
+
@block_parsers[@block_parsers.index(current)] = replacement
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
FENCED_CODEBLOCK_START = /^[ ]{0,3}[~`]{3,}/.freeze
|
17
|
+
FENCED_CODEBLOCK_MATCH = /^[ ]{0,3}(([~`]){3,})\s*?((\S+?)(?:\?\S*)?)?\s*?\n(.*?)^[ ]{0,3}\1\2*\s*?\n/m.freeze
|
18
|
+
|
19
|
+
define_parser(:codeblock_fenced_ext, FENCED_CODEBLOCK_START, nil,
|
20
|
+
"parse_codeblock_fenced")
|
21
|
+
end # KramdownExt
|
22
|
+
end # Parser
|
23
|
+
end # TTY
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require 'tty-color'
|
3
|
+
require "pastel"
|
4
|
+
require "rouge"
|
6
5
|
|
7
6
|
module TTY
|
8
7
|
module Markdown
|
@@ -32,33 +31,38 @@ module TTY
|
|
32
31
|
#
|
33
32
|
# @api private
|
34
33
|
def guess_lang(code)
|
35
|
-
lang = nil
|
36
34
|
start_line = code.lines[0]
|
37
35
|
if available_lexers.include?(start_line.strip.downcase)
|
38
|
-
|
36
|
+
start_line.strip.downcase
|
39
37
|
end
|
40
38
|
end
|
41
39
|
module_function :guess_lang
|
42
40
|
|
43
41
|
# Highlight code snippet
|
44
42
|
#
|
43
|
+
# @param [String] code
|
44
|
+
# @param [Integer] mode
|
45
|
+
# the color mode supported by the terminal
|
46
|
+
# @param [String] lang
|
47
|
+
# the code snippet language
|
48
|
+
# @param [Boolean] enabled
|
49
|
+
# whether or not coloring is enabled
|
50
|
+
# @param [Proc] color
|
51
|
+
# the fallback coloring
|
52
|
+
#
|
45
53
|
# @api public
|
46
|
-
def highlight(code,
|
47
|
-
|
48
|
-
|
49
|
-
lines = code.dup.lines
|
50
|
-
if options[:fenced].nil?
|
51
|
-
code = lines[1...-1].join + lines[-1].strip
|
52
|
-
end
|
53
|
-
|
54
|
+
def highlight(code, mode: 256, lang: nil, enabled: nil,
|
55
|
+
color: ->(line) { line })
|
56
|
+
lang = guess_lang(code) if lang.nil?
|
54
57
|
lexer = Rouge::Lexer.find_fancy(lang, code) || Rouge::Lexers::PlainText
|
55
58
|
|
56
|
-
if
|
59
|
+
if enabled == false
|
60
|
+
code
|
61
|
+
elsif 256 <= mode
|
57
62
|
formatter = Rouge::Formatters::Terminal256.new
|
58
63
|
formatter.format(lexer.lex(code))
|
59
64
|
else
|
60
|
-
|
61
|
-
code.split("\n").map { |line| pastel.yellow(line) }.join("\n")
|
65
|
+
code.lines.map { |line| color.(line.chomp) }.join("\n")
|
62
66
|
end
|
63
67
|
end
|
64
68
|
module_function :highlight
|
data/lib/tty/markdown/version.rb
CHANGED
metadata
CHANGED
@@ -1,113 +1,105 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tty-markdown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Murach
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kramdown
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 1.16.2
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 1.16.2
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: pastel
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
37
|
- - "~>"
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
39
|
+
version: '0.8'
|
34
40
|
type: :runtime
|
35
41
|
prerelease: false
|
36
42
|
version_requirements: !ruby/object:Gem::Requirement
|
37
43
|
requirements:
|
38
44
|
- - "~>"
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
46
|
+
version: '0.8'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: rouge
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
44
50
|
requirements:
|
45
51
|
- - "~>"
|
46
52
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
53
|
+
version: '3.14'
|
48
54
|
type: :runtime
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
51
57
|
requirements:
|
52
58
|
- - "~>"
|
53
59
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
60
|
+
version: '3.14'
|
55
61
|
- !ruby/object:Gem::Dependency
|
56
62
|
name: strings
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
58
64
|
requirements:
|
59
65
|
- - "~>"
|
60
66
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
67
|
+
version: 0.2.0
|
62
68
|
type: :runtime
|
63
69
|
prerelease: false
|
64
70
|
version_requirements: !ruby/object:Gem::Requirement
|
65
71
|
requirements:
|
66
72
|
- - "~>"
|
67
73
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.
|
74
|
+
version: 0.2.0
|
69
75
|
- !ruby/object:Gem::Dependency
|
70
76
|
name: tty-color
|
71
77
|
requirement: !ruby/object:Gem::Requirement
|
72
78
|
requirements:
|
73
79
|
- - "~>"
|
74
80
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
81
|
+
version: '0.5'
|
76
82
|
type: :runtime
|
77
83
|
prerelease: false
|
78
84
|
version_requirements: !ruby/object:Gem::Requirement
|
79
85
|
requirements:
|
80
86
|
- - "~>"
|
81
87
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0.
|
88
|
+
version: '0.5'
|
83
89
|
- !ruby/object:Gem::Dependency
|
84
90
|
name: tty-screen
|
85
91
|
requirement: !ruby/object:Gem::Requirement
|
86
92
|
requirements:
|
87
93
|
- - "~>"
|
88
94
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
95
|
+
version: '0.8'
|
90
96
|
type: :runtime
|
91
97
|
prerelease: false
|
92
98
|
version_requirements: !ruby/object:Gem::Requirement
|
93
99
|
requirements:
|
94
100
|
- - "~>"
|
95
101
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0.
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: bundler
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '1.16'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '1.16'
|
102
|
+
version: '0.8'
|
111
103
|
- !ruby/object:Gem::Dependency
|
112
104
|
name: rake
|
113
105
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,53 +129,34 @@ dependencies:
|
|
137
129
|
- !ruby/object:Gem::Version
|
138
130
|
version: '3.0'
|
139
131
|
description: Convert a markdown text or document into a terminal friendly output.
|
140
|
-
email:
|
132
|
+
email:
|
133
|
+
- piotr@piotrmurach.com
|
141
134
|
executables: []
|
142
135
|
extensions: []
|
143
|
-
extra_rdoc_files:
|
136
|
+
extra_rdoc_files:
|
137
|
+
- README.md
|
138
|
+
- CHANGELOG.md
|
139
|
+
- LICENSE.txt
|
144
140
|
files:
|
145
141
|
- CHANGELOG.md
|
146
142
|
- LICENSE.txt
|
147
143
|
- README.md
|
148
|
-
- Rakefile
|
149
|
-
- assets/headers.png
|
150
|
-
- assets/hr.png
|
151
|
-
- assets/link.png
|
152
|
-
- assets/list.png
|
153
|
-
- assets/quote.png
|
154
|
-
- assets/syntax_highlight.png
|
155
|
-
- assets/table.png
|
156
|
-
- bin/console
|
157
|
-
- bin/setup
|
158
|
-
- examples/man.rb
|
159
|
-
- examples/marked.rb
|
160
144
|
- lib/tty-markdown.rb
|
161
145
|
- lib/tty/markdown.rb
|
162
|
-
- lib/tty/markdown/
|
146
|
+
- lib/tty/markdown/converter.rb
|
147
|
+
- lib/tty/markdown/kramdown_ext.rb
|
163
148
|
- lib/tty/markdown/syntax_highlighter.rb
|
164
149
|
- lib/tty/markdown/version.rb
|
165
|
-
|
166
|
-
- spec/unit/parse/abbrev_spec.rb
|
167
|
-
- spec/unit/parse/blockquote_spec.rb
|
168
|
-
- spec/unit/parse/codeblock_spec.rb
|
169
|
-
- spec/unit/parse/comment_spec.rb
|
170
|
-
- spec/unit/parse/emphasis_spec.rb
|
171
|
-
- spec/unit/parse/entity_spec.rb
|
172
|
-
- spec/unit/parse/header_spec.rb
|
173
|
-
- spec/unit/parse/hr_spec.rb
|
174
|
-
- spec/unit/parse/link_spec.rb
|
175
|
-
- spec/unit/parse/list_spec.rb
|
176
|
-
- spec/unit/parse/math_spec.rb
|
177
|
-
- spec/unit/parse/paragraph_spec.rb
|
178
|
-
- spec/unit/parse/table_spec.rb
|
179
|
-
- spec/unit/parse/typography_spec.rb
|
180
|
-
- tasks/console.rake
|
181
|
-
- tasks/coverage.rake
|
182
|
-
- tasks/spec.rake
|
183
|
-
homepage: https://piotrmurach.github.io/tty
|
150
|
+
homepage: https://ttytoolkit.org
|
184
151
|
licenses:
|
185
152
|
- MIT
|
186
|
-
metadata:
|
153
|
+
metadata:
|
154
|
+
allowed_push_host: https://rubygems.org
|
155
|
+
bug_tracker_uri: https://github.com/piotrmurach/tty-markdown/issues
|
156
|
+
changelog_uri: https://github.com/piotrmurach/tty-markdown/blob/master/CHANGELOG.md
|
157
|
+
documentation_uri: https://www.rubydoc.info/gems/tty-markdown
|
158
|
+
homepage_uri: https://ttytoolkit.org
|
159
|
+
source_code_uri: https://github.com/piotrmurach/tty-markdown
|
187
160
|
post_install_message:
|
188
161
|
rdoc_options: []
|
189
162
|
require_paths:
|
@@ -199,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
172
|
- !ruby/object:Gem::Version
|
200
173
|
version: '0'
|
201
174
|
requirements: []
|
202
|
-
rubygems_version: 3.
|
175
|
+
rubygems_version: 3.1.2
|
203
176
|
signing_key:
|
204
177
|
specification_version: 4
|
205
178
|
summary: Convert a markdown text or document into a terminal friendly output.
|
data/Rakefile
DELETED
data/assets/headers.png
DELETED
Binary file
|
data/assets/hr.png
DELETED
Binary file
|
data/assets/link.png
DELETED
Binary file
|
data/assets/list.png
DELETED
Binary file
|
data/assets/quote.png
DELETED
Binary file
|
data/assets/syntax_highlight.png
DELETED
Binary file
|
data/assets/table.png
DELETED
Binary file
|
data/bin/console
DELETED
@@ -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
data/examples/man.rb
DELETED
data/examples/marked.rb
DELETED
data/lib/tty/markdown/parser.rb
DELETED
@@ -1,482 +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
|
-
opts[:indent] = @current_indent
|
73
|
-
opts[:strip] = false
|
74
|
-
|
75
|
-
case opts[:parent].type
|
76
|
-
when :li
|
77
|
-
bullet = TTY::Markdown.symbols[:bullet]
|
78
|
-
index = @stack.last[1][:index] + 1
|
79
|
-
symbol = opts[:ordered] ? "#{index}." : bullet
|
80
|
-
styles = Array(@theme[:list])
|
81
|
-
opts[:result] << @pastel.decorate(symbol, *styles) + ' '
|
82
|
-
opts[:indent] += @indent
|
83
|
-
opts[:strip] = true
|
84
|
-
when :blockquote
|
85
|
-
opts[:indent] = 0
|
86
|
-
end
|
87
|
-
|
88
|
-
inner(el, opts)
|
89
|
-
|
90
|
-
if opts[:parent].type == :blockquote
|
91
|
-
format_blockquote(result_before, opts[:result])
|
92
|
-
end
|
93
|
-
|
94
|
-
unless opts[:result].last.end_with?("\n")
|
95
|
-
opts[:result] << "\n"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Format current element by inserting prefix for each
|
100
|
-
# quoted line within the allowed screen size.
|
101
|
-
#
|
102
|
-
# @param [Array[String]] result_before
|
103
|
-
# @param [Array[String]] result
|
104
|
-
#
|
105
|
-
# @return [nil]
|
106
|
-
#
|
107
|
-
# @api private
|
108
|
-
def format_blockquote(result_before, result)
|
109
|
-
indent = ' ' * @current_indent
|
110
|
-
start_index = result_before.size
|
111
|
-
max_index = result.size - 1
|
112
|
-
bar_symbol = TTY::Markdown.symbols[:bar]
|
113
|
-
styles = Array(@theme[:quote])
|
114
|
-
prefix = "#{indent}#{@pastel.decorate(bar_symbol, *styles)} "
|
115
|
-
|
116
|
-
result.map!.with_index do |str, i|
|
117
|
-
if i == start_index
|
118
|
-
str.insert(0, prefix)
|
119
|
-
end
|
120
|
-
|
121
|
-
# only modify blockquote element
|
122
|
-
if i >= start_index && str.to_s.include?("\n") # multiline string found
|
123
|
-
str.lines.map! do |line|
|
124
|
-
if (line != str.lines.last || i < max_index)
|
125
|
-
line.insert(-1, line.end_with?("\n") ? prefix : "\n" + prefix)
|
126
|
-
else
|
127
|
-
line
|
128
|
-
end
|
129
|
-
end.join
|
130
|
-
else
|
131
|
-
str
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def convert_text(el, opts)
|
137
|
-
text = Strings.wrap(el.value, @width)
|
138
|
-
text = text.chomp if opts[:strip]
|
139
|
-
indent = ' ' * opts[:indent]
|
140
|
-
text = text.gsub(/\n/, "\n#{indent}")
|
141
|
-
opts[:result] << text
|
142
|
-
end
|
143
|
-
|
144
|
-
def convert_strong(el, opts)
|
145
|
-
styles = Array(@theme[:strong])
|
146
|
-
opts[:result] << @pastel.lookup(*styles)
|
147
|
-
inner(el, opts)
|
148
|
-
opts[:result] << @pastel.lookup(:reset)
|
149
|
-
end
|
150
|
-
|
151
|
-
def convert_em(el, opts)
|
152
|
-
styles = Array(@theme[:em])
|
153
|
-
opts[:result] << @pastel.lookup(*styles)
|
154
|
-
inner(el, opts)
|
155
|
-
opts[:result] << @pastel.lookup(:reset)
|
156
|
-
end
|
157
|
-
|
158
|
-
def convert_blank(el, opts)
|
159
|
-
opts[:result] << "\n"
|
160
|
-
end
|
161
|
-
|
162
|
-
def convert_smart_quote(el, opts)
|
163
|
-
opts[:result] << TTY::Markdown.symbols[el.value]
|
164
|
-
end
|
165
|
-
|
166
|
-
def convert_codespan(el, opts)
|
167
|
-
raw_code = Strings.wrap(el.value, @width)
|
168
|
-
highlighted = SyntaxHighliter.highlight(raw_code, @color_opts.merge(opts))
|
169
|
-
code = highlighted.split("\n").map.with_index do |line, i|
|
170
|
-
if i.zero? # first line
|
171
|
-
line
|
172
|
-
else
|
173
|
-
line.insert(0, ' ' * @current_indent)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
opts[:result] << code.join("\n")
|
177
|
-
end
|
178
|
-
|
179
|
-
def convert_codeblock(el, opts)
|
180
|
-
opts[:fenced] = false
|
181
|
-
convert_codespan(el, opts)
|
182
|
-
end
|
183
|
-
|
184
|
-
def convert_blockquote(el, opts)
|
185
|
-
inner(el, opts)
|
186
|
-
end
|
187
|
-
|
188
|
-
def convert_ul(el, opts)
|
189
|
-
@current_indent += @indent unless opts[:parent].type == :root
|
190
|
-
inner(el, opts)
|
191
|
-
@current_indent -= @indent unless opts[:parent].type == :root
|
192
|
-
end
|
193
|
-
alias convert_ol convert_ul
|
194
|
-
|
195
|
-
def convert_li(el, opts)
|
196
|
-
if opts[:parent].type == :ol
|
197
|
-
opts[:ordered] = true
|
198
|
-
end
|
199
|
-
inner(el, opts)
|
200
|
-
end
|
201
|
-
|
202
|
-
def convert_table(el, opts)
|
203
|
-
opts[:alignment] = el.options[:alignment]
|
204
|
-
|
205
|
-
result = opts[:result]
|
206
|
-
opts[:result] = []
|
207
|
-
data = []
|
208
|
-
|
209
|
-
el.children.each do |container|
|
210
|
-
container.children.each do |row|
|
211
|
-
data_row = []
|
212
|
-
data << data_row
|
213
|
-
row.children.each do |cell|
|
214
|
-
opts[:result] = []
|
215
|
-
cell_data = inner(cell, opts)
|
216
|
-
data_row << cell_data[1][:result]
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
opts[:result] = result
|
222
|
-
opts[:table_data] = data
|
223
|
-
|
224
|
-
inner(el, opts)
|
225
|
-
end
|
226
|
-
|
227
|
-
def convert_thead(el, opts)
|
228
|
-
indent = ' ' * @current_indent
|
229
|
-
table_data = opts[:table_data]
|
230
|
-
|
231
|
-
opts[:result] << indent
|
232
|
-
opts[:result] << border(table_data, :top)
|
233
|
-
opts[:result] << "\n"
|
234
|
-
inner(el, opts)
|
235
|
-
end
|
236
|
-
|
237
|
-
# Render horizontal border line
|
238
|
-
#
|
239
|
-
# @param [Array[Array[String]]] table_data
|
240
|
-
# table rows and cells
|
241
|
-
# @param [Symbol] location
|
242
|
-
# location out of :top, :mid, :bottom
|
243
|
-
#
|
244
|
-
# @return [String]
|
245
|
-
#
|
246
|
-
# @api private
|
247
|
-
def border(table_data, location)
|
248
|
-
symbols = TTY::Markdown.symbols
|
249
|
-
result = []
|
250
|
-
result << symbols[:"#{location}_left"]
|
251
|
-
distribute_widths(max_widths(table_data)).each.with_index do |width, i|
|
252
|
-
result << symbols[:"#{location}_center"] if i != 0
|
253
|
-
result << (symbols[:line] * (width + 2))
|
254
|
-
end
|
255
|
-
result << symbols[:"#{location}_right"]
|
256
|
-
styles = Array(@theme[:table])
|
257
|
-
@pastel.decorate(result.join, *styles)
|
258
|
-
end
|
259
|
-
|
260
|
-
def convert_tbody(el, opts)
|
261
|
-
indent = ' ' * @current_indent
|
262
|
-
table_data = opts[:table_data]
|
263
|
-
|
264
|
-
opts[:result] << indent
|
265
|
-
if opts[:prev] && opts[:prev].type == :thead
|
266
|
-
opts[:result] << border(table_data, :mid)
|
267
|
-
else
|
268
|
-
opts[:result] << border(table_data, :top)
|
269
|
-
end
|
270
|
-
opts[:result] << "\n"
|
271
|
-
|
272
|
-
inner(el, opts)
|
273
|
-
|
274
|
-
opts[:result] << indent
|
275
|
-
opts[:result] << border(table_data, :bottom)
|
276
|
-
opts[:result] << "\n"
|
277
|
-
end
|
278
|
-
|
279
|
-
def convert_tfoot(el, opts)
|
280
|
-
inner(el, opts)
|
281
|
-
end
|
282
|
-
|
283
|
-
def convert_tr(el, opts)
|
284
|
-
indent = ' ' * @current_indent
|
285
|
-
table_data = opts[:table_data]
|
286
|
-
|
287
|
-
if opts[:prev] && opts[:prev].type == :tr
|
288
|
-
opts[:result] << indent
|
289
|
-
opts[:result] << border(table_data, :mid)
|
290
|
-
opts[:result] << "\n"
|
291
|
-
end
|
292
|
-
|
293
|
-
opts[:cells] = []
|
294
|
-
|
295
|
-
inner(el, opts)
|
296
|
-
|
297
|
-
columns = table_data.first.count
|
298
|
-
|
299
|
-
row = opts[:cells].each_with_index.reduce([]) do |acc, (cell, i)|
|
300
|
-
if cell.size > 1 # multiline
|
301
|
-
cell.each_with_index do |c, j| # zip columns
|
302
|
-
acc[j] = [] if acc[j].nil?
|
303
|
-
acc[j] << c.chomp
|
304
|
-
acc[j] << "\n" if i == (columns - 1)
|
305
|
-
end
|
306
|
-
else
|
307
|
-
acc << cell
|
308
|
-
acc << "\n" if i == (columns - 1)
|
309
|
-
end
|
310
|
-
acc
|
311
|
-
end.join
|
312
|
-
|
313
|
-
opts[:result] << row
|
314
|
-
end
|
315
|
-
|
316
|
-
def convert_td(el, opts)
|
317
|
-
indent = ' ' * @current_indent
|
318
|
-
pipe = TTY::Markdown.symbols[:pipe]
|
319
|
-
styles = Array(@theme[:table])
|
320
|
-
table_data = opts[:table_data]
|
321
|
-
result = opts[:cells]
|
322
|
-
suffix = " #{@pastel.decorate(pipe, *styles)} "
|
323
|
-
opts[:result] = []
|
324
|
-
|
325
|
-
inner(el, opts)
|
326
|
-
|
327
|
-
row, column = *find_row_column(table_data, opts[:result])
|
328
|
-
cell_widths = distribute_widths(max_widths(table_data))
|
329
|
-
cell_width = cell_widths[column]
|
330
|
-
cell_height = max_height(table_data, row, cell_widths)
|
331
|
-
alignment = opts[:alignment][column]
|
332
|
-
align_opts = alignment == :default ? {} : { direction: alignment }
|
333
|
-
|
334
|
-
wrapped = Strings.wrap(opts[:result].join, cell_width)
|
335
|
-
aligned = Strings.align(wrapped, cell_width, align_opts)
|
336
|
-
padded = if aligned.lines.size < cell_height
|
337
|
-
Strings.pad(aligned, [0, 0, cell_height - aligned.lines.size, 0])
|
338
|
-
else
|
339
|
-
aligned.dup
|
340
|
-
end
|
341
|
-
|
342
|
-
result << padded.lines.map do |line|
|
343
|
-
# add pipe to first column
|
344
|
-
(column.zero? ? indent + @pastel.decorate("#{pipe} ", *styles) : '') +
|
345
|
-
(line.end_with?("\n") ? line.insert(-2, suffix) : line << suffix)
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
# Find row and column indexes
|
350
|
-
#
|
351
|
-
# @return [Array[Integer, Integer]]
|
352
|
-
#
|
353
|
-
# @api private
|
354
|
-
def find_row_column(table_data, cell)
|
355
|
-
table_data.each_with_index do |row, row_no|
|
356
|
-
row.size.times do |col|
|
357
|
-
return [row_no, col] if row[col] == cell
|
358
|
-
end
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
# Calculate maximum cell width for a given column
|
363
|
-
#
|
364
|
-
# @return [Integer]
|
365
|
-
#
|
366
|
-
# @api private
|
367
|
-
def max_width(table_data, col)
|
368
|
-
table_data.map do |row|
|
369
|
-
Strings.sanitize(row[col].join).lines.map(&:length).max
|
370
|
-
end.max
|
371
|
-
end
|
372
|
-
|
373
|
-
# Calculate maximum cell height for a given row
|
374
|
-
#
|
375
|
-
# @return [Integer]
|
376
|
-
#
|
377
|
-
# @api private
|
378
|
-
def max_height(table_data, row, cell_widths)
|
379
|
-
table_data[row].map.with_index do |col, i|
|
380
|
-
Strings.wrap(col.join, cell_widths[i]).lines.size
|
381
|
-
end.max
|
382
|
-
end
|
383
|
-
|
384
|
-
def max_widths(table_data)
|
385
|
-
table_data.first.each_with_index.reduce([]) do |acc, (*, col)|
|
386
|
-
acc << max_width(table_data, col)
|
387
|
-
acc
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
def distribute_widths(widths)
|
392
|
-
indent = ' ' * @current_indent
|
393
|
-
total_width = widths.reduce(&:+)
|
394
|
-
screen_width = @width - (indent.length + 1) * 2 - (widths.size + 1)
|
395
|
-
return widths if total_width <= screen_width
|
396
|
-
|
397
|
-
extra_width = total_width - screen_width
|
398
|
-
|
399
|
-
widths.map do |w|
|
400
|
-
ratio = w / total_width.to_f
|
401
|
-
w - (extra_width * ratio).floor
|
402
|
-
end
|
403
|
-
end
|
404
|
-
|
405
|
-
def convert_hr(el, opts)
|
406
|
-
indent = ' ' * @current_indent
|
407
|
-
symbols = TTY::Markdown.symbols
|
408
|
-
width = @width - (indent.length + 1) * 2
|
409
|
-
styles = Array(@theme[:hr])
|
410
|
-
line = symbols[:diamond] + symbols[:line] * width + symbols[:diamond]
|
411
|
-
|
412
|
-
opts[:result] << indent
|
413
|
-
opts[:result] << @pastel.decorate(line, *styles)
|
414
|
-
opts[:result] << "\n"
|
415
|
-
end
|
416
|
-
|
417
|
-
def convert_a(el, opts)
|
418
|
-
symbols = TTY::Markdown.symbols
|
419
|
-
styles = Array(@theme[:link])
|
420
|
-
if el.children.size == 1 && el.children[0].type == :text
|
421
|
-
opts[:result] << @pastel.decorate(el.attr['href'], *styles)
|
422
|
-
else
|
423
|
-
if el.attr['title']
|
424
|
-
opts[:result] << el.attr['title']
|
425
|
-
else
|
426
|
-
inner(el, opts)
|
427
|
-
end
|
428
|
-
opts[:result] << " #{symbols[:arrow]} "
|
429
|
-
opts[:result] << @pastel.decorate(el.attr['href'], *styles)
|
430
|
-
opts[:result] << "\n"
|
431
|
-
end
|
432
|
-
end
|
433
|
-
|
434
|
-
def convert_math(el, opts)
|
435
|
-
if opts[:prev] && opts[:prev].type == :blank
|
436
|
-
indent = ' ' * @current_indent
|
437
|
-
opts[:result] << indent
|
438
|
-
end
|
439
|
-
convert_codespan(el, opts)
|
440
|
-
opts[:result] << "\n"
|
441
|
-
end
|
442
|
-
|
443
|
-
def convert_abbreviation(el, opts)
|
444
|
-
opts[:result] << el.value
|
445
|
-
end
|
446
|
-
|
447
|
-
def convert_typographic_sym(el, opts)
|
448
|
-
opts[:result] << TTY::Markdown.symbols[el.value]
|
449
|
-
end
|
450
|
-
|
451
|
-
def convert_entity(el, opts)
|
452
|
-
opts[:result] << unicode_char(el.value.code_point)
|
453
|
-
end
|
454
|
-
|
455
|
-
# Convert codepoint to UTF-8 representation
|
456
|
-
def unicode_char(codepoint)
|
457
|
-
[codepoint].pack('U*')
|
458
|
-
end
|
459
|
-
|
460
|
-
def convert_footnote(*)
|
461
|
-
warning("Footnotes are not supported")
|
462
|
-
end
|
463
|
-
|
464
|
-
def convert_raw(*)
|
465
|
-
warning("Raw content is not supported")
|
466
|
-
end
|
467
|
-
|
468
|
-
def convert_img(*)
|
469
|
-
warning("Images are not supported")
|
470
|
-
end
|
471
|
-
|
472
|
-
def convert_html_element(*)
|
473
|
-
warning("HTML elements are not supported")
|
474
|
-
end
|
475
|
-
|
476
|
-
def convert_xml_comment(el, opts)
|
477
|
-
opts[:result] << el.value << "\n"
|
478
|
-
end
|
479
|
-
alias convert_comment convert_xml_comment
|
480
|
-
end # Parser
|
481
|
-
end # Markdown
|
482
|
-
end # TTY
|