Module: OllamaChat::KramdownANSI
Overview
A module that provides Kramdown::ANSI styling, parsing, and terminal table wrapping for OllamaChat.
This module handles three concerns:
- Styling configuration – sets up ANSI styles either from the
KRAMDOWN_ANSI_OLLAMA_CHAT_STYLESenvironment variable or from built-in defaults. - Markdown parsing – converts raw markdown into ANSI-formatted terminal output using the configured styles, with a fallback for invalid encodings.
- Table wrapping – computes per-column width budgets and wraps
cell content so that
Terminal::Tableoutput stays within a configurable percentage of the terminal width.
Instance Method Summary collapse
-
#configure_kramdown_ansi_styles ⇒ Hash
private
The configure_kramdown_ansi_styles method sets up ANSI styling for Kramdown::ANSI output by checking for specific environment variables and falling back to default styles.
-
#kramdown_ansi_parse(content) ⇒ String
The kramdown_ansi_parse method processes content using Kramdown::ANSI with custom ANSI styles.
-
#kramdown_markdown_remove(content, markdown: true) ⇒ String
private
The kramdown_markdown_remove method strips markdown formatting, emoji, ANSI escape sequences, and box-drawing characters from content, yielding plain text suitable for TTS playback.
-
#table_column_budgets(rows, cols, percentage: 90) ⇒ Array<Integer>
private
Computes a per-column display-width budget for a terminal table.
-
#wrap_table_rows(rows, cols, headings: nil, percentage: 90) ⇒ Array<Array<String, Hash>>
private
Wraps the cells of a terminal table to fit within a terminal-width budget.
Instance Method Details
#configure_kramdown_ansi_styles ⇒ Hash (private)
The configure_kramdown_ansi_styles method sets up ANSI styling for Kramdown::ANSI output by checking for specific environment variables and falling back to default styles.
52 53 54 55 56 57 58 |
# File 'lib/ollama_chat/kramdown_ansi.rb', line 52 def configure_kramdown_ansi_styles if json = OC::KRAMDOWN_ANSI_OLLAMA_CHAT_STYLES? Kramdown::ANSI::Styles.from_json(json).ansi_styles else Kramdown::ANSI::Styles.new.ansi_styles end end |
#kramdown_ansi_parse(content) ⇒ String
The kramdown_ansi_parse method processes content using Kramdown::ANSI with custom ANSI styles.
This method takes raw content and converts it into formatted ANSI output by applying the instance's configured ANSI styles. It is used to render content with appropriate terminal formatting based on the application's styling configuration.
33 34 35 36 37 38 39 40 41 42 |
# File 'lib/ollama_chat/kramdown_ansi.rb', line 33 def kramdown_ansi_parse(content) retried ||= false content.nil? and return '' Kramdown::ANSI.parse(content, ansi_styles: @kramdown_ansi_styles) rescue Encoding::UndefinedConversionError return content if retried retried = true content = content.encode(invalid: :replace) retry end |
#kramdown_markdown_remove(content, markdown: true) ⇒ String (private)
The kramdown_markdown_remove method strips markdown formatting, emoji, ANSI escape sequences, and box-drawing characters from content, yielding plain text suitable for TTS playback.
72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/ollama_chat/kramdown_ansi.rb', line 72 def kramdown_markdown_remove(content, markdown: true) content.full? or return '' content = content.gsub(/\p{Extended_Pictographic}/, '').full? or return '' if markdown content = kramdown_ansi_parse(content).full? or return '' end content = OllamaChat::Utils::StripANSI.strip_ansi(content) # Remove box-drawing characters (U+2500..U+257F) that kramdown uses # for table borders/separators — TTS engines stammer on these. content.gsub(/[\u2500-\u257F]/, ' ') .gsub(/\s{2,}/, ' ') .strip end |
#table_column_budgets(rows, cols, percentage: 90) ⇒ Array<Integer> (private)
Computes a per-column display-width budget for a terminal table.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/ollama_chat/kramdown_ansi.rb', line 133 def table_column_budgets(rows, cols, percentage: 90) max_w = Array.new(cols, 0) rows.each do |row| row.each_with_index do |cell, i| cell.is_a?(Hash) and next w = cell.to_s.lines.map { |l| Kramdown::ANSI::Width.display_width(l) }.max || 0 max_w[i] = [max_w[i], w].max end end total = max_w.sum budget = Kramdown::ANSI::Width.width(percentage:) - (3 * cols + 1) return Array.new(cols, (budget.to_f / cols).to_i) if total.zero? if total <= budget extra = budget - total max_w.map { |w| w + ((w.to_f / total) * extra).floor } else share = (budget.to_f / cols).to_i alloc = max_w.map { |w| [w, share].min } unused = budget - alloc.sum return alloc if unused.zero? capped = (0...cols).to_a.select { |i| alloc[i] < max_w[i] } need = capped.sum { |i| max_w[i] - alloc[i] } return alloc if need.zero? capped.each do |i| extra = ((max_w[i] - alloc[i]).to_f / need * unused).floor alloc[i] = [max_w[i], alloc[i] + extra].min end alloc end end |
#wrap_table_rows(rows, cols, headings: nil, percentage: 90) ⇒ Array<Array<String, Hash>> (private)
Wraps the cells of a terminal table to fit within a terminal-width budget.
Terminal::Table sizes each column to its widest cell, so a single long cell forces the whole table wider than the terminal. This computes a per-column budget from the widest cell in each column across all rows, then wraps each cell to it. Cells expressed as Hashes (e.g. colspan rows) are passed through untouched.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/ollama_chat/kramdown_ansi.rb', line 105 def wrap_table_rows(rows, cols, headings: nil, percentage: 90) all = rows + (headings ? [headings] : []) budget = table_column_budgets(all, cols, percentage:) wrap_row = ->(row) { row.each_with_index.map { |cell, i| cell.is_a?(Hash) and next cell Kramdown::ANSI::Width.wrap(cell.to_s, length: budget[i], reflow: false) } } result = rows.map(&wrap_row) if headings padded = headings.each_with_index.map { |h, i| w = Kramdown::ANSI::Width.display_width(h) w < budget[i] ? h + ' ' * (budget[i] - w) : h } result.unshift(padded) end result end |