ruby_rich 0.3.1 → 0.4.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.
@@ -1,29 +1,247 @@
1
1
  module RubyRich
2
2
  class ProgressBar
3
3
 
4
- attr_reader :progress
4
+ attr_reader :progress, :total, :start_time
5
5
 
6
- def initialize(total, width: 50, style: :default)
6
+ # 进度条样式
7
+ STYLES = {
8
+ default: { filled: '=', empty: ' ', prefix: '[', suffix: ']' },
9
+ blocks: { filled: '█', empty: '░', prefix: '[', suffix: ']' },
10
+ arrows: { filled: '>', empty: '-', prefix: '[', suffix: ']' },
11
+ dots: { filled: '●', empty: '○', prefix: '(', suffix: ')' },
12
+ line: { filled: '━', empty: '─', prefix: '│', suffix: '│' },
13
+ gradient: { filled: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'], empty: ' ', prefix: '[', suffix: ']' }
14
+ }.freeze
15
+
16
+ def initialize(total, width: 50, style: :default, title: nil, show_percentage: true, show_rate: false, show_eta: false)
7
17
  @total = total
8
18
  @progress = 0
9
19
  @width = width
10
20
  @style = style
21
+ @title = title
22
+ @show_percentage = show_percentage
23
+ @show_rate = show_rate
24
+ @show_eta = show_eta
25
+ @start_time = nil
26
+ @last_update_time = nil
27
+ @update_history = []
11
28
  end
12
29
 
13
- def advance(amount)
30
+ def start
31
+ @start_time = Time.now
32
+ @last_update_time = @start_time
33
+ render
34
+ end
35
+
36
+ def advance(amount = 1)
37
+ @start_time ||= Time.now
14
38
  @progress += amount
15
- @progress = @total if @progress > @total
39
+ @progress = [@progress, @total].min
40
+
41
+ current_time = Time.now
42
+ @update_history << { time: current_time, progress: @progress }
43
+
44
+ # 保留最近的几个更新用于计算速率
45
+ @update_history = @update_history.last(10)
46
+ @last_update_time = current_time
47
+
16
48
  render
49
+ puts if completed?
50
+ end
51
+
52
+ def set_progress(value)
53
+ @start_time ||= Time.now
54
+ @progress = [[value, 0].max, @total].min
55
+
56
+ current_time = Time.now
57
+ @update_history << { time: current_time, progress: @progress }
58
+ @update_history = @update_history.last(10)
59
+ @last_update_time = current_time
60
+
61
+ render
62
+ puts if completed?
63
+ end
64
+
65
+ def completed?
66
+ @progress >= @total
67
+ end
68
+
69
+ def percentage
70
+ return 0 if @total == 0
71
+ (@progress.to_f / @total * 100).round(1)
72
+ end
73
+
74
+ def elapsed_time
75
+ return 0 unless @start_time
76
+ Time.now - @start_time
77
+ end
78
+
79
+ def rate
80
+ return 0 if @update_history.length < 2
81
+
82
+ first_update = @update_history.first
83
+ last_update = @update_history.last
84
+
85
+ time_diff = last_update[:time] - first_update[:time]
86
+ progress_diff = last_update[:progress] - first_update[:progress]
87
+
88
+ return 0 if time_diff == 0
89
+ progress_diff / time_diff
90
+ end
91
+
92
+ def eta
93
+ return 0 if rate == 0 || completed?
94
+ remaining = @total - @progress
95
+ remaining / rate
17
96
  end
18
97
 
19
98
  def render
20
- percentage = (@progress.to_f / @total * 100).to_i
21
- completed_width = (@progress.to_f / @total * @width).to_i
22
- incomplete_width = @width - completed_width
99
+ bar_content = render_bar
100
+ status_text = render_status
101
+
102
+ output = ""
103
+ output << "\e[94m#{@title}: \e[0m" if @title
104
+ output << bar_content
105
+ output << " #{status_text}" unless status_text.empty?
106
+
107
+ print "\r\e[K#{output}"
108
+ $stdout.flush
109
+ end
110
+
111
+ def finish(message: nil)
112
+ if message
113
+ puts "\r\e[K#{message}"
114
+ else
115
+ puts
116
+ end
117
+ end
118
+
119
+ # 静态方法:创建带回调的进度条
120
+ def self.with_progress(total, **options)
121
+ bar = new(total, **options)
122
+ bar.start
123
+
124
+ begin
125
+ yield(bar) if block_given?
126
+ ensure
127
+ bar.finish
128
+ end
129
+
130
+ bar
131
+ end
132
+
133
+ # 多进度条管理器
134
+ class MultiProgress
135
+ def initialize
136
+ @bars = []
137
+ @active = false
138
+ end
139
+
140
+ def add(title, total, **options)
141
+ bar = ProgressBar.new(total, title: title, **options)
142
+ @bars << bar
143
+ bar
144
+ end
145
+
146
+ def start
147
+ @active = true
148
+ render_all
149
+ end
150
+
151
+ def render_all
152
+ return unless @active
153
+
154
+ print "\e[#{@bars.length}A" unless @bars.empty? # 移动光标到顶部
155
+
156
+ @bars.each_with_index do |bar, index|
157
+ bar_output = render_bar_line(bar)
158
+ puts "\e[K#{bar_output}"
159
+ end
160
+
161
+ $stdout.flush
162
+ end
163
+
164
+ def finish_all
165
+ @active = false
166
+ puts
167
+ end
168
+
169
+ private
170
+
171
+ def render_bar_line(bar)
172
+ bar_content = bar.send(:render_bar)
173
+ status_text = bar.send(:render_status)
174
+
175
+ output = ""
176
+ output << "\e[94m#{bar.instance_variable_get(:@title)}: \e[0m" if bar.instance_variable_get(:@title)
177
+ output << bar_content
178
+ output << " #{status_text}" unless status_text.empty?
179
+ output
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ def render_bar
186
+ style_config = STYLES[@style] || STYLES[:default]
187
+
188
+ completed_width = (@progress.to_f / @total * @width).round
189
+
190
+ if @style == :gradient
191
+ filled_chars = style_config[:filled]
192
+ filled_full = filled_chars.last * (completed_width / filled_chars.length)
193
+ filled_partial = filled_chars[completed_width % filled_chars.length] if completed_width % filled_chars.length > 0
194
+ filled = filled_full + (filled_partial || '')
195
+ empty = style_config[:empty] * (@width - filled.length)
196
+ else
197
+ filled = style_config[:filled] * completed_width
198
+ empty = style_config[:empty] * (@width - completed_width)
199
+ end
200
+
201
+ # 添加颜色
202
+ color_filled = completed? ? "\e[92m" : "\e[96m" # 完成时绿色,进行中蓝色
203
+ color_empty = "\e[90m" # 空白部分灰色
204
+ color_reset = "\e[0m"
205
+
206
+ "#{style_config[:prefix]}#{color_filled}#{filled}#{color_empty}#{empty}#{color_reset}#{style_config[:suffix]}"
207
+ end
208
+
209
+ def render_status
210
+ parts = []
211
+
212
+ if @show_percentage
213
+ parts << "#{percentage}%"
214
+ end
215
+
216
+ parts << "(#{@progress}/#{@total})"
217
+
218
+ if @show_rate && rate > 0
219
+ parts << sprintf("%.1f/s", rate)
220
+ end
221
+
222
+ if @show_eta && eta > 0 && !completed?
223
+ eta_formatted = format_time(eta)
224
+ parts << "ETA: #{eta_formatted}"
225
+ end
226
+
227
+ if @start_time
228
+ elapsed_formatted = format_time(elapsed_time)
229
+ parts << "#{elapsed_formatted}"
230
+ end
231
+
232
+ parts.join(" ")
233
+ end
23
234
 
24
- bar = "[#{"=" * completed_width}#{" " * incomplete_width}]"
25
- print "\r#{bar} #{percentage}%"
26
- puts if @progress == @total
235
+ def format_time(seconds)
236
+ if seconds < 60
237
+ sprintf("%.1fs", seconds)
238
+ elsif seconds < 3600
239
+ minutes = seconds / 60
240
+ sprintf("%.1fm", minutes)
241
+ else
242
+ hours = seconds / 3600
243
+ sprintf("%.1fh", hours)
244
+ end
27
245
  end
28
246
  end
29
- end
247
+ end
@@ -0,0 +1,246 @@
1
+ module RubyRich
2
+ class Status
3
+ # 状态指示器类型
4
+ INDICATORS = {
5
+ # 简单状态
6
+ success: { symbol: '✅', color: "\e[92m", text: 'Success' },
7
+ error: { symbol: '❌', color: "\e[91m", text: 'Error' },
8
+ warning: { symbol: '⚠️', color: "\e[93m", text: 'Warning' },
9
+ info: { symbol: 'ℹ️', color: "\e[94m", text: 'Info' },
10
+
11
+ # 进度状态
12
+ pending: { symbol: '⏳', color: "\e[93m", text: 'Pending' },
13
+ running: { symbol: '🏃', color: "\e[94m", text: 'Running' },
14
+ completed: { symbol: '✅', color: "\e[92m", text: 'Completed' },
15
+ failed: { symbol: '💥', color: "\e[91m", text: 'Failed' },
16
+
17
+ # 系统状态
18
+ online: { symbol: '🟢', color: "\e[92m", text: 'Online' },
19
+ offline: { symbol: '🔴', color: "\e[91m", text: 'Offline' },
20
+ maintenance: { symbol: '🔧', color: "\e[93m", text: 'Maintenance' },
21
+
22
+ # 安全状态
23
+ secure: { symbol: '🔒', color: "\e[92m", text: 'Secure' },
24
+ insecure: { symbol: '🔓', color: "\e[91m", text: 'Insecure' },
25
+
26
+ # 等级状态
27
+ low: { symbol: '🔵', color: "\e[94m", text: 'Low' },
28
+ medium: { symbol: '🟡', color: "\e[93m", text: 'Medium' },
29
+ high: { symbol: '🔴', color: "\e[91m", text: 'High' },
30
+ critical: { symbol: '💀', color: "\e[95m", text: 'Critical' }
31
+ }.freeze
32
+
33
+ # 加载动画帧
34
+ SPINNER_FRAMES = {
35
+ dots: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
36
+ line: ['|', '/', '-', '\\'],
37
+ arrow: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'],
38
+ bounce: ['⠁', '⠂', '⠄', '⠂'],
39
+ pulse: ['●', '◐', '◑', '◒', '◓', '◔', '◕', '○'],
40
+ clock: ['🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛']
41
+ }.freeze
42
+
43
+ def self.indicator(type, text: nil, show_text: true, colorize: true)
44
+ config = INDICATORS[type.to_sym]
45
+ return "Unknown status: #{type}" unless config
46
+
47
+ symbol = config[:symbol]
48
+ color = colorize ? config[:color] : ""
49
+ reset = colorize ? "\e[0m" : ""
50
+ status_text = text || config[:text]
51
+
52
+ if show_text
53
+ "#{color}#{symbol} #{status_text}#{reset}"
54
+ else
55
+ "#{color}#{symbol}#{reset}"
56
+ end
57
+ end
58
+
59
+ def self.spinner(type: :dots, text: 'Loading...', delay: 0.1)
60
+ frames = SPINNER_FRAMES[type.to_sym] || SPINNER_FRAMES[:dots]
61
+
62
+ Thread.new do
63
+ frame_index = 0
64
+ loop do
65
+ print "\r\e[K\e[94m#{frames[frame_index]}\e[0m #{text}"
66
+ $stdout.flush
67
+ sleep delay
68
+ frame_index = (frame_index + 1) % frames.length
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.stop_spinner(final_message: nil)
74
+ if final_message
75
+ print "\r\e[K#{final_message}\n"
76
+ else
77
+ print "\r\e[K"
78
+ end
79
+ $stdout.flush
80
+ end
81
+
82
+ # 静态进度条
83
+ def self.progress_bar(current, total, width: 30, style: :filled)
84
+ percentage = (current.to_f / total * 100).round(1)
85
+ filled_width = (current.to_f / total * width).round
86
+
87
+ case style
88
+ when :filled
89
+ filled = '█' * filled_width
90
+ empty = '░' * (width - filled_width)
91
+ bar = "#{filled}#{empty}"
92
+ when :blocks
93
+ filled = '■' * filled_width
94
+ empty = '□' * (width - filled_width)
95
+ bar = "#{filled}#{empty}"
96
+ when :dots
97
+ filled = '●' * filled_width
98
+ empty = '○' * (width - filled_width)
99
+ bar = "#{filled}#{empty}"
100
+ else
101
+ filled = '=' * filled_width
102
+ empty = '-' * (width - filled_width)
103
+ bar = "#{filled}#{empty}"
104
+ end
105
+
106
+ "\e[92m[#{bar}]\e[0m #{percentage}% (#{current}/#{total})"
107
+ end
108
+
109
+ # 状态板
110
+ class StatusBoard
111
+ def initialize(width: 60)
112
+ @width = width
113
+ @items = []
114
+ end
115
+
116
+ def add_item(label, status, description: nil)
117
+ @items << {
118
+ label: label,
119
+ status: status,
120
+ description: description
121
+ }
122
+ self
123
+ end
124
+
125
+ def render(show_descriptions: true, align_status: :right)
126
+ lines = []
127
+ lines << "┌#{'─' * (@width - 2)}┐"
128
+
129
+ @items.each do |item|
130
+ label = item[:label]
131
+ status_text = RubyRich::Status.indicator(item[:status])
132
+ description = item[:description]
133
+
134
+ # 计算实际显示宽度(排除 ANSI 代码)
135
+ status_display_width = status_text.gsub(/\e\[[0-9;]*m/, '').length
136
+
137
+ case align_status
138
+ when :right
139
+ padding = @width - 4 - label.length - status_display_width
140
+ padding = [padding, 1].max
141
+ main_line = "│ #{label}#{' ' * padding}#{status_text} │"
142
+ when :left
143
+ padding = @width - 4 - label.length - status_display_width
144
+ padding = [padding, 1].max
145
+ main_line = "│ #{status_text} #{label}#{' ' * padding}│"
146
+ else # center
147
+ total_content = label.length + status_display_width + 1
148
+ left_padding = [(@width - 2 - total_content) / 2, 1].max
149
+ right_padding = @width - 2 - total_content - left_padding
150
+ main_line = "│#{' ' * left_padding}#{label} #{status_text}#{' ' * right_padding}│"
151
+ end
152
+
153
+ lines << main_line
154
+
155
+ if show_descriptions && description
156
+ desc_lines = wrap_text(description, @width - 4)
157
+ desc_lines.each do |desc_line|
158
+ padding = @width - 4 - desc_line.length
159
+ lines << "│ \e[90m#{desc_line}#{' ' * padding}\e[0m │"
160
+ end
161
+ end
162
+ end
163
+
164
+ lines << "└#{'─' * (@width - 2)}┘"
165
+ lines.join("\n")
166
+ end
167
+
168
+ private
169
+
170
+ def wrap_text(text, max_width)
171
+ words = text.split(' ')
172
+ lines = []
173
+ current_line = ''
174
+
175
+ words.each do |word|
176
+ if (current_line + ' ' + word).length <= max_width
177
+ current_line += current_line.empty? ? word : ' ' + word
178
+ else
179
+ lines << current_line unless current_line.empty?
180
+ current_line = word
181
+ end
182
+ end
183
+
184
+ lines << current_line unless current_line.empty?
185
+ lines
186
+ end
187
+ end
188
+
189
+ # 实时状态监控
190
+ class Monitor
191
+ def initialize(refresh_rate: 1.0)
192
+ @refresh_rate = refresh_rate
193
+ @items = {}
194
+ @running = false
195
+ end
196
+
197
+ def add_item(key, label, &block)
198
+ @items[key] = {
199
+ label: label,
200
+ block: block
201
+ }
202
+ self
203
+ end
204
+
205
+ def start
206
+ @running = true
207
+
208
+ Thread.new do
209
+ while @running
210
+ system('clear')
211
+ puts render_status
212
+ sleep @refresh_rate
213
+ end
214
+ end
215
+ end
216
+
217
+ def stop
218
+ @running = false
219
+ end
220
+
221
+ private
222
+
223
+ def render_status
224
+ lines = []
225
+ lines << "\e[1m\e[96mSystem Status Monitor\e[0m"
226
+ lines << "─" * 40
227
+ lines << ""
228
+
229
+ @items.each do |key, item|
230
+ begin
231
+ status = item[:block].call
232
+ status_indicator = RubyRich::Status.indicator(status)
233
+ lines << "#{item[:label]}: #{status_indicator}"
234
+ rescue => e
235
+ error_indicator = RubyRich::Status.indicator(:error, text: "Error: #{e.message}")
236
+ lines << "#{item[:label]}: #{error_indicator}"
237
+ end
238
+ end
239
+
240
+ lines << ""
241
+ lines << "\e[90mPress Ctrl+C to stop monitoring\e[0m"
242
+ lines.join("\n")
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,171 @@
1
+ require 'rouge'
2
+
3
+ module RubyRich
4
+ class Syntax
5
+ # 支持的语言别名映射
6
+ LANGUAGE_ALIASES = {
7
+ 'rb' => 'ruby',
8
+ 'py' => 'python',
9
+ 'js' => 'javascript',
10
+ 'ts' => 'typescript',
11
+ 'sh' => 'shell',
12
+ 'bash' => 'shell',
13
+ 'zsh' => 'shell',
14
+ 'yml' => 'yaml',
15
+ 'md' => 'markdown'
16
+ }.freeze
17
+
18
+ # 语法高亮主题颜色映射
19
+ THEME_COLORS = {
20
+ # Rouge token types to ANSI colors
21
+ 'Comment' => "\e[90m", # Bright black (gray)
22
+ 'Comment.Single' => "\e[90m",
23
+ 'Comment.Multiline' => "\e[90m",
24
+ 'Comment.Preproc' => "\e[95m", # Bright magenta
25
+
26
+ 'Keyword' => "\e[94m", # Bright blue
27
+ 'Keyword.Constant' => "\e[94m",
28
+ 'Keyword.Declaration' => "\e[94m",
29
+ 'Keyword.Namespace' => "\e[94m",
30
+ 'Keyword.Pseudo' => "\e[94m",
31
+ 'Keyword.Reserved' => "\e[94m",
32
+ 'Keyword.Type' => "\e[94m",
33
+
34
+ 'Literal' => "\e[96m", # Bright cyan
35
+ 'Literal.Date' => "\e[96m",
36
+ 'Literal.Number' => "\e[93m", # Bright yellow
37
+ 'Literal.Number.Bin' => "\e[93m",
38
+ 'Literal.Number.Float' => "\e[93m",
39
+ 'Literal.Number.Hex' => "\e[93m",
40
+ 'Literal.Number.Integer' => "\e[93m",
41
+ 'Literal.Number.Oct' => "\e[93m",
42
+
43
+ 'Literal.String' => "\e[92m", # Bright green
44
+ 'Literal.String.Affix' => "\e[92m",
45
+ 'Literal.String.Backtick' => "\e[92m",
46
+ 'Literal.String.Char' => "\e[92m",
47
+ 'Literal.String.Doc' => "\e[92m",
48
+ 'Literal.String.Double' => "\e[92m",
49
+ 'Literal.String.Escape' => "\e[96m",
50
+ 'Literal.String.Heredoc' => "\e[92m",
51
+ 'Literal.String.Interpol' => "\e[96m",
52
+ 'Literal.String.Other' => "\e[92m",
53
+ 'Literal.String.Regex' => "\e[91m", # Bright red
54
+ 'Literal.String.Single' => "\e[92m",
55
+ 'Literal.String.Symbol' => "\e[95m", # Bright magenta
56
+
57
+ 'Name' => "\e[97m", # Bright white
58
+ 'Name.Attribute' => "\e[93m", # Bright yellow
59
+ 'Name.Builtin' => "\e[96m", # Bright cyan
60
+ 'Name.Builtin.Pseudo' => "\e[96m",
61
+ 'Name.Class' => "\e[93m", # Bright yellow
62
+ 'Name.Constant' => "\e[93m",
63
+ 'Name.Decorator' => "\e[95m", # Bright magenta
64
+ 'Name.Entity' => "\e[93m",
65
+ 'Name.Exception' => "\e[91m", # Bright red
66
+ 'Name.Function' => "\e[96m", # Bright cyan
67
+ 'Name.Property' => "\e[96m",
68
+ 'Name.Label' => "\e[95m",
69
+ 'Name.Namespace' => "\e[93m",
70
+ 'Name.Other' => "\e[97m",
71
+ 'Name.Tag' => "\e[94m", # Bright blue
72
+ 'Name.Variable' => "\e[97m",
73
+ 'Name.Variable.Class' => "\e[97m",
74
+ 'Name.Variable.Global' => "\e[97m",
75
+ 'Name.Variable.Instance' => "\e[97m",
76
+
77
+ 'Operator' => "\e[91m", # Bright red
78
+ 'Operator.Word' => "\e[94m", # Bright blue
79
+
80
+ 'Punctuation' => "\e[97m", # Bright white
81
+
82
+ 'Error' => "\e[101m\e[97m", # Red background, white text
83
+ 'Generic.Deleted' => "\e[91m", # Bright red
84
+ 'Generic.Emph' => "\e[3m", # Italic
85
+ 'Generic.Error' => "\e[91m", # Bright red
86
+ 'Generic.Heading' => "\e[1m\e[94m", # Bold bright blue
87
+ 'Generic.Inserted' => "\e[92m", # Bright green
88
+ 'Generic.Output' => "\e[90m", # Bright black (gray)
89
+ 'Generic.Prompt' => "\e[1m", # Bold
90
+ 'Generic.Strong' => "\e[1m", # Bold
91
+ 'Generic.Subheading' => "\e[95m", # Bright magenta
92
+ 'Generic.Traceback' => "\e[91m" # Bright red
93
+ }.freeze
94
+
95
+ def self.highlight(code, language = nil, theme: :default)
96
+ new(theme: theme).highlight(code, language)
97
+ end
98
+
99
+ def initialize(theme: :default)
100
+ @theme = theme
101
+ end
102
+
103
+ def highlight(code, language = nil)
104
+ return code if code.nil? || code.empty?
105
+
106
+ # 检测或规范化语言
107
+ language = detect_language(code) if language.nil?
108
+ language = normalize_language(language) if language
109
+
110
+ # 如果无法检测语言,返回原始代码
111
+ return code unless language
112
+
113
+ begin
114
+ lexer = Rouge::Lexer.find(language)
115
+ return code unless lexer
116
+
117
+ # 使用自定义格式化器
118
+ formatter = AnsiFormatter.new
119
+ formatter.format(lexer.lex(code))
120
+ rescue => e
121
+ # 如果高亮失败,返回原始代码
122
+ code
123
+ end
124
+ end
125
+
126
+ def list_languages
127
+ Rouge::Lexer.all.map(&:tag).sort
128
+ end
129
+
130
+ private
131
+
132
+ def detect_language(code)
133
+ # 简单的语言检测启发式
134
+ return 'ruby' if code.include?('def ') && code.include?('end')
135
+ return 'python' if code.include?('def ') && code.include?(':')
136
+ return 'javascript' if code.include?('function') || code.include?('=>')
137
+ return 'html' if code.include?('<html') || code.include?('<!DOCTYPE')
138
+ return 'css' if code.match?(/\w+\s*{[^}]*}/)
139
+ return 'shell' if code.start_with?('#!/bin/') || code.include?('$ ')
140
+ return 'json' if code.strip.start_with?('{') && code.include?(':')
141
+ return 'yaml' if code.include?('---') || code.match?(/^\w+:/)
142
+
143
+ nil
144
+ end
145
+
146
+ def normalize_language(language)
147
+ return nil if language.nil?
148
+ language = language.to_s.downcase.strip
149
+ LANGUAGE_ALIASES[language] || language
150
+ end
151
+
152
+ # 自定义 ANSI 格式化器
153
+ class AnsiFormatter
154
+ def format(tokens)
155
+ output = ''
156
+ tokens.each do |token, value|
157
+ color_code = THEME_COLORS[token.qualname] ||
158
+ THEME_COLORS[token.token_chain.map(&:qualname).find { |t| THEME_COLORS[t] }] ||
159
+ ''
160
+
161
+ if color_code.empty?
162
+ output << value
163
+ else
164
+ output << "#{color_code}#{value}\e[0m"
165
+ end
166
+ end
167
+ output
168
+ end
169
+ end
170
+ end
171
+ end