kramdown-ansi 0.5.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/.utilsrc +0 -3
- data/CHANGES.md +94 -0
- data/README.md +10 -5
- data/Rakefile +20 -1
- data/kramdown-ansi.gemspec +7 -6
- data/lib/kramdown/ansi/styles.rb +9 -4
- data/lib/kramdown/ansi/width.rb +89 -17
- data/lib/kramdown/ansi.rb +101 -6
- data/lib/kramdown/version.rb +7 -2
- data/scripts/capture-ansi +3 -0
- data/spec/assets/html.ansi +28 -0
- data/spec/assets/html.md +28 -0
- data/spec/assets/tables.ansi +345 -0
- data/spec/assets/tables.md +241 -0
- data/spec/kramdown/ansi/width_spec.rb +90 -0
- data/spec/kramdown/ansi_golden_spec.rb +19 -0
- data/spec/kramdown/ansi_spec.rb +7 -3
- data/spec/kramdown/render_spec.rb +156 -0
- data/spec/spec_helper.rb +33 -0
- data/tmp/.gitkeep +0 -0
- metadata +28 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d35fcfbc735d86a3f58f59c1eaded0ebdc4f706db3cedb0c807aa6f9f420fe8
|
|
4
|
+
data.tar.gz: cd32746d6d592ee48e59bda8211adf670bdef612a5c65da97e114234ef8f0e83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7607e68a92ac9d968febb1179c60a291550306b56ada967fd29f2c9f1dd413f6d18981895b358b5a0ac34bdaae0291d1ed7e452a76dbe52329582fc11b11dc5
|
|
7
|
+
data.tar.gz: 07b078aad8c0e3d0b9cf1b509b9f21dc4a2cbdbf3101f4997a079349fb7272848fe58080a18ab4e79c4b13b07061cbe61dfb43c511b8630eb73c077a6ecb866b
|
data/.utilsrc
CHANGED
|
@@ -41,7 +41,6 @@ code_indexer do
|
|
|
41
41
|
date
|
|
42
42
|
debug
|
|
43
43
|
diff-lcs
|
|
44
|
-
docile
|
|
45
44
|
erb
|
|
46
45
|
excon
|
|
47
46
|
fileutils
|
|
@@ -79,8 +78,6 @@ code_indexer do
|
|
|
79
78
|
search_ui
|
|
80
79
|
shellwords
|
|
81
80
|
simplecov
|
|
82
|
-
simplecov-html
|
|
83
|
-
simplecov_json_formatter
|
|
84
81
|
stringio
|
|
85
82
|
sync
|
|
86
83
|
term-ansicolor
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-10-22 v0.7.0
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- Fixed GFM table parsing for non-header tables by rendering `:table` elements
|
|
8
|
+
without a `:thead` as plain text, as GFM requires a header row and delimiter
|
|
9
|
+
row for a valid table.
|
|
10
|
+
- Corrected table continuation detection in `self.parse` by checking if the
|
|
11
|
+
next line contains a `|`, resolving issues where delimiter or body rows
|
|
12
|
+
lacked a leading pipe (e.g., `:---|---:`, `1 | 2`).
|
|
13
|
+
|
|
14
|
+
### Improvements
|
|
15
|
+
|
|
16
|
+
- Replaced `Regexp.last_match` calls with direct captured group assignments
|
|
17
|
+
(`$1`, `$2`) in `ansi.rb` for clarity.
|
|
18
|
+
|
|
19
|
+
### Build & CI
|
|
20
|
+
|
|
21
|
+
- Added `scripts/capture-ansi` and `.envrc` for local golden file regeneration.
|
|
22
|
+
- Added `.envrc` and `scripts/*` to `package_ignore` in `Rakefile`.
|
|
23
|
+
|
|
24
|
+
### Testing
|
|
25
|
+
|
|
26
|
+
- Added Case 27 (pipe in a sentence) to `spec/assets/tables.md` and regenerated
|
|
27
|
+
`spec/assets/tables.ansi`.
|
|
28
|
+
|
|
29
|
+
## 2026-10-21 v0.6.0
|
|
30
|
+
|
|
31
|
+
### Features
|
|
32
|
+
|
|
33
|
+
- Add support for nested blockquotes with cycling typographic quotation marks
|
|
34
|
+
based on nesting depth.
|
|
35
|
+
- Add ANSI styling support for HTML elements: `ins`/`u` (underline), `mark`
|
|
36
|
+
(negative/inverse), `tt` (code), `small` (faint), and passthrough for
|
|
37
|
+
`span`/`sub`/`sup`.
|
|
38
|
+
- Add strikethrough support for `del` and `s` HTML elements.
|
|
39
|
+
- Introduce `ProtectEnvVars` module in test helper to isolate environment
|
|
40
|
+
variable changes during tests tagged with `:protect_env`.
|
|
41
|
+
|
|
42
|
+
### Fixes
|
|
43
|
+
|
|
44
|
+
- Fix GFM table parsing by inserting required blank lines before and after pipe
|
|
45
|
+
tables to ensure correct recognition by `kramdown`.
|
|
46
|
+
- Fix `Integer#size` regression in table column width calculation by using
|
|
47
|
+
`display_width` directly instead of `.size`, ensuring correct column ratios
|
|
48
|
+
on Ruby 4.
|
|
49
|
+
- Fix empty table cell rendering by replacing empty strings with a single space
|
|
50
|
+
and falling back to equal-width splits when total width is zero.
|
|
51
|
+
- Fix typo in `convert_html_element` where `e.value` was used instead of
|
|
52
|
+
`el.value` for the `del`/`s` branch.
|
|
53
|
+
- Make `Token` definition in `Kramdown::ANSI::Width` compatible with Ruby
|
|
54
|
+
versions lacking `Data` by providing a `Struct`-based fallback.
|
|
55
|
+
|
|
56
|
+
### Improvements
|
|
57
|
+
|
|
58
|
+
- Rewrite `Kramdown::ANSI::Width#truncate` to be ANSI-aware, correctly handling
|
|
59
|
+
escape sequences, wide characters (emojis), and zero-width characters using
|
|
60
|
+
`display_width`.
|
|
61
|
+
- Optimize `wrap` and `truncate` methods by using `scan` instead of `gsub` to
|
|
62
|
+
reduce allocation.
|
|
63
|
+
- Generalize the `capture:tables` Rake task to `capture:ansi` to regenerate
|
|
64
|
+
golden files for all `spec/assets/*.md` fixtures.
|
|
65
|
+
- Update `README.md` style examples to include `blockquote` and add a nested
|
|
66
|
+
dialog example.
|
|
67
|
+
|
|
68
|
+
### Build & CI
|
|
69
|
+
|
|
70
|
+
- Add `tins` gem (`~> 1.61`) as a runtime dependency.
|
|
71
|
+
- Upgrade `gem_hadar` development dependency to `>= 2.20.1`.
|
|
72
|
+
- Update `rubygems_version` in gemspec to **4.0.22**.
|
|
73
|
+
- Remove unused gems (`docile`, `simplecov-html`, `simplecov_json_formatter`)
|
|
74
|
+
from `.utilsrc`.
|
|
75
|
+
- Add `git config --global --add safe.directory /work` to Docker test script.
|
|
76
|
+
- Use `nproc` instead of `getconf _NPROCESSORS_ONLN` for CPU count in Docker
|
|
77
|
+
test script.
|
|
78
|
+
- Add `tmp/.gitkeep` to track the empty `tmp` directory.
|
|
79
|
+
|
|
80
|
+
### Testing
|
|
81
|
+
|
|
82
|
+
- Add `spec/kramdown/render_spec.rb` to test `Kramdown::ANSI.parse` for
|
|
83
|
+
headers, inline emphasis, code blocks, blockquotes, horizontal rules, lists,
|
|
84
|
+
links, images, and paragraphs.
|
|
85
|
+
- Add `spec/assets/tables.md` and `spec/assets/tables.ansi` golden files
|
|
86
|
+
covering 26 edge cases for table rendering.
|
|
87
|
+
- Add `spec/kramdown/ansi_golden_spec.rb` for output regression testing against
|
|
88
|
+
golden files.
|
|
89
|
+
- Add `spec/assets/html.md` and `spec/assets/html.ansi` golden files covering
|
|
90
|
+
HTML element styling.
|
|
91
|
+
- Add 9 new specs for `Kramdown::ANSI::Width#truncate` covering wide emojis,
|
|
92
|
+
empty strings, pure ANSI, zero-width characters, newline preservation,
|
|
93
|
+
multi-cell ellipsis, nested-style reset, and post-cut escape omission.
|
|
94
|
+
- Stub `Tins::Terminal.columns` to 80 in `ansi_spec.rb` and `render_spec.rb`
|
|
95
|
+
for deterministic output.
|
|
96
|
+
|
|
3
97
|
## 2026-03-21 v0.5.0
|
|
4
98
|
|
|
5
99
|
- Added `spec/kramdown/ansi_table_spec.rb` to the test suite and gemspec file
|
data/README.md
CHANGED
|
@@ -47,10 +47,11 @@ You can customize the ANSI styles by passing an `ansi_styles` option:
|
|
|
47
47
|
require 'kramdown/ansi'
|
|
48
48
|
|
|
49
49
|
ansi_styles = {
|
|
50
|
-
header:
|
|
51
|
-
strong:
|
|
52
|
-
em:
|
|
53
|
-
code:
|
|
50
|
+
header: [:bold, :magenta],
|
|
51
|
+
strong: :green,
|
|
52
|
+
em: :yellow,
|
|
53
|
+
code: [:cyan, :underline],
|
|
54
|
+
blockquote: :blue,
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
puts Kramdown::ANSI.parse(markdown, ansi_styles:)
|
|
@@ -65,7 +66,7 @@ methods:
|
|
|
65
66
|
require 'kramdown/ansi'
|
|
66
67
|
|
|
67
68
|
# Using from_json method
|
|
68
|
-
json_config = '{"header": ["bold", "magenta"], "code": ["cyan", "underline"]}'
|
|
69
|
+
json_config = '{"header": ["bold", "magenta"], "code": ["cyan", "underline"], "blockquote": ["magenta", "underline"]}'
|
|
69
70
|
ansi_styles = Kramdown::ANSI::Styles.from_json(json_config)
|
|
70
71
|
|
|
71
72
|
# Apply to parsing
|
|
@@ -177,6 +178,10 @@ The homepage of this library is located at
|
|
|
177
178
|
|
|
178
179
|

|
|
179
180
|
|
|
181
|
+
Dialog:
|
|
182
|
+
> What's that, Fluffy?
|
|
183
|
+
>> That's my new friend.
|
|
184
|
+
|
|
180
185
|
---
|
|
181
186
|
|
|
182
187
|
This is the end.
|
data/Rakefile
CHANGED
|
@@ -19,7 +19,7 @@ GemHadar do
|
|
|
19
19
|
'.yardoc', 'doc', 'tags', 'errors.lst', 'cscope.out', 'coverage', 'tmp',
|
|
20
20
|
'yard'
|
|
21
21
|
package_ignore '.all_images.yml', '.tool-versions', '.gitignore', 'VERSION',
|
|
22
|
-
'.rspec', '.github'
|
|
22
|
+
'.rspec', '.github', '.envrc', 'scripts/*'
|
|
23
23
|
readme 'README.md'
|
|
24
24
|
|
|
25
25
|
executables << 'md' << 'git-md'
|
|
@@ -37,6 +37,7 @@ GemHadar do
|
|
|
37
37
|
dependency 'term-ansicolor', '~> 1.11'
|
|
38
38
|
dependency 'kramdown-parser-gfm', '~> 1.1'
|
|
39
39
|
dependency 'terminal-table', '~> 4.0'
|
|
40
|
+
dependency 'tins', '~> 1.61'
|
|
40
41
|
dependency 'json', '~> 2.0'
|
|
41
42
|
dependency 'unicode-display_width', '~> 3.0'
|
|
42
43
|
development_dependency 'all_images', '~> 0.4'
|
|
@@ -48,3 +49,21 @@ GemHadar do
|
|
|
48
49
|
|
|
49
50
|
clobber 'coverage'
|
|
50
51
|
end
|
|
52
|
+
|
|
53
|
+
namespace :capture do
|
|
54
|
+
desc 'Capture golden file ansi output for all spec/assets/*.md files'
|
|
55
|
+
task :ansi do
|
|
56
|
+
require 'kramdown/ansi'
|
|
57
|
+
module Tins::Terminal
|
|
58
|
+
def self.columns
|
|
59
|
+
80
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
Dir[File.join('spec', 'assets', '*.md')].sort.each do |md|
|
|
63
|
+
out_path = md.sub('.md', '.ansi')
|
|
64
|
+
out = Kramdown::ANSI.parse(File.read(md))
|
|
65
|
+
File.write(out_path, out)
|
|
66
|
+
puts "Captured #{out_path} (#{out.bytesize} bytes)"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/kramdown-ansi.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: kramdown-ansi 0.
|
|
2
|
+
# stub: kramdown-ansi 0.7.0 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "kramdown-ansi".freeze
|
|
6
|
-
s.version = "0.
|
|
6
|
+
s.version = "0.7.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]
|
|
@@ -13,18 +13,18 @@ Gem::Specification.new do |s|
|
|
|
13
13
|
s.email = "flori@ping.de".freeze
|
|
14
14
|
s.executables = ["md".freeze, "git-md".freeze]
|
|
15
15
|
s.extra_rdoc_files = ["README.md".freeze, "lib/kramdown/ansi.rb".freeze, "lib/kramdown/ansi/pager.rb".freeze, "lib/kramdown/ansi/styles.rb".freeze, "lib/kramdown/ansi/width.rb".freeze, "lib/kramdown/version.rb".freeze]
|
|
16
|
-
s.files = [".utilsrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/git-md".freeze, "bin/md".freeze, "img/git-md.png".freeze, "img/md.png".freeze, "kramdown-ansi.gemspec".freeze, "lib/kramdown/ansi.rb".freeze, "lib/kramdown/ansi/pager.rb".freeze, "lib/kramdown/ansi/styles.rb".freeze, "lib/kramdown/ansi/width.rb".freeze, "lib/kramdown/version.rb".freeze, "spec/assets/README.ansi".freeze, "spec/assets/kitten.jpg".freeze, "spec/kramdown/ansi/pager_spec.rb".freeze, "spec/kramdown/ansi/styles_spec.rb".freeze, "spec/kramdown/ansi/width_spec.rb".freeze, "spec/kramdown/ansi_spec.rb".freeze, "spec/kramdown/ansi_table_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
16
|
+
s.files = [".utilsrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/git-md".freeze, "bin/md".freeze, "img/git-md.png".freeze, "img/md.png".freeze, "kramdown-ansi.gemspec".freeze, "lib/kramdown/ansi.rb".freeze, "lib/kramdown/ansi/pager.rb".freeze, "lib/kramdown/ansi/styles.rb".freeze, "lib/kramdown/ansi/width.rb".freeze, "lib/kramdown/version.rb".freeze, "scripts/capture-ansi".freeze, "spec/assets/README.ansi".freeze, "spec/assets/html.ansi".freeze, "spec/assets/html.md".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/tables.ansi".freeze, "spec/assets/tables.md".freeze, "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, "tmp/.gitkeep".freeze]
|
|
17
17
|
s.homepage = "https://github.com/flori/kramdown-ansi".freeze
|
|
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.
|
|
21
|
+
s.rubygems_version = "4.0.22".freeze
|
|
22
22
|
s.summary = "Output markdown in the terminal with ANSI escape sequences".freeze
|
|
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_spec.rb".freeze, "spec/kramdown/ansi_table_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
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.
|
|
27
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.20.1".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])
|
|
@@ -32,6 +32,7 @@ Gem::Specification.new do |s|
|
|
|
32
32
|
s.add_runtime_dependency(%q<term-ansicolor>.freeze, ["~> 1.11".freeze])
|
|
33
33
|
s.add_runtime_dependency(%q<kramdown-parser-gfm>.freeze, ["~> 1.1".freeze])
|
|
34
34
|
s.add_runtime_dependency(%q<terminal-table>.freeze, ["~> 4.0".freeze])
|
|
35
|
+
s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.61".freeze])
|
|
35
36
|
s.add_runtime_dependency(%q<json>.freeze, ["~> 2.0".freeze])
|
|
36
37
|
s.add_runtime_dependency(%q<unicode-display_width>.freeze, ["~> 3.0".freeze])
|
|
37
38
|
end
|
data/lib/kramdown/ansi/styles.rb
CHANGED
|
@@ -24,10 +24,15 @@ class Kramdown::ANSI::Styles
|
|
|
24
24
|
# configuration.
|
|
25
25
|
def initialize
|
|
26
26
|
@default_ansi_styles = {
|
|
27
|
-
header:
|
|
28
|
-
strong:
|
|
29
|
-
em:
|
|
30
|
-
code:
|
|
27
|
+
header: %i[ bold underline ],
|
|
28
|
+
strong: :bold,
|
|
29
|
+
em: :italic,
|
|
30
|
+
code: :blue,
|
|
31
|
+
blockquote: :faint,
|
|
32
|
+
del: :strikethrough,
|
|
33
|
+
ins: :underline,
|
|
34
|
+
mark: :negative,
|
|
35
|
+
small: :faint,
|
|
31
36
|
}
|
|
32
37
|
@ansi_styles = @default_ansi_styles.dup
|
|
33
38
|
end
|
data/lib/kramdown/ansi/width.rb
CHANGED
|
@@ -19,20 +19,24 @@ module Kramdown::ANSI::Width
|
|
|
19
19
|
|
|
20
20
|
module_function
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# @return [Integer] the calculated width
|
|
26
|
-
def width(percentage: 100.0)
|
|
27
|
-
((Float(percentage) * Tins::Terminal.columns) / 100).floor
|
|
28
|
-
end
|
|
22
|
+
ANSI_ESCAPES = /\e\[.*?m|\e\].*?(\e|\a)\\?/
|
|
23
|
+
|
|
24
|
+
private_constant :ANSI_ESCAPES
|
|
29
25
|
|
|
30
26
|
# Removes ANSI escape sequences from the given string.
|
|
31
27
|
#
|
|
32
28
|
# @param line [String] the string potentially containing ANSI escape codes.
|
|
33
29
|
# @return [String] the string with all ANSI escape sequences removed.
|
|
34
30
|
def remove_ansi_escapes(line)
|
|
35
|
-
line.to_s.gsub(
|
|
31
|
+
line.to_s.gsub(ANSI_ESCAPES, '')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the width of the terminal in characters, given a percentage.
|
|
35
|
+
#
|
|
36
|
+
# @param percentage [Numeric] the percentage value (defaults to 100.0)
|
|
37
|
+
# @return [Integer] the calculated width
|
|
38
|
+
def width(percentage: 100.0)
|
|
39
|
+
((Float(percentage) * Tins::Terminal.columns) / 100).floor
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
# Calculates the display width of a string after removing ANSI escape
|
|
@@ -67,8 +71,8 @@ module Kramdown::ANSI::Width
|
|
|
67
71
|
end
|
|
68
72
|
length = length.clamp(1..)
|
|
69
73
|
length_without_ansi = 0
|
|
70
|
-
result = ''
|
|
71
|
-
text.
|
|
74
|
+
result = +''
|
|
75
|
+
text.scan(/(?<nws>\S+)|(?<ws>\s)/) do
|
|
72
76
|
token_value = $&
|
|
73
77
|
token_length = display_width(token_value)
|
|
74
78
|
if $~[:nws]
|
|
@@ -91,6 +95,28 @@ module Kramdown::ANSI::Width
|
|
|
91
95
|
result
|
|
92
96
|
end
|
|
93
97
|
|
|
98
|
+
if defined?(Data)
|
|
99
|
+
# A frozen data record for a single token in the `truncate` scan.
|
|
100
|
+
#
|
|
101
|
+
# +type+ is `:esc` (ANSI escape, zero display width) or
|
|
102
|
+
# `:txt` (visible character, display width from
|
|
103
|
+
# `Unicode::DisplayWidth`).
|
|
104
|
+
# +value+ is the raw matched substring.
|
|
105
|
+
# +length+ is the display width in terminal columns (0 for escapes).
|
|
106
|
+
Token = Data.define(:type, :value, :length)
|
|
107
|
+
else
|
|
108
|
+
class Token < Struct.new(:type, :value, :length)
|
|
109
|
+
def initialize(**opts)
|
|
110
|
+
opts.each do |key, value|
|
|
111
|
+
self.send("#{key}=", value)
|
|
112
|
+
end
|
|
113
|
+
freeze
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private_constant :Token
|
|
119
|
+
|
|
94
120
|
# Truncates a given string to a specified length or percentage. If the text
|
|
95
121
|
# is longer an ellipsis sequence is added at the end of the generated string,
|
|
96
122
|
# to indicate that a truncation has been performed.
|
|
@@ -105,13 +131,59 @@ module Kramdown::ANSI::Width
|
|
|
105
131
|
percentage.nil? ^ length.nil? or
|
|
106
132
|
raise ArgumentError, "either pass percentage or length argument"
|
|
107
133
|
percentage and length ||= width(percentage:)
|
|
108
|
-
ellipsis_length = ellipsis
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
ellipsis_length = display_width(remove_ansi_escapes(ellipsis))
|
|
135
|
+
found_esc = false
|
|
136
|
+
result = +''
|
|
137
|
+
|
|
138
|
+
return result if text.empty?
|
|
139
|
+
return result if length < ellipsis_length
|
|
140
|
+
|
|
141
|
+
current_length = 0
|
|
142
|
+
tokens = []
|
|
143
|
+
|
|
144
|
+
text.scan(/(?<esc>#{ANSI_ESCAPES})|(?<txt>.)/m) do
|
|
145
|
+
token = Token.new(
|
|
146
|
+
type: $~[:esc] ? :esc : :txt,
|
|
147
|
+
value: $&,
|
|
148
|
+
length: $~[:esc] ? 0 : display_width($&)
|
|
149
|
+
)
|
|
150
|
+
tokens << token
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
while token = tokens.shift
|
|
154
|
+
if token.type == :esc
|
|
155
|
+
found_esc = true
|
|
156
|
+
result << token.value
|
|
157
|
+
next
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if current_length + token.length == length && tokens.empty?
|
|
161
|
+
result << token.value
|
|
162
|
+
current_length += token.length
|
|
163
|
+
break
|
|
164
|
+
elsif current_length + token.length + ellipsis_length > length
|
|
165
|
+
result << ellipsis
|
|
166
|
+
break
|
|
167
|
+
elsif current_length + token.length + ellipsis_length == length
|
|
168
|
+
result << token.value
|
|
169
|
+
current_length += token.length
|
|
170
|
+
remaining_text_length = tokens.select { _1.type == :txt }.map(&:length).sum
|
|
171
|
+
if current_length + remaining_text_length == length
|
|
172
|
+
next
|
|
173
|
+
elsif current_length + remaining_text_length > length
|
|
174
|
+
result << ellipsis
|
|
175
|
+
break
|
|
176
|
+
else
|
|
177
|
+
next
|
|
178
|
+
end
|
|
179
|
+
else
|
|
180
|
+
current_length += token.length
|
|
181
|
+
result << token.value
|
|
182
|
+
end
|
|
183
|
+
|
|
115
184
|
end
|
|
185
|
+
|
|
186
|
+
result << reset if found_esc && !result.end_with?(reset)
|
|
187
|
+
result
|
|
116
188
|
end
|
|
117
189
|
end
|
data/lib/kramdown/ansi.rb
CHANGED
|
@@ -91,6 +91,34 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
|
91
91
|
options = { input: :mygfm, auto_ids: false, entity_output: :as_char }.merge(
|
|
92
92
|
options.transform_keys(&:to_sym)
|
|
93
93
|
)
|
|
94
|
+
# Insert blank line before GFM pipe tables glued to preceding
|
|
95
|
+
# content; kramdown requires it to parse the table correctly.
|
|
96
|
+
#
|
|
97
|
+
# Matches: a non-table line immediately followed by a table header,
|
|
98
|
+
# a valid GFM separator row, and at least one body row.
|
|
99
|
+
# A two-line "table" (header + separator, no body) is not a table
|
|
100
|
+
# per GFM, so no blank line is inserted.
|
|
101
|
+
source = source.gsub(
|
|
102
|
+
/
|
|
103
|
+
( [^|\n][^\n]*\n ) # $1: preceding line (not a pipe line)
|
|
104
|
+
( \|[^\n]*\n ) # $2: table header (starts with |)
|
|
105
|
+
( \|(?:\s*:?-+:?\s*\|)+\n ) # $3: GFM separator + newline
|
|
106
|
+
( \|[^\n]* ) # $4: first body row (starts with |)
|
|
107
|
+
\n
|
|
108
|
+
/x,
|
|
109
|
+
"\\1\n\\2\\3\\4\n"
|
|
110
|
+
)
|
|
111
|
+
# Insert blank line after a table's last row when glued to
|
|
112
|
+
# following content; kramdown requires it to close the table.
|
|
113
|
+
#
|
|
114
|
+
# A following line that contains a pipe is likely a table row
|
|
115
|
+
# (separator or body), so we keep it glued.
|
|
116
|
+
source = source.gsub(
|
|
117
|
+
/(\|[^\n]*\n)([^|\n][^\n]*)/
|
|
118
|
+
) do |match|
|
|
119
|
+
row, next_line = $1, $2
|
|
120
|
+
next_line.include?('|') ? match : "#{row}\n#{next_line}"
|
|
121
|
+
end
|
|
94
122
|
@doc = Kramdown::Document.new(source, options).to_ansi
|
|
95
123
|
end
|
|
96
124
|
|
|
@@ -253,6 +281,28 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
|
253
281
|
apply_style(:code) { el.value }
|
|
254
282
|
end
|
|
255
283
|
|
|
284
|
+
BLOCKQUOTE_QUOTES = [
|
|
285
|
+
%w[“ ”],
|
|
286
|
+
%w[‘ ’],
|
|
287
|
+
%w[ » « ],
|
|
288
|
+
%w[ › ‹ ],
|
|
289
|
+
].map(&:freeze).freeze
|
|
290
|
+
private_constant :BLOCKQUOTE_QUOTES
|
|
291
|
+
|
|
292
|
+
# Wraps the content of a blockquote level with the corresponding
|
|
293
|
+
# typographic quotation marks, cycling through a predefined set of
|
|
294
|
+
# quote pairs based on the nesting depth.
|
|
295
|
+
#
|
|
296
|
+
# @param level [ Integer ] the nesting level of the blockquote
|
|
297
|
+
# @yield [ String ] the inner content to be wrapped in quotation marks
|
|
298
|
+
# @return [ String ] the content enclosed by the appropriate opening and
|
|
299
|
+
# closing quote characters
|
|
300
|
+
def blockquote_quote(level, &block)
|
|
301
|
+
quote = BLOCKQUOTE_QUOTES[level % BLOCKQUOTE_QUOTES.size]
|
|
302
|
+
quote[0] + block.() + quote[1]
|
|
303
|
+
end
|
|
304
|
+
private :blockquote_quote
|
|
305
|
+
|
|
256
306
|
# The convert_blockquote method processes a blockquote element by applying
|
|
257
307
|
# proper formatting.
|
|
258
308
|
#
|
|
@@ -266,7 +316,19 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
|
266
316
|
# @return [String] the formatted blockquote content with quotation marks and
|
|
267
317
|
# proper newlines
|
|
268
318
|
def convert_blockquote(el, opts)
|
|
269
|
-
|
|
319
|
+
old_level = opts[:blockquote_level] ||= -1
|
|
320
|
+
level = opts[:blockquote_level] = old_level + 1
|
|
321
|
+
if level.zero?
|
|
322
|
+
newline(
|
|
323
|
+
blockquote_quote(level) {
|
|
324
|
+
apply_style(:blockquote) { inner(el, opts).strip }
|
|
325
|
+
}.gsub(/^/, ' ')
|
|
326
|
+
)
|
|
327
|
+
else
|
|
328
|
+
blockquote_quote(level) { inner(el, opts).strip }.gsub(/^/, ' ')
|
|
329
|
+
end
|
|
330
|
+
ensure
|
|
331
|
+
opts[:blockquote_level] = old_level
|
|
270
332
|
end
|
|
271
333
|
|
|
272
334
|
# The convert_hr method processes a horizontal rule element by generating a
|
|
@@ -373,6 +435,18 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
|
373
435
|
apply_style(:em) { inner(el, opts) }
|
|
374
436
|
elsif el.value == 'b' || el.value == 'strong'
|
|
375
437
|
apply_style(:strong) { inner(el, opts) }
|
|
438
|
+
elsif el.value == 'ins' || el.value == 'u'
|
|
439
|
+
apply_style(:ins) { inner(el, opts) }
|
|
440
|
+
elsif el.value == 'mark'
|
|
441
|
+
apply_style(:mark) { inner(el, opts) }
|
|
442
|
+
elsif el.value == 'del' || el.value == 's'
|
|
443
|
+
apply_style(:del) { inner(el, opts) }
|
|
444
|
+
elsif el.value == 'tt'
|
|
445
|
+
apply_style(:code) { inner(el, opts) }
|
|
446
|
+
elsif el.value == 'small'
|
|
447
|
+
apply_style(:small) { inner(el, opts) }
|
|
448
|
+
elsif %w[ span sub sup ].include?(el.value)
|
|
449
|
+
inner(el, opts)
|
|
376
450
|
else
|
|
377
451
|
''
|
|
378
452
|
end
|
|
@@ -386,6 +460,18 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
|
386
460
|
# @param opts [Hash] conversion options containing context for processing
|
|
387
461
|
# @return [String] the formatted table content with proper styling and newlines
|
|
388
462
|
def convert_table(el, opts)
|
|
463
|
+
# Kramdown's base parser is more permissive than GFM: it accepts
|
|
464
|
+
# any pipe-delimited line as a table row, even without a header.
|
|
465
|
+
# GFM mandates a header + separator row minimum, so a :table
|
|
466
|
+
# element without :thead is just pipe-separated prose.
|
|
467
|
+
unless el.children.any? { |c| c.type == :thead }
|
|
468
|
+
rows = el.children.select { |c| c.type == :tbody }.flat_map do |tbody|
|
|
469
|
+
tbody.children.map do |row|
|
|
470
|
+
row.children.map { |cell| convert(cell, opts).strip }.join(' | ')
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
return newline rows.join("\n")
|
|
474
|
+
end
|
|
389
475
|
table = Terminal::Table.new
|
|
390
476
|
table.style = {
|
|
391
477
|
all_separators: true,
|
|
@@ -457,13 +543,22 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
|
457
543
|
# addition of the row to the table instance
|
|
458
544
|
def convert_tr(el, opts)
|
|
459
545
|
return '' if el.children.empty?
|
|
460
|
-
cols = el.children.map
|
|
546
|
+
cols = el.children.map do |c|
|
|
547
|
+
stripped = convert(c, opts).strip
|
|
548
|
+
stripped.empty? ? ' ' : stripped
|
|
549
|
+
end
|
|
461
550
|
return '' if cols.empty?
|
|
462
|
-
col_widths
|
|
551
|
+
col_widths = cols.map { |c| display_width(c) }
|
|
463
552
|
col_widths_sum = col_widths.sum
|
|
464
|
-
full_width
|
|
465
|
-
ratios
|
|
466
|
-
|
|
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) }
|
|
467
562
|
opts[:table] << wrapped_cols
|
|
468
563
|
''
|
|
469
564
|
end
|
data/lib/kramdown/version.rb
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
# Version information for the Kramdown gem.
|
|
2
|
+
#
|
|
3
|
+
# This module is auto-generated by GemHadar and exposes the
|
|
4
|
+
# current release version as a set of constants. Do not edit it
|
|
5
|
+
# by hand; it is rewritten on every `rake version` invocation.
|
|
1
6
|
module Kramdown
|
|
2
|
-
# Kramdown
|
|
3
|
-
VERSION = '0.
|
|
7
|
+
# The current version of the Kramdown gem as a String.
|
|
8
|
+
VERSION = '0.7.0'
|
|
4
9
|
VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
|
|
5
10
|
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
|
6
11
|
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[1m[4mHTML Elements[0m[0m
|
|
2
|
+
|
|
3
|
+
[1m[4mEmphasis[0m[0m
|
|
4
|
+
|
|
5
|
+
[3mitalic i[0m and [3mitalic em[0m
|
|
6
|
+
|
|
7
|
+
[1m[4mStrong[0m[0m
|
|
8
|
+
|
|
9
|
+
[1mbold b[0m and [1mbold strong[0m
|
|
10
|
+
|
|
11
|
+
[1m[4mStrikethrough[0m[0m
|
|
12
|
+
|
|
13
|
+
[9mstrikethrough[0m and [9mhtml s tag[0m and [9mdel tag[0m
|
|
14
|
+
|
|
15
|
+
[1m[4mUnderline[0m[0m
|
|
16
|
+
|
|
17
|
+
[1m[4mCode[0m[0m
|
|
18
|
+
|
|
19
|
+
[34mtt text[0m
|
|
20
|
+
|
|
21
|
+
[1m[4mMark[0m[0m
|
|
22
|
+
|
|
23
|
+
[7mmarked[0m
|
|
24
|
+
|
|
25
|
+
[1m[4mPassthrough (no style)[0m[0m
|
|
26
|
+
|
|
27
|
+
span text and subscript and superscript
|
|
28
|
+
and [2msmall text[0m
|
data/spec/assets/html.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# HTML Elements
|
|
2
|
+
|
|
3
|
+
## Emphasis
|
|
4
|
+
|
|
5
|
+
<i>italic i</i> and <em>italic em</em>
|
|
6
|
+
|
|
7
|
+
## Strong
|
|
8
|
+
|
|
9
|
+
<b>bold b</b> and <strong>bold strong</strong>
|
|
10
|
+
|
|
11
|
+
## Strikethrough
|
|
12
|
+
|
|
13
|
+
~~strikethrough~~ and <s>html s tag</s> and <del>del tag</del>
|
|
14
|
+
|
|
15
|
+
## Underline
|
|
16
|
+
|
|
17
|
+
## Code
|
|
18
|
+
|
|
19
|
+
<tt>tt text</tt>
|
|
20
|
+
|
|
21
|
+
## Mark
|
|
22
|
+
|
|
23
|
+
<mark>marked</mark>
|
|
24
|
+
|
|
25
|
+
## Passthrough (no style)
|
|
26
|
+
|
|
27
|
+
<span>span text</span> and <sub>subscript</sub> and <sup>superscript</sup>
|
|
28
|
+
and <small>small text</small>
|