class ButlerMainframe::HostBase

This is the host class base that contains high level logic It uses sub method that have to be defined in the specific sub class

Constants

MAX_TERMINAL_COLUMNS
MAX_TERMINAL_ROWS
WAIT_AFTER_START_CONNECTION
WAIT_AFTER_START_SESSION

Attributes

action[R]
close_session[RW]
debug[RW]
wait[R]

Public Class Methods

new(options={}) click to toggle source
# File lib/mainframe/host_base.rb, line 17
def initialize(options={})
  options = {
      :session_tag          => ButlerMainframe.configuration.session_tag,
      :wait                 => 0.01,  # wait screen in seconds
      :wait_debug           => 2,     # wait time for debug purpose
      :debug                => false,
      :browser_path         => ButlerMainframe.configuration.browser_path,
      :session_url          => ButlerMainframe.configuration.session_url,
      :session_path         => ButlerMainframe.configuration.session_path,
      :timeout              => ButlerMainframe.configuration.timeout,
      :erase_before_writing => false, # execute an erase until end of field before write a text
      :close_session        => :evaluate
                                #:evaluate    if the session is found will not be closed
                                #:never       never close the session
                                #:always      the session is always closed
  }.merge(options)

  @debug                = options[:debug]
  @wait                 = options[:wait]
  @wait_debug           = options[:wait_debug]
  @session_tag          = options[:session_tag]
  @close_session        = options[:close_session]
  @timeout              = options[:timeout]
  @timeout_screen       = @wait * 1000
  @erase_before_writing = options[:timeout]
  @action               = {}
  @pid                  = nil

  create_object options
end

Public Instance Methods

exec_command(cmd) click to toggle source

Execute keyboard command like PF1 or PA2 or ENTER …

# File lib/mainframe/host_base.rb, line 84
def exec_command(cmd)
  puts "Command: #{cmd}" if @debug
  sub_exec_command cmd
  wait_session
end
get_cursor_axes() click to toggle source

Return the coordinates of the cursor

# File lib/mainframe/host_base.rb, line 158
def get_cursor_axes
  sub_get_cursor_axes
end
quit() click to toggle source
# File lib/mainframe/host_base.rb, line 53
def quit
  puts "Closing session with criterion \"#{@close_session}\"" if @debug
  case @close_session
    when :always, :yes
      sub_close_session
      puts "Session closed" if @debug
      wait_session 0.1
    when :never, :no
      if @pid
        puts "Session forced to stay open" if @debug
      else
        puts "Session not closed because it was already existing anyway it would not been closed" if @debug
      end
    when :evaluate
      if @pid
        sub_close_session
        puts "Session closed because started by this process with id #{@pid}" if @debug
        wait_session 0.1
      else
        puts "Session not closed because it was already existing" if @debug
      end
  end
end
scan(options={}) click to toggle source

It reads one line or an area on the screen according to parameters supplied

# File lib/mainframe/host_base.rb, line 91
def scan(options={})
  options = {
      :y  => nil, :x  => nil, :len => nil,
      :y1 => nil, :x1 => nil, :y2  => nil, :x2 => nil,
  }.merge(options)
  if options[:len]
    scan_row(options[:y], options[:x], options[:len])
  else
    scan_area(options[:y1], options[:x1], options[:y2], options[:x2])
  end
end
scan_page() click to toggle source

Scans and returns the text of the entire page

# File lib/mainframe/host_base.rb, line 104
def scan_page
  scan_area(1, 1, MAX_TERMINAL_ROWS, MAX_TERMINAL_COLUMNS)
end
set_cursor_axes(y, x, options={}) click to toggle source

Move the cursor at given coordinates

# File lib/mainframe/host_base.rb, line 163
def set_cursor_axes(y, x, options={})
  sub_set_cursor_axes(y, x, options)
end
wait_session(wait=nil) click to toggle source

Sleep time between operations

# File lib/mainframe/host_base.rb, line 79
def wait_session(wait=nil)
  sleep(wait || (@debug ? @wait_debug : @wait))
end
write(text, options={}) click to toggle source

Write text on screen at the coordinates Based on the parameters provided it writes a line or an area Options:

