Class: String
- Inherits:
-
Object
- Object
- String
- Defined in:
- lib/string_extensions.rb
Overview
string_extensions.rb
Constant Summary collapse
- ANSI_SGR_RE =
Strip all ANSI SGR sequences
/\e\[\d+(?:;\d+)*m/.freeze
Instance Method Summary collapse
-
#bd ⇒ 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.
-
#inject(insertion, pos) ⇒ Object
Insert
insertionat visible positionpos(negative → end), respecting and re-inserting existing ANSI sequences. - #it ⇒ Object
- #l ⇒ Object
- #pure ⇒ Object
- #rv ⇒ 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.
- #ul ⇒ Object
-
#visible_length ⇒ Object
Get the visible length (without ANSI codes).
Instance Method Details
#bd ⇒ Object
bold, italic, underline, blink, reverse
48 |
# File 'lib/string_extensions.rb', line 48 def bd; 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
101 102 103 |
# File 'lib/string_extensions.rb', line 101 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
193 194 195 |
# File 'lib/string_extensions.rb', line 193 def has_ansi? !!(self =~ Rcurses::ANSI_RE) end |
#inject(insertion, pos) ⇒ Object
Insert insertion at visible position pos (negative → end),
respecting and re-inserting existing ANSI sequences.
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 159 |
# File 'lib/string_extensions.rb', line 127 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 |
#it ⇒ Object
49 |
# File 'lib/string_extensions.rb', line 49 def it; color(self, "\e[3m", "\e[23m"); end |
#l ⇒ Object
51 |
# File 'lib/string_extensions.rb', line 51 def l; color(self, "\e[5m", "\e[25m"); end |
#pure ⇒ Object
96 97 98 |
# File 'lib/string_extensions.rb', line 96 def pure gsub(ANSI_SGR_RE, '') end |
#rv ⇒ Object
52 |
# File 'lib/string_extensions.rb', line 52 def rv; color(self, "\e[7m", "\e[27m"); end |
#safe_bg(color) ⇒ Object
207 208 209 |
# File 'lib/string_extensions.rb', line 207 def safe_bg(color) has_ansi? ? self : bg(color) end |
#safe_fg(color) ⇒ Object
Apply color only if not already colored
203 204 205 |
# File 'lib/string_extensions.rb', line 203 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
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/string_extensions.rb', line 163 def safe_gsub(pattern, replacement = nil, &block) # Store all ANSI sequences and replace with placeholders ansi_sequences = [] placeholder_text = self.gsub(Rcurses::ANSI_RE) 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
187 188 189 190 |
# File 'lib/string_extensions.rb', line 187 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
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/string_extensions.rb', line 106 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 |
#ul ⇒ Object
50 |
# File 'lib/string_extensions.rb', line 50 def ul; color(self, "\e[4m", "\e[24m"); end |
#visible_length ⇒ Object
Get the visible length (without ANSI codes)
198 199 200 |
# File 'lib/string_extensions.rb', line 198 def visible_length pure.length end |