devex 0.3.5
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 +7 -0
- data/.obsidian/app.json +6 -0
- data/.obsidian/appearance.json +4 -0
- data/.obsidian/community-plugins.json +5 -0
- data/.obsidian/core-plugins.json +33 -0
- data/.obsidian/plugins/obsidian-minimal-settings/data.json +34 -0
- data/.obsidian/plugins/obsidian-minimal-settings/main.js +8 -0
- data/.obsidian/plugins/obsidian-minimal-settings/manifest.json +11 -0
- data/.obsidian/plugins/obsidian-style-settings/data.json +15 -0
- data/.obsidian/plugins/obsidian-style-settings/main.js +165 -0
- data/.obsidian/plugins/obsidian-style-settings/manifest.json +10 -0
- data/.obsidian/plugins/obsidian-style-settings/styles.css +243 -0
- data/.obsidian/plugins/table-editor-obsidian/data.json +6 -0
- data/.obsidian/plugins/table-editor-obsidian/main.js +236 -0
- data/.obsidian/plugins/table-editor-obsidian/manifest.json +17 -0
- data/.obsidian/plugins/table-editor-obsidian/styles.css +78 -0
- data/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
- data/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
- data/.obsidian/themes/Minimal/manifest.json +8 -0
- data/.obsidian/themes/Minimal/theme.css +2251 -0
- data/.rubocop.yml +231 -0
- data/CHANGELOG.md +97 -0
- data/LICENSE +21 -0
- data/README.md +314 -0
- data/Rakefile +13 -0
- data/devex-logo.jpg +0 -0
- data/docs/developing-tools.md +1000 -0
- data/docs/ref/agent-mode.md +46 -0
- data/docs/ref/cli-interface.md +60 -0
- data/docs/ref/configuration.md +46 -0
- data/docs/ref/design-philosophy.md +17 -0
- data/docs/ref/error-handling.md +38 -0
- data/docs/ref/io-handling.md +88 -0
- data/docs/ref/signals.md +141 -0
- data/docs/ref/temporal-software-theory.md +790 -0
- data/exe/dx +52 -0
- data/lib/devex/builtins/.index.rb +10 -0
- data/lib/devex/builtins/debug.rb +43 -0
- data/lib/devex/builtins/format.rb +44 -0
- data/lib/devex/builtins/gem.rb +77 -0
- data/lib/devex/builtins/lint.rb +61 -0
- data/lib/devex/builtins/test.rb +76 -0
- data/lib/devex/builtins/version.rb +156 -0
- data/lib/devex/cli.rb +340 -0
- data/lib/devex/context.rb +433 -0
- data/lib/devex/core/configuration.rb +136 -0
- data/lib/devex/core.rb +79 -0
- data/lib/devex/dirs.rb +210 -0
- data/lib/devex/dsl.rb +100 -0
- data/lib/devex/exec/controller.rb +245 -0
- data/lib/devex/exec/result.rb +229 -0
- data/lib/devex/exec.rb +662 -0
- data/lib/devex/loader.rb +136 -0
- data/lib/devex/output.rb +257 -0
- data/lib/devex/project_paths.rb +309 -0
- data/lib/devex/support/ansi.rb +437 -0
- data/lib/devex/support/core_ext.rb +560 -0
- data/lib/devex/support/global.rb +68 -0
- data/lib/devex/support/path.rb +357 -0
- data/lib/devex/support.rb +71 -0
- data/lib/devex/template_helpers.rb +136 -0
- data/lib/devex/templates/debug.erb +24 -0
- data/lib/devex/tool.rb +374 -0
- data/lib/devex/version.rb +5 -0
- data/lib/devex/working_dir.rb +99 -0
- data/lib/devex.rb +158 -0
- data/ruby-project-template/.gitignore +0 -0
- data/ruby-project-template/Gemfile +0 -0
- data/ruby-project-template/README.md +0 -0
- data/ruby-project-template/docs/README.md +0 -0
- data/sig/devex.rbs +4 -0
- metadata +122 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devex
|
|
4
|
+
module Support
|
|
5
|
+
# ANSI terminal color and style support with truecolor (24-bit) capability.
|
|
6
|
+
# Zero dependencies - inspired by the paint gem but self-contained.
|
|
7
|
+
#
|
|
8
|
+
# ## Modes
|
|
9
|
+
#
|
|
10
|
+
# ANSI.mode = 0xFFFFFF # Truecolor (default)
|
|
11
|
+
# ANSI.mode = 256 # 256-color palette
|
|
12
|
+
# ANSI.mode = 16 # 16 ANSI colors
|
|
13
|
+
# ANSI.mode = 0 # Disabled
|
|
14
|
+
#
|
|
15
|
+
# ## Basic Usage
|
|
16
|
+
#
|
|
17
|
+
# ANSI["text", :bold, :success] # Styled text
|
|
18
|
+
# ANSI["text", [0x5A, 0xF7, 0x8E]] # RGB foreground
|
|
19
|
+
# ANSI["text", "#5AF78E"] # Hex foreground
|
|
20
|
+
# ANSI["text", :bold, :success, bg: :error] # With background
|
|
21
|
+
#
|
|
22
|
+
# ## Nested Colors (relative to parent)
|
|
23
|
+
#
|
|
24
|
+
# When you need nested colored spans that reset back to their parent's
|
|
25
|
+
# color rather than to default, use the `%` method with substitutions:
|
|
26
|
+
#
|
|
27
|
+
# ANSI % ["Outer %{inner} text", :yellow, inner: ["nested", :blue]]
|
|
28
|
+
# # => Yellow text, with "nested" in blue, then back to yellow
|
|
29
|
+
#
|
|
30
|
+
# This is essential for building complex colorized output where inner
|
|
31
|
+
# spans shouldn't break the outer context.
|
|
32
|
+
#
|
|
33
|
+
# ## Direct Methods
|
|
34
|
+
#
|
|
35
|
+
# ANSI.bold("text")
|
|
36
|
+
# ANSI.color("text", 0x5A, 0xF7, 0x8E)
|
|
37
|
+
#
|
|
38
|
+
# ## String Refinements
|
|
39
|
+
#
|
|
40
|
+
# using Devex::Support::ANSI::StringMethods
|
|
41
|
+
# "text".ansi(:bold, :success)
|
|
42
|
+
# "text".bold
|
|
43
|
+
#
|
|
44
|
+
module ANSI
|
|
45
|
+
# Reset sequence - clears all formatting
|
|
46
|
+
RESET = "\e[0m"
|
|
47
|
+
|
|
48
|
+
# Semantic colors (truecolor RGB values)
|
|
49
|
+
# These are the primary colors for CLI output
|
|
50
|
+
COLORS = {
|
|
51
|
+
success: [0x5A, 0xF7, 0x8E], # Green
|
|
52
|
+
error: [0xFF, 0x6B, 0x6B], # Red
|
|
53
|
+
warning: [0xFF, 0xE6, 0x6D], # Yellow
|
|
54
|
+
info: [0x6B, 0xC5, 0xFF], # Blue
|
|
55
|
+
header: [0xC4, 0xB5, 0xFD], # Purple
|
|
56
|
+
muted: [0x88, 0x88, 0x88], # Gray
|
|
57
|
+
emphasis: [0xFF, 0xFF, 0xFF] # White
|
|
58
|
+
}.freeze
|
|
59
|
+
|
|
60
|
+
# Basic ANSI colors (for 16-color mode fallback)
|
|
61
|
+
BASIC_COLORS = {
|
|
62
|
+
black: 30, red: 31, green: 32, yellow: 33,
|
|
63
|
+
blue: 34, magenta: 35, cyan: 36, white: 37,
|
|
64
|
+
default: 39,
|
|
65
|
+
# Bright variants
|
|
66
|
+
bright_black: 90, bright_red: 91, bright_green: 92,
|
|
67
|
+
bright_yellow: 93, bright_blue: 94, bright_magenta: 95,
|
|
68
|
+
bright_cyan: 96, bright_white: 97
|
|
69
|
+
}.freeze
|
|
70
|
+
|
|
71
|
+
# Style codes
|
|
72
|
+
STYLES = {
|
|
73
|
+
bold: 1, bright: 1, # bright is alias for bold
|
|
74
|
+
dim: 2, faint: 2,
|
|
75
|
+
italic: 3,
|
|
76
|
+
underline: 4,
|
|
77
|
+
blink: 5,
|
|
78
|
+
reverse: 7, inverse: 7,
|
|
79
|
+
hidden: 8, conceal: 8,
|
|
80
|
+
strike: 9, crossed: 9
|
|
81
|
+
}.freeze
|
|
82
|
+
|
|
83
|
+
# Map semantic colors to basic ANSI for 16-color mode
|
|
84
|
+
SEMANTIC_TO_BASIC = {
|
|
85
|
+
success: :bright_green,
|
|
86
|
+
error: :bright_red,
|
|
87
|
+
warning: :bright_yellow,
|
|
88
|
+
info: :bright_blue,
|
|
89
|
+
header: :bright_magenta,
|
|
90
|
+
muted: :white,
|
|
91
|
+
emphasis: :bright_white
|
|
92
|
+
}.freeze
|
|
93
|
+
|
|
94
|
+
class << self
|
|
95
|
+
# Color mode: 0xFFFFFF (truecolor), 256, 16, or 0 (disabled)
|
|
96
|
+
# nil means auto-detect
|
|
97
|
+
def mode=(value)
|
|
98
|
+
@mode = value
|
|
99
|
+
@cache = {} # Clear cache on mode change
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def mode
|
|
103
|
+
return @mode if defined?(@mode) && @mode
|
|
104
|
+
|
|
105
|
+
detect_mode
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Detect appropriate color mode from environment
|
|
109
|
+
def detect_mode
|
|
110
|
+
return 0 if ENV["NO_COLOR"]
|
|
111
|
+
return 0xFFFFFF if ENV["FORCE_COLOR"]
|
|
112
|
+
|
|
113
|
+
# Check if Context is available for more sophisticated detection
|
|
114
|
+
if defined?(Devex::Context) && Devex::Context.respond_to?(:color?)
|
|
115
|
+
return 0 unless Devex::Context.color?
|
|
116
|
+
else
|
|
117
|
+
return 0 unless $stdout.tty?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Default to truecolor - modern terminals support it
|
|
121
|
+
0xFFFFFF
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Check if colors are enabled
|
|
125
|
+
def enabled? = mode > 0
|
|
126
|
+
|
|
127
|
+
# ─────────────────────────────────────────────────────────────
|
|
128
|
+
# Main API: ANSI["text", :bold, :success]
|
|
129
|
+
# ─────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
# Primary interface - apply styles and colors to text.
|
|
132
|
+
# Text is FIRST argument, followed by styles/colors (matches Paint API).
|
|
133
|
+
# Uses caching for compiled escape sequences.
|
|
134
|
+
#
|
|
135
|
+
# @example
|
|
136
|
+
# ANSI["hello", :bold]
|
|
137
|
+
# ANSI["hello", :success]
|
|
138
|
+
# ANSI["hello", :bold, :success]
|
|
139
|
+
# ANSI["hello", [0x5A, 0xF7, 0x8E]]
|
|
140
|
+
# ANSI["hello", "#5AF78E"]
|
|
141
|
+
# ANSI["hello", :bold, bg: :error]
|
|
142
|
+
#
|
|
143
|
+
def [](*args, bg: nil)
|
|
144
|
+
return "" if args.empty?
|
|
145
|
+
|
|
146
|
+
text = args.shift.to_s
|
|
147
|
+
return text unless enabled?
|
|
148
|
+
return text if args.empty? && bg.nil?
|
|
149
|
+
return text if text.empty? # Don't wrap empty strings
|
|
150
|
+
|
|
151
|
+
# Build cache key from options (include bg in key for caching, pass separately for processing)
|
|
152
|
+
cache_key = bg ? [args, bg].freeze : args.freeze
|
|
153
|
+
prefix = cached_prefix(args, bg, cache_key)
|
|
154
|
+
|
|
155
|
+
return text if prefix.empty?
|
|
156
|
+
|
|
157
|
+
"#{prefix}#{text}#{RESET}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# ─────────────────────────────────────────────────────────────
|
|
161
|
+
# Nested Colors: ANSI % ["text %{key}", :style, key: [...]]
|
|
162
|
+
# ─────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
# Apply colors with nested substitutions that reset to parent context.
|
|
165
|
+
#
|
|
166
|
+
# The key feature: when a nested span ends, it resets back to the
|
|
167
|
+
# parent's colors, not to default. This allows building complex
|
|
168
|
+
# colorized strings without breaking the outer context.
|
|
169
|
+
#
|
|
170
|
+
# @param paint_args [Array] [text, *styles, substitutions_hash]
|
|
171
|
+
# @param clear_color [String] ANSI sequence to reset to (internal use)
|
|
172
|
+
#
|
|
173
|
+
# @example Simple nested color
|
|
174
|
+
# ANSI % ["Hello %{name}!", :yellow, name: ["World", :blue]]
|
|
175
|
+
# # => Yellow "Hello ", blue "World", yellow "!"
|
|
176
|
+
#
|
|
177
|
+
# @example Multiple substitutions
|
|
178
|
+
# ANSI % ["%{status}: %{msg}", :muted,
|
|
179
|
+
# status: ["OK", :success],
|
|
180
|
+
# msg: ["All tests passed", :emphasis]]
|
|
181
|
+
#
|
|
182
|
+
# @example Deeply nested
|
|
183
|
+
# ANSI % ["Outer %{mid} end", :yellow,
|
|
184
|
+
# mid: ["middle %{inner} more", :blue,
|
|
185
|
+
# inner: ["deep", :red]]]
|
|
186
|
+
#
|
|
187
|
+
def %(paint_args, clear_color = RESET)
|
|
188
|
+
args = paint_args.dup
|
|
189
|
+
text = args.shift.to_s
|
|
190
|
+
|
|
191
|
+
# Extract substitution hash if present
|
|
192
|
+
substitutions = args.last.is_a?(Hash) ? args.pop : nil
|
|
193
|
+
|
|
194
|
+
# Get the color sequence for this level
|
|
195
|
+
cache_key = args.freeze
|
|
196
|
+
current_color = cached_prefix(args, nil, cache_key)
|
|
197
|
+
|
|
198
|
+
# Process substitutions recursively
|
|
199
|
+
if substitutions
|
|
200
|
+
substitutions.each do |key, value|
|
|
201
|
+
placeholder = "%{#{key}}"
|
|
202
|
+
replacement = if value.is_a?(Array)
|
|
203
|
+
# Recursive call - nested span resets to current_color, not RESET
|
|
204
|
+
self.%(value, clear_color + current_color)
|
|
205
|
+
else
|
|
206
|
+
value.to_s
|
|
207
|
+
end
|
|
208
|
+
text = text.gsub(placeholder, replacement)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
return text unless enabled?
|
|
213
|
+
|
|
214
|
+
if current_color.empty?
|
|
215
|
+
text
|
|
216
|
+
else
|
|
217
|
+
"#{current_color}#{text}#{clear_color}"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# ─────────────────────────────────────────────────────────────
|
|
222
|
+
# Direct Color Methods
|
|
223
|
+
# ─────────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
# Truecolor (24-bit) foreground
|
|
226
|
+
def color(text, r, g, b) = self[text, [r, g, b]]
|
|
227
|
+
|
|
228
|
+
# Truecolor (24-bit) background
|
|
229
|
+
def background(text, r, g, b) = self[text, bg: [r, g, b]]
|
|
230
|
+
|
|
231
|
+
# Hex color foreground: ANSI.hex("text", "#5AF78E")
|
|
232
|
+
def hex(text, hex_color) = self[text, hex_color]
|
|
233
|
+
|
|
234
|
+
# Named semantic color
|
|
235
|
+
def named(text, name) = self[text, name]
|
|
236
|
+
|
|
237
|
+
# ─────────────────────────────────────────────────────────────
|
|
238
|
+
# Style Methods
|
|
239
|
+
# ─────────────────────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
def bold(text) = self[text, :bold]
|
|
242
|
+
def dim(text) = self[text, :dim]
|
|
243
|
+
def italic(text) = self[text, :italic]
|
|
244
|
+
def underline(text) = self[text, :underline]
|
|
245
|
+
def blink(text) = self[text, :blink]
|
|
246
|
+
def reverse(text) = self[text, :reverse]
|
|
247
|
+
def hidden(text) = self[text, :hidden]
|
|
248
|
+
def strike(text) = self[text, :strike]
|
|
249
|
+
|
|
250
|
+
# ─────────────────────────────────────────────────────────────
|
|
251
|
+
# Utility Methods
|
|
252
|
+
# ─────────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
# Strip ANSI codes from text
|
|
255
|
+
def strip(text) = text.to_s.gsub(/\e\[[0-9;]*m/, "")
|
|
256
|
+
|
|
257
|
+
# Calculate visible length (without ANSI codes)
|
|
258
|
+
def visible_length(text) = strip(text).length
|
|
259
|
+
|
|
260
|
+
# Raw escape sequence without text wrapping.
|
|
261
|
+
# Useful for building custom sequences or streaming output.
|
|
262
|
+
def esc(*args) = cached_prefix(args, nil, args.freeze)
|
|
263
|
+
|
|
264
|
+
# Reset sequence
|
|
265
|
+
def reset = RESET
|
|
266
|
+
|
|
267
|
+
# Clear the escape sequence cache
|
|
268
|
+
def clear_cache! = @cache = {}
|
|
269
|
+
|
|
270
|
+
private
|
|
271
|
+
|
|
272
|
+
# Get or compute cached escape sequence prefix
|
|
273
|
+
# @param fg_args [Array] foreground styles/colors
|
|
274
|
+
# @param bg [Symbol, Array, String, nil] background color
|
|
275
|
+
# @param cache_key [Object] key for caching (includes both fg and bg)
|
|
276
|
+
def cached_prefix(fg_args, bg, cache_key)
|
|
277
|
+
@cache ||= {}
|
|
278
|
+
|
|
279
|
+
@cache[cache_key] ||= begin
|
|
280
|
+
codes = []
|
|
281
|
+
|
|
282
|
+
# Process foreground styles and colors
|
|
283
|
+
fg_args.each do |arg|
|
|
284
|
+
code = resolve_code(arg, foreground: true)
|
|
285
|
+
codes << code if code
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Process background
|
|
289
|
+
if bg
|
|
290
|
+
code = resolve_code(bg, foreground: false)
|
|
291
|
+
codes << code if code
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
codes.empty? ? "" : "\e[#{codes.join(';')}m"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Resolve an argument to ANSI code(s)
|
|
299
|
+
def resolve_code(arg, foreground:)
|
|
300
|
+
case arg
|
|
301
|
+
when Symbol then resolve_symbol(arg, foreground)
|
|
302
|
+
when Array
|
|
303
|
+
# RGB array
|
|
304
|
+
rgb_code(*arg, foreground: foreground)
|
|
305
|
+
when String
|
|
306
|
+
# Hex string like "#5AF78E" or "5AF78E"
|
|
307
|
+
resolve_hex(arg, foreground)
|
|
308
|
+
when Integer
|
|
309
|
+
# Direct ANSI code
|
|
310
|
+
arg
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def resolve_symbol(sym, foreground)
|
|
315
|
+
# Check styles first
|
|
316
|
+
if STYLES.key?(sym)
|
|
317
|
+
STYLES[sym]
|
|
318
|
+
# Then semantic colors
|
|
319
|
+
elsif COLORS.key?(sym)
|
|
320
|
+
rgb = COLORS[sym]
|
|
321
|
+
rgb_code(*rgb, foreground: foreground)
|
|
322
|
+
# Then basic ANSI colors
|
|
323
|
+
elsif BASIC_COLORS.key?(sym)
|
|
324
|
+
code = BASIC_COLORS[sym]
|
|
325
|
+
foreground ? code : code + 10
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def resolve_hex(hex_str, foreground)
|
|
330
|
+
hex_str = hex_str.delete_prefix("#")
|
|
331
|
+
|
|
332
|
+
# Expand 3-char hex: "FFF" -> "FFFFFF"
|
|
333
|
+
hex_str = hex_str.chars.map { |c| c * 2 }.join if hex_str.length == 3
|
|
334
|
+
|
|
335
|
+
return nil unless hex_str.length == 6
|
|
336
|
+
|
|
337
|
+
r = hex_str[0, 2].to_i(16)
|
|
338
|
+
g = hex_str[2, 2].to_i(16)
|
|
339
|
+
b = hex_str[4, 2].to_i(16)
|
|
340
|
+
|
|
341
|
+
rgb_code(r, g, b, foreground: foreground)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def rgb_code(r, g, b, foreground:)
|
|
345
|
+
case mode
|
|
346
|
+
when 0xFFFFFF, (257..)
|
|
347
|
+
# Truecolor
|
|
348
|
+
foreground ? "38;2;#{r};#{g};#{b}" : "48;2;#{r};#{g};#{b}"
|
|
349
|
+
when 256
|
|
350
|
+
# 256-color: convert RGB to nearest color cube index
|
|
351
|
+
index = rgb_to_256(r, g, b)
|
|
352
|
+
foreground ? "38;5;#{index}" : "48;5;#{index}"
|
|
353
|
+
when 16
|
|
354
|
+
# 16-color: find nearest basic color
|
|
355
|
+
basic = rgb_to_basic(r, g, b)
|
|
356
|
+
foreground ? basic : basic + 10
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Convert RGB to 256-color palette index
|
|
361
|
+
def rgb_to_256(r, g, b)
|
|
362
|
+
# Check if it's a grayscale
|
|
363
|
+
if r == g && g == b
|
|
364
|
+
return 16 if r < 8
|
|
365
|
+
return 231 if r > 248
|
|
366
|
+
|
|
367
|
+
return ((r - 8) / 10.0).round + 232
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Color cube: 6x6x6 starting at index 16
|
|
371
|
+
16 + (36 * (r / 51.0).round) + (6 * (g / 51.0).round) + (b / 51.0).round
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Convert RGB to nearest basic ANSI color code
|
|
375
|
+
def rgb_to_basic(r, g, b)
|
|
376
|
+
# Simple brightness-based mapping
|
|
377
|
+
bright = (r + g + b) > 382
|
|
378
|
+
base = 30
|
|
379
|
+
|
|
380
|
+
# Determine primary color
|
|
381
|
+
base += if r > g && r > b
|
|
382
|
+
1 # red
|
|
383
|
+
elsif g > r && g > b
|
|
384
|
+
2 # green
|
|
385
|
+
elsif b > r && b > g
|
|
386
|
+
4 # blue
|
|
387
|
+
elsif r > b
|
|
388
|
+
3 # yellow (r+g)
|
|
389
|
+
elsif g > r
|
|
390
|
+
6 # cyan (g+b)
|
|
391
|
+
elsif r > g
|
|
392
|
+
5 # magenta (r+b)
|
|
393
|
+
else
|
|
394
|
+
7 # white/gray
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
bright ? base + 60 : base
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# String refinements for ANSI colors
|
|
402
|
+
module StringMethods
|
|
403
|
+
refine String do
|
|
404
|
+
# Primary interface: "text".ansi(:bold, :success)
|
|
405
|
+
def ansi(*styles, bg: nil) = ANSI[self, *styles, bg: bg]
|
|
406
|
+
|
|
407
|
+
# RGB colors
|
|
408
|
+
def color(r, g, b) = ANSI.color(self, r, g, b)
|
|
409
|
+
|
|
410
|
+
def background(r, g, b) = ANSI.background(self, r, g, b)
|
|
411
|
+
|
|
412
|
+
# Hex color: "text".hex("#5AF78E")
|
|
413
|
+
def hex(hex_color) = ANSI.hex(self, hex_color)
|
|
414
|
+
|
|
415
|
+
# Named semantic color: "text".named(:success)
|
|
416
|
+
def named(name) = ANSI.named(self, name)
|
|
417
|
+
|
|
418
|
+
# Style shortcuts
|
|
419
|
+
def bold = ANSI.bold(self)
|
|
420
|
+
def dim = ANSI.dim(self)
|
|
421
|
+
def italic = ANSI.italic(self)
|
|
422
|
+
def underline = ANSI.underline(self)
|
|
423
|
+
def blink = ANSI.blink(self)
|
|
424
|
+
def reverse = ANSI.reverse(self)
|
|
425
|
+
def hidden = ANSI.hidden(self)
|
|
426
|
+
def strike = ANSI.strike(self)
|
|
427
|
+
|
|
428
|
+
# Strip ANSI codes
|
|
429
|
+
def strip_ansi = ANSI.strip(self)
|
|
430
|
+
|
|
431
|
+
# Visible length without ANSI codes
|
|
432
|
+
def visible_length = ANSI.visible_length(self)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|