kramdown-ansi 0.7.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d35fcfbc735d86a3f58f59c1eaded0ebdc4f706db3cedb0c807aa6f9f420fe8
4
- data.tar.gz: cd32746d6d592ee48e59bda8211adf670bdef612a5c65da97e114234ef8f0e83
3
+ metadata.gz: 6038ac6683b6c30a71839080b16df7c60c2633e53964ac57bcc7537ba5aa581e
4
+ data.tar.gz: 7c613274ea82fbb0d69d202545cb4d9e277981f4a6993bdcdbcfef0464b79d18
5
5
  SHA512:
6
- metadata.gz: d7607e68a92ac9d968febb1179c60a291550306b56ada967fd29f2c9f1dd413f6d18981895b358b5a0ac34bdaae0291d1ed7e452a76dbe52329582fc11b11dc5
7
- data.tar.gz: 07b078aad8c0e3d0b9cf1b509b9f21dc4a2cbdbf3101f4997a079349fb7272848fe58080a18ab4e79c4b13b07061cbe61dfb43c511b8630eb73c077a6ecb866b
6
+ metadata.gz: f70b2e7878236a1c819ac5019be8f6bb7d1f1fe2a05c6e2103e607ca07cc5acd42c34ae8ec70377e7cccad915d9667b7a8741e1dc0d3142065c91333b571ad6c
7
+ data.tar.gz: b703677d623fc0ca36df6c4aa80d8f1199a0ee94f79dcbb517b4d80827c1434d42524810eead7aac8abb8d1082b7adea4e03811e66d6d6617ec3693ec62328fc
data/CHANGES.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-10-29 v0.9.0
4
+
5
+ ### Features
6
+
7
+ - Added `reflow:` keyword argument to `Kramdown::ANSI::Width.wrap`. When set to
8
+ `false`, each line is wrapped independently, preserving original line breaks.
9
+ When set to `true` (the default), newlines are treated as whitespace and the
10
+ text is re-flowed as a single paragraph.
11
+
12
+ ### Testing
13
+
14
+ - Added specs in `spec/kramdown/ansi/width_spec.rb` covering both `reflow:
15
+ false` (line-preserving wrap) and default `reflow: true` (paragraph reflow)
16
+ behavior.
17
+
18
+ ### Documentation
19
+
20
+ - Updated YARD documentation for `Kramdown::ANSI::Width.wrap` to describe the
21
+ new `reflow:` option.
22
+
23
+ ## 2026-10-29 v0.8.0
24
+
25
+ ### Fixes
26
+
27
+ - Fixed table width overflow by restructuring column width calculation from
28
+ per-row to per-table in `convert_table`, ensuring `Terminal::Table` is not
29
+ forced wider than the terminal by a single row's content distribution.
30
+ - Fixed a bug in `Kramdown::ANSI::Width.wrap` where a newline was incorrectly
31
+ prepended to the first token when it exceeded the target length, eliminating
32
+ phantom blank lines in narrow table cells.
33
+ - Subtracted table chrome (border, padding, and separator overhead) from the
34
+ column width budget in `convert_table` to prevent tables from exceeding
35
+ width.
36
+
37
+ ### Improvements
38
+
39
+ - Simplified `convert_tr` to read pre-computed column widths and wrap each cell
40
+ to its fixed budget.
41
+
42
+ ### Build & CI
43
+
44
+ - Updated the minimum version of the `gem_hadar` development dependency to
45
+ **2.21.0**.
46
+ - Bumped `rubygems_version` to **4.0.23**.
47
+
48
+ ### Testing
49
+
50
+ - Regenerated `spec/assets/tables.ansi` golden file.
51
+
3
52
  ## 2026-10-22 v0.7.0
4
53
 
5
54
  ### Fixes
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: kramdown-ansi 0.7.0 ruby lib
2
+ # stub: kramdown-ansi 0.9.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "kramdown-ansi".freeze
6
- s.version = "0.7.0".freeze
6
+ s.version = "0.9.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -18,13 +18,13 @@ Gem::Specification.new do |s|
18
18
  s.licenses = ["MIT".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "Kramdown-ansi - Output markdown in the terminal with ANSI escape sequences".freeze, "--main".freeze, "README.md".freeze]