:hook                       => nil,
:y                          => nil, #row
:x                          => nil, #column
:check                      => true,
:raise_error_on_check       => true,
:sensible_data              => nil,
:clean_chars_before_writing => nil, # clean x chars before writing a value, it switch off erase_before_writing
:erase_before_writing       => nil  # execute an erase until end of field before write a text
# File lib/mainframe/host_base.rb, line 119
def write(text, options={})
  options = show_deprecated_param(:erase_field_first, :erase_before_writing, options)       if options[:erase_field_first]
  options = show_deprecated_param(:clean_first_chars, :clean_chars_before_writing, options) if options[:clean_first_chars]
  options = {
      :hook                       => nil,
      :y                          => nil,
      :x                          => nil,
      :check                      => true,
      :raise_error_on_check       => true,
      :sensible_data              => nil,
      :clean_chars_before_writing => nil,
      :erase_before_writing       => @erase_before_writing
  }.merge(options)
  options[:erase_before_writing] = false if options[:clean_chars_before_writing]

  y   = options[:y] || get_cursor_axes[0]
  x   = options[:x] || get_cursor_axes[1]

  hooked_rows = 2
  raise "Missing coordinates! y(row)=#{y} x(column)=#{x} "  unless x && y
  raise "Sorry, cannot write null values at y=#{y} x=#{x}"  unless text

  bol_written = false
  if options[:hook]
    (y-hooked_rows..y+hooked_rows).each do |row_number|
      if /#{options[:hook]}/ === scan_row(row_number, 1, MAX_TERMINAL_COLUMNS)
        puts "Change y from #{y} to #{row_number} cause hook to:#{options[:hook]}" if row_number != y && @debug
        bol_written = write_text_on_map(text, row_number, x, options)
        break
      end
    end
  else
    #If no control is required or was not found the label reference
    bol_written = write_text_on_map(text, y, x, options) unless bol_written
  end
  bol_written
end

Private Instance Methods

create_object(options={}) click to toggle source

It creates the object calling subclass method It depends on the emulator chosen but typically the object is present after starting the terminal session These are the options with default values:

:session_tag      => Fixnum, String or null depending on emulator
:debug            => boolean
# File lib/mainframe/host_base.rb, line 174
def create_object(options={})
  connection_attempts       = 10
  seconds_between_attempts  = 1

  # Creating session object for emulators managed by API
  # Some emulator may start session terminal and return a process id in @pid
  sub_create_object options

  if sub_object_created?
    puts "Using the terminal with process id #{@pid}" if @pid && @debug
  else
    # if the terminal is not found then we start it
    puts "Session #{@session_tag} not found, starting new..." if @debug

    # Starting executable, check configuration file
    start_terminal_session options

    # New connection attempts after starting session...
    connection_attempts.times do
      puts "Detecting session #{@session_tag}, wait please..." if @debug
      sub_create_object
      sub_object_created? ? break : sleep(seconds_between_attempts)
    end
  end

  raise "Session #{@session_tag} not started. Check the session #{options[:browser_path]} #{options[:session_path]}" unless sub_object_created?

  # Session detected and waiting it become operative
  unless sub_object_ready?
    connection_attempts.times do
      puts "Waiting for the session to be ready..." if @debug
      sub_object_ready? ? break : sleep(seconds_between_attempts)
    end
  end

  # At this stage the session must be operative otherwise raise an exception
  if sub_object_ready?
    puts "** Connection established with #{sub_name} **"
    puts "Session full name: #{sub_fullname}" if @debug == :full
  else
    raise "Connection refused. Check session #{@session_tag}#{" with process id #{@pid}" if @pid}!"
  end

rescue
  puts $!.message
  raise $!
end
method_missing(method_name, *args) click to toggle source

If is called a not existing method there is the chance that an optional module may not have been added

# File lib/mainframe/host_base.rb, line 322
def method_missing(method_name, *args)
  raise NoMethodError, "Method #{method_name} not found! Please check you have included any optional modules"
end
scan_area(y1, x1, y2, x2) click to toggle source

It reads a rectangle on the screen

# File lib/mainframe/host_base.rb, line 258
def scan_area(y1, x1, y2, x2)
  str = sub_scan_area(y1, x1, y2, x2)
  puts "Scan area y1:#{y1} x1:#{x1} y2:#{y2} x2:#{x2} = #{str}" if @debug
  str
