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 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: []