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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1518a44da5a730fbc604c32cfcff2bfd7841de1423a6fe17ab4c60265cb108e6
4
- data.tar.gz: bcbd914a3469fcbe72367e2638bd868e7f4322244b9db5b62e7e27f20333a536
3
+ metadata.gz: 3a6aa1a68093731d47d9bc460ea4a2e5d498ae9dd126c56402e87affd2b6e00e
4
+ data.tar.gz: 2a1acad5300c9f9efb133e277998937a983256e2a90a1e9932fc6a3af9db5ec4
5
5
  SHA512:
6
- metadata.gz: 8fd9aed00b8212f853b34697991bc0eb88b18b99356c263ae062a8b69e10d973f6565a06013a6e767fae656dea7df169487987604e0f53c84d2cba57186fa346
7
- data.tar.gz: bc2438d30689ae6e3db356ac33c6ff76576b817bd1cc96d088820d984dbbbfa3567cb83be64a2fc1777f1cfb177f67e290d257b9d5c059d75dc38254d46231c5
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: A library for rendering Markdown(ish) documents with beautiful
6
- ANSI escape sequences in the terminal.
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
  ![md output](./img/md.png)
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
  ![git md output](./img/git-md.png)
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
- This software is licensed under the <i>MIT</i> license.
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
- rendered = Kramdown::ANSI.parse(ARGF.read)
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'
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: kramdown-ansi 0.0.4 ruby lib
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.4".freeze
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, ["~> 1.20".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
@@ -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
@@ -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
- def self.parse(source)
35
- @doc = Kramdown::Document.new(
36
- source, input: :mygfm, auto_ids: false, entity_output: :as_char
37
- ).to_ansi
38
- end
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 bold { underline { inner(el, opts) } }
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
- bold { inner(el, opts) }
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
- italic { inner(el, opts) }
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
- blue { el.value }
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
- blue { el.value }
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
- italic { inner(el, opts) }
373
+ apply_style(:em) { inner(el, opts) }
150
374
  elsif el.value == 'b' || el.value == 'strong'
151
- bold { inner(el, opts) }
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)
@@ -1,6 +1,6 @@
1
1
  module Kramdown
2
2
  # Kramdown version
3
- VERSION = '0.0.4'
3
+ VERSION = '0.1.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe Kramdown::ANSI::Pager do
3
+ describe Kramdown::ANSI::Pager do
4
4
  let :command do
5
5
  'cat'
6
6
  end
@@ -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
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe Kramdown::ANSI::Width do
3
+ describe Kramdown::ANSI::Width do
4
4
  before do
5
5
  allow(Tins::Terminal).to receive(:columns).and_return 80
6
6
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'pathname'
3
3
 
4
- RSpec.describe Kramdown::ANSI do
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 match("This is the end.")
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
@@ -1,9 +1,5 @@
1
- if ENV['START_SIMPLECOV'].to_i == 1
2
- require 'simplecov'
3
- SimpleCov.start do
4
- add_filter "#{File.basename(File.dirname(__FILE__))}/"
5
- end
6
- end
1
+ require 'gem_hadar/simplecov'
2
+ GemHadar::SimpleCov.start
7
3
  require 'rspec'
8
4
  begin
9
5
  require 'debug'
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
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: '1.20'
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: '1.20'
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