end
scan_row(y, x, len) click to toggle source

It reads one line on the screen

# File lib/mainframe/host_base.rb, line 251
def scan_row(y, x, len)
  str = sub_scan_row(y, x, len)
  puts "Scan row y:#{y} x:#{x} lungo:#{len} = #{str}" if @debug
  str
end
show_deprecated_method(new) click to toggle source
# File lib/mainframe/host_base.rb, line 317
def show_deprecated_method(new)
  puts "[DEPRECATION] please use #{new} method instead of #{caller[0][/`([^']*)'/, 1]}"
end
show_deprecated_param(old, new, params={}) click to toggle source
# File lib/mainframe/host_base.rb, line 311
def show_deprecated_param(old, new, params={})
  #Ruby 2+ caller_locations(1,1)[0].label
  puts "[DEPRECATION] please use param :#{new} instead of :#{old} for method #{caller[0][/`([^']*)'/, 1]}"
  # Creating new param with the value of the old param
  {new => params[old]}.merge(params)
end
start_terminal_session(options) click to toggle source

Starting terminal session Options:

:browser_path     => browser executable path, default value ButlerMainframe::Settings.browser_path (used by web emulator)
:session_url      => the session url used by browser
:session_path     => terminal session executable path, default value ButlerMainframe::Settings.session_path
# File lib/mainframe/host_base.rb, line 227
def start_terminal_session(options)
  # Check configuration to know emulator starting type
  executable, args =  if options[:browser_path] && !options[:browser_path].empty?
                        [options[:browser_path], options[:session_url]]
                      elsif options[:session_path] && !options[:session_path].empty?
                        [options[:session_path], nil]
                      else
                        [nil, nil]
                      end
  raise "Specify an executable in the configuration file!" unless executable

  if /^1.8/ === RUBY_VERSION
    Thread.new {system "#{executable} #{args}"}
    @pid = $?.pid if $?
  else
    #It works only on ruby 1.9+
    @pid = Process.spawn *[executable, args].compact
  end

  sleep WAIT_AFTER_START_SESSION
  puts "Started session with process id #{@pid}, wait please..." if @debug
end
write_text_on_map(text, y, x, options={}) click to toggle source

Write a text on the screen It also contains the logic to control the successful writing

# File lib/mainframe/host_base.rb, line 266
def write_text_on_map(text, y, x, options={})
  options = {
      :check                      => true,
      :raise_error_on_check       => true,
      :sensible_data              => nil,
      :clean_chars_before_writing => nil,
      :erase_before_writing       => nil
  }.merge(options)
  raise "Impossible to write beyond row #{MAX_TERMINAL_ROWS}"       if y > MAX_TERMINAL_ROWS
  raise "Impossible to write beyond column #{MAX_TERMINAL_COLUMNS}" if x > MAX_TERMINAL_COLUMNS
  raise "Impossible to write a null value"                          unless text

  if options[:clean_chars_before_writing] && options[:clean_chars_before_writing].to_i > 0
    puts "write_text_on_map: Clean #{options[:clean_chars_before_writing]} char#{options[:clean_chars_before_writing] == 1 ? '' : 's'} y:#{y} x:#{x}" if @debug
    sub_write_text(" " * options[:clean_chars_before_writing], y, x, :check_protect => false)
  end

  if options[:erase_before_writing]
    set_cursor_axes(y, x)
    do_erase
  end

  sub_write_text(text, y, x, :check_protect => options[:check])
  res = true
  # If check is required it verify text is on the screen at given coordinates
  # Sensible data option disable the check because it could be on hidden fields
  if options[:check] && !options[:sensible_data]
    if sub_keyboard_locked
      if options[:raise_error_on_check]
        raise "write_text_on_map: keyboard locked #{options[:sensible_data] ? ('*' * text.size) : text} at row #{y} column #{x}"
      else
        res = false
      end
    elsif !sub_wait_for_string(text, y, x)
      # It expects the string is present on the session at the specified coordinates
      if options[:raise_error_on_check]
        raise "write_text_on_map: impossible write #{options[:sensible_data] ? ('*' * text.size) : text} at row #{y} column #{x}"
      else
        res = false
      end
    end
  end
  res
end