Class: String
- Inherits:
-
Object
- Object
- String
- Defined in:
- lib/string_extensions.rb
Overview
string_extensions.rb
Instance Method Summary collapse
-
#b ⇒ Object
bold, italic, underline, blink, reverse.
-
#bg(color) ⇒ Object
256-color or truecolor RGB backgroundbreset only the bg (SGR 49).
-
#c(code) ⇒ Object
Combined code: “foo”.c(“FF0000,00FF00,bui”) — 6-hex or decimal for fg, then for bg, then letters b/i/u/l/r.
-
#clean_ansi ⇒ Object
Remove stray leading/trailing reset if the string has no other styling.
-
#color(text, sp, ep = "\e[0m") ⇒ Object
Internal helper - wraps
textin start/end sequences, and re-applies start on every newline. -
#fb(fg_color, bg_color) ⇒ Object
Both fg and bg in one go.
-
#fg(color) ⇒ Object
256-color or truecolor RGB foregroundbreset only the fg (SGR 39).
-
#has_ansi? ⇒ Boolean
Check if string contains ANSI codes.
- #i ⇒ Object
-
#inject(insertion, pos) ⇒ Object
Insert
insertionat visible positionpos(negative → end), respecting and re-inserting existing ANSI sequences. - #l ⇒ Object
-
#pure ⇒ Object
Strip all ANSI SGR sequences.
- #r ⇒ Object
- #safe_bg(color) ⇒ Object
-
#safe_fg(color) ⇒ Object
Apply color only if not already colored.
-
#safe_gsub(pattern, replacement = nil, &block) ⇒ Object
Safely apply regex replacements without corrupting ANSI sequences This method temporarily removes ANSI codes, applies the regex, then restores them.
-
#safe_gsub!(pattern, replacement = nil, &block) ⇒ Object
Safe version of gsub! that modifies in place.
-
#shorten(n) ⇒ Object
Truncate the visible length to n, but preserve embedded ANSI.
- #u ⇒ Object
-
#visible_length ⇒ Object
Get the visible length (without ANSI codes).
Instance Method Details
#b ⇒ Object
bold, italic, underline, blink, reverse
48 |
# File 'lib/string_extensions.rb', line 48 def b; color(self, "\e[1m", "\e[22m"); end |
#bg(color) ⇒ Object
256-color or truecolor RGB backgroundbreset only the bg (SGR 49)
16 17 18 19 20 21 22 23 24 |
# File 'lib/string_extensions.rb', line 16 def bg(color) sp, ep = if color.to_s =~ /\A[0-9A-Fa-f]{6}\z/ r, g, b = color.scan(/../).map { |c| c.to_i(16) } ["\e[48;2;#{r};#{g};#{b}m", "\e[49m"] else ["\e[48;5;#{color}m", "\e[49m"] end color(self, sp, ep) end |
#c(code) ⇒ Object
Combined code: “foo”.c(“FF0000,00FF00,bui”) — 6-hex or decimal for fg, then for bg, then letters b/i/u/l/r
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/string_extensions.rb', line 63 def c(code) parts = code.split(',') seq = [] fg = parts.shift if fg =~ /\A[0-9A-Fa-f]{6}\z/ r,g,b = fg.scan(/../).map{|c|c.to_i(16)} seq << "38;2;#{r};#{g};#{b}" elsif fg =~ /\A\d+\z/ seq << "38;5;#{fg}" end if parts.any? bg = parts.shift if bg =~ /\A[0-9A-Fa-f]{6}\z/ r,g,b = bg.scan(/../).map{|c|c.to_i(16)} seq << "48;2;#{r};#{g};#{b}" elsif bg =~ /\A\d+\z/ seq << "48;5;#{bg}" end end seq << '1' if code.include?('b') seq << '3' if code.include?('i') seq << '4' if code.include?('u') seq << '5' if code.include?('l') seq << '7' if code.include?('r') "\e[#{seq.join(';')}m#{self}\e[0m" end |
#clean_ansi ⇒ Object
Remove stray leading/trailing reset if the string has no other styling
100 101 102 |
# File 'lib/string_extensions.rb', line 100 def clean_ansi gsub(/\A(?:\e\[0m)+/, '').gsub(/\e\[0m\z/, '') end |
#color(text, sp, ep = "\e[0m") ⇒ Object
Internal helper - wraps text in start/end sequences, and re-applies start on every newline.
56 57 58 59 |
# File 'lib/string_extensions.rb', line 56 def color(text, sp, ep = "\e[0m") t = text.gsub("\n", "#{ep}\n#{sp}") "#{sp}#{t}#{ep}" end |
#fb(fg_color, bg_color) ⇒ Object
Both fg and bg in one go
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/string_extensions.rb', line 27 def fb(fg_color, bg_color) parts = [] if fg_color.to_s =~ /\A[0-9A-Fa-f]{6}\z/ r, g, b = fg_color.scan(/../).map { |c| c.to_i(16) } parts << "38;2;#{r};#{g};#{b}" else parts << "38;5;#{fg_color}" end if bg_color.to_s =~ /\A[0-9A-Fa-f]{6}\z/ r, g, b = bg_color.scan(/../).map { |c| c.to_i(16) } parts << "48;2;#{r};#{g};#{b}" else parts << "48;5;#{bg_color}" end sp = "\e[#{parts.join(';')}m" color(self, sp, "\e[39;49m") end |
#fg(color) ⇒ Object
256-color or truecolor RGB foregroundbreset only the fg (SGR 39)
5 6 7 8 9 10 11 12 13 |
# File 'lib/string_extensions.rb', line 5 def fg(color) sp, ep = if color.to_s =~ /\A[0-9A-Fa-f]{6}\z/ r, g, b = color.scan(/../).map { |c| c.to_i(16) } ["\e[38;2;#{r};#{g};#{b}m", "\e[39m"] else ["\e[38;5;#{color}m", "\e[39m"] end color(self, sp, ep) end |
#has_ansi? ⇒ Boolean
Check if string contains ANSI codes
192 193 194 |
# File 'lib/string_extensions.rb', line 192 def has_ansi? !!(self =~ /\e\[[0-9;]*m/) end |
#i ⇒ Object
49 |
# File 'lib/string_extensions.rb', line 49 def i; color(self, "\e[3m", "\e[23m"); end |
#inject(insertion, pos) ⇒ Object
Insert insertion at visible position pos (negative → end), respecting and re-inserting existing ANSI sequences.
126 127 128 129 130 131 132 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 |
# File 'lib/string_extensions.rb', line 126 def inject(insertion, pos) pure_txt = pure visible_len = pure_txt.length pos = visible_len if pos < 0 count, out, i, injected = 0, '', 0, false while i < length if self[i] == "\e" && (m = self[i..-1].match(/\A(\e\[\d+(?:;\d+)*m)/)) out << m[1] i += m[1].length else if count == pos && !injected out << insertion injected = true end out << self[i] count += 1 i += 1 end end unless injected if out =~ /(\e\[\d+(?:;\d+)*m)\z/ trailing = $1 out = out[0...-trailing.length] + insertion + trailing else out << insertion end end out end |
#l ⇒ Object
51 |
# File 'lib/string_extensions.rb', line 51 def l; color(self, "\e[5m", "\e[25m"); end |
#pure ⇒ Object
Strip all ANSI SGR sequences
95 96 97 |
# File 'lib/string_extensions.rb', line 95 def pure gsub(/\e\[\d+(?:;\d+)*m/, '') end |
#r ⇒ Object
52 |
# File 'lib/string_extensions.rb', line 52 def r; color(self, "\e[7m", "\e[27m"); end |
#safe_bg(color) ⇒ Object
206 207 208 |
# File 'lib/string_extensions.rb', line 206 def safe_bg(color) has_ansi? ? self : bg(color) end |
#safe_fg(color) ⇒ Object
Apply color only if not already colored
202 203 204 |
# File 'lib/string_extensions.rb', line 202 def safe_fg(color) has_ansi? ? self : fg(color) end |
#safe_gsub(pattern, replacement = nil, &block) ⇒ Object
Safely apply regex replacements without corrupting ANSI sequences This method temporarily removes ANSI codes, applies the regex, then restores them
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/string_extensions.rb', line 162 def safe_gsub(pattern, replacement = nil, &block) # Store all ANSI sequences and replace with placeholders ansi_sequences = [] placeholder_text = self.gsub(/\e\[[0-9;]*m/) do |match| ansi_sequences << match "⟨ANSI#{ansi_sequences.length - 1}⟩" end # Apply the regex to the placeholder text result = if block_given? placeholder_text.gsub(pattern, &block) else placeholder_text.gsub(pattern, replacement) end # Restore ANSI sequences ansi_sequences.each_with_index do |ansi, index| result.gsub!("⟨ANSI#{index}⟩", ansi) end result end |
#safe_gsub!(pattern, replacement = nil, &block) ⇒ Object
Safe version of gsub! that modifies in place
186 187 188 189 |
# File 'lib/string_extensions.rb', line 186 def safe_gsub!(pattern, replacement = nil, &block) result = safe_gsub(pattern, replacement, &block) self.replace(result) end |
#shorten(n) ⇒ Object
Truncate the visible length to n, but preserve embedded ANSI
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/string_extensions.rb', line 105 def shorten(n) count = 0 out = '' i = 0 while i < length && count < n if self[i] == "\e" && (m = self[i..-1].match(/\A(\e\[\d+(?:;\d+)*m)/)) out << m[1] i += m[1].length else out << self[i] i += 1 count += 1 end end out end |
#u ⇒ Object
50 |
# File 'lib/string_extensions.rb', line 50 def u; color(self, "\e[4m", "\e[24m"); end |
#visible_length ⇒ Object
Get the visible length (without ANSI codes)
197 198 199 |
# File 'lib/string_extensions.rb', line 197 def visible_length pure.length end |