ruby_rich 0.4.6 → 0.4.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e895cf85cb90d2d805f086ad45e2724e01cef3bc816e0b72017c828243e0f9e
4
- data.tar.gz: 7f4295486466411de2d4e5b6e6ada0158c8b6c5d4266075c1dadef0500fc4dab
3
+ metadata.gz: 568e2001bde24749d1410dadda7e77090963eedc48e9ad9e6d77f036e245a51f
4
+ data.tar.gz: 99a40412ae33eb985e7b0e1d4dc0516b8539ca32eb79ae7ec881b673d350ecc5
5
5
  SHA512:
6
- metadata.gz: 16701c1d49e1be7a951d3a1c51de0a7c0f68237bf8869318904975f98e09681a3dac4458f81d2a16f93b26e5fadd372143cde05626e64d4933b483f6e4a1f751
7
- data.tar.gz: b0ed0f6cccf9d69d1e718fb538fa0e331ad28a4b652f4bdc124e316ceccfa5f245d081bfda82d71e0a1b0310f8315744ddf76490738518c1e9ee49fcf971c44d
6
+ metadata.gz: 187311b6561fcfea7c51af5ab44bd8b8dcbead8fabd5108d9356b07eacf82f0994ef5c6c64e00db1764a7cd8a546feb6df17c2ab119f21e11898a924a485499e
7
+ data.tar.gz: ff1f361748e077f0ad6f59c30b22a8502e83a2196e7ab23a2365ee67362283036f0fb1e3da4eea102cdacfcea1c8c1ab1c08b15e8841c0da1b270182e0497e4b
@@ -1,205 +1,404 @@
1
- require 'redcarpet'
1
+ require 'kramdown'
2
2
 
3
3
  module RubyRich
4
4
  class Markdown
