butler-mainframe 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[ ](https://rubygems.org/gems/butler-mainframe)
|
4
|
+
[ ](https://travis-ci.org/marcomd/butler-mainframe)
|
5
|
+
[](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: []
|