20
20
  s.required_ruby_version = Gem::Requirement.new(">= 3.1".freeze)
21
- s.rubygems_version = "4.0.22".freeze
21
+ s.rubygems_version = "4.0.23".freeze
22
22
  s.summary = "Output markdown in the terminal with ANSI escape sequences".freeze
23
23
  s.test_files = ["spec/kramdown/ansi/pager_spec.rb".freeze, "spec/kramdown/ansi/styles_spec.rb".freeze, "spec/kramdown/ansi/width_spec.rb".freeze, "spec/kramdown/ansi_golden_spec.rb".freeze, "spec/kramdown/ansi_spec.rb".freeze, "spec/kramdown/ansi_table_spec.rb".freeze, "spec/kramdown/render_spec.rb".freeze, "spec/spec_helper.rb".freeze]
24
24
 
25
25
  s.specification_version = 4
26
26
 
27
- s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.20.1".freeze])
27
+ s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.21.0".freeze])
28
28
  s.add_development_dependency(%q<all_images>.freeze, ["~> 0.4".freeze])
29
29
  s.add_development_dependency(%q<rspec>.freeze, ["~> 3.2".freeze])
30
30
  s.add_development_dependency(%q<debug>.freeze, [">= 0".freeze])
@@ -54,12 +54,16 @@ module Kramdown::ANSI::Width
54
54
  # @param text [String] the text to wrap
55
55
  # @option percentage [Numeric] the percentage of terminal width to use
56
56
  # @option length [Integer] the explicit character width to wrap to
57
+ # @option reflow [Boolean] when true (default), newlines are treated as
58
+ # whitespace and the text is re-flowed as a single paragraph; when
59
+ # false, each line is wrapped independently and original line breaks
60
+ # are preserved
57
61
  # @return [String] the wrapped string, preserving ANSI escape sequences
58
62
  # @raise [ArgumentError] if neither `percentage` nor `length` is provided
59
63
  # @example
60
64
  # Kramdown::ANSI::Width.wrap("Hello world!", percentage: 50)
61
65
  # # => "Hello\nworld!"
62
- def wrap(text, percentage: nil, length: nil)
66
+ def wrap(text, percentage: nil, length: nil, reflow: true)
63
67
  percentage.nil? ^ length.nil? or
64
68
  raise ArgumentError, "either pass percentage or length argument"
65
69
  percentage and length ||= width(percentage:)
@@ -70,6 +74,9 @@ module Kramdown::ANSI::Width
70
74
  return text
71
75
  end
72
76
  length = length.clamp(1..)
77
+ unless reflow
78
+ return text.lines.map { |l| wrap(l.chomp, length:, reflow: true) } * ?\n
79
+ end
73
80
  length_without_ansi = 0
74
81
  result = +''
75
82
  text.scan(/(?<nws>\S+)|(?<ws>\s)/) do
@@ -78,7 +85,9 @@ module Kramdown::ANSI::Width
78
85
  if $~[:nws]
79
86
  if length_without_ansi + token_length > length
80
87
  length_without_ansi = token_length
81
- if result[-1] == ' '
88
+ if result.empty?
89
+ result = token_value
90
+ elsif result[-1] == ' '
82
91
  result[-1, 1] = "\n#{token_value}"
83
92
  else
84
93
  result << "\n#{token_value}"
data/lib/kramdown/ansi.rb CHANGED
@@ -472,6 +472,29 @@ class Kramdown::ANSI < Kramdown::Converter::Base
472
472
  end
473
473
  return newline rows.join("\n")
474
474
  end
