Ansi_again2
I had the idea that ansi_again should not create all methods.. but create them on demand.
So.. get rid of
.each {|k,v|
define_singleton_method(k) {|*args, **kw| v.call(*args, **kw) }
}
And store the hash in a variable @cmds = { .. all the procs ..}
Instead we use method_missing
to create methods as they are called:
I also want to chain method-calls by returning self.. except for methods returning a result.. ie scr_size
& cur_pos?
.
def method_missing(name, ...)
if @cmds.key?(name)
define_singleton_method(name) {|*args, **kw|
res = @cmds[name].call(*args, **kw)
[:scr_size, :cur_pos?].include?(name) ? res : self
}
send(name, ...)
else super; end
end
And that’s it!
NOTE Link to heading
I see now that I don’t want the include? check inside the method. Better to do the check before and modifiy the method creation.
Update 2025-02-03
Added method .col (for color).
col
uses only grayscale from the 256-color palette (232-255).
Usage: col(0-23) / col('0-7') / col(:a - :d)
The 24 colors are divided into 8 sections if addressed from String, and 4 sections if addressed from Symbol. Furthermore bg: true
makes the color a background-color. Bare col
resets all attributes (including color).
# Example calls
X.col(22).col(8, bg: true)
X.col('3')
X.col(:c, bg: true).col(:b)
Code Link to heading
module Screen
class Ansi
attr_reader :cmds
require 'io/console' # for stdin.raw (cur_pos?)
# TODO: auto-proc for simple print
def initialize
@cmds = {
# hide or show cursor
cur: proc {|show=true| → show ? '?25h' : '?25l' },
cur_u: proc {|st=1| self.cur_n(st) }, # aliases Up
cur_d: proc {|st=1| self.cur_s(st) }, # Down
cur_r: proc {|st=1| self.cur_e(st) }, # Right
cur_l: proc {|st=1| self.cur_w(st) }, # Left
at: proc {|ln, col| self.cur_at(ln, col) }, # alias cur_at
pat: proc {|ln, col, str| → '%s;%sH' % [ln, col]; print str },
cur_pos?: proc { # get cursor position
res = ''
$stdin.raw {|stdin|
$stdout << "\e[6n"; $stdout.flush
while (c = stdin.getc) != 'R' do; res << c if c; end
}
m = res.match /(?<ln>\d+);(?<col>\d+)/
Struct.new(:ln, :col).new(*m.to_a[1..].map(&:to_i))
}, # :bar, :line, :block or default (not given)
# shape can be :block, :line or :bar. blink: true or FALSE
cur_shape: proc {|shape, blink: true| # cursor shape and blink
val = shape ?
(%i(block line bar).index(shape) * 2 + (blink ? 1 : 2)) : 0
→ '%s q' % [val]
},
cur_color: proc {|col = '#FFFFFF'| # cursor color
print "\e]12;#{col}\a" if col[/^#[0-9A-F]{6}$/i]
},
cur_sto: proc { system('tput sc') }, # store cursor position
cur_rec: proc { system('tput rc') }, # recall cursor position
scr_sto: proc { system('tput smcup') }, # store screen
scr_rec: proc { system('tput rmcup') }, # recall screen
# columns and lines of terminal window
scr_size: proc { [`tput cols`.to_i, `tput lines`.to_i] },
# init and de-init (not required)
init: proc { self.scr_sto; self.cls; self.cur(false) },
de_init: proc {
self.scr_rec; self.cur; self.cur_shape; self.cur_color
},
# colors (only grayscale from 256 color palette)
col: proc {|c, bg: false|
scol = %i(a b c d)
case c
when Integer
color = c + 232 if c.between?(0, 23)
when String
color = (c.to_i * 3) + 232 if c.to_i.between?(0, 7)
when Symbol
color = (scol.index(c) * 6) + 232 if scol.include?(c)
end
if color
self.tap { → '%s;5;%sm' % [bg ? 48 : 38, color] }
else
self.tap { → '31;1;0m' }
end
}
}
# for pm ...
{
cls: '2J', cls_l: '1J', cls_r: '0J', cl_l: '1K', cl_r: '0K',
cl_ln: '2K', cur_n: '%sA', cur_s: '%sB', cur_e: '%sC', cur_w: '%sD',
cur_col: '%sG', cur_ln: '%sH',cur_at: '%s;%sH',
cur_ld: '%sE', cur_lu: '%sF'
}.each {|k,v| pm(k, v) }
end
def method_missing(n, ...)
if @cmds.key?(n)
if [:scr_size, :cur_pos?].include?(n)
define_singleton_method(n) {|*ar, **kw| @cmds[n].call(*ar, **kw) }
else define_singleton_method(n) {|*ar, **kw|
self.tap { @cmds[n].call(*ar, **kw) }
}; end; send(n, ...)
else super; end
end
# print ESC method
def →(s) = ( print "\e[#{s}" )
# proc-maker
def pm(name, str) = ( @cmds[name] =
proc {|*args| print "\e[#{str}" % [*args]} )
end
end