butler-mainframe 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ 0.0.1 October 5th, 2015
2
+ ------------------------------
3
+ * First release
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ #gem "docile"
4
+ gem "i18n"
5
+ gem "butler-mainframe", path: "."
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,10 @@
1
+ Description:
2
+ Copy configurations file into local rails app
3
+
4
+ Example:
5
+ rails generate butler:install
6
+
7
+ Check all your generators:
8
+ rails g --help
9
+
10
+
@@ -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: []