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