5
- # Converts markdown to ANSI-styled terminal output.
6
- # Uses Redcarpet for block parsing with custom inline processing.
7
- class TerminalRenderer < Redcarpet::Render::Base
8
- INLINE_MARKERS = {
9
- # triple-backtick must come before double-backtick
10
- %r{```(.+?)```}m => ->(m) { codespan_compat(Regexp.last_match(1)) },
11
- %r{``(.+?)``}m => ->(m) { codespan_compat(Regexp.last_match(1)) },
12
- %r{`(.+?)`} => ->(m) { codespan_compat(Regexp.last_match(1)) },
13
- %r{\*\*\*(.+?)\*\*\*} => ->(m) { "#{AnsiCode.bold}#{AnsiCode.italic}#{Regexp.last_match(1)}#{AnsiCode.reset}" },
14
- %r{\*\*(.+?)\*\*} => ->(m) { "#{AnsiCode.bold}#{Regexp.last_match(1)}#{AnsiCode.reset}" },
15
- %r{(?<!\*)\*([^*]+)\*(?!\*)} => ->(m) { "#{AnsiCode.italic}#{Regexp.last_match(1)}#{AnsiCode.reset}" },
16
- %r{~~(.+?)~~} => ->(m) { "#{AnsiCode.strikethrough}#{Regexp.last_match(1)}#{AnsiCode.reset}" },
17
- %r{\[([^\]]+)\]\(([^)]+)\)} => ->(m) {
18
- link_text = Regexp.last_match(1)
19
- url = Regexp.last_match(2)
20
- "#{AnsiCode.color(:blue, true)}#{AnsiCode.underline}#{link_text}#{AnsiCode.reset} #{AnsiCode.color(:black, true)}(#{url})#{AnsiCode.reset}"
21
- }
22
- }.freeze
23
-
24
- def initialize(options = {})
25
- @options = {
26
- width: 80,
27
- indent: ' '
28
- }.merge(options)
29
- super()
30
- reset_table_state
31
- end
32
-
33
- def reset_table_state
34
- @table_state = { current_row: [], all_rows: [] }
35
- end
36
-
37
- # ---- block-level callbacks ----
38
-
39
- def paragraph(text)
40
- "#{process_inline(text)}\n\n"
41
- end
42
-
43
- def header(text, level)
44
- processed = process_inline(text)
5
+ # Markdown 转换为 ANSI 终端输出。
6
+ # 使用 kramdown AST 遍历 + 自定义 Converter 实现。
7
+ #
8
+ # kramdown 比 redcarpet 的优势:
9
+ # - Ruby 实现,无需 C 扩展编译
10
+ # - 原生 GFM 支持(表格、任务列表、删除线)
11
+ # - 定义列表 (definition lists)
12
+ # - 脚注 (footnotes)
13
+ # - 数学公式 ( math engine)
14
+ # - 缩写 (abbreviations)
15
+ # - 活跃维护
16
+ class TerminalConverter < Kramdown::Converter::Base
17
+
18
+ def initialize(root, options)
19
+ super
20
+ @width = options[:width] || 80
21
+ @indent_str = options[:indent] || ' '
22
+ @table_border_style = options[:table_border_style] || :simple
23
+ # 用于有序列表编号
24
+ @list_counters = []
25
+ end
26
+
27
+ # 主分发方法 — 根据 AST 元素类型路由到对应处理方法
28
+ def convert(el, _indent = 0)
29
+ case el.type
30
+ when :root then convert_children(el)
31
+ when :blank then "\n"
32
+ when :text then el.value
33
+ when :p then convert_p(el)
34
+ when :header then convert_header(el)
35
+ when :codeblock then convert_codeblock(el)
36
+ when :codespan then convert_codespan(el)
37
+ when :blockquote then convert_blockquote(el)
38
+ when :ul then convert_list(el)
39
+ when :ol then convert_list(el)
40
+ when :li then convert_li(el)
41
+ when :em then convert_em(el)
42
+ when :strong then convert_strong(el)
43
+ when :em_strong then convert_em_strong(el)
44
+ when :a then convert_link(el)
45
+ when :img then convert_image(el)
46
+ when :hr then convert_hr(el)
47
+ when :br then "\n"
48
+ when :table then convert_table(el)
49
+ when :thead then convert_children(el)
50
+ when :tbody then convert_children(el)
51
+ when :tr then convert_table_row(el)
52
+ when :th then convert_table_cell(el)
53
+ when :td then convert_table_cell(el)
54
+ when :html_element then convert_html_element(el)
55
+ when :html_entity then convert_html_entity(el)
56
+ when :smart_quote then convert_smart_quote(el)
57
+ when :entity then el.value.to_s
58
+ when :raw then convert_raw(el)
59
+ when :comment then ''
60
+ when :footnote then convert_footnote(el)
61
+ when :dl then convert_definition_list(el)
62
+ when :dt then convert_definition_term(el)
63
+ when :dd then convert_definition_desc(el)
64
+ when :abbreviation then convert_abbreviation(el)
65
+ when :math then convert_math(el)
66
+ else
67
+ # 未知类型,递归处理子元素
68
+ if el.children && !el.children.empty?
69
+ convert_children(el)
70
+ else
71
+ el.value || el.text || ''
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ # ---- 辅助方法 ----
79
+
80
+ def convert_children(el)
81
+ return '' unless el.children
82
+ el.children.map { |child| convert(child) }.join
83
+ end
84
+
85
+ def inline_content(el)
86
+ return '' unless el.children
87
+ el.children.map { |child| convert(child) }.join
88
+ end
89
+
90
+ def visible_width(text)
91
+ text.to_s.gsub(/\e\[[0-9;]*m/, '').length
92
+ end
93
+
94
+ # ---- 块级元素 ----
95
+
96
+ def convert_p(el)
97
+ "#{inline_content(el)}\n\n"
98
+ end
99
+
100
+ def convert_header(el)
101
+ level = el.options[:level] || 1
102
+ text = inline_content(el)
103
+ vw = visible_width(text)
45
104
  case level
46
- when 1 then "#{AnsiCode.font(:cyan, font_bright: true, bold: true)}#{processed}#{AnsiCode.reset}\n#{AnsiCode.color(:cyan, true)}#{'=' * visible_width(text)}#{AnsiCode.reset}\n\n"
47
- when 2 then "#{AnsiCode.font(:blue, font_bright: true, bold: true)}#{processed}#{AnsiCode.reset}\n#{AnsiCode.color(:blue, true)}#{'-' * visible_width(text)}#{AnsiCode.reset}\n\n"
48
- when 3 then "#{AnsiCode.font(:yellow, font_bright: true, bold: true)}### #{processed}#{AnsiCode.reset}\n\n"
49
- else "#{AnsiCode.font(:black, font_bright: true, bold: true)}#{'#' * level} #{processed}#{AnsiCode.reset}\n\n"
105
+ when 1
106
+ "#{AnsiCode.font(:cyan, font_bright: true, bold: true)}#{text}#{AnsiCode.reset}\n" \
107
+ "#{AnsiCode.color(:cyan, true)}#{'=' * [vw, 1].max}#{AnsiCode.reset}\n\n"
108
+ when 2
109
+ "#{AnsiCode.font(:blue, font_bright: true, bold: true)}#{text}#{AnsiCode.reset}\n" \
110
+ "#{AnsiCode.color(:blue, true)}#{'-' * [vw, 1].max}#{AnsiCode.reset}\n\n"
111
+ when 3
112
+ "#{AnsiCode.font(:yellow, font_bright: true, bold: true)}### #{text}#{AnsiCode.reset}\n\n"
113
+ else
114
+ prefix = '#' * level
115
+ "#{AnsiCode.font(:black, font_bright: true, bold: true)}#{prefix} #{text}#{AnsiCode.reset}\n\n"
50
116
  end
51
117
  end
52
118
 
53
- def block_code(code, language)
54
- lang = language&.strip
119
+ def convert_codeblock(el)
120
+ lang = el.options[:lang]&.strip
55
121
  lang = nil if lang && lang.empty?
56
- highlighted = Syntax.highlight(code.strip, lang)
57
- bg = AnsiCode.background(:black, true)
58
- fg = AnsiCode.color(:white, true)
59
- pad = @options[:indent]
122
+ code = el.value.strip
123
+ highlighted = Syntax.highlight(code, lang)
124
+ bg = AnsiCode.background(:black, true)
125
+ fg = AnsiCode.color(:white, true)
60
126
  "#{bg}#{fg}#{indent_lines(highlighted)}#{AnsiCode.reset}\n\n"
61
127
  end
62
128
 
63
- def codespan(code)
129
+ def convert_codespan(el)
130
+ code = el.value
64
131
  "#{AnsiCode.background(:white)}#{AnsiCode.color(:black)} #{code} #{AnsiCode.reset}"
65
132
  end
66
133
 
67
- def block_quote(quote)
68
- lines = quote.strip.split("\n")
69
- quoted_lines = lines.map { |line| "#{AnsiCode.color(:black, true)}│ #{AnsiCode.color(:white, true)}#{process_inline(line.strip)}" }
70
- "#{quoted_lines.join("\n")}#{AnsiCode.reset}\n\n"
134
+ def convert_blockquote(el)
135
+ content = inline_content(el)
136
+ lines = content.strip.split("\n")
137
+ quoted = lines.map do |line|
138
+ "#{AnsiCode.color(:black, true)}│ #{AnsiCode.color(:white, true)}#{strip_ansi_reset(line)}"
139
+ end
140
+ "#{quoted.join("\n")}#{AnsiCode.reset}\n\n"
71
141
  end
72
142
 
73
- def list_item(text, list_type)
74
- marker = list_type == :ordered ? '1.' : '•'
75
- "#{AnsiCode.color(:cyan, true)}#{marker}#{AnsiCode.reset} #{process_inline(text.strip)}\n"
143
+ # ---- 列表 ----
144
+
145
+ def convert_list(el)
146
+ # 保存/恢复计数器,支持嵌套列表
147
+ @list_counters.push(0)
148
+ result = convert_children(el)
149
+ @list_counters.pop
150
+ "#{result}\n"
76
151
  end
77
152
 
78
- def list(contents, list_type)
79
- "#{contents}\n"
153
+ def convert_li(el)
154
+ marker = if el.options[:parent]&.type == :ol
155
+ @list_counters[-1] += 1
156
+ "#{@list_counters[-1]}."
157
+ else
158
+ '•'
159
+ end
160
+ text = inline_content(el)
161
+ "#{AnsiCode.color(:cyan, true)}#{marker}#{AnsiCode.reset} #{text.strip}\n"
80
162
  end
81
163
 
82
- def emphasis(text) = "#{AnsiCode.italic}#{text}#{AnsiCode.reset}"
83
- def double_emphasis(text) = "#{AnsiCode.bold}#{text}#{AnsiCode.reset}"
84
- def strikethrough(text) = "#{AnsiCode.strikethrough}#{text}#{AnsiCode.reset}"
164
+ # ---- 内联样式 ----
85
165
 
86
- def link(link, title, content)
87
- title_part = title && !title.empty? ? " - #{title}" : ""
88
- "#{AnsiCode.color(:blue, true)}#{AnsiCode.underline}#{content}#{AnsiCode.reset} #{AnsiCode.color(:black, true)}(#{link}#{title_part})#{AnsiCode.reset}"
166
+ def convert_em(el)
167
+ "#{AnsiCode.italic}#{inline_content(el)}#{AnsiCode.reset}"
89
168
  end
90
169
 
91
- def image(link, title, alt_text)
92
- title_part = title && !title.empty? ? " - #{title}" : ""
93
- "#{AnsiCode.color(:magenta, true)}[Image: #{alt_text}]#{AnsiCode.reset} #{AnsiCode.color(:black, true)}(#{link}#{title_part})#{AnsiCode.reset}"
170
+ def convert_strong(el)
171
+ "#{AnsiCode.bold}#{inline_content(el)}#{AnsiCode.reset}"
94
172
  end
95
173
 
96
- def hrule
97
- "#{AnsiCode.color(:black, true)}#{"─" * @options[:width]}#{AnsiCode.reset}\n\n"
174
+ def convert_em_strong(el)
175
+ "#{AnsiCode.bold}#{AnsiCode.italic}#{inline_content(el)}#{AnsiCode.reset}"
176
+ end
177
+
178
+ # ---- 链接与图片 ----
179
+
180
+ def convert_link(el)
181
+ url = el.attr['href'] || ''
182
+ title = el.attr['title']
183
+ text = inline_content(el)
184
+ title_part = title && !title.empty? ? " - #{title}" : ""
185
+ "#{AnsiCode.color(:blue, true)}#{AnsiCode.underline}#{text}#{AnsiCode.reset} " \
186
+ "#{AnsiCode.color(:black, true)}(#{url}#{title_part})#{AnsiCode.reset}"
98
187
  end
99
188
 
100
- def linebreak = "\n"
189
+ def convert_image(el)
190
+ url = el.attr['src'] || ''
191
+ title = el.attr['title']
192
+ alt = el.attr['alt'] || ''
193
+ title_part = title && !title.empty? ? " - #{title}" : ""
194
+ "#{AnsiCode.color(:magenta, true)}[Image: #{alt}]#{AnsiCode.reset} " \
195
+ "#{AnsiCode.color(:black, true)}(#{url}#{title_part})#{AnsiCode.reset}"
196
+ end
101
197
 
102
- # ---- table callbacks ----
198
+ # ---- 水平线 ----
103
199
 
104
- def table(header, body)
105
- all_rows = @table_state[:all_rows]
106
- reset_table_state
107
- return "" if all_rows.empty?
200
+ def convert_hr(_el)
201
+ line_char = '─'
202
+ "#{AnsiCode.color(:black, true)}#{line_char * @width}#{AnsiCode.reset}\n\n"
203
+ end
108
204
 
109
- header_line_count = [header.to_s.strip.split("\n").size, 1].max
110
- header_rows = all_rows[0...header_line_count]
111
- body_rows = all_rows[header_line_count..] || []
205
+ # ---- 表格(kramdown 原生 AST) ----
206
+
207
+ def convert_table(el)
208
+ # 收集表头行和表体行
209
+ header_rows = []
210
+ body_rows = []
211
+ el.children.each do |section|
212
+ case section.type
213
+ when :thead
214
+ section.children.each { |tr| header_rows << collect_row_cells(tr) }
215
+ when :tbody
216
+ section.children.each { |tr| body_rows << collect_row_cells(tr) }
217
+ when :tr
218
+ body_rows << collect_row_cells(section)
219
+ end
220
+ end
112
221
 
113
222
  return "" if header_rows.empty? || body_rows.empty?
114
223
 
115
- headers = header_rows.last.map { |c| process_inline(c) }
224
+ headers = header_rows.last
116
225
  begin
117
- tbl = RubyRich::Table.new(headers: headers, border_style: @options[:table_border_style] || :simple)
226
+ tbl = RubyRich::Table.new(
227
+ headers: headers,
228
+ border_style: @table_border_style
229
+ )
118
230
  body_rows.each do |row|
119
- processed = row.map { |c| process_inline(c) }
120
- padded = processed + Array.new([0, headers.length - processed.length].max, "")
231
+ padded = row + Array.new([0, headers.length - row.length].max, "")
121
232
  tbl.add_row(padded[0...headers.length])
122
233
  end
123
- return "#{tbl.render}\n\n"
234
+ "#{tbl.render}\n\n"
124
235
  rescue
125
- # fallback
236
+ # fallback: 纯文本表格
237
+ result = "\n"
238
+ result += header_rows.last.join(" | ")
239
+ result += "\n#{"-" * [result.strip.length, 20].min}\n"
240
+ body_rows.each { |row| result += row.join(" | ") + "\n" }
241
+ "#{result}\n"
126
242
  end
243
+ end
127
244
 
128
- result = "\n"
129
- result += "#{header.strip}\n"
130
- result += "#{"-" * [header.strip.length, 20].min}\n"
131
- result += "#{body.strip}\n" if body && !body.strip.empty?
132
- "#{result}\n"
245
+ def collect_row_cells(tr)
246
+ tr.children.select { |c| [:th, :td].include?(c.type) }
247
+ .map { |c| inline_content(c) }
133
248
  end
134
249
 
135
- def table_row(content)
136
- @table_state[:all_rows] << @table_state[:current_row].dup
137
- @table_state[:current_row] = []
138
- "#{content}\n"
250
+ def convert_table_row(el)
251
+ convert_children(el)
139
252
  end
140
253
 
141
- def table_cell(content, alignment)
142
- @table_state[:current_row] << content.strip
143
- content
254
+ def convert_table_cell(el)
255
+ inline_content(el)
144
256
  end
145
257
 
146
- private
258
+ # ---- HTML 元素处理 ----
259
+
260
+ def convert_html_element(el)
261
+ tag = el.value.to_s.downcase
262
+ content = inline_content(el)
263
+
264
+ case tag
265
+ when 'del', 's', 'strike'
266
+ "#{AnsiCode.strikethrough}#{content}#{AnsiCode.reset}"
267
+ when 'ins', 'u'
268
+ "#{AnsiCode.underline}#{content}#{AnsiCode.reset}"
269
+ when 'sub'
270
+ content # 终端无下标,保留文本
271
+ when 'sup'
272
+ content # 终端无上标,保留文本
273
+ when 'kbd'
274
+ "#{AnsiCode.background(:white)}#{AnsiCode.color(:black)} #{content} #{AnsiCode.reset}"
275
+ when 'mark'
276
+ "#{AnsiCode.background(:yellow)}#{AnsiCode.color(:black)}#{content}#{AnsiCode.reset}"
277
+ when 'details', 'summary'
278
+ content
279
+ when 'br'
280
+ "\n"
281
+ when 'hr'
282
+ convert_hr(nil)
283
+ else
284
+ content
285
+ end
286
+ end
287
+
288
+ def convert_html_entity(el)
289
+ # kramdown 已解析 HTML 实体为 UTF-8 字符
290
+ el.value.to_s
291
+ end
147
292
 
148
- def process_inline(text)
149
- return text if text.nil? || text.empty?
293
+ # ---- 智能引号 ----
150
294
 
151
- result = text.dup
152
- INLINE_MARKERS.each do |regex, handler|
153
- result.gsub!(regex, &handler)
295
+ def convert_smart_quote(el)
296
+ case el.value
297
+ when :lsquo then "'"
298
+ when :rsquo then "'"
299
+ when :ldquo then '"'
300
+ when :rdquo then '"'
301
+ else el.value.to_s
154
302
  end
155
- result
156
303
  end
157
304
 
158
- def self.codespan_compat(code)
159
- "#{AnsiCode.background(:white)}#{AnsiCode.color(:black)} #{code} #{AnsiCode.reset}"
305
+ # ---- 原始内容 ----
306
+
307
+ def convert_raw(el)
308
+ # 原始 HTML,在终端中通常跳过
309
+ el.value.to_s.start_with?('<') ? '' : el.value.to_s
310
+ end
311
+
312
+ # ---- 脚注 ----
313
+
314
+ def convert_footnote(el)
315
+ # 脚注内容在文档末尾自动收集
316
+ content = inline_content(el)
317
+ name = el.options[:name]
318
+ "#{AnsiCode.color(:magenta, true)}[^#{name}]#{AnsiCode.reset}"
160
319
  end
161
320
 
162
- def wrap_text(text, width = nil)
163
- width ||= @options[:width]
164
- return text if text.length <= width
321
+ # ---- 定义列表(kramdown 独有) ----
165
322
 
166
- words = text.split(' ')
167
- lines = []
168
- current_line = ''
323
+ def convert_definition_list(el)
324
+ "#{convert_children(el)}\n"
325
+ end
169
326
 
170
- words.each do |word|
171
- if (current_line + ' ' + word).length <= width
172
- current_line += current_line.empty? ? word : ' ' + word
173
- else
174
- lines << current_line unless current_line.empty?
175
- current_line = word
176
- end
327
+ def convert_definition_term(el)
328
+ "#{AnsiCode.bold}#{inline_content(el)}#{AnsiCode.reset}\n"
329
+ end
330
+
331
+ def convert_definition_desc(el)
332
+ "#{@indent_str}#{inline_content(el)}\n\n"
333
+ end
334
+
335
+ # ---- 缩写(kramdown 独有) ----
336
+
337
+ def convert_abbreviation(el)
338
+ title = el.attr['title']
339
+ text = inline_content(el)
340
+ if title && !title.empty?
341
+ "#{AnsiCode.underline}#{text}#{AnsiCode.reset}#{AnsiCode.color(:black, true)}(#{title})#{AnsiCode.reset}"
342
+ else
343
+ text
177
344
  end
345
+ end
346
+
347
+ # ---- 数学公式(kramdown 独有,需 math engine) ----
178
348
 
179
- lines << current_line unless current_line.empty?
180
- lines.join("\n")
349
+ def convert_math(el)
350
+ # 在终端中显示原始 LaTeX 公式
351
+ mode = el.options[:category] == :block ? 'block' : 'inline'
352
+ formula = el.value.to_s.strip
353
+ if mode == 'block'
354
+ "#{AnsiCode.color(:magenta, true)}[Math]\n#{formula}\n[/Math]#{AnsiCode.reset}\n\n"
355
+ else
356
+ "#{AnsiCode.color(:magenta, true)}[Math: #{formula}]#{AnsiCode.reset}"
357
+ end
181
358
  end
182
359
 
360
+ # ---- 辅助 ----
361
+
183
362
  def indent_lines(text)
184
- text.split("\n").map { |line| "#{@options[:indent]}#{line}" }.join("\n")
363
+ text.split("\n").map { |line| "#{@indent_str}#{line}" }.join("\n")
185
364
  end
186
365
 
187
- def visible_width(text)
188
- text.gsub(/\e\[[0-9;]*m/, '').length
366
+ def strip_ansi_reset(text)
367
+ # 去掉末尾 AnsiCode.reset,让 blockquote 统一添加
368
+ text.gsub(/\e\[0m$/, '')
189
369
  end
190
370
  end
191
371
 
372
+ # ---- 公开 API ----
373
+
374
+ # 渲染 Markdown 文本为 ANSI 终端输出
375
+ #
376
+ # @param markdown_text [String] 输入的 Markdown 文本
377
+ # @param options [Hash] 渲染选项
378
+ # @option options [Integer] :width 终端宽度(默认 80)
379
+ # @option options [String] :indent 缩进字符串(默认 ' ')
380
+ # @option options [Symbol] :table_border_style 表格边框样式 (:none, :simple, :full)
381
+ # @option options [Hash] :kramdown 传递给 Kramdown::Document 的额外选项
382
+ #
383
+ # @return [String] ANSI 格式的终端输出
192
384
  def self.render(markdown_text, options = {})
193
- renderer = TerminalRenderer.new(options)
194
- markdown_processor = Redcarpet::Markdown.new(renderer, {
195
- fenced_code_blocks: true,
196
- tables: true,
197
- autolink: true,
198
- strikethrough: true,
199
- space_after_headers: true
200
- })
201
-
202
- markdown_processor.render(markdown_text)
385
+ converter_options = {
386
+ width: options[:width] || 80,
387
+ indent: options[:indent] || ' ',
388
+ table_border_style: options[:table_border_style] || :simple
389
+ }
390
+
391
+ kramdown_opts = {
392
+ input: 'GFM', # GitHub Flavored Markdown
393
+ syntax_highlighter: nil, # 自行处理语法高亮
394
+ hard_wrap: false,
395
+ html_to_native: true,
396
+ line_width: converter_options[:width]
397
+ }.merge(options[:kramdown] || {})
398
+
399
+ doc = Kramdown::Document.new(markdown_text, kramdown_opts)
400
+ result, _warnings = TerminalConverter.convert(doc.root, converter_options)
401
+ result
203
402
  end
204
403
 
205
404
  def initialize(options = {})
@@ -1,3 +1,3 @@
1
1
  module RubyRich
2
- VERSION = "0.4.6"
2
+ VERSION = "0.4.8"
3
3
  end
data/lib/ruby_rich.rb CHANGED
@@ -5,7 +5,7 @@ require 'logger'
5
5
  require 'rouge'
6
6
  require 'tty-cursor'
7
7
  require 'tty-screen'
8
- require 'redcarpet'
8
+ require 'kramdown'
9
9
 
10
10
  # Load all internal modules
11
11
  require_relative 'ruby_rich/console'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_rich
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - zhuang biaowei
@@ -86,19 +86,33 @@ dependencies:
86
86
  - !ruby/object:Gem::Version
87
87
  version: 0.8.2
88
88
  - !ruby/object:Gem::Dependency
89
- name: redcarpet
89
+ name: kramdown
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: 3.6.1
94
+ version: '2.4'
95
95
  type: :runtime
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: 3.6.1
101
+ version: '2.4'
102
+ - !ruby/object:Gem::Dependency
103
+ name: kramdown-parser-gfm
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '1.1'
109
+ type: :runtime
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.1'
102
116
  - !ruby/object:Gem::Dependency
103
117
  name: unicode-display_width
104
118
  requirement: !ruby/object:Gem::Requirement
@@ -176,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
190
  - !ruby/object:Gem::Version
177
191
  version: '0'
178
192
  requirements: []
179
- rubygems_version: 4.0.10
193
+ rubygems_version: 4.0.13
180
194
  specification_version: 4
181
195
  summary: Rich text formatting and console output for Ruby
182
196
  test_files: []