butler-mainframe 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +5 -0
- data/LICENSE +16 -0
- data/README.md +154 -0
- data/lib/butler-mainframe.rb +55 -0
- data/lib/config/config.rb +7 -0
- data/lib/config/settings.yml +20 -0
- data/lib/core/configuration.rb +27 -0
- data/lib/core/configuration_dynamic.rb +67 -0
- data/lib/generators/butler/USAGE +10 -0
- data/lib/generators/butler/install_generator.rb +23 -0
- data/lib/generators/butler/templates/custom_functions.rb +159 -0
- data/lib/generators/butler_mainframe.rb +38 -0
- data/lib/mainframe/customization/active_record.rb +26 -0
- data/lib/mainframe/customization/generic_functions.rb +156 -0
- data/lib/mainframe/host_base.rb +232 -0
- data/lib/mainframe/passport.rb +66 -0
- data/lib/vendor/deep_symbolize.rb +60 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 707011c7063ddf39ff8cfa148f2b91ddaa8a555b
|
4
|
+
data.tar.gz: 3b42e4a7fe6b12d18c746801b3f79bf714c9bd4b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 31fef2374924d2681e5b68c2e87cde56cc5aeb5038609b7c6bb107ad8bcc7224121d3e1bdbe0515ff71ab2f31af7df022d80b2ec2c97d352b4f376115e75e805
|
7
|
+
data.tar.gz: 4fd2d8220c14bc1edcbca590757d3b9956dcb8de0ef317b54ad6ee18f5368c0d41e5ba150c76b2c320807e49c8cfd9fbcae0f8736c941dc48bf8b6def90efcfc
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
butler-mainframe
|
2
|
+
This software can perform your custom tasks on a 3270 emulator.
|
3
|
+
|
4
|
+
Copyright (C) 2015 Marco Mastrodonato, m.mastrodonato@gmail.com
|
5
|
+
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License
|
16
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# Butler-mainframe
|
2
|
+
|
3
|
+
[![Version ](http://img.shields.io/gem/v/butler-mainframe.svg) ](https://rubygems.org/gems/butler-mainframe)
|
4
|
+
[![Travis CI ](http://img.shields.io/travis/marcomd/butler-mainframe/master.svg) ](https://travis-ci.org/marcomd/butler-mainframe)
|
5
|
+
[![Quality ](http://img.shields.io/codeclimate/github/marcomd/butler-mainframe.svg)](https://codeclimate.com/github/marcomd/butler-mainframe)
|
6
|
+
|
7
|
+
This gem provides a virtual butler which can perform your custom tasks on a 3270 emulator.
|
8
|
+
You just have to choose your emulator and configure your task.
|
9
|
+
|
10
|
+
## Compatibility
|
11
|
+
|
12
|
+
At the moment it works only on windows plaftorms.
|
13
|
+
Check on travis badge
|
14
|
+
|
15
|
+
|
16
|
+
## Install
|
17
|
+
|
18
|
+
Instal the gem from rubygems
|
19
|
+
|
20
|
+
gem install butler-mainframe
|
21
|
+
|
22
|
+
Then you have to install your favorite emulator.
|
23
|
+
At the moment is supported only [Passport web to host by rocket software](http://www.rocketsoftware.com/products/rocket-passport-web-to-host)
|
24
|
+
|
25
|
+
## How to use
|
26
|
+
|
27
|
+
In irb:
|
28
|
+
|
29
|
+
C:\Ruby>irb
|
30
|
+
irb(main):001:0> require 'butler-mainframe'
|
31
|
+
|
32
|
+
Use ButlerMainframe::Host.new
|
33
|
+
Please read the documentation on github for more information
|
34
|
+
=> true
|
35
|
+
irb(main):002:0>
|
36
|
+
|
37
|
+
You can start the emulator 3270 manually, the butler will lean on that and will let it open at the end.
|
38
|
+
In this example, I dont start the session and immediately create a new instance:
|
39
|
+
|
40
|
+
irb(main):002:0> host = ButlerMainframe::Host.new
|
41
|
+
Session not found, starting new...
|
42
|
+
Starting session with process id 8560, wait please...
|
43
|
+
** Connection established with host3270 **
|
44
|
+
=> #<ButlerMainframe::Host:0x29f3358 @debug=true, @wait=0.01, @wait_debug=2, @session=1, @close_session=:evaluate, @pid=8560, @action=#<WIN32OLE:0x29ebe10>, @session_started_by_me=true>
|
45
|
+
|
46
|
+
now session is ready to use:
|
47
|
+
|
48
|
+
host.scan_page
|
49
|
+
|
50
|
+
should return you what you see on your terminal
|
51
|
+
|
52
|
+
## Commands
|
53
|
+
|
54
|
+
You can do anything (disclaimer: as long as it was planned :innocent:)
|
55
|
+
|
56
|
+
### Scan
|
57
|
+
```ruby
|
58
|
+
# Scan and return all text in the terminal:
|
59
|
+
host.scan_page
|
60
|
+
|
61
|
+
# only a rectangle, in this example the first two rows:
|
62
|
+
host.scan x1: 1, y1: 1, x2: 80, y2: 2
|
63
|
+
|
64
|
+
# only a text
|
65
|
+
host.scan x: 10, y: 10, len: 10
|
66
|
+
```
|
67
|
+
|
68
|
+
### Write
|
69
|
+
```ruby
|
70
|
+
# write a text at the coordinates
|
71
|
+
host.write 'ruby on rails', :y => 6, :x => 15
|
72
|
+
|
73
|
+
# write a text using a hook
|
74
|
+
# With the hook you can use a regular expression to search a label on the y axis (3 rows up and down)
|
75
|
+
# It is usefull when the y position could change (atm it does not use x axis)
|
76
|
+
host.write 'ruby on rails', :y => 6, :x => 15, hook: 'SYSTEM='
|
77
|
+
```
|
78
|
+
|
79
|
+
### Navigate
|
80
|
+
|
81
|
+
The aim is to speed up browsing through static screens.
|
82
|
+
The butler detects the current screen and It moves towards the target.
|
83
|
+
For example, if the current screen is the login_session and you want to go to the next, to do that Butler log in
|
84
|
+
In this case it identifies the first map the session login to the mainframe and it does the login to go to the next screen.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# Go to the login session screen
|
88
|
+
host.navigate :login_session
|
89
|
+
|
90
|
+
# Now go to the next
|
91
|
+
host.navigate :next
|
92
|
+
|
93
|
+
# To go to the next screen Butler login for you
|
94
|
+
|
95
|
+
# Go back is simple as well
|
96
|
+
host.navigate :back
|
97
|
+
|
98
|
+
# You can configure your own destination in the generic functions module
|
99
|
+
host.navigate :your_favourite_screen
|
100
|
+
```
|
101
|
+
|
102
|
+
This is possible because it is configured in the navigate method which have to be configured to fit your needs. You can find it inside lib\mainframe\customization.
|
103
|
+
|
104
|
+
My advice is to use navigate method for generic navigation and use a specific module (or rails model) for each task.
|
105
|
+
|
106
|
+
## With Rails
|
107
|
+
|
108
|
+
This module can be use on rails project.
|
109
|
+
Add in your gemfile
|
110
|
+
|
111
|
+
gem 'butler-mainframe'
|
112
|
+
|
113
|
+
then
|
114
|
+
|
115
|
+
bundle install
|
116
|
+
|
117
|
+
run generator to copy configuration files
|
118
|
+
|
119
|
+
rails g butler:install
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
Class Invoice
|
123
|
+
... your code
|
124
|
+
|
125
|
+
def host3270
|
126
|
+
host = ButlerMainframe::Host.new
|
127
|
+
host.navigate :my_starting_position
|
128
|
+
host.write 'Hello world'
|
129
|
+
host.close_session
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
Create a polimorphic model:
|
135
|
+
rails generate screen hook_id:integer 'hook_type:string{30}' 'screen_type:integer{1}' video:text 'message:string{160}' 'cursor_x:integer{1}' 'cursor_y:integer{1}'
|
136
|
+
|
137
|
+
In the model to be related to screen we insert:
|
138
|
+
has_many :screens, :as => :hook, :dependent => :destroy
|
139
|
+
|
140
|
+
## License
|
141
|
+
|
142
|
+
The GNU Lesser General Public License, version 3.0 (LGPL-3.0)
|
143
|
+
See LICENSE file
|
144
|
+
Custom files are yours and not under license.
|
145
|
+
|
146
|
+
|
147
|
+
## Found a bug?
|
148
|
+
|
149
|
+
If you are having a problem please submit an issue at
|
150
|
+
* m.mastrodonato@gmail.com
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# This software can perform your custom tasks on a 3270 emulator.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2015 Marco Mastrodonato, m.mastrodonato@gmail.com
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License
|
8
|
+
|
9
|
+
require 'core/configuration'
|
10
|
+
require 'core/configuration_dynamic'
|
11
|
+
require 'config/config'
|
12
|
+
|
13
|
+
module ButlerMainframe
|
14
|
+
def self.root
|
15
|
+
File.expand_path('../..', __FILE__)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO
|
20
|
+
# require 'i18n'
|
21
|
+
# I18n.load_path=Dir['config/locales/*.yml']
|
22
|
+
# I18n.locale = ButlerMainframe.configuration.language
|
23
|
+
|
24
|
+
env = Rails.env if defined?(Rails)
|
25
|
+
env ||= $ARGV[0] if $ARGV
|
26
|
+
env ||= "development"
|
27
|
+
debug = env == "development" ? true : false
|
28
|
+
require "mainframe/#{ButlerMainframe.configuration.host_gateway.to_s.downcase}"
|
29
|
+
|
30
|
+
|
31
|
+
ButlerMainframe::Settings.load!(File.join(ButlerMainframe.root,'lib','config','settings.yml'), :env => env)
|
32
|
+
|
33
|
+
if ButlerMainframe::Settings.modules_included[:activerecord]
|
34
|
+
require 'mainframe/customization/active_record'
|
35
|
+
# puts "Extending Host class with #{Host3270::ActiveRecord}" if debug
|
36
|
+
ButlerMainframe::Host.include Host3270::ActiveRecord
|
37
|
+
end
|
38
|
+
|
39
|
+
if ButlerMainframe::Settings.modules_included[:generic_functions]
|
40
|
+
require 'mainframe/customization/generic_functions'
|
41
|
+
# puts "Extending Host class with #{Host3270::GenericFunctions}" if debug
|
42
|
+
ButlerMainframe::Host.include Host3270::GenericFunctions
|
43
|
+
end
|
44
|
+
|
45
|
+
if ButlerMainframe::Settings.modules_included[:custom_functions] && defined?(Host3270::CustomFunctions)
|
46
|
+
# puts "Extending Host class with #{Host3270::CustomFunctions}" if debug
|
47
|
+
ButlerMainframe::Host.include Host3270::CustomFunctions
|
48
|
+
end
|
49
|
+
|
50
|
+
=begin
|
51
|
+
# To test in irb
|
52
|
+
require 'butler-mainframe'
|
53
|
+
host=ButlerMainframe::Host.new
|
54
|
+
host.scan_page
|
55
|
+
=end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
ButlerMainframe.configure do |config|
|
2
|
+
# config.language = :it #NOT READY YET
|
3
|
+
config.host_gateway = 'passport'
|
4
|
+
config.browser_path = 'c:/Program Files (x86)/Internet Explorer/iexplore.exe'
|
5
|
+
config.session_path = 'https://ergmilict10/zephyr/Ecomes.zwh?sessionprofile=3270dsp/Sessions/host3270'
|
6
|
+
end
|
7
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
defaults: &defaults
|
2
|
+
cics: 7
|
3
|
+
user: "IBM0001"
|
4
|
+
password: "password"
|
5
|
+
life_user: "LIFE1"
|
6
|
+
life_password: "password"
|
7
|
+
modules_included:
|
8
|
+
activerecord: yes
|
9
|
+
generic_functions: yes
|
10
|
+
custom_functions: yes
|
11
|
+
|
12
|
+
development:
|
13
|
+
<<: *defaults
|
14
|
+
|
15
|
+
test:
|
16
|
+
<<: *defaults
|
17
|
+
|
18
|
+
production:
|
19
|
+
<<: *defaults
|
20
|
+
cics: 4
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ButlerMainframe
|
2
|
+
class Configuration
|
3
|
+
attr_writer :allow_sign_up
|
4
|
+
|
5
|
+
attr_accessor :language, :host_gateway, :browser_path, :session_path
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@language = :en
|
9
|
+
@host_gateway = nil
|
10
|
+
@browser_path = ""
|
11
|
+
@session_path = ""
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configuration
|
17
|
+
@configuration ||= Configuration.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.configuration=(config)
|
21
|
+
@configuration = config
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.configure
|
25
|
+
yield configuration
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'vendor/deep_symbolize'
|
3
|
+
|
4
|
+
module ButlerMainframe
|
5
|
+
# we don't want to instantiate this class - it's a singleton,
|
6
|
+
# so just keep it as a self-extended module
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Appdata provides a basic single-method DSL with .parameter method
|
10
|
+
# being used to define a set of available settings.
|
11
|
+
# This method takes one or more symbols, with each one being
|
12
|
+
# a name of the configuration option.
|
13
|
+
def parameter(*names)
|
14
|
+
names.each do |name|
|
15
|
+
attr_accessor name
|
16
|
+
|
17
|
+
# For each given symbol we generate accessor method that sets option's
|
18
|
+
# value being called with an argument, or returns option's current value
|
19
|
+
# when called without arguments
|
20
|
+
define_method name do |*values|
|
21
|
+
value = values.first
|
22
|
+
value ? self.send("#{name}=", value) : instance_variable_get("@#{name}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# And we define a wrapper for the configuration block, that we'll use to set up
|
28
|
+
# our set of options
|
29
|
+
def configure_on_the_fly(&block)
|
30
|
+
instance_eval &block
|
31
|
+
end
|
32
|
+
|
33
|
+
module Settings
|
34
|
+
# again - it's a singleton, thus implemented as a self-extended module
|
35
|
+
extend self
|
36
|
+
|
37
|
+
@_settings = {}
|
38
|
+
attr_reader :_settings
|
39
|
+
|
40
|
+
# This is the main point of entry - we call Settings.load! and provide
|
41
|
+
# a name of the file to read as it's argument. We can also pass in some
|
42
|
+
# options, but at the moment it's being used to allow per-environment
|
43
|
+
# overrides in Rails
|
44
|
+
def load!(filename, options = {})
|
45
|
+
newsets = YAML::load_file(filename)
|
46
|
+
newsets.extend DeepSymbolizable
|
47
|
+
newsets = newsets.deep_symbolize
|
48
|
+
newsets = newsets[options[:env].to_sym] if options[:env] && \
|
49
|
+
newsets[options[:env].to_sym]
|
50
|
+
deep_merge!(@_settings, newsets)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Deep merging of hashes
|
54
|
+
# deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
55
|
+
def deep_merge!(target, data)
|
56
|
+
merger = proc{|key, v1, v2|
|
57
|
+
Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
58
|
+
target.merge! data, &merger
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(name, *args, &block)
|
62
|
+
@_settings[name.to_sym] ||
|
63
|
+
fail(NoMethodError, "unknown configuration root #{name}", caller)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
module Butler
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
desc "Installs ButlerMainframe configuration files"
|
7
|
+
source_root File.expand_path('../../', __FILE__)
|
8
|
+
|
9
|
+
def copy_to_local
|
10
|
+
copy_file "butler/templates/custom_functions.rb", "config/initializers/butler_custom_functions.rb"
|
11
|
+
copy_file "../config/settings.yml", "config/butler.yml"
|
12
|
+
file = "config/initializers/butler.rb"
|
13
|
+
copy_file "../config/config.rb", file
|
14
|
+
append_file file do
|
15
|
+
<<-FILE.gsub(/^ /, '')
|
16
|
+
ButlerMainframe::Settings.load!(File.join(Rails.root,'config','butler.yml'), :env => Rails.env)
|
17
|
+
FILE
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# These modules contain extensions for the Host class
|
2
|
+
module Host3270
|
3
|
+
module CustomFunctions
|
4
|
+
### Insert here your custom methods ###
|
5
|
+
|
6
|
+
### Update default generic function ###
|
7
|
+
# def do_enter; exec_command "ENTER" end
|
8
|
+
#
|
9
|
+
# def go_back; exec_command "PA2" end
|
10
|
+
#
|
11
|
+
# def do_confirm; exec_command "PF3" end
|
12
|
+
#
|
13
|
+
# def do_quit; exec_command "CLEAR" end
|
14
|
+
#
|
15
|
+
# def destination_list
|
16
|
+
# [
|
17
|
+
# :company_menu,
|
18
|
+
# :cics_selection,
|
19
|
+
# :session_login,
|
20
|
+
# :next,
|
21
|
+
# :back]
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def navigate destination, options={}
|
25
|
+
# options = {
|
26
|
+
# :cics => ButlerMainframe::Settings.cics,
|
27
|
+
# :user => ButlerMainframe::Settings.user,
|
28
|
+
# :password => ButlerMainframe::Settings.password,
|
29
|
+
# :raise_on_abend => false
|
30
|
+
# }.merge(options)
|
31
|
+
# attempts_number = 10
|
32
|
+
# transactions_after_cics = ['tra1','tra2']
|
33
|
+
#
|
34
|
+
# raise "Destination #{destination} not valid, please use: #{destination_list.join(', ')}" unless destination_list.include? destination
|
35
|
+
# bol_found = nil
|
36
|
+
# attempts_number.times do
|
37
|
+
# if abend?
|
38
|
+
# options[:raise_on_abend] ? raise(catch_abend) : do_quit
|
39
|
+
# elsif cics?
|
40
|
+
# case destination
|
41
|
+
# when :cics_selection,
|
42
|
+
# :session_login then
|
43
|
+
# write "cesf logoff", :y => 1, :x => 1
|
44
|
+
# do_enter
|
45
|
+
# when :back then
|
46
|
+
# write "cesf logoff", :y => 1, :x => 1
|
47
|
+
# do_enter
|
48
|
+
# bol_found = true; break
|
49
|
+
# when :next then
|
50
|
+
# write transactions_after_cics[0], :y => 1, :x => 1
|
51
|
+
# do_enter
|
52
|
+
# bol_found = true; break
|
53
|
+
# when :company_menu then write transactions_after_cics[1], :y => 1, :x => 1
|
54
|
+
# else
|
55
|
+
# #Se siamo nel cics avviamo direttamente il life
|
56
|
+
# write transactions_after_cics[0], :y => 1, :x => 1
|
57
|
+
# do_enter
|
58
|
+
# end
|
59
|
+
# elsif cics_selection?
|
60
|
+
# case destination
|
61
|
+
# when :cics_selection then bol_found = true; break
|
62
|
+
# when :session_login then exec_command("PF3")
|
63
|
+
# when :next then
|
64
|
+
# cics_selection options[:cics] if options[:cics]
|
65
|
+
# bol_found = true; break
|
66
|
+
# when :back then
|
67
|
+
# exec_command("PF3")
|
68
|
+
# bol_found = true; break
|
69
|
+
# else
|
70
|
+
# cics_selection options[:cics] if options[:cics]
|
71
|
+
# end
|
72
|
+
# elsif session_login?
|
73
|
+
# case destination
|
74
|
+
# when :session_login,
|
75
|
+
# :back then bol_found = true; break
|
76
|
+
# when :next then
|
77
|
+
# session_login options[:user], options[:password] if options[:user] && options[:password]
|
78
|
+
# bol_found = true; break
|
79
|
+
# else
|
80
|
+
# session_login options[:user], options[:password] if options[:user] && options[:password]
|
81
|
+
# end
|
82
|
+
# else
|
83
|
+
# # If we do not know where we are...
|
84
|
+
# case destination
|
85
|
+
# when :back then
|
86
|
+
# # ...we can try to go back
|
87
|
+
# go_back
|
88
|
+
# bol_found = true; break
|
89
|
+
# when :next then
|
90
|
+
# # ...but we dont know how to move forward
|
91
|
+
# else
|
92
|
+
# # We unlock the position with both commands to be sure that they are managed by all maps cics
|
93
|
+
# go_back; do_quit
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
# wait_session
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# raise "It was waiting #{destination} map instead of: #{screen_title(:rows => 2).strip}" unless bol_found
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# def cics?
|
103
|
+
# scan(:y1 => 1, :x1 => 1, :y2 => 22, :x2 => 80).strip.empty?
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# #Login to mainframe
|
107
|
+
# def session_login?
|
108
|
+
# /EMSP00/i === screen_title
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# def session_login user, password
|
112
|
+
# puts "Starting session login..." if @debug
|
113
|
+
# wait_session
|
114
|
+
# #inizializza_sessione
|
115
|
+
# raise "It was waiting session login map instead of: #{screen_title}" unless session_login?
|
116
|
+
# write user, :y => 16, :x => 36
|
117
|
+
# write password, :y => 17, :x => 36, :sensible_data => true
|
118
|
+
# do_enter
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# # We need a label to know when we are on the cics selection map, usually ibm use EMSP01
|
122
|
+
# def cics_selection?
|
123
|
+
# /EMSP01/i === screen_title
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# # On this map, we have to select the cics environment
|
127
|
+
# def cics_selection cics
|
128
|
+
# puts "Starting selezione_cics..." if @debug
|
129
|
+
# wait_session
|
130
|
+
# raise "It was waiting cics selezion map instead of: #{screen_title}, messaggio: #{catch_message}" unless cics_selection?
|
131
|
+
# write cics, :y => 23, :x => 14
|
132
|
+
# do_enter
|
133
|
+
# wait_session 1
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
#
|
137
|
+
# def catch_message options={}
|
138
|
+
# options = {
|
139
|
+
# :first_row => 22,
|
140
|
+
# :last_row => 23
|
141
|
+
# }.merge(options)
|
142
|
+
# scan(:y1 => options[:first_row], :x1 => 1, :y2 => options[:last_row], :x2 => 80).gsub(/\s+/, " ").strip
|
143
|
+
# end
|
144
|
+
# def catch_abend
|
145
|
+
# scan(:y1 => 23, :x1 => 1, :y2 => 23, :x2 => 80)
|
146
|
+
# end
|
147
|
+
# def abend?
|
148
|
+
# /DFHA/i === catch_abend
|
149
|
+
# end
|
150
|
+
# def screen_title options={}
|
151
|
+
# options = {
|
152
|
+
# :rows => 1
|
153
|
+
# }.merge(options)
|
154
|
+
# scan(:y1 => 1, :x1 => 1, :y2 => options[:rows], :x2 => 80)
|
155
|
+
# end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
ButlerMainframe::Host.include Host3270::CustomFunctions
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ButlerMainframe
|
2
|
+
module Base
|
3
|
+
protected
|
4
|
+
# def authorization_file
|
5
|
+
# "app/models/ability.rb"
|
6
|
+
# end
|
7
|
+
# def authorization?
|
8
|
+
# File.exists? authorization_file
|
9
|
+
# end
|
10
|
+
# def authentication_file auth_class="User"
|
11
|
+
# "app/models/#{auth_class.downcase}.rb"
|
12
|
+
# end
|
13
|
+
# def authentication? auth_class="User"
|
14
|
+
# return true if File.exists? authentication_file(auth_class)
|
15
|
+
# File.exists? "config/initializers/devise.rb"
|
16
|
+
# end
|
17
|
+
# def activeadmin_file
|
18
|
+
# "config/initializers/active_admin.rb"
|
19
|
+
# end
|
20
|
+
# def activeadmin?
|
21
|
+
# File.exists? activeadmin_file
|
22
|
+
# end
|
23
|
+
# def auth_class
|
24
|
+
# return unless options[:auth_class]
|
25
|
+
# options[:auth_class].classify
|
26
|
+
# end
|
27
|
+
#def formtastic?
|
28
|
+
# return false unless options.formtastic?
|
29
|
+
# File.exists? "config/initializers/formtastic.rb"
|
30
|
+
#end
|
31
|
+
#def jquery_ui?
|
32
|
+
# File.exists? "vendor/assets/javascripts/jquery-ui"
|
33
|
+
#end
|
34
|
+
#def pagination?
|
35
|
+
# File.exists? "config/initializers/kaminari_config.rb"
|
36
|
+
#end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# These modules contain extensions for the Host class
|
2
|
+
module Host3270
|
3
|
+
# This module can be use on rails project.
|
4
|
+
#
|
5
|
+
# Create a polimorphic model:
|
6
|
+
# rails generate screen hook_id:integer 'hook_type:string{30}' 'screen_type:integer{1}' video:text 'message:string{160}' 'cursor_x:integer{1}' 'cursor_y:integer{1}'
|
7
|
+
#
|
8
|
+
# In the model to be related to screen we insert:
|
9
|
+
# has_many :screens, :as => :hook, :dependent => :destroy
|
10
|
+
module ActiveRecord
|
11
|
+
# screen_type: error, alert, notice...
|
12
|
+
def screenshot screen_type, options={}
|
13
|
+
options = {
|
14
|
+
:message => nil,
|
15
|
+
:video => nil,
|
16
|
+
:rails_model => Screen
|
17
|
+
}.merge(options)
|
18
|
+
screen = options[:rails_model].new
|
19
|
+
screen.screen_type = screen_type
|
20
|
+
screen.message = options[:message] || catch_message
|
21
|
+
screen.video = options[:video] || scan(:y1 => 1, :x1 => 1, :y2 => 22, :x2 => 80)
|
22
|
+
screen.cursor_y, screen.cursor_x = get_cursor_axes
|
23
|
+
screen
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# These modules contain extensions for the Host class
|
2
|
+
module Host3270
|
3
|
+
module GenericFunctions
|
4
|
+
|
5
|
+
def do_enter; exec_command "ENTER" end
|
6
|
+
|
7
|
+
def go_back; exec_command "PA2" end
|
8
|
+
|
9
|
+
def do_confirm; exec_command "PF3" end
|
10
|
+
|
11
|
+
def do_quit; exec_command "CLEAR" end
|
12
|
+
|
13
|
+
def destination_list
|
14
|
+
[
|
15
|
+
:company_menu,
|
16
|
+
:cics_selection,
|
17
|
+
:session_login,
|
18
|
+
:next,
|
19
|
+
:back]
|
20
|
+
end
|
21
|
+
|
22
|
+
def navigate destination, options={}
|
23
|
+
options = {
|
24
|
+
:cics => ButlerMainframe::Settings.cics,
|
25
|
+
:user => ButlerMainframe::Settings.user,
|
26
|
+
:password => ButlerMainframe::Settings.password,
|
27
|
+
:raise_on_abend => false
|
28
|
+
}.merge(options)
|
29
|
+
attempts_number = 10
|
30
|
+
transactions_after_cics = ['tra1','tra2']
|
31
|
+
|
32
|
+
raise "Destination #{destination} not valid, please use: #{destination_list.join(', ')}" unless destination_list.include? destination
|
33
|
+
bol_found = nil
|
34
|
+
attempts_number.times do
|
35
|
+
if abend?
|
36
|
+
options[:raise_on_abend] ? raise(catch_abend) : do_quit
|
37
|
+
elsif cics?
|
38
|
+
case destination
|
39
|
+
when :cics_selection,
|
40
|
+
:session_login then
|
41
|
+
write "cesf logoff", :y => 1, :x => 1
|
42
|
+
do_enter
|
43
|
+
when :back then
|
44
|
+
write "cesf logoff", :y => 1, :x => 1
|
45
|
+
do_enter
|
46
|
+
bol_found = true; break
|
47
|
+
when :next then
|
48
|
+
write transactions_after_cics[0], :y => 1, :x => 1
|
49
|
+
do_enter
|
50
|
+
bol_found = true; break
|
51
|
+
when :company_menu then write transactions_after_cics[1], :y => 1, :x => 1
|
52
|
+
else
|
53
|
+
#Se siamo nel cics avviamo direttamente il life
|
54
|
+
write transactions_after_cics[0], :y => 1, :x => 1
|
55
|
+
do_enter
|
56
|
+
end
|
57
|
+
elsif cics_selection?
|
58
|
+
case destination
|
59
|
+
when :cics_selection then bol_found = true; break
|
60
|
+
when :session_login then exec_command("PF3")
|
61
|
+
when :next then
|
62
|
+
cics_selection options[:cics] if options[:cics]
|
63
|
+
bol_found = true; break
|
64
|
+
when :back then
|
65
|
+
exec_command("PF3")
|
66
|
+
bol_found = true; break
|
67
|
+
else
|
68
|
+
cics_selection options[:cics] if options[:cics]
|
69
|
+
end
|
70
|
+
elsif session_login?
|
71
|
+
case destination
|
72
|
+
when :session_login,
|
73
|
+
:back then bol_found = true; break
|
74
|
+
when :next then
|
75
|
+
session_login options[:user], options[:password] if options[:user] && options[:password]
|
76
|
+
bol_found = true; break
|
77
|
+
else
|
78
|
+
session_login options[:user], options[:password] if options[:user] && options[:password]
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# If we do not know where we are...
|
82
|
+
case destination
|
83
|
+
when :back then
|
84
|
+
# ...we can try to go back
|
85
|
+
go_back
|
86
|
+
bol_found = true; break
|
87
|
+
when :next then
|
88
|
+
# ...but we dont know how to move forward
|
89
|
+
else
|
90
|
+
# We unlock the position with both commands to be sure that they are managed by all maps cics
|
91
|
+
go_back; do_quit
|
92
|
+
end
|
93
|
+
end
|
94
|
+
wait_session
|
95
|
+
end
|
96
|
+
|
97
|
+
raise "It was waiting #{destination} map instead of: #{screen_title(:rows => 2).strip}" unless bol_found
|
98
|
+
end
|
99
|
+
|
100
|
+
def cics?
|
101
|
+
scan(:y1 => 1, :x1 => 1, :y2 => 22, :x2 => 80).strip.empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
#Login to mainframe
|
105
|
+
def session_login?
|
106
|
+
/EMSP00/i === screen_title
|
107
|
+
end
|
108
|
+
|
109
|
+
def session_login user, password
|
110
|
+
puts "Starting session login..." if @debug
|
111
|
+
wait_session
|
112
|
+
#inizializza_sessione
|
113
|
+
raise "It was waiting session login map instead of: #{screen_title}" unless session_login?
|
114
|
+
write user, :y => 16, :x => 36
|
115
|
+
write password, :y => 17, :x => 36, :sensible_data => true
|
116
|
+
do_enter
|
117
|
+
end
|
118
|
+
|
119
|
+
# We need a label to know when we are on the cics selection map, usually ibm use EMSP01
|
120
|
+
def cics_selection?
|
121
|
+
/EMSP01/i === screen_title
|
122
|
+
end
|
123
|
+
|
124
|
+
# On this map, we have to select the cics environment
|
125
|
+
def cics_selection cics
|
126
|
+
puts "Starting selezione_cics..." if @debug
|
127
|
+
wait_session
|
128
|
+
raise "It was waiting cics selezion map instead of: #{screen_title}, messaggio: #{catch_message}" unless cics_selection?
|
129
|
+
write cics, :y => 23, :x => 14
|
130
|
+
do_enter
|
131
|
+
wait_session 1
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
def catch_message options={}
|
136
|
+
options = {
|
137
|
+
:first_row => 22,
|
138
|
+
:last_row => 23
|
139
|
+
}.merge(options)
|
140
|
+
scan(:y1 => options[:first_row], :x1 => 1, :y2 => options[:last_row], :x2 => 80).gsub(/\s+/, " ").strip
|
141
|
+
end
|
142
|
+
def catch_abend
|
143
|
+
scan(:y1 => 23, :x1 => 1, :y2 => 23, :x2 => 80)
|
144
|
+
end
|
145
|
+
def abend?
|
146
|
+
/DFHA/i === catch_abend
|
147
|
+
end
|
148
|
+
def screen_title options={}
|
149
|
+
options = {
|
150
|
+
:rows => 1
|
151
|
+
}.merge(options)
|
152
|
+
scan(:y1 => 1, :x1 => 1, :y2 => options[:rows], :x2 => 80)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module ButlerMainframe
|
2
|
+
# This is the host class base that contains high level logic
|
3
|
+
# It uses sub method that have to be defined in the specific sub class
|
4
|
+
class HostBase
|
5
|
+
|
6
|
+
attr_reader :action, :wait
|
7
|
+
attr_accessor :debug
|
8
|
+
|
9
|
+
MAX_TERMINAL_COLUMNS = 80
|
10
|
+
MAX_TERMINAL_ROWS = 24
|
11
|
+
|
12
|
+
def initialize options={}
|
13
|
+
options = {
|
14
|
+
:session => 1,
|
15
|
+
:wait => 0.01, #wait screen in seconds
|
16
|
+
:wait_debug => 2, #wait time for debug purpose
|
17
|
+
:debug => true,
|
18
|
+
:browser_path => ButlerMainframe.configuration.browser_path,
|
19
|
+
:session_path => ButlerMainframe.configuration.session_path,
|
20
|
+
:close_session => :evaluate
|
21
|
+
#:evaluate if the session is found will not be closed
|
22
|
+
#:never never close the session
|
23
|
+
#:always the session is always closed
|
24
|
+
}.merge(options)
|
25
|
+
|
26
|
+
@debug = options[:debug]
|
27
|
+
@wait = options[:wait]
|
28
|
+
@wait_debug = options[:wait_debug]
|
29
|
+
@session = options[:session]
|
30
|
+
@close_session = options[:close_session]
|
31
|
+
@pid = nil
|
32
|
+
|
33
|
+
create_object options
|
34
|
+
end
|
35
|
+
|
36
|
+
# Ends the connection and closes the session
|
37
|
+
def close_session
|
38
|
+
puts "Closing session with criterion \"#{@close_session}\"" if @debug
|
39
|
+
case @close_session
|
40
|
+
when :always
|
41
|
+
sub_close_session
|
42
|
+
puts "Session closed" if @debug
|
43
|
+
wait_session 0.1
|
44
|
+
when :evaluate
|
45
|
+
if @session_started_by_me
|
46
|
+
sub_close_session
|
47
|
+
puts "Session closed cause started by this process" if @debug
|
48
|
+
wait_session 0.1
|
49
|
+
else
|
50
|
+
puts "Session not closed because it was already existing" if @debug
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@action = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sleep time between operations
|
57
|
+
def wait_session wait=nil
|
58
|
+
sleep(wait || (@debug ? @wait_debug : @wait))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Execute keyboard command like PF1 or PA2 or ENTER ...
|
62
|
+
def exec_command cmd
|
63
|
+
cmd = "<#{cmd}>"
|
64
|
+
puts "Command: #{cmd}" if @debug
|
65
|
+
sub_exec_command cmd
|
66
|
+
wait_session
|
67
|
+
end
|
68
|
+
|
69
|
+
# It reads one line or an area on the screen according to parameters supplied
|
70
|
+
def scan options={}
|
71
|
+
options = {
|
72
|
+
:y => nil, :x => nil, :len => nil,
|
73
|
+
:y1 => nil, :x1 => nil, :y2 => nil, :x2 => nil,
|
74
|
+
}.merge(options)
|
75
|
+
if options[:len]
|
76
|
+
scan_row options[:y], options[:x], options[:len]
|
77
|
+
else
|
78
|
+
scan_area options[:y1], options[:x1], options[:y2], options[:x2]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Scans and returns the text of the entire page
|
83
|
+
def scan_page
|
84
|
+
scan_area 1, 1, MAX_TERMINAL_ROWS, MAX_TERMINAL_COLUMNS
|
85
|
+
end
|
86
|
+
|
87
|
+
# Write text on screen at the coordinates
|
88
|
+
# Based on the parameters provided it writes a line or an area
|
89
|
+
def write text, options={}
|
90
|
+
options = {
|
91
|
+
:hook => nil,
|
92
|
+
:y => nil, #riga
|
93
|
+
:x => nil, #colonna
|
94
|
+
:check => true,
|
95
|
+
:raise_error_on_check => true,
|
96
|
+
:sensible_data => nil,
|
97
|
+
:clean_first_chars => nil
|
98
|
+
}.merge(options)
|
99
|
+
|
100
|
+
y=options[:y]
|
101
|
+
x=options[:x]
|
102
|
+
raise "Missing coordinates! y(row)=#{y} x(column)=#{x} " unless x && y
|
103
|
+
raise "Sorry, cannot write null values" unless text
|
104
|
+
|
105
|
+
bol_written = nil
|
106
|
+
if options[:hook]
|
107
|
+
(y-2..y+2).each do |y_riga|
|
108
|
+
if /#{options[:hook]}/ === scan_row(y_riga, 1, MAX_TERMINAL_COLUMNS)
|
109
|
+
puts "Change y from #{y} to #{y_riga} cause hook to:#{options[:hook]}" if y_riga != y && @debug
|
110
|
+
bol_written = write_clean_text_on_map text, y_riga, x, options
|
111
|
+
break
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
#If no control is required or was not found the label reference
|
116
|
+
bol_written = write_clean_text_on_map(text, y, x, options) unless bol_written
|
117
|
+
bol_written
|
118
|
+
end
|
119
|
+
|
120
|
+
# It returns the coordinates of the cursor
|
121
|
+
def get_cursor_axes
|
122
|
+
sub_get_cursor_axes
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# It creates the object calling subclass method
|
128
|
+
# These are the options with default values:
|
129
|
+
# :session => 1,
|
130
|
+
# :debug => true,
|
131
|
+
# :browser_path => ButlerMainframe::Settings.browser_path,
|
132
|
+
# :session_path => ButlerMainframe::Settings.session_path,
|
133
|
+
def create_object options={}
|
134
|
+
sub_create_object
|
135
|
+
unless sub_object_created?
|
136
|
+
puts "Session not found, starting new..." if @debug
|
137
|
+
|
138
|
+
if /^1.8/ === RUBY_VERSION
|
139
|
+
Thread.new {system "#{options[:browser_path]} #{options[:session_path]}"}
|
140
|
+
@pid = $?.pid if $?
|
141
|
+
else
|
142
|
+
#It works only on ruby 1.9+
|
143
|
+
@pid = Process.spawn "#{options[:browser_path]}", "#{options[:session_path]}"
|
144
|
+
end
|
145
|
+
|
146
|
+
puts "Starting session with process id #{@pid}, wait please..." if @debug
|
147
|
+
sleep 2
|
148
|
+
5.times do
|
149
|
+
puts "Wait please..." if @debug
|
150
|
+
sub_create_object
|
151
|
+
sub_object_created? ? break : sleep(10)
|
152
|
+
end
|
153
|
+
@session_started_by_me = true
|
154
|
+
end
|
155
|
+
|
156
|
+
if sub_object_created?
|
157
|
+
puts "** Connection established with #{sub_name} **"
|
158
|
+
puts "Session full name: #{sub_fullname}" if @debug == :full
|
159
|
+
else
|
160
|
+
raise "Connection refused. Check the session #{options[:session]} and it was on the initial screen."
|
161
|
+
end
|
162
|
+
|
163
|
+
rescue
|
164
|
+
puts $!.message
|
165
|
+
raise $!
|
166
|
+
end
|
167
|
+
|
168
|
+
#It reads one line on the screen
|
169
|
+
def scan_row y, x, len
|
170
|
+
str = sub_scan_row y, x, len
|
171
|
+
puts "Scan row y:#{y} x:#{x} lungo:#{len} = #{str}" if @debug
|
172
|
+
str
|
173
|
+
end
|
174
|
+
|
175
|
+
#It reads a rectangle on the screen
|
176
|
+
def scan_area y1, x1, y2, x2
|
177
|
+
str = sub_scan_area y1, x1, y2, x2
|
178
|
+
puts "Scan area y1:#{y1} x1:#{x1} y2:#{y2} x2:#{x2} = #{str}" if @debug
|
179
|
+
str
|
180
|
+
end
|
181
|
+
|
182
|
+
# It has an additional option to clean the line before writing a value
|
183
|
+
def write_clean_text_on_map text, y, x, options={}
|
184
|
+
if options[:clean_first_chars] && options[:clean_first_chars].to_i > 0
|
185
|
+
puts "Clean #{options[:clean_first_chars]} char#{options[:clean_first_chars] == 1 ? '' : 's'} y:#{y} x:#{x}" if @debug
|
186
|
+
bol_cleaned = write_text_on_map(" " * options[:clean_first_chars], y, x, options)
|
187
|
+
unless bol_cleaned
|
188
|
+
puts "EHI! Impossible to clean the area specified" if @debug
|
189
|
+
return false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
puts "Clean: #{options[:sensible_data] ? ('*' * text.size) : text} y:#{y} x:#{x}" if @debug
|
193
|
+
write_text_on_map(text, y, x, options)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Write a text on the screen
|
197
|
+
# It also contains the logic to control the successful writing
|
198
|
+
def write_text_on_map(text, y, x, options={})
|
199
|
+
options = {
|
200
|
+
:check => true,
|
201
|
+
:raise_error_on_check => true,
|
202
|
+
:sensible_data => nil
|
203
|
+
}.merge(options)
|
204
|
+
raise "Impossible to write beyond row #{MAX_TERMINAL_ROWS}" if y > MAX_TERMINAL_ROWS
|
205
|
+
raise "Impossible to write beyond column #{MAX_TERMINAL_COLUMNS}" if x > MAX_TERMINAL_COLUMNS
|
206
|
+
raise "Impossible to write a null value" unless text
|
207
|
+
sub_write_text text, y, x
|
208
|
+
# It returns the function's result
|
209
|
+
if options[:check]
|
210
|
+
# It expects the string is present on the session at the specified coordinates
|
211
|
+
if sub_wait_for_string text, y, x
|
212
|
+
return true
|
213
|
+
else
|
214
|
+
if options[:raise_error_on_check]
|
215
|
+
raise "Impossible to write #{options[:sensible_data] ? ('*' * text.size) : text} at row #{y} column #{x}"
|
216
|
+
else
|
217
|
+
return false
|
218
|
+
end
|
219
|
+
end
|
220
|
+
else
|
221
|
+
return true
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# If is called a not existing method there is the chance that an optional module may not have been added
|
226
|
+
def method_missing method_name, *args
|
227
|
+
raise NoMethodError, "Method #{method_name} not found! Please check you have included any optional modules"
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'win32ole'
|
2
|
+
require 'mainframe/host_base'
|
3
|
+
|
4
|
+
# This class use Rocket 3270 emulator API
|
5
|
+
# http://www.rocketsoftware.com/products/rocket-passport-web-to-host
|
6
|
+
module ButlerMainframe
|
7
|
+
class Host < HostBase
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def sub_create_object
|
12
|
+
str_obj = 'PASSPORT.System'
|
13
|
+
puts "#{Time.now.strftime "%H:%M:%S"} Creating object #{str_obj}..." if @debug == :full
|
14
|
+
@action = WIN32OLE.new(str_obj)
|
15
|
+
end
|
16
|
+
|
17
|
+
def sub_object_created?
|
18
|
+
puts "#{Time.now.strftime "%H:%M:%S"} Terminal successfully detected" if @debug == :full
|
19
|
+
@action.Sessions(@session)
|
20
|
+
end
|
21
|
+
|
22
|
+
def sub_name
|
23
|
+
@action.Sessions(@session).Name
|
24
|
+
end
|
25
|
+
|
26
|
+
def sub_fullname
|
27
|
+
@action.Sessions(@session).FullName
|
28
|
+
end
|
29
|
+
|
30
|
+
#Ends the connection and closes the session
|
31
|
+
def sub_close_session
|
32
|
+
@action.Sessions(@session).Close
|
33
|
+
@action.Quit
|
34
|
+
end
|
35
|
+
|
36
|
+
#Execute keyboard command like PF1 or PA2 or ENTER ...
|
37
|
+
def sub_exec_command cmd
|
38
|
+
@action.Sessions(@session).Screen.SendKeys cmd
|
39
|
+
@action.Sessions(@session).Screen.WaitHostQuiet
|
40
|
+
end
|
41
|
+
|
42
|
+
#It reads one line part of the screen
|
43
|
+
def sub_scan_row y, x, len
|
44
|
+
@action.Sessions(@session).Screen.GetString(y, x, len)
|
45
|
+
end
|
46
|
+
|
47
|
+
#It reads a rectangle on the screen
|
48
|
+
def sub_scan_area y1, x1, y2, x2
|
49
|
+
@action.Sessions(@session).Screen.Area(y1, x1, y2, x2).Value
|
50
|
+
end
|
51
|
+
|
52
|
+
def sub_get_cursor_axes
|
53
|
+
[@action.Sessions(@session).Screen.Col, @action.Sessions(@session).Screen.Row]
|
54
|
+
end
|
55
|
+
|
56
|
+
def sub_write_text text, y, x
|
57
|
+
@action.Sessions(@session).Screen.PutString(text, y, x)
|
58
|
+
end
|
59
|
+
|
60
|
+
def sub_wait_for_string text, y, x
|
61
|
+
@action.Sessions(@session).Screen.WaitForString(text, y, x).Value == -1
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Symbolizes all of hash's keys and subkeys.
|
2
|
+
# Also allows for custom pre-processing of keys (e.g. downcasing, etc)
|
3
|
+
# if the block is given:
|
4
|
+
#
|
5
|
+
# somehash.deep_symbolize { |key| key.downcase }
|
6
|
+
#
|
7
|
+
# Usage: either include it into global Hash class to make it available to
|
8
|
+
# to all hashes, or extend only your own hash objects with this
|
9
|
+
# module.
|
10
|
+
# E.g.:
|
11
|
+
# 1) class Hash; include DeepSymbolizable; end
|
12
|
+
# 2) myhash.extend DeepSymbolizable
|
13
|
+
|
14
|
+
module DeepSymbolizable
|
15
|
+
def deep_symbolize(&block)
|
16
|
+
method = self.class.to_s.downcase.to_sym
|
17
|
+
syms = DeepSymbolizable::Symbolizers
|
18
|
+
syms.respond_to?(method) ? syms.send(method, self, &block) : self
|
19
|
+
end
|
20
|
+
|
21
|
+
module Symbolizers
|
22
|
+
extend self
|
23
|
+
|
24
|
+
# the primary method - symbolizes keys of the given hash,
|
25
|
+
# preprocessing them with a block if one was given, and recursively
|
26
|
+
# going into all nested enumerables
|
27
|
+
def hash(hash, &block)
|
28
|
+
hash.inject({}) do |result, (key, value)|
|
29
|
+
# Recursively deep-symbolize subhashes
|
30
|
+
value = _recurse_(value, &block)
|
31
|
+
|
32
|
+
# Pre-process the key with a block if it was given
|
33
|
+
key = yield key if block_given?
|
34
|
+
# Symbolize the key string if it responds to to_sym
|
35
|
+
sym_key = key.to_sym rescue key
|
36
|
+
|
37
|
+
# write it back into the result and return the updated hash
|
38
|
+
result[sym_key] = value
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# walking over arrays and symbolizing all nested elements
|
44
|
+
def array(ary, &block)
|
45
|
+
ary.map { |v| _recurse_(v, &block) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# handling recursion - any Enumerable elements (except String)
|
49
|
+
# is being extended with the module, and then symbolized
|
50
|
+
def _recurse_(value, &block)
|
51
|
+
if value.is_a?(Enumerable) && !value.is_a?(String)
|
52
|
+
# support for a use case without extended core Hash
|
53
|
+
value.extend DeepSymbolizable unless value.class.include?(DeepSymbolizable)
|
54
|
+
value = value.deep_symbolize(&block)
|
55
|
+
end
|
56
|
+
value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: butler-mainframe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marco Mastrodonato
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-07 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This gem provides a virtual butler which can perform your custom tasks
|
14
|
+
on a 3270 emulator. You just have to choose your emulator (atm only one choice)
|
15
|
+
and configure your task.
|
16
|
+
email:
|
17
|
+
- m.mastrodonato@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- CHANGELOG.md
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- lib/butler-mainframe.rb
|
27
|
+
- lib/config/config.rb
|
28
|
+
- lib/config/settings.yml
|
29
|
+
- lib/core/configuration.rb
|
30
|
+
- lib/core/configuration_dynamic.rb
|
31
|
+
- lib/generators/butler/USAGE
|
32
|
+
- lib/generators/butler/install_generator.rb
|
33
|
+
- lib/generators/butler/templates/custom_functions.rb
|
34
|
+
- lib/generators/butler_mainframe.rb
|
35
|
+
- lib/mainframe/customization/active_record.rb
|
36
|
+
- lib/mainframe/customization/generic_functions.rb
|
37
|
+
- lib/mainframe/host_base.rb
|
38
|
+
- lib/mainframe/passport.rb
|
39
|
+
- lib/vendor/deep_symbolize.rb
|
40
|
+
homepage: https://github.com/marcomd/butler-mainframe
|
41
|
+
licenses:
|
42
|
+
- LGPL-3.0
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.8.7
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements:
|
59
|
+
- So many patience
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.2.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: A virtual butler to perform tasks on a 3270 emulator
|
65
|
+
test_files: []
|