kramdown-ansi 0.0.4 → 0.1.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/CHANGES.md +23 -1
- data/README.md +68 -7
- data/Rakefile +1 -0
- data/bin/git-md +7 -1
- data/bin/md +7 -1
- data/kramdown-ansi.gemspec +7 -6
- data/lib/kramdown/ansi/pager.rb +14 -0
- data/lib/kramdown/ansi/styles.rb +129 -0
- data/lib/kramdown/ansi/width.rb +12 -0
- data/lib/kramdown/ansi.rb +358 -17
- data/lib/kramdown/version.rb +1 -1
- data/spec/kramdown/ansi/pager_spec.rb +1 -1
- data/spec/kramdown/ansi/styles_spec.rb +117 -0
- data/spec/kramdown/ansi/width_spec.rb +1 -1
- data/spec/kramdown/ansi_spec.rb +25 -2
- data/spec/spec_helper.rb +2 -6
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a6aa1a68093731d47d9bc460ea4a2e5d498ae9dd126c56402e87affd2b6e00e
|
4
|
+
data.tar.gz: 2a1acad5300c9f9efb133e277998937a983256e2a90a1e9932fc6a3af9db5ec4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7e3e9028eae28075319c67afe076404f3cc4164c7f8c476cf0f1ac1fe67f5eba3b4cd1d6418c24730a7ef65af947b929abe5d9f47338e573f5764545c16214b
|
7
|
+
data.tar.gz: 1c8ae07dbceba44a93195567e70fb0e4f03d4db8e21768d4107e44e9e14b5c82279c58b0fe2fd80890d5016bf29557ac286b7d3adbde5da2af31d7abc35b8d97
|
data/CHANGES.md
CHANGED
@@ -1,10 +1,32 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 2025-08-17 v0.1.0
|
4
|
+
|
5
|
+
- Added comprehensive ANSI style configuration support with
|
6
|
+
`Kramdown::ANSI::Styles` class
|
7
|
+
- Implemented `from_json` and `from_env_var` methods for loading styles
|
8
|
+
programmatically
|
9
|
+
- Added environment variable precedence for style configuration:
|
10
|
+
- `KRAMDOWN_ANSI_GIT_MD_STYLES` → `KRAMDOWN_ANSI_STYLES`
|
11
|
+
- `KRAMDOWN_ANSI_MD_STYLES` → `KRAMDOWN_ANSI_STYLES`
|
12
|
+
- Updated `md` and `git-md` executables to use new style configuration system
|
13
|
+
- Added **JSON** dependency to gemspec
|
14
|
+
- Enhanced README with documentation for style configuration
|
15
|
+
- Added extensive test coverage for `Styles` class functionality
|
16
|
+
- Supported custom ANSI styles via `ansi_styles` option in
|
17
|
+
`Kramdown::ANSI.parse`
|
18
|
+
- Implemented proper fallback logic for default styles when custom
|
19
|
+
configurations are applied
|
20
|
+
- Updated license reference to link to LICENSE file
|
21
|
+
- Updated `gem_hadar` development dependency from version **1.20** to **2.0**
|
22
|
+
- Simplified test coverage initialization with `GemHadar::SimpleCov.start`
|
23
|
+
- Updated RSpec syntax in spec files, changing `RSpec.describe` to `describe`
|
24
|
+
|
3
25
|
## 2025-07-09 v0.0.4
|
4
26
|
|
5
27
|
* **Pager Detection Update**
|
6
28
|
+ Updated pager detection to use the `--get` option with `git config` instead
|
7
|
-
of `get`.
|
29
|
+
of `get` in `git-md`.
|
8
30
|
|
9
31
|
## 2025-02-28 v0.0.3
|
10
32
|
|
data/README.md
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
## Description
|
4
4
|
|
5
|
-
Kramdown::ANSI
|
6
|
-
ANSI escape sequences
|
5
|
+
**Kramdown::ANSI**: A library for rendering Markdown(ish) documents with beautiful
|
6
|
+
ANSI escape sequences _in the terminal_.
|
7
7
|
|
8
8
|
## Installation (gem & bundler)
|
9
9
|
|
@@ -35,12 +35,59 @@ require 'kramdown/ansi'
|
|
35
35
|
puts Kramdown::ANSI.parse(markdown)
|
36
36
|
```
|
37
37
|
|
38
|
+
### Custom ANSI Styles
|
39
|
+
|
40
|
+
You can customize the ANSI styles by passing an `ansi_styles` option:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
require 'kramdown/ansi'
|
44
|
+
|
45
|
+
ansi_styles = {
|
46
|
+
header: [:bold, :magenta],
|
47
|
+
strong: :green,
|
48
|
+
em: :yellow,
|
49
|
+
code: [:cyan, :underline]
|
50
|
+
}
|
51
|
+
|
52
|
+
puts Kramdown::ANSI.parse(markdown, ansi_styles:)
|
53
|
+
```
|
54
|
+
|
55
|
+
### Custom ANSI Styles from JSON and JSON env vars
|
56
|
+
|
57
|
+
You can configure ANSI styles programmatically from JSON using the built-in
|
58
|
+
methods:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
require 'kramdown/ansi'
|
62
|
+
|
63
|
+
# Using from_json method
|
64
|
+
json_config = '{"header": ["bold", "magenta"], "code": ["cyan", "underline"]}'
|
65
|
+
ansi_styles = Kramdown::ANSI::Styles.from_json(json_config)
|
66
|
+
|
67
|
+
# Apply to parsing
|
68
|
+
puts Kramdown::ANSI.parse(markdown, ansi_styles:)
|
69
|
+
|
70
|
+
# Using from_env_var method
|
71
|
+
ENV['MY_STYLES'] = '{"em": "yellow", "strong": "green"}'
|
72
|
+
ansi_styles = Kramdown::ANSI::Styles.from_env_var('MY_STYLES')
|
73
|
+
|
74
|
+
# Apply to parsing
|
75
|
+
puts Kramdown::ANSI.parse(markdown, ansi_styles:)
|
76
|
+
```
|
77
|
+
|
38
78
|
## Executables
|
39
79
|
|
40
|
-
| Method | Description |
|
41
|
-
|
|
42
|
-
| `md` executable | Outputs [Markdown](https://spec.commonmark.org/current/) files with ANSI escape sequences in the terminal |
|
43
|
-
| `git-md` executable | A Git plugin that outputs [Markdown](https://spec.commonmark.org/current/) formatted git commit messages into the terminal |
|
80
|
+
| Method | Description | Environment Variables |
|
81
|
+
| :------- | :---------- | :-------------------- |
|
82
|
+
| `md` executable | Outputs [Markdown](https://spec.commonmark.org/current/) files with ANSI escape sequences in the terminal | `PAGER`, `KRAMDOWN_ANSI_MD_STYLES`, `KRAMDOWN_ANSI_STYLES` |
|
83
|
+
| `git-md` executable | A Git plugin that outputs [Markdown](https://spec.commonmark.org/current/) formatted git commit messages into the terminal | `PAGER`, `GIT_PAGER`, `KRAMDOWN_ANSI_GIT_MD_STYLES`, `KRAMDOWN_ANSI_STYLES` |
|
84
|
+
|
85
|
+
The `md` executable first checks for `KRAMDOWN_ANSI_MD_STYLES` environment
|
86
|
+
variable, and if not found, falls back to `KRAMDOWN_ANSI_STYLES`. These
|
87
|
+
variables can be used to customize ANSI styles for Markdown rendering. The
|
88
|
+
`git-md` executable follows the same pattern, checking
|
89
|
+
`KRAMDOWN_ANSI_GIT_MD_STYLES` first, then `KRAMDOWN_ANSI_STYLES`, allowing
|
90
|
+
customization of ANSI styles for Git commit message formatting.
|
44
91
|
|
45
92
|
### The md executable
|
46
93
|
|
@@ -66,6 +113,14 @@ The output of the `md` command can be seen in this screenshot:
|
|
66
113
|
|
67
114
|

|
68
115
|
|
116
|
+
|
117
|
+
### Example: Configuring `md` with Specific Environment Variable
|
118
|
+
|
119
|
+
```bash
|
120
|
+
# Using KRAMDOWN_ANSI_MD_STYLES (most specific for md executable)
|
121
|
+
KRAMDOWN_ANSI_MD_STYLES='{"header": ["bold", "magenta"], "code": ["cyan", "underline"]}' md README.md
|
122
|
+
```
|
123
|
+
|
69
124
|
### The git-md executable
|
70
125
|
|
71
126
|
The `git-md` executable is a git plugin that can be used to output markdown
|
@@ -93,6 +148,12 @@ The output of the `git md` command can be seen in this screenshot:
|
|
93
148
|
|
94
149
|

|
95
150
|
|
151
|
+
### Example: Configuring `git-md` with Specific Environment Variable
|
152
|
+
|
153
|
+
```bash
|
154
|
+
# Using KRAMDOWN_ANSI_GIT_MD_STYLES (most specific for git-md executable)
|
155
|
+
KRAMDOWN_ANSI_GIT_MD_STYLES='{"header": ["bold", "blue"], "code": "red"}' git md
|
156
|
+
```
|
96
157
|
|
97
158
|
## Download
|
98
159
|
|
@@ -106,7 +167,7 @@ The homepage of this library is located at
|
|
106
167
|
|
107
168
|
## License
|
108
169
|
|
109
|
-
|
170
|
+
[MIT License](./LICENSE)
|
110
171
|
|
111
172
|
## Mandatory Kitten Image
|
112
173
|
|
data/Rakefile
CHANGED
@@ -29,6 +29,7 @@ GemHadar do
|
|
29
29
|
dependency 'term-ansicolor', '~> 1.11'
|
30
30
|
dependency 'kramdown-parser-gfm', '~> 1.1'
|
31
31
|
dependency 'terminal-table', '~> 3.0'
|
32
|
+
dependency 'json', '~> 2.0'
|
32
33
|
development_dependency 'all_images', '~> 0.4'
|
33
34
|
development_dependency 'rspec', '~> 3.2'
|
34
35
|
development_dependency 'debug'
|
data/bin/git-md
CHANGED
@@ -4,6 +4,12 @@ require 'kramdown/ansi'
|
|
4
4
|
include Term::ANSIColor
|
5
5
|
require 'shellwords'
|
6
6
|
|
7
|
+
ansi_styles =
|
8
|
+
if env_var = %w[ KRAMDOWN_ANSI_GIT_MD_STYLES KRAMDOWN_ANSI_STYLES ].find { ENV.key?(_1) }
|
9
|
+
Kramdown::ANSI::Styles.from_env_var(env_var).ansi_styles
|
10
|
+
else
|
11
|
+
Kramdown::ANSI::Styles.new.ansi_styles
|
12
|
+
end
|
7
13
|
cmd = %{git log --color=always --pretty=format:"commit %H%C(auto)%d%nDate: %Cgreen%cD (%cr)%Creset%nAuthor: %Cblue%an <%ae>%Creset%n%nMARKUP%n%s%n%n%b%nMARKDOWN%n"}
|
8
14
|
|
9
15
|
core_pager = `git config --get core.pager`.chomp.full?
|
@@ -31,7 +37,7 @@ Kramdown::ANSI::Pager.pager(command: my_pager) do |output|
|
|
31
37
|
when /^MARKUP$/
|
32
38
|
message = ''
|
33
39
|
when /^MARKDOWN$/
|
34
|
-
output.puts Kramdown::ANSI.parse(message + "\n---\n")
|
40
|
+
output.puts Kramdown::ANSI.parse(message + "\n---\n", ansi_styles:)
|
35
41
|
message = nil
|
36
42
|
else
|
37
43
|
if message
|
data/bin/md
CHANGED
@@ -2,7 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'kramdown/ansi'
|
4
4
|
|
5
|
-
|
5
|
+
ansi_styles =
|
6
|
+
if env_var = %w[ KRAMDOWN_ANSI_MD_STYLES KRAMDOWN_ANSI_STYLES ].find { ENV.key?(_1) }
|
7
|
+
Kramdown::ANSI::Styles.from_env_var(env_var).ansi_styles
|
8
|
+
else
|
9
|
+
Kramdown::ANSI::Styles.new.ansi_styles
|
10
|
+
end
|
11
|
+
rendered = Kramdown::ANSI.parse(ARGF.read, ansi_styles:)
|
6
12
|
default_pager = ENV['PAGER'].full?
|
7
13
|
if fallback_pager = `which less`.chomp.full? || `which more`.chomp.full?
|
8
14
|
fallback_pager << ' -r'
|
data/kramdown-ansi.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: kramdown-ansi 0.0
|
2
|
+
# stub: kramdown-ansi 0.1.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "kramdown-ansi".freeze
|
6
|
-
s.version = "0.0
|
6
|
+
s.version = "0.1.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]
|
@@ -12,19 +12,19 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.description = "Kramdown::ANSI: A library for rendering Markdown(ish) documents with\nbeautiful ANSI escape sequences in the terminal.\n".freeze
|
13
13
|
s.email = "flori@ping.de".freeze
|
14
14
|
s.executables = ["md".freeze, "git-md".freeze]
|
15
|
-
s.extra_rdoc_files = ["README.md".freeze, "lib/kramdown/ansi.rb".freeze, "lib/kramdown/ansi/pager.rb".freeze, "lib/kramdown/ansi/width.rb".freeze, "lib/kramdown/version.rb".freeze]
|
16
|
-
s.files = ["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/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/width_spec.rb".freeze, "spec/kramdown/ansi_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
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 = ["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/spec_helper.rb".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
21
|
s.rubygems_version = "3.6.9".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/width_spec.rb".freeze, "spec/kramdown/ansi_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_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, ["~>
|
27
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 2.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])
|
@@ -32,4 +32,5 @@ 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, ["~> 3.0".freeze])
|
35
|
+
s.add_runtime_dependency(%q<json>.freeze, ["~> 2.0".freeze])
|
35
36
|
end
|
data/lib/kramdown/ansi/pager.rb
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
require 'tins/terminal'
|
2
2
|
require 'term/ansicolor'
|
3
3
|
|
4
|
+
# A module that provides paging functionality for terminal output.
|
5
|
+
#
|
6
|
+
# The Pager module handles the logic for determining when to use a pager
|
7
|
+
# command like 'less' or 'more' when the output exceeds the terminal's line
|
8
|
+
# capacity. It also manages the execution of these pagers and ensures proper
|
9
|
+
# terminal state restoration when paging is used.
|
10
|
+
#
|
11
|
+
# @example Using the pager with a block
|
12
|
+
# Kramdown::ANSI::Pager.pager(command: 'less', lines: 50) do |output|
|
13
|
+
# output.puts "Content to display"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @example Checking if a pager is needed
|
17
|
+
# pager_command = Kramdown::ANSI::Pager.pager(command: 'more', lines: 100)
|
4
18
|
module Kramdown::ANSI::Pager
|
5
19
|
module_function
|
6
20
|
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
# A configuration class for managing ANSI styles used in Markdown rendering.
|
4
|
+
#
|
5
|
+
# The Styles class provides functionality to initialize default ANSI styling
|
6
|
+
# options for various Markdown elements and allows customization through
|
7
|
+
# JSON configuration or environment variables.
|
8
|
+
#
|
9
|
+
# @example Using default styles
|
10
|
+
# styles = Kramdown::ANSI::Styles.new
|
11
|
+
#
|
12
|
+
# @example Creating styles from JSON configuration
|
13
|
+
# json_config = '{"header": ["bold", "underline"], "code": "red"}'
|
14
|
+
# styles = Kramdown::ANSI::Styles.from_json(json_config)
|
15
|
+
#
|
16
|
+
# @example Creating styles from environment variable
|
17
|
+
# ENV['MY_STYLES'] = '{"em": "italic"}'
|
18
|
+
# styles = Kramdown::ANSI::Styles.from_env_var('MY_STYLES')
|
19
|
+
class Kramdown::ANSI::Styles
|
20
|
+
# Initializes a new instance with default ANSI styles configuration.
|
21
|
+
#
|
22
|
+
# Sets up the default styling options for various Markdown elements and
|
23
|
+
# prepares the instance variable to hold the current ANSI style
|
24
|
+
# configuration.
|
25
|
+
def initialize
|
26
|
+
@default_ansi_styles = {
|
27
|
+
header: %i[ bold underline ],
|
28
|
+
strong: :bold,
|
29
|
+
em: :italic,
|
30
|
+
code: :blue,
|
31
|
+
}
|
32
|
+
@ansi_styles = @default_ansi_styles.dup
|
33
|
+
end
|
34
|
+
|
35
|
+
# The default_ansi_styles reader method provides access to the default ANSI
|
36
|
+
# style configuration used by the Styles class.
|
37
|
+
#
|
38
|
+
# This method returns the @default_ansi_styles instance variable, which holds
|
39
|
+
# the predefined styling options for various Markdown elements before any
|
40
|
+
# custom configuration has been applied.
|
41
|
+
#
|
42
|
+
# @return [Hash] the default ANSI styles configuration hash containing symbol keys
|
43
|
+
# and style value arrays or single style values
|
44
|
+
attr_reader :default_ansi_styles
|
45
|
+
|
46
|
+
# The ansi_styles reader method provides access to the current ANSI style
|
47
|
+
# configuration.
|
48
|
+
#
|
49
|
+
# This method returns the @ansi_styles instance variable, which holds the
|
50
|
+
# active ANSI styling options used for formatting Markdown elements in
|
51
|
+
# terminal output.
|
52
|
+
#
|
53
|
+
# @return [Hash] the current ANSI styles configuration hash
|
54
|
+
attr_reader :ansi_styles
|
55
|
+
|
56
|
+
# Creates a new instance by parsing JSON configuration and applying it.
|
57
|
+
#
|
58
|
+
# @param json [String] A JSON string containing the configuration options
|
59
|
+
# @return [Kramdown::ANSI::Styles] A new instance with the parsed
|
60
|
+
# configuration applied
|
61
|
+
def self.from_json(json)
|
62
|
+
new.configure(JSON.parse(json))
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates a new Styles instance by parsing JSON configuration from an
|
66
|
+
# environment variable.
|
67
|
+
#
|
68
|
+
# @param name [String] the name of the environment variable containing the
|
69
|
+
# JSON configuration
|
70
|
+
# @return [Kramdown::ANSI::Styles] a new instance with the parsed
|
71
|
+
# configuration applied
|
72
|
+
def self.from_env_var(name)
|
73
|
+
from_json(ENV.fetch(name))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Configures the ANSI styles using the provided options.
|
77
|
+
#
|
78
|
+
# Merges the given options with the default ANSI styles to customize the
|
79
|
+
# styling configuration for Markdown elements.
|
80
|
+
#
|
81
|
+
# @param options [Hash] a hash containing style configuration options
|
82
|
+
# @return [Kramdown::ANSI::Styles] returns self to allow for method chaining
|
83
|
+
def configure(options)
|
84
|
+
options_ansi_styles = options.to_h.transform_keys(&:to_sym).
|
85
|
+
transform_values {
|
86
|
+
array = Array(_1).map(&:to_sym)
|
87
|
+
array.size == 1 ? array.first : array
|
88
|
+
}
|
89
|
+
@ansi_styles = @default_ansi_styles.merge(options_ansi_styles)
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
# Applies ANSI styles to the given text block based on the specified style
|
94
|
+
# name.
|
95
|
+
#
|
96
|
+
# @param name [Symbol] the style name to apply
|
97
|
+
# @yield [String] the text to be styled
|
98
|
+
# @return [String] the text with ANSI styles applied
|
99
|
+
def apply(name, &block)
|
100
|
+
styles = Array(@ansi_styles.fetch(name))
|
101
|
+
styles.reverse_each.reduce(block.()) do |text, style|
|
102
|
+
if style.is_a?(Symbol) || style.is_a?(String)
|
103
|
+
text = Term::ANSIColor.send(style) { text }
|
104
|
+
end
|
105
|
+
text
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# The as_json method converts the current ANSI styles configuration into a
|
110
|
+
# JSON-serializable format.
|
111
|
+
#
|
112
|
+
# @return [Hash] A hash representation of the current ANSI styles configuration.
|
113
|
+
def as_json(*)
|
114
|
+
@ansi_styles
|
115
|
+
end
|
116
|
+
|
117
|
+
alias to_hash as_json
|
118
|
+
|
119
|
+
alias to_h to_hash
|
120
|
+
|
121
|
+
# The to_json method converts the current ANSI styles configuration into a
|
122
|
+
# JSON-serializable format.
|
123
|
+
#
|
124
|
+
# @return [String] A JSON string representation of the current ANSI styles
|
125
|
+
# configuration.
|
126
|
+
def to_json(*)
|
127
|
+
as_json.to_json(*)
|
128
|
+
end
|
129
|
+
end
|
data/lib/kramdown/ansi/width.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
require 'tins/terminal'
|
2
2
|
require 'term/ansicolor'
|
3
3
|
|
4
|
+
# A module that provides functionality for calculating terminal width
|
5
|
+
# percentages and wrapping/truncating text accordingly.
|
6
|
+
#
|
7
|
+
# The Width module includes methods to determine the available terminal width
|
8
|
+
# based on a percentage, and provides text wrapping and truncation capabilities
|
9
|
+
# that respect terminal dimensions.
|
10
|
+
#
|
11
|
+
# @example Wrapping text to 80% of terminal width
|
12
|
+
# Kramdown::ANSI::Width.wrap("This is a long line of text", percentage: 80)
|
13
|
+
#
|
14
|
+
# @example Truncating text to 50 characters
|
15
|
+
# Kramdown::ANSI::Width.truncate("This is a long line of text", length: 50)
|
4
16
|
module Kramdown::ANSI::Width
|
5
17
|
include Term::ANSIColor
|
6
18
|
extend Term::ANSIColor
|
data/lib/kramdown/ansi.rb
CHANGED
@@ -2,23 +2,74 @@ require 'kramdown'
|
|
2
2
|
require 'kramdown-parser-gfm'
|
3
3
|
require 'terminal-table'
|
4
4
|
require 'term/ansicolor'
|
5
|
+
|
6
|
+
# A namespace module for the Kramdown::ANSI library.
|
7
|
+
#
|
8
|
+
# The Kramdown module serves as the root namespace for the Kramdown::ANSI
|
9
|
+
# library, which provides functionality for rendering Markdown documents with
|
10
|
+
# ANSI escape sequences in terminal environments. This module encapsulates the
|
11
|
+
# core components and converters necessary for transforming Markdown content
|
12
|
+
# into beautifully formatted terminal output.
|
5
13
|
module Kramdown
|
6
14
|
class Kramdown::ANSI < Kramdown::Converter::Base
|
7
15
|
end
|
8
16
|
end
|
9
17
|
require 'kramdown/ansi/width'
|
10
18
|
require 'kramdown/ansi/pager'
|
11
|
-
|
19
|
+
require 'kramdown/ansi/styles'
|
20
|
+
|
21
|
+
# A converter class that transforms Kramdown documents into ANSI-formatted
|
22
|
+
# terminal output.
|
23
|
+
#
|
24
|
+
# The Kramdown::ANSI class extends Kramdown::Converter::Base to provide
|
25
|
+
# functionality for rendering Markdown documents with ANSI escape sequences for
|
26
|
+
# terminal display. It handles various Markdown elements such as headers,
|
27
|
+
# paragraphs, lists, and code blocks, applying configurable ANSI styles to
|
28
|
+
# enhance the visual presentation in terminals.
|
29
|
+
#
|
30
|
+
# @example Converting a markdown string to ANSI formatted output
|
31
|
+
# ansi_output = Kramdown::ANSI.parse(markdown_string)
|
32
|
+
#
|
33
|
+
# @example Using custom ANSI styles
|
34
|
+
# styles = { header: [:bold, :underline], code: :red }
|
35
|
+
# ansi_output = Kramdown::ANSI.parse(markdown_string, ansi_styles: styles)
|
12
36
|
class Kramdown::ANSI < Kramdown::Converter::Base
|
13
|
-
include Term::ANSIColor
|
14
37
|
include Kramdown::ANSI::Width
|
15
38
|
|
39
|
+
# A utility module that provides ANSI escape sequence removal functionality
|
40
|
+
# for terminal table content.
|
41
|
+
#
|
42
|
+
# The Terminal::Table::Util module contains helper methods for processing
|
43
|
+
# text that may contain ANSI escape sequences, particularly for cleaning up
|
44
|
+
# terminal table output before further processing or display.
|
45
|
+
#
|
46
|
+
# @example Removing ANSI escape sequences from table lines
|
47
|
+
# cleaned_line = Terminal::Table::Util.ansi_escape("\e[1mBold Text\e[0m")
|
16
48
|
module Terminal::Table::Util
|
49
|
+
# Removes ANSI escape sequences from the given line of text.
|
50
|
+
#
|
51
|
+
# This method strips out all ANSI escape codes that are commonly used to
|
52
|
+
# format terminal output with colors, styles, and cursor movements.
|
53
|
+
#
|
54
|
+
# @param line [String] the input string that may contain ANSI escape
|
55
|
+
# sequences
|
56
|
+
# @return [String] a copy of the input string with all ANSI escape
|
57
|
+
# sequences removed
|
17
58
|
def self.ansi_escape(line)
|
18
59
|
line.to_s.gsub(/\e\[.*?m|\e\].*?(\e|\a)\\?/, '')
|
19
60
|
end
|
20
61
|
end
|
21
62
|
|
63
|
+
# A custom Markdown parser that extends GFM to provide enhanced parsing
|
64
|
+
# capabilities for Kramdown::ANSI.
|
65
|
+
#
|
66
|
+
# This class modifies the standard GFM parser by disabling certain quirks and
|
67
|
+
# removing specific block and span parsers to tailor the Markdown processing
|
68
|
+
# for ANSI terminal output.
|
69
|
+
#
|
70
|
+
# @example Using the Mygfm parser with Kramdown::ANSI
|
71
|
+
# parser = Kramdown::Parser::Mygfm.new(markdown_content, {})
|
72
|
+
# document = Kramdown::Document.new(markdown_content, input: :mygfm)
|
22
73
|
class ::Kramdown::Parser::Mygfm < ::Kramdown::Parser::GFM
|
23
74
|
def initialize(source, options)
|
24
75
|
options[:gfm_quirks] << :no_auto_typographic
|
@@ -31,20 +82,49 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
31
82
|
end
|
32
83
|
end
|
33
84
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
85
|
+
# Parses the given markdown source into ANSI formatted terminal output.
|
86
|
+
#
|
87
|
+
# @param source [String] the markdown content to be converted
|
88
|
+
# @param options [Hash] additional options for the parsing process
|
89
|
+
# @return [String] the ANSI formatted terminal output
|
90
|
+
def self.parse(source, options = {})
|
91
|
+
options = { input: :mygfm, auto_ids: false, entity_output: :as_char }.merge(
|
92
|
+
options.transform_keys(&:to_sym)
|
93
|
+
)
|
94
|
+
@doc = Kramdown::Document.new(source, options).to_ansi
|
95
|
+
end
|
96
|
+
|
97
|
+
# Initializes a new Kramdown::ANSI converter instance with optional ANSI
|
98
|
+
# styling configuration.
|
99
|
+
#
|
100
|
+
# @param root [Kramdown::Element] the root element of the document to convert
|
101
|
+
# @param options [Hash] additional options for the conversion process
|
102
|
+
# @return [void]
|
40
103
|
def initialize(root, options)
|
104
|
+
@ansi_styles = Kramdown::ANSI::Styles.new.configure(options.delete(:ansi_styles))
|
41
105
|
super
|
42
106
|
end
|
43
107
|
|
108
|
+
# The convert method dispatches to type-specific conversion methods based on
|
109
|
+
# the element's type.
|
110
|
+
#
|
111
|
+
# @param el [Kramdown::Element] the element to convert
|
112
|
+
# @param opts [Hash] additional options for the conversion process
|
113
|
+
# @return [String] the converted content of the element
|
44
114
|
def convert(el, opts = {})
|
45
115
|
send("convert_#{el.type}", el, opts)
|
46
116
|
end
|
47
117
|
|
118
|
+
# The inner method processes child elements of a given element and applies
|
119
|
+
# optional block processing to each child.
|
120
|
+
#
|
121
|
+
# @param el [Kramdown::Element] the element whose children will be processed
|
122
|
+
# @param opts [Hash] additional options for the processing
|
123
|
+
# @yield [Kramdown::Element, Integer, String] optional block to customize processing of each child element
|
124
|
+
# @yieldparam inner_el [Kramdown::Element] the current child element being processed
|
125
|
+
# @yieldparam index [Integer] the index of the current child element
|
126
|
+
# @yieldparam content [String] the converted content of the current child element
|
127
|
+
# @return [String] the concatenated result of processing all child elements
|
48
128
|
def inner(el, opts, &block)
|
49
129
|
result = +''
|
50
130
|
options = opts.dup.merge(parent: el)
|
@@ -61,57 +141,156 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
61
141
|
result
|
62
142
|
end
|
63
143
|
|
144
|
+
# The convert_root method processes the root element of a Kramdown document.
|
145
|
+
#
|
146
|
+
# This method serves as the entry point for converting the top-level element
|
147
|
+
# of a Markdown document into its ANSI-formatted terminal representation.
|
148
|
+
# It delegates the actual processing to the inner method, which handles
|
149
|
+
# the recursive conversion of child elements.
|
150
|
+
#
|
151
|
+
# @param el [Kramdown::Element] the root element to convert
|
152
|
+
# @param opts [Hash] additional options for the conversion process
|
153
|
+
# @return [String] the ANSI formatted content of the root element and its children
|
64
154
|
def convert_root(el, opts)
|
65
155
|
inner(el, opts)
|
66
156
|
end
|
67
157
|
|
158
|
+
# The convert_blank method handles blank line conversion in Markdown
|
159
|
+
# documents.
|
160
|
+
#
|
161
|
+
# This method determines whether a blank element should produce output based
|
162
|
+
# on the context of the surrounding content. It checks if the current result
|
163
|
+
# ends with double newlines or is empty, and returns an appropriate newline
|
164
|
+
# character to maintain proper spacing in the rendered output.
|
165
|
+
#
|
166
|
+
# @param _el [Kramdown::Element] the blank element being converted (unused)
|
167
|
+
# @param opts [Hash] conversion options containing the result context
|
168
|
+
# @return [String] either an empty string or a single newline character
|
68
169
|
def convert_blank(_el, opts)
|
69
170
|
opts[:result] =~ /\n\n\Z|\A\Z/ ? "" : "\n"
|
70
171
|
end
|
71
172
|
|
173
|
+
# The convert_text method processes a text element by extracting its value.
|
174
|
+
#
|
175
|
+
# @param el [Kramdown::Element] the text element to convert
|
176
|
+
# @param _opts [Hash] additional options (unused)
|
177
|
+
# @return [String] the raw text content of the element
|
72
178
|
def convert_text(el, _opts)
|
73
179
|
el.value
|
74
180
|
end
|
75
181
|
|
182
|
+
# The convert_header method processes a header element by applying ANSI
|
183
|
+
# styling and adding a newline.
|
184
|
+
#
|
185
|
+
# @param el [Kramdown::Element] the header element to convert
|
186
|
+
# @param opts [Hash] conversion options containing context for processing
|
187
|
+
# @return [String] the styled header content with a trailing newline
|
76
188
|
def convert_header(el, opts)
|
77
|
-
newline
|
189
|
+
newline apply_style(:header) { inner(el, opts) }
|
78
190
|
end
|
79
191
|
|
192
|
+
# The convert_p method processes a paragraph element by calculating the
|
193
|
+
# appropriate text width based on the terminal size and list indentation,
|
194
|
+
# then wraps the content accordingly.
|
195
|
+
#
|
196
|
+
# @param el [Kramdown::Element] the paragraph element to convert
|
197
|
+
# @param opts [Hash] conversion options containing context for processing
|
198
|
+
# @return [String] the wrapped and formatted paragraph content with proper newlines
|
80
199
|
def convert_p(el, opts)
|
81
200
|
length = width(percentage: 90) - opts[:list_indent].to_i
|
82
201
|
length < 0 and return ''
|
83
202
|
newline wrap(inner(el, opts), length:)
|
84
203
|
end
|
85
204
|
|
205
|
+
# The convert_strong method processes a strong emphasis element by applying
|
206
|
+
# ANSI styling to its content.
|
207
|
+
#
|
208
|
+
# @param el [Kramdown::Element] the strong element to convert
|
209
|
+
# @param opts [Hash] conversion options containing context for processing
|
210
|
+
# @return [String] the styled strong content
|
86
211
|
def convert_strong(el, opts)
|
87
|
-
|
212
|
+
apply_style(:strong) { inner(el, opts) }
|
88
213
|
end
|
89
214
|
|
215
|
+
# The convert_em method processes an emphasis element by applying ANSI
|
216
|
+
# styling to its content.
|
217
|
+
#
|
218
|
+
# @param el [Kramdown::Element] the emphasis element to convert
|
219
|
+
# @param opts [Hash] conversion options containing context for processing
|
220
|
+
# @return [String] the styled emphasis content
|
90
221
|
def convert_em(el, opts)
|
91
|
-
|
222
|
+
apply_style(:em) { inner(el, opts) }
|
92
223
|
end
|
93
224
|
|
225
|
+
# The convert_a method processes an anchor element by applying hyperlink
|
226
|
+
# formatting to its content.
|
227
|
+
#
|
228
|
+
# @param el [Kramdown::Element] the anchor element to convert
|
229
|
+
# @param opts [Hash] conversion options containing context for processing
|
230
|
+
# @return [String] the hyperlink formatted content
|
94
231
|
def convert_a(el, opts)
|
95
232
|
url = el.attr['href']
|
96
233
|
hyperlink(url) { inner(el, opts) }
|
97
234
|
end
|
98
235
|
|
236
|
+
# The convert_codespan method processes a code span element by applying ANSI
|
237
|
+
# styling to its content.
|
238
|
+
#
|
239
|
+
# @param el [Kramdown::Element] the code span element to convert
|
240
|
+
# @param _opts [Hash] additional options (unused)
|
241
|
+
# @return [String] the styled code span content
|
99
242
|
def convert_codespan(el, _opts)
|
100
|
-
|
243
|
+
apply_style(:code) { el.value }
|
101
244
|
end
|
102
245
|
|
246
|
+
# The convert_codeblock method processes a code block element by applying
|
247
|
+
# ANSI styling to its content.
|
248
|
+
#
|
249
|
+
# @param el [Kramdown::Element] the code block element to convert
|
250
|
+
# @param _opts [Hash] additional options (unused)
|
251
|
+
# @return [String] the styled code block content
|
103
252
|
def convert_codeblock(el, _opts)
|
104
|
-
|
105
|
-
end
|
106
|
-
|
253
|
+
apply_style(:code) { el.value }
|
254
|
+
end
|
255
|
+
|
256
|
+
# The convert_blockquote method processes a blockquote element by applying
|
257
|
+
# proper formatting.
|
258
|
+
#
|
259
|
+
# This method takes a blockquote element and formats its content with
|
260
|
+
# quotation marks while ensuring appropriate newline handling. It delegates
|
261
|
+
# the inner content processing to the inner method and then wraps the result
|
262
|
+
# with quotation marks.
|
263
|
+
#
|
264
|
+
# @param el [Kramdown::Element] the blockquote element to convert
|
265
|
+
# @param opts [Hash] conversion options containing context for processing
|
266
|
+
# @return [String] the formatted blockquote content with quotation marks and
|
267
|
+
# proper newlines
|
107
268
|
def convert_blockquote(el, opts)
|
108
269
|
newline ?“ + inner(el, opts).sub(/\n+\z/, '') + ?”
|
109
270
|
end
|
110
271
|
|
272
|
+
# The convert_hr method processes a horizontal rule element by generating a
|
273
|
+
# full-width separator line.
|
274
|
+
#
|
275
|
+
# This method creates a horizontal rule (divider) using the Unicode
|
276
|
+
# line-drawing character and applies proper newline formatting to ensure
|
277
|
+
# correct spacing in the terminal output.
|
278
|
+
#
|
279
|
+
# @param _el [Kramdown::Element] the horizontal rule element to convert (unused)
|
280
|
+
# @param _opts [Hash] conversion options (unused)
|
281
|
+
# @return [String] a formatted horizontal rule line spanning the full
|
282
|
+
# terminal width with newlines
|
111
283
|
def convert_hr(_el, _opts)
|
112
284
|
newline ?─ * width(percentage: 100)
|
113
285
|
end
|
114
286
|
|
287
|
+
# The convert_img method processes an image element by extracting its source
|
288
|
+
# URL and alternative text, formatting the display with a fallback to the URL
|
289
|
+
# if the alt text is empty, and applying hyperlink styling.
|
290
|
+
#
|
291
|
+
# @param el [Kramdown::Element] the image element to convert
|
292
|
+
# @param _opts [Hash] additional options (unused)
|
293
|
+
# @return [String] the formatted image content with hyperlink styling
|
115
294
|
def convert_img(el, _opts)
|
116
295
|
url = el.attr['src']
|
117
296
|
alt = el.attr['alt']
|
@@ -120,6 +299,18 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
120
299
|
hyperlink(url) { alt }
|
121
300
|
end
|
122
301
|
|
302
|
+
# The convert_ul method processes an unordered list element by applying
|
303
|
+
# bullet point formatting and indentation.
|
304
|
+
#
|
305
|
+
# This method takes an unordered list element and formats its child elements
|
306
|
+
# with bullet points, applying appropriate newline handling based on the list
|
307
|
+
# item's position, and adding indentation to align the content properly
|
308
|
+
# within the terminal output.
|
309
|
+
#
|
310
|
+
# @param el [Kramdown::Element] the unordered list element to convert
|
311
|
+
# @param opts [Hash] conversion options containing context for processing
|
312
|
+
# @return [String] the formatted unordered list content with bullet points
|
313
|
+
# and proper indentation
|
123
314
|
def convert_ul(el, opts)
|
124
315
|
list_indent = opts[:list_indent].to_i
|
125
316
|
inner(el, opts) { |_inner_el, index, content|
|
@@ -129,6 +320,18 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
129
320
|
}
|
130
321
|
end
|
131
322
|
|
323
|
+
# The convert_ol method processes an ordered list element by applying
|
324
|
+
# numbered bullet point formatting and indentation.
|
325
|
+
#
|
326
|
+
# This method takes an ordered list element and formats its child elements
|
327
|
+
# with sequential numbers, applying appropriate newline handling based on the
|
328
|
+
# list item's position, and adding indentation to align the content properly
|
329
|
+
# within the terminal output.
|
330
|
+
#
|
331
|
+
# @param el [Kramdown::Element] the ordered list element to convert
|
332
|
+
# @param opts [Hash] conversion options containing context for processing
|
333
|
+
# @return [String] the formatted ordered list content with numbered points
|
334
|
+
# and proper indentation
|
132
335
|
def convert_ol(el, opts)
|
133
336
|
list_indent = opts[:list_indent].to_i
|
134
337
|
inner(el, opts) { |_inner_el, index, content|
|
@@ -138,22 +341,50 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
138
341
|
}
|
139
342
|
end
|
140
343
|
|
344
|
+
# The convert_li method processes a list item element by applying indentation
|
345
|
+
# and handling newlines.
|
346
|
+
#
|
347
|
+
# This method takes a list item element and modifies the conversion options
|
348
|
+
# to include appropriate indentation for nested lists. It then delegates the
|
349
|
+
# inner content processing to the inner method and ensures proper newline
|
350
|
+
# handling at the end of the content.
|
351
|
+
#
|
352
|
+
# @param el [Kramdown::Element] the list item element to convert
|
353
|
+
# @param opts [Hash] conversion options containing context for processing
|
354
|
+
# @return [String] the formatted list item content with proper indentation
|
355
|
+
# and newlines
|
141
356
|
def convert_li(el, opts)
|
142
357
|
opts = opts.dup
|
143
358
|
opts[:list_indent] = 2 + opts[:list_indent].to_i
|
144
359
|
newline inner(el, opts).sub(/\n+\Z/, '')
|
145
360
|
end
|
146
361
|
|
362
|
+
# The convert_html_element method processes HTML inline elements such as <i>,
|
363
|
+
# <em>, <b>, and <strong> by applying the corresponding ANSI styles to their
|
364
|
+
# content. It handles emphasis and strong formatting tags specifically, while
|
365
|
+
# ignoring other HTML elements by returning an empty string.
|
366
|
+
#
|
367
|
+
# @param el [Kramdown::Element] the HTML element being converted
|
368
|
+
# @param opts [Hash] conversion options containing context for processing
|
369
|
+
# @return [String] the styled content for emphasis or strong elements, or an
|
370
|
+
# empty string for other elements
|
147
371
|
def convert_html_element(el, opts)
|
148
372
|
if el.value == 'i' || el.value == 'em'
|
149
|
-
|
373
|
+
apply_style(:em) { inner(el, opts) }
|
150
374
|
elsif el.value == 'b' || el.value == 'strong'
|
151
|
-
|
375
|
+
apply_style(:strong) { inner(el, opts) }
|
152
376
|
else
|
153
377
|
''
|
154
378
|
end
|
155
379
|
end
|
156
380
|
|
381
|
+
# The convert_table method processes a table element by creating a terminal
|
382
|
+
# table instance, applying styling and alignment options, and generating
|
383
|
+
# formatted output with newlines.
|
384
|
+
#
|
385
|
+
# @param el [Kramdown::Element] the table element to convert
|
386
|
+
# @param opts [Hash] conversion options containing context for processing
|
387
|
+
# @return [String] the formatted table content with proper styling and newlines
|
157
388
|
def convert_table(el, opts)
|
158
389
|
table = Terminal::Table.new
|
159
390
|
table.style = {
|
@@ -169,6 +400,14 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
169
400
|
newline table.to_s
|
170
401
|
end
|
171
402
|
|
403
|
+
# The convert_thead method processes a table header element by extracting and
|
404
|
+
# formatting the heading rows from the table content, then assigns them to
|
405
|
+
# the terminal table instance.
|
406
|
+
#
|
407
|
+
# @param el [Kramdown::Element] the table header element to convert
|
408
|
+
# @param opts [Hash] conversion options containing context for processing
|
409
|
+
# @return [String] an empty string to indicate successful processing and
|
410
|
+
# assignment of headings to the table instance
|
172
411
|
def convert_thead(el, opts)
|
173
412
|
rows = inner(el, opts)
|
174
413
|
rows = rows.split(/\s*\|\s*/)[1..].map(&:strip)
|
@@ -176,15 +415,46 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
176
415
|
''
|
177
416
|
end
|
178
417
|
|
418
|
+
# The convert_tbody method processes a table body element by collecting its
|
419
|
+
# inner content.
|
420
|
+
#
|
421
|
+
# This method handles the conversion of HTML table body elements, gathering
|
422
|
+
# all child elements' content and returning it as a string. It serves as part
|
423
|
+
# of the table conversion process, working in conjunction with other
|
424
|
+
# table-related methods to generate properly formatted terminal output.
|
425
|
+
#
|
426
|
+
# @param el [Kramdown::Element] the table body element to convert
|
427
|
+
# @param opts [Hash] conversion options containing context for processing
|
428
|
+
# @return [String] the concatenated content of all child elements within the
|
429
|
+
# table body
|
179
430
|
def convert_tbody(el, opts)
|
180
431
|
res = +''
|
181
432
|
res << inner(el, opts)
|
182
433
|
end
|
183
434
|
|
435
|
+
# The convert_tfoot method handles the conversion of table footer elements.
|
436
|
+
#
|
437
|
+
# This method processes table footer elements by returning an empty string,
|
438
|
+
# effectively ignoring footer content during the ANSI terminal output
|
439
|
+
# conversion. It is part of the table conversion process, working alongside
|
440
|
+
# other table-related methods to generate properly formatted terminal output.
|
441
|
+
#
|
442
|
+
# @param el [Kramdown::Element] the table footer element to convert
|
443
|
+
# @param opts [Hash] conversion options containing context for processing
|
444
|
+
# @return [String] an empty string to indicate successful handling of the
|
445
|
+
# footer element
|
184
446
|
def convert_tfoot(el, opts)
|
185
447
|
''
|
186
448
|
end
|
187
449
|
|
450
|
+
# The convert_tr method processes a table row element by calculating column
|
451
|
+
# widths based on content size, wrapping text to fit within the terminal
|
452
|
+
# width, and adding the formatted row to the terminal table instance.
|
453
|
+
#
|
454
|
+
# @param el [Kramdown::Element] the table row element to convert
|
455
|
+
# @param opts [Hash] conversion options containing context for processing
|
456
|
+
# @return [String] an empty string to indicate successful processing and
|
457
|
+
# addition of the row to the table instance
|
188
458
|
def convert_tr(el, opts)
|
189
459
|
return '' if el.children.empty?
|
190
460
|
full_width = width(percentage: 90)
|
@@ -198,33 +468,104 @@ class Kramdown::ANSI < Kramdown::Converter::Base
|
|
198
468
|
''
|
199
469
|
end
|
200
470
|
|
471
|
+
# The convert_td method processes a table data cell element by delegating its
|
472
|
+
# content conversion to the inner method.
|
473
|
+
#
|
474
|
+
# @param el [Kramdown::Element] the table data cell element to convert
|
475
|
+
# @param opts [Hash] conversion options containing context for processing
|
476
|
+
# @return [String] the converted content of the table data cell element
|
201
477
|
def convert_td(el, opts)
|
202
478
|
inner(el, opts)
|
203
479
|
end
|
204
480
|
|
481
|
+
# The convert_entity method processes an entity element by extracting its
|
482
|
+
# character value.
|
483
|
+
#
|
484
|
+
# @param el [Kramdown::Element] the entity element to convert
|
485
|
+
# @param _opts [Hash] additional options (unused)
|
486
|
+
# @return [String] the character representation of the entity
|
205
487
|
def convert_entity(el, _opts)
|
206
488
|
el.value.char
|
207
489
|
end
|
208
490
|
|
491
|
+
# The convert_xml_comment method handles XML comment elements by returning an
|
492
|
+
# empty string.
|
493
|
+
#
|
494
|
+
# This method is part of the Kramdown::ANSI converter and is responsible for
|
495
|
+
# processing XML comment elements that appear in the Markdown document. It
|
496
|
+
# ignores XML comments during the ANSI terminal output conversion process,
|
497
|
+
# returning an empty string to indicate that no content should be rendered
|
498
|
+
# for these elements.
|
499
|
+
#
|
500
|
+
# @return [String] an empty string to indicate that XML comments are ignored
|
501
|
+
# during conversion
|
209
502
|
def convert_xml_comment(*)
|
210
503
|
''
|
211
504
|
end
|
212
505
|
|
506
|
+
# The convert_xml_pi method handles the conversion of XML processing
|
507
|
+
# instruction elements.
|
508
|
+
#
|
509
|
+
# This method is part of the Kramdown::ANSI converter and is responsible for
|
510
|
+
# processing XML processing instruction elements that appear in the Markdown
|
511
|
+
# document. It ignores these elements during the ANSI terminal output
|
512
|
+
# conversion process, returning an empty string to indicate that no content
|
513
|
+
# should be rendered for these elements.
|
514
|
+
#
|
515
|
+
# @return [String] an empty string to indicate that XML processing
|
516
|
+
# instructions are ignored during conversion
|
213
517
|
def convert_xml_pi(*)
|
214
518
|
''
|
215
519
|
end
|
216
520
|
|
521
|
+
# The convert_br method handles the conversion of line break elements.
|
522
|
+
#
|
523
|
+
# This method processes HTML line break elements (br tags) by returning an
|
524
|
+
# empty string, effectively ignoring them during the ANSI terminal output
|
525
|
+
# conversion process. It is part of the Kramdown::ANSI converter's element
|
526
|
+
# handling suite.
|
527
|
+
#
|
528
|
+
# @param _el [Kramdown::Element] the line break element being converted (unused)
|
529
|
+
# @param opts [Hash] conversion options containing context for processing
|
530
|
+
# @return [String] an empty string to indicate that line breaks are ignored
|
531
|
+
# during conversion
|
217
532
|
def convert_br(_el, opts)
|
218
533
|
''
|
219
534
|
end
|
220
535
|
|
536
|
+
# The convert_smart_quote method processes smart quote entities by
|
537
|
+
# determining whether to output a double quotation mark or single quotation
|
538
|
+
# mark based on the entity's type.
|
539
|
+
#
|
540
|
+
# @param el [Kramdown::Element] the smart quote element being converted
|
541
|
+
# @param _opts [Hash] additional options (unused)
|
542
|
+
# @return [String] a double quotation mark for right double quotes or left
|
543
|
+
# double quotes, and a single quotation mark for other smart quote entities
|
221
544
|
def convert_smart_quote(el, _opts)
|
222
545
|
el.value.to_s =~ /[rl]dquo/ ? "\"" : "'"
|
223
546
|
end
|
224
547
|
|
548
|
+
# The newline method appends a specified number of newline characters to the
|
549
|
+
# end of a given text string.
|
550
|
+
#
|
551
|
+
# @param text [String] the input text to which newlines will be appended
|
552
|
+
# @param count [Integer] the number of newline characters to append (defaults to 1)
|
553
|
+
# @return [String] the text with the specified number of trailing newline characters
|
225
554
|
def newline(text, count: 1)
|
226
555
|
text.gsub(/\n*\z/, ?\n * count)
|
227
556
|
end
|
557
|
+
|
558
|
+
private
|
559
|
+
|
560
|
+
# The apply_style method applies ANSI styling to content by utilizing the
|
561
|
+
# configured styles and yielding the content to be styled.
|
562
|
+
#
|
563
|
+
# @param name [Symbol] the style name to apply
|
564
|
+
# @yield [String] the content to be styled
|
565
|
+
# @return [String] the content with the specified ANSI styles applied
|
566
|
+
def apply_style(name, &block)
|
567
|
+
@ansi_styles.apply(name, &block)
|
568
|
+
end
|
228
569
|
end
|
229
570
|
|
230
571
|
Kramdown::Converter.const_set(:Ansi, Kramdown::ANSI)
|
data/lib/kramdown/version.rb
CHANGED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe Kramdown::ANSI::Styles do
|
5
|
+
describe '.from_json' do
|
6
|
+
it 'configures styles from JSON' do
|
7
|
+
json = '{"header": ["bold", "underline"], "code": "red"}'
|
8
|
+
styles = described_class.from_json(json)
|
9
|
+
|
10
|
+
expect(styles.ansi_styles[:header]).to eq([:bold, :underline])
|
11
|
+
expect(styles.ansi_styles[:code]).to eq(:red)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '.from_env_var' do
|
16
|
+
around do |example|
|
17
|
+
ENV['TEST_STYLES'] = '{"em": "yellow"}'
|
18
|
+
example.run
|
19
|
+
ENV.delete('TEST_STYLES')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'configures styles from environment variable' do
|
23
|
+
styles = described_class.from_env_var('TEST_STYLES')
|
24
|
+
|
25
|
+
expect(styles.ansi_styles[:em]).to eq(:yellow)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'raises an error when env var is not set' do
|
29
|
+
ENV.delete('TEST_STYLES')
|
30
|
+
expect {
|
31
|
+
described_class.from_env_var('TEST_STYLES')
|
32
|
+
}.to raise_error(KeyError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#as_json' do
|
37
|
+
it 'can be prepared for JSON conversion' do
|
38
|
+
expect(described_class.new.as_json).to include(:code)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#to_hash' do
|
43
|
+
it 'can be converted to a hash' do
|
44
|
+
expect(described_class.new.to_hash).to include(:code)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#to_h' do
|
49
|
+
it 'can be converted to a hash more forecefully' do
|
50
|
+
expect(described_class.new.to_h).to include(:code)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#to_json' do
|
55
|
+
it 'can be converted to JSON' do
|
56
|
+
expect(described_class.new.to_json).to include('"code":')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#configure' do
|
61
|
+
let(:styles) { described_class.new }
|
62
|
+
|
63
|
+
it 'merges options with defaults' do
|
64
|
+
styles.configure(header: [:bold, :underline])
|
65
|
+
|
66
|
+
expect(styles.ansi_styles[:header]).to eq([:bold, :underline])
|
67
|
+
expect(styles.ansi_styles[:strong]).to eq(:bold) # default preserved
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'handles string keys gracefully' do
|
71
|
+
styles.configure('em' => 'italic')
|
72
|
+
|
73
|
+
expect(styles.ansi_styles[:em]).to eq(:italic)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'preserves default styles when not overridden' do
|
77
|
+
styles.configure(code: :green)
|
78
|
+
|
79
|
+
expect(styles.default_ansi_styles[:header]).to eq(%i[bold underline])
|
80
|
+
expect(styles.ansi_styles[:header]).to eq(%i[bold underline])
|
81
|
+
expect(styles.ansi_styles[:code]).to eq(:green)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#apply' do
|
86
|
+
let(:styles) { described_class.new }
|
87
|
+
|
88
|
+
it 'applies single style to text' do
|
89
|
+
result = styles.apply(:strong) { "Hello" }
|
90
|
+
expect(result).to include("\e[1m") # bold ANSI code
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'applies multiple styles in correct order' do
|
94
|
+
result = styles.apply(:header) { "Header" }
|
95
|
+
expect(result).to include("\e[1m\e[4m") # bold + underline
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'handles non-existent style by raising KeyError' do
|
99
|
+
expect {
|
100
|
+
styles.apply(:nonexistent) { "Text" }
|
101
|
+
}.to raise_error(KeyError)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'applies styles correctly with complex text' do
|
105
|
+
result = styles.apply(:code) { "puts 'hello'" }
|
106
|
+
expect(result).to include("\e[34m") # blue ANSI code
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'allows inspection of current configuration' do
|
110
|
+
expect(styles.ansi_styles).to eq(styles.default_ansi_styles)
|
111
|
+
|
112
|
+
styles.configure(em: :underline)
|
113
|
+
expect(styles.ansi_styles[:em]).to eq(:underline)
|
114
|
+
expect(styles.ansi_styles[:strong]).to eq(:bold) # unchanged
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/spec/kramdown/ansi_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'pathname'
|
3
3
|
|
4
|
-
|
4
|
+
describe Kramdown::ANSI do
|
5
5
|
let :source do
|
6
6
|
File.read(Pathname.new(__dir__) + '..' + '..' + 'README.md')
|
7
7
|
end
|
@@ -9,8 +9,31 @@ RSpec.describe Kramdown::ANSI do
|
|
9
9
|
it 'can parse' do
|
10
10
|
File.open('tmp/README.ansi', ?w) do |output|
|
11
11
|
ansi = described_class.parse(source)
|
12
|
-
expect(ansi).to
|
12
|
+
expect(ansi).to include("\e[1m\e[4mDescription\e[0m\e[0m")
|
13
|
+
expect(ansi).to include("\e[1mKramdown::ANSI\e[0m")
|
14
|
+
expect(ansi).to include("\e[3min the terminal\e[0m")
|
15
|
+
expect(ansi).to include("\e[34mgem install kramdown-ansi\n\e[0m")
|
16
|
+
expect(ansi).to include("\e[34mbundle install\e[0m")
|
17
|
+
expect(ansi).to include("This is the end.")
|
13
18
|
output.puts ansi
|
14
19
|
end
|
15
20
|
end
|
21
|
+
|
22
|
+
it 'can parse with options' do
|
23
|
+
File.open('tmp/README.ansi', ?w) do |output|
|
24
|
+
ansi_styles = Kramdown::ANSI::Styles.new.configure(
|
25
|
+
header: %i[ red underline ],
|
26
|
+
strong: %i[ green ],
|
27
|
+
em: %i[ yellow ],
|
28
|
+
code: %i[ bold color208 ],
|
29
|
+
)
|
30
|
+
ansi = described_class.parse(source, ansi_styles:)
|
31
|
+
expect(ansi).to include("\e[31m\e[4mDescription\e[0m\e[0")
|
32
|
+
expect(ansi).to include("\e[32mKramdown::ANSI\e[0m")
|
33
|
+
expect(ansi).to include("\e[33min the terminal\e[0m")
|
34
|
+
expect(ansi).to include("\e[1m\e[38;5;208mgem install kramdown-ansi\n\e[0m\e[0m")
|
35
|
+
expect(ansi).to include("\e[1m\e[38;5;208mbundle install\e[0m\e[0m")
|
36
|
+
expect(ansi).to include("This is the end.")
|
37
|
+
end
|
38
|
+
end
|
16
39
|
end
|
data/spec/spec_helper.rb
CHANGED
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.0
|
4
|
+
version: 0.1.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: '
|
18
|
+
version: '2.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: '
|
25
|
+
version: '2.0'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: all_images
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,6 +121,20 @@ dependencies:
|
|
121
121
|
- - "~>"
|
122
122
|
- !ruby/object:Gem::Version
|
123
123
|
version: '3.0'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: json
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '2.0'
|
131
|
+
type: :runtime
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '2.0'
|
124
138
|
description: |
|
125
139
|
Kramdown::ANSI: A library for rendering Markdown(ish) documents with
|
126
140
|
beautiful ANSI escape sequences in the terminal.
|
@@ -133,6 +147,7 @@ extra_rdoc_files:
|
|
133
147
|
- README.md
|
134
148
|
- lib/kramdown/ansi.rb
|
135
149
|
- lib/kramdown/ansi/pager.rb
|
150
|
+
- lib/kramdown/ansi/styles.rb
|
136
151
|
- lib/kramdown/ansi/width.rb
|
137
152
|
- lib/kramdown/version.rb
|
138
153
|
files:
|
@@ -148,11 +163,13 @@ files:
|
|
148
163
|
- kramdown-ansi.gemspec
|
149
164
|
- lib/kramdown/ansi.rb
|
150
165
|
- lib/kramdown/ansi/pager.rb
|
166
|
+
- lib/kramdown/ansi/styles.rb
|
151
167
|
- lib/kramdown/ansi/width.rb
|
152
168
|
- lib/kramdown/version.rb
|
153
169
|
- spec/assets/README.ansi
|
154
170
|
- spec/assets/kitten.jpg
|
155
171
|
- spec/kramdown/ansi/pager_spec.rb
|
172
|
+
- spec/kramdown/ansi/styles_spec.rb
|
156
173
|
- spec/kramdown/ansi/width_spec.rb
|
157
174
|
- spec/kramdown/ansi_spec.rb
|
158
175
|
- spec/spec_helper.rb
|
@@ -183,6 +200,7 @@ specification_version: 4
|
|
183
200
|
summary: Output markdown in the terminal with ANSI escape sequences
|
184
201
|
test_files:
|
185
202
|
- spec/kramdown/ansi/pager_spec.rb
|
203
|
+
- spec/kramdown/ansi/styles_spec.rb
|
186
204
|
- spec/kramdown/ansi/width_spec.rb
|
187
205
|
- spec/kramdown/ansi_spec.rb
|
188
206
|
- spec/spec_helper.rb
|