475
+ # Compute per-column budgets from the widest cell in each column
476
+ # across all rows. Terminal::Table sizes each column to its widest
477
+ # cell, so per-row ratios would produce columns wider than the
478
+ # terminal budget.
479
+ max_w = Hash.new(0)
480
+ el.children.each do |section|
481
+ next unless %i[thead tbody tfoot].include?(section.type)
482
+ section.children.each do |row|
483
+ row.children.each_with_index do |cell, i|
484
+ max_w[i] = [max_w[i], display_width(convert(cell, opts).strip)].max
485
+ end
486
+ end
487
+ end
488
+ n = max_w.keys.map(&:to_i).max.to_i + 1
489
+ total = max_w.values.sum
490
+ budget = width(percentage: 90) - (3 * n + 1)
491
+ col_w = if total.zero?
492
+ same = (budget.to_f / n).to_i
493
+ Array.new(n, same)
494
+ else
495
+ (0...n).map { |i| ((max_w[i].to_f / total) * budget).to_i }
496
+ end
497
+ opts[:table_col_widths] = col_w
475
498
  table = Terminal::Table.new
476
499
  table.style = {
477
500
  all_separators: true,
@@ -533,33 +556,23 @@ class Kramdown::ANSI < Kramdown::Converter::Base
533
556
  ''
534
557
  end
535
558
 
536
- # The convert_tr method processes a table row element by calculating column
537
- # widths based on content size, wrapping text to fit within the terminal
538
- # width, and adding the formatted row to the terminal table instance.
559
+ # The convert_tr method processes a table row element by wrapping each cell
560
+ # to the pre-computed column width and adding the formatted row to the
561
+ # terminal table instance.
539
562
  #
540
563
  # @param el [Kramdown::Element] the table row element to convert
541
564
  # @param opts [Hash] conversion options containing context for processing
565
+ # (must include :table and :table_col_widths)
542
566
  # @return [String] an empty string to indicate successful processing and
543
567
  # addition of the row to the table instance
544
568
  def convert_tr(el, opts)
545
569
  return '' if el.children.empty?
546
- cols = el.children.map do |c|
570
+ col_w = opts[:table_col_widths]
571
+ cols = el.children.each_with_index.map do |c, i|
547
572
  stripped = convert(c, opts).strip
548
- stripped.empty? ? ' ' : stripped
573
+ stripped.empty? ? ' ' : wrap(stripped, length: col_w[i])
549
574
  end
550
- return '' if cols.empty?
551
- col_widths = cols.map { |c| display_width(c) }
552
- col_widths_sum = col_widths.sum
553
- full_width = width(percentage: 90)
554
- ratios = cols.each_with_index.map do |c, i|
555
- if col_widths_sum.zero?
556
- 1.0 / cols.size
557
- else
558
- col_widths[i] / col_widths_sum.to_f
559
- end
560
- end
561
- wrapped_cols = cols.zip(ratios).map { |c, r| wrap(c, length: r * full_width) }
562
- opts[:table] << wrapped_cols
575
+ opts[:table] << cols
563
576
  ''
564
577
  end
565
578
 
@@ -662,5 +675,4 @@ class Kramdown::ANSI < Kramdown::Converter::Base
662
675
  @ansi_styles.apply(name, &block)
663
676
  end
664
677
  end
665
-
666
678
  Kramdown::Converter.const_set(:Ansi, Kramdown::ANSI)
@@ -5,7 +5,7 @@
5
5
  # by hand; it is rewritten on every `rake version` invocation.
6
6
  module Kramdown
7
7
  # The current version of the Kramdown gem as a String.
8
- VERSION = '0.7.0'
8
+ VERSION = '0.9.0'
9
9
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
10
10
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
11
11
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -286,60 +286,61 @@ a | b
286
286
 
287
287
  ────────────────────────────────────────────────────────────────────────────────
288
288
 
289
- ╭──────┬────────────────────────────────────────────────────────────────╮
290
- │ Case │ What It Stresses │
291
- ├──────┼────────────────────────────────────────────────────────────────┤
292
- │ 01 │ Column count validation / overflow / underflow │
293
- ├──────┼────────────────────────────────────────────────────────────────┤
294
- │ 02 │ Pipe-stripping logic (leading/trailing optional) │
295
- ├──────┼────────────────────────────────────────────────────────────────┤
296
- │ 03 │ Escape sequence handling (\|) │
297
- ├──────┼────────────────────────────────────────────────────────────────┤
298
- │ 04 │ Empty-string cell rendering, border alignment │
299
- ├──────┼────────────────────────────────────────────────────────────────┤
300
- │ 05 │ Single-column edge (degenerate table) │
301
- ├──────┼────────────────────────────────────────────────────────────────┤
302
- │ 06 │ Zero data rows — does it draw a header-only box? │
303
- ├──────┼────────────────────────────────────────────────────────────────┤
304
- │ 07 │ Consecutive tables — does the second become data of the first? │
305
- ├──────┼────────────────────────────────────────────────────────────────┤
306
- │ 08 │ Unicode width calculation for column sizing │
307
- ├──────┼────────────────────────────────────────────────────────────────┤
308
- │ 09 │ Inline markdown rendering inside a table cell │
309
- ├──────┼────────────────────────────────────────────────────────────────┤
310
- │ 10 │ Alignment row with fewer columns than header │
311
- ├──────┼────────────────────────────────────────────────────────────────┤
312
- │ 11 │ Blank line splitting one table into two │
313
- ├──────┼────────────────────────────────────────────────────────────────┤
314
- │ 12 │ Whitespace normalization / trimming │
315
- ├──────┼────────────────────────────────────────────────────────────────┤
316
- │ 13 │ Raw HTML + <br> in cell context │
317
- ├──────┼────────────────────────────────────────────────────────────────┤
318
- │ 14 │ Cell with only whitespace — empty or not? │
319
- ├──────┼────────────────────────────────────────────────────────────────┤
320
- │ 15 │ Very wide table — does it wrap or overflow? │
321
- ├──────┼────────────────────────────────────────────────────────────────┤
322
- │ 16 │ Non-standard alignment markers │
323
- ├──────┼────────────────────────────────────────────────────────────────┤
324
- │ 17 │ Table at doc start, glued to following header │
325
- ├──────┼────────────────────────────────────────────────────────────────┤
326
- │ 18 │ Table glued to preceding header (no blank before) │
327
- ├──────┼────────────────────────────────────────────────────────────────┤
328
- │ 19 │ Single col, missing closing pipe on header │
329
- ├──────┼────────────────────────────────────────────────────────────────┤
330
- │ 20 │ Not a table: single pipe line │
331
- ├──────┼────────────────────────────────────────────────────────────────┤
332
- │ 21 │ Not a table: header + sep, no body, missing pipe │
333
- ├──────┼────────────────────────────────────────────────────────────────┤
334
- │ 22 │ Table after code block, no blank between │
335
- ├──────┼────────────────────────────────────────────────────────────────┤
336
- │ 23 │ Table after list item, no blank between │
337
- ├──────┼────────────────────────────────────────────────────────────────┤
338
- │ 24 │ No leading pipe on separator row │
339
- ├──────┼────────────────────────────────────────────────────────────────┤
340
- │ 25 │ No leading pipe on body row │
341
- ├──────┼────────────────────────────────────────────────────────────────┤
342
- │ 26 │ Indented table inside ordered list item │
343
- ├──────┼────────────────────────────────────────────────────────────────┤
344
- │ 27 │ Not a table just a pipe in a sentence │
345
- ╰──────┴────────────────────────────────────────────────────────────────╯
289
+ ╭──────┬─────────────────────────────────────────────────────────╮
290
+ │ Case │ What It Stresses │
291
+ ├──────┼─────────────────────────────────────────────────────────┤
292
+ │ 01 │ Column count validation / overflow / underflow │
293
+ ├──────┼─────────────────────────────────────────────────────────┤
294
+ │ 02 │ Pipe-stripping logic (leading/trailing optional) │
295
+ ├──────┼─────────────────────────────────────────────────────────┤
296
+ │ 03 │ Escape sequence handling (\|) │
297
+ ├──────┼─────────────────────────────────────────────────────────┤
298
+ │ 04 │ Empty-string cell rendering, border alignment │
299
+ ├──────┼─────────────────────────────────────────────────────────┤
300
+ │ 05 │ Single-column edge (degenerate table) │
301
+ ├──────┼─────────────────────────────────────────────────────────┤
302
+ │ 06 │ Zero data rows — does it draw a header-only box? │
303
+ ├──────┼─────────────────────────────────────────────────────────┤
304
+ │ 07 │ Consecutive tables — does the second become data of the │
305
+ │ │ first? │
306
+ ├──────┼─────────────────────────────────────────────────────────┤
307
+ │ 08 │ Unicode width calculation for column sizing │
308
+ ├──────┼─────────────────────────────────────────────────────────┤
309
+ │ 09 │ Inline markdown rendering inside a table cell │
310
+ ├──────┼─────────────────────────────────────────────────────────┤
311
+ │ 10 │ Alignment row with fewer columns than header │
312
+ ├──────┼─────────────────────────────────────────────────────────┤
313
+ │ 11 │ Blank line splitting one table into two │
314
+ ├──────┼─────────────────────────────────────────────────────────┤
315
+ │ 12 │ Whitespace normalization / trimming │
316
+ ├──────┼─────────────────────────────────────────────────────────┤
317
+ │ 13 │ Raw HTML + <br> in cell context │
318
+ ├──────┼─────────────────────────────────────────────────────────┤
319
+ │ 14 │ Cell with only whitespace — empty or not? │
320
+ ├──────┼─────────────────────────────────────────────────────────┤
321
+ │ 15 │ Very wide table — does it wrap or overflow? │
322
+ ├──────┼─────────────────────────────────────────────────────────┤
323
+ │ 16 │ Non-standard alignment markers │
324
+ ├──────┼─────────────────────────────────────────────────────────┤
325
+ │ 17 │ Table at doc start, glued to following header │
326
+ ├──────┼─────────────────────────────────────────────────────────┤
327
+ │ 18 │ Table glued to preceding header (no blank before) │
328
+ ├──────┼─────────────────────────────────────────────────────────┤
329
+ │ 19 │ Single col, missing closing pipe on header │
330
+ ├──────┼─────────────────────────────────────────────────────────┤
331
+ │ 20 │ Not a table: single pipe line │
332
+ ├──────┼─────────────────────────────────────────────────────────┤
333
+ │ 21 │ Not a table: header + sep, no body, missing pipe │
334
+ ├──────┼─────────────────────────────────────────────────────────┤
335
+ │ 22 │ Table after code block, no blank between │
336
+ ├──────┼─────────────────────────────────────────────────────────┤
337
+ │ 23 │ Table after list item, no blank between │
338
+ ├──────┼─────────────────────────────────────────────────────────┤
339
+ │ 24 │ No leading pipe on separator row │
340
+ ├──────┼─────────────────────────────────────────────────────────┤
341
+ │ 25 │ No leading pipe on body row │
342
+ ├──────┼─────────────────────────────────────────────────────────┤
343
+ │ 26 │ Indented table inside ordered list item │
344
+ ├──────┼─────────────────────────────────────────────────────────┤
345
+ │ 27 │ Not a table just a pipe in a sentence │
346
+ ╰──────┴─────────────────────────────────────────────────────────╯
@@ -43,6 +43,18 @@ describe Kramdown::ANSI::Width do
43
43
  "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA"
44
44
  )
45
45
  end
46
+
47
+ it 'wraps each line independently with reflow: false' do
48
+ text = "aaaa bbbb cccc\ndddd eeee"
49
+ result = described_class.wrap(text, length: 9, reflow: false)
50
+ expect(result).to eq("aaaa bbbb\ncccc\ndddd eeee")
51
+ end
52
+
53
+ it 'reflows all lines as one paragraph by default' do
54
+ text = "aaaa\nbbbb cccc"
55
+ result = described_class.wrap(text, length: 9)
56
+ expect(result).to eq("aaaa\nbbbb\ncccc")
57
+ end
46
58
  end
47
59
 
48
60
  describe '.truncate' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kramdown-ansi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 2.20.1
18
+ version: 2.21.0
19
19
  type: :development
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 2.20.1
25
+ version: 2.21.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: all_images
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -233,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
233
233
  - !ruby/object:Gem::Version
234
234
  version: '0'
235
235
  requirements: []
236
- rubygems_version: 4.0.22
236
+ rubygems_version: 4.0.23
237
237
  specification_version: 4
238
238
  summary: Output markdown in the terminal with ANSI escape sequences
239
239
  test_files: