butler-mainframe 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 622cd36fbabc8ca579e69b36ef0d3ce535995854
4
- data.tar.gz: bb98f4819d07e639c8cb88e092cc3a3a443fa648
3
+ metadata.gz: 54a4d86d300e73b9a93e3877cb23884911c51bd3
4
+ data.tar.gz: 4eb90e449628bca9f88f45f7b06038296ea86ff0
5
5
  SHA512:
6
- metadata.gz: 98f255d73ae417b65735be57cea9571d8d9f181ff794e4459a07e494ee8d1866af234d681a1f3af8301233d2b66ca166985677bbd94c14f23f33ae7c5a6565b7
7
- data.tar.gz: 0f7ae6b50f6f8d5bdd0f0ede9fae786f3927f88ef707531f4c8fa871a8f3ddd0885331d98176b9a1fcdb6d4a82c9eeb0a30ac3ecfe6719b394505b6124e6c391
6
+ metadata.gz: da00ccbc5269ee7cf04bf3cf3cd396b919e31da9456d1bfd13103b2942cdd7bf50158a574e4880e217b8030df5f96e491de4d5389de23cb509290b7c1f139fa2
7
+ data.tar.gz: 8ab5b447a48c02abb919ea0d82294b44be93685845f51a967a95aaa7d18821feb93e10fff80ff7362a8a5d0e73d6eeb88cb6eae50d0dcfe72197c42dd529caf2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ 0.3.0 [☰](https://github.com/marcomd/butler-mainframe/compare/v0.2.0...v0.3.0) October 22th, 2015
2
+ ------------------------------
3
+ * Improved the host base class code to make it simpler
4
+ * Improved automated tests with a more complex sequence and three iterations with decreasing latencies
5
+ * Pcomm sub class now use safer method to close session
6
+ * x3270 sub class is now more stable, It now passes all the tests although not always, rarely this does not happen with short delay time. There is still something to fix.
7
+ * Improved navigation and added new setting parameters
8
+ * Improved documentation
9
+ * Several little improvements
10
+
1
11
  0.2.0 [☰](https://github.com/marcomd/butler-mainframe/compare/v0.1.0...v0.2.0) October 20th, 2015
2
12
  ------------------------------
3
13
  * Added support for the free and open source x3270 terminal emulator, use :x3270 as host gateway in butler configuration
data/README.md CHANGED
@@ -26,7 +26,7 @@ At the moment are managed the below emulators. First two are commercial which mu
26
26
 
27
27
  * [Passport web to host by Rocket Software](http://www.rocketsoftware.com/resource/rocket-passport-web-host-overview)
28
28
  * [Personal communication by IBM](http://www-03.ibm.com/software/products/en/pcomm)
29
- * [x3270 maintained by Paul Mates](http://x3270.bgp.nu/)
29
+ * [x3270 maintained by Paul Mates](http://x3270.bgp.nu/) (only on ruby 1.9+)
30
30
 
31
31
 
32
32
  ## Configuration
@@ -40,9 +40,9 @@ In the config folder there are two files:
40
40
 
41
41
  config.rb can be used for the configuration of the gem and the emulator
42
42
 
43
- ```ruby
44
- # Example to configure Passport web to host
43
+ Example to configure Passport web to host:
45
44
 
45
+ ```ruby
46
46
  ButlerMainframe.configure do |config|
47
47
  config.host_gateway = :passport
48
48
  config.browser_path = 'c:/Program Files (x86)/Internet Explorer/iexplore.exe'
@@ -52,9 +52,9 @@ ButlerMainframe.configure do |config|
52
52
  end
53
53
  ```
54
54
 
55
- ```ruby
56
- # Example to configure Personal communication
55
+ Example to configure Personal communication:
57
56
 
57
+ ```ruby
58
58
  ButlerMainframe.configure do |config|
59
59
  config.host_gateway = :pcomm
60
60
  config.session_path = '"C:/Program Files (x86)/IBM/Personal Communications/pcsws.exe" "C:/Users/Marco/AppData/Roaming/IBM/Personal Communications/host3270.ws"'
@@ -63,8 +63,9 @@ ButlerMainframe.configure do |config|
63
63
  end
64
64
  ```
65
65
 
66
+ Example to configure X3270:
67
+
66
68
  ```ruby
67
- # Example to configure X3270
68
69
  ButlerMainframe.configure do |config|
69
70
  config.host_gateway = :x3270
70
71
  config.session_path = '"C:/Program Files (x86)/wc3270/ws3270.exe" 127.0.0.1 -model 2 --'
@@ -171,42 +172,58 @@ run generator to copy configuration files suitable for the emulator you need
171
172
 
172
173
  rails g butler:install --emulator=passport
173
174
 
175
+ You should have a model for every function to perform.
176
+ In this simple example i have to insert an invoice number on a cics map so i create the invoice model:
177
+
178
+ rails generate scaffold invoice number:integer
179
+
180
+ You may create a polimorphic model to save mainframe screen:
181
+
182
+ rails generate migration CreateScreens hook_id:integer 'hook_type:string{30}' 'screen_type:integer{1}' video:text 'message:string{160}' 'cursor_x:integer{1}' 'cursor_y:integer{1}'
183
+
184
+ Create something like this:
185
+
174
186
  ```ruby
175
187
  Class Invoice
176
- # ... your code
188
+ has_many :screens, :as => :hook, :dependent => :destroy
189
+
190
+ # ... your rails code: validations, scopes etc.
177
191
 
192
+ # Your main function method that perform the action on the mainframe
178
193
  def host3270
179
194
  @host = ButlerMainframe::Host.new
195
+
196
+ # Move to your starting position
197
+ # They often are static screens so it's easier to use navigate method
180
198
  @host.navigate :my_starting_position
181
199
 
182
200
  # Always check whether we are positioned on the screen that we expect
183
- raise 'Screen not expected' unless self.my_function_start_screen?
201
+ raise 'Screen not expected' unless @host.my_function_start_screen?
184
202
 
185
203
  # We develop the function.
186
- # In this simple case we put a number in a map cics and press Enter
187
- @host.write self.invoice_number
204
+ # In this simple case we put a number in a map CICS at row 10 and column 5
205
+ # as option we also choose to erase any previous value in the field
206
+ @host.write self.number, y: 10, x: 5, erase_field_first: true
207
+
208
+ # Press enter because the example mainframe program expects it as confirmation
188
209
  @host.do_enter
189
210
 
190
- # to read the confirmation message
191
- raise 'Message not expected' unless /SUCCESSFUL/ === self.catch_message
211
+ # Read the confirmation message otherwise raise an exception
212
+ raise 'Message not expected' unless /SUCCESSFUL/ === @host.catch_message
192
213
 
214
+ # At the end close the session
193
215
  @host.close_session
194
216
  rescue
195
- host.screenshot :error
217
+ # Save the screen as error to show to your mainframe users
218
+ @host.screenshot :error
196
219
  # Manage the invoice status etc.
197
220
  end
198
221
  end
199
222
  ```
200
223
 
201
- Create a polimorphic model:
202
-
203
- 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}'
204
-
205
- In the model to be related to screen we insert:
206
-
207
- ```ruby
208
- has_many :screens, :as => :hook, :dependent => :destroy
209
- ```
224
+ Massive uses or one shot depends on your needs.
225
+ The uses are many and only limited by your imagination!
226
+ Experiment and you'll find the solution right for you :rocket:
210
227
 
211
228
 
212
229
  ## Test with rake
@@ -220,6 +237,10 @@ For more informations:
220
237
 
221
238
  bundle exec rake -T
222
239
 
240
+ These tests consist in iterations of a simple navigation sequence.
241
+ Each iteration uses different latency times, we start from high and therefore simple for the emulator to the default very low.
242
+ It is not so easy make more complex sequence to share because mainframe screens are strongly diversified but everyone can add their own and iterate as many times as deemed appropriate.
243
+
223
244
 
224
245
  ## More informations about supported emulators
225
246
 
@@ -336,7 +357,9 @@ session.autECLOIA.ole_methods (screen):
336
357
  ### x3270
337
358
 
338
359
  Documentation can be found [here](http://x3270.bgp.nu/documentation-manpages.html)
360
+
339
361
  __At the moment it doesn't support check on protect area__
362
+
340
363
  __x3270 module works only on ruby 1.9+__
341
364
 
342
365
  ```ruby
@@ -348,7 +371,7 @@ Read the methods list documentation: [windows](http://x3270.bgp.nu/Windows/wc327
348
371
 
349
372
  ## ToDo
350
373
 
351
- * Improve unit test
374
+ * <s>Improve unit test</s> **Done** although it is still a simple rake
352
375
  * Improve static navigation
353
376
  * Add meta class to choose your host method name and multi language support as well
354
377
 
data/lib/config/config.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  ButlerMainframe.configure do |config|
2
2
  config.host_gateway = :x3270
3
3
  config.session_path = '"C:/Program Files (x86)/wc3270/ws3270.exe" YOUR_HOST_IP -model 2 --'
4
- config.timeout = 5 # In seconds
4
+ config.timeout = 6 # In seconds
5
5
  end
6
6
 
@@ -3,6 +3,6 @@ ButlerMainframe.configure do |config|
3
3
  config.browser_path = 'c:/Program Files (x86)/Internet Explorer/iexplore.exe'
4
4
  config.session_url = 'https://localhost/zephyr/Ecomes.zwh?sessionprofile=3270dsp/Sessions/host3270'
5
5
  config.session_tag = 1
6
- config.timeout = 3000
6
+ config.timeout = 6000
7
7
  end
8
8
 
@@ -1,6 +1,6 @@
1
1
  ButlerMainframe.configure do |config|
2
2
  config.host_gateway = :pcomm
3
- config.session_path = '"C:/Program Files (x86)/IBM/Personal Communications/pcsws.exe" "C:/Users/XXXXXXXXXX/AppData/Roaming/IBM/Personal Communications/host3270.ws"'
3
+ config.session_path = '"C:/Program Files (x86)/IBM/Personal Communications/pcsws.exe" "C:/Users/XXXXXXXXXX/AppData/Roaming/IBM/Personal Communications/host3270.ws" /Q /H /S=A'
4
4
  config.session_tag = 'A'
5
5
  config.timeout = 6000
6
6
  end
@@ -1,6 +1,6 @@
1
1
  ButlerMainframe.configure do |config|
2
2
  config.host_gateway = :x3270
3
3
  config.session_path = '"C:/Program Files (x86)/wc3270/ws3270.exe" YOUR_HOST_IP -model 2 --'
4
- config.timeout = 5 # In seconds
4
+ config.timeout = 6 # In seconds
5
5
  end
6
6
 
@@ -1,10 +1,19 @@
1
1
  defaults: &defaults
2
- cics: 7
3
- user: "IBM0001"
4
- password: "password"
5
- #foo: add every variable you need and use it with => ButlerMainframe::Settings.foo
6
- # bar: sub variable are accessible with hash => ButlerMainframe::Settings.foo[:bar]
7
-
2
+ # The number of iteration through static screen
3
+ navigation_iterations: 10
4
+ cics: '7'
5
+ user: 'YOURUSER'
6
+ password: 'YOURPASSWORD'
7
+ #foo: add every variable you need and use it with => ButlerMainframe::Settings.foo
8
+ # bar: sub variable are accessible with hash => ButlerMainframe::Settings.foo[:bar]
9
+ logoff_cics: 'cesf logoff'
10
+ session_login_tag: 'EMSP00' #use a regular expression
11
+ cics_selection_tag: 'EMSP01' #use a regular expression
12
+ company_menu_tag: '\*\* \*\* \*\* \*\*' #use a regular expression
13
+ # Starts these transactions from blank cics in order to move forward
14
+ transactions_cics:
15
+ company_menu: 'vita'
16
+ main_application: 'life'
8
17
 
9
18
  development:
10
19
  <<: *defaults
@@ -1,10 +1,19 @@
1
1
  defaults: &defaults
2
- cics: 7
3
- user: "IBM0001"
4
- password: "password"
5
- #foo: add every variable you need and use it with => ButlerMainframe::Settings.foo
6
- # bar: sub variable are accessible with hash => ButlerMainframe::Settings.foo[:bar]
7
-
2
+ # The number of iteration through static screen
3
+ navigation_iterations: 10
4
+ cics: '7'
5
+ user: 'YOURUSER'
6
+ password: 'YOURPASSWORD'
7
+ #foo: add every variable you need and use it with => ButlerMainframe::Settings.foo
8
+ # bar: sub variable are accessible with hash => ButlerMainframe::Settings.foo[:bar]
9
+ logoff_cics: 'cesf logoff'
10
+ session_login_tag: 'EMSP00' #use a regular expression
11
+ cics_selection_tag: 'EMSP01' #use a regular expression
12
+ company_menu_tag: '\*\* \*\* \*\* \*\*' #use a regular expression
13
+ # Starts these transactions from blank cics in order to move forward
14
+ transactions_cics:
15
+ company_menu: 'vita'
16
+ main_application: 'life'
8
17
 
9
18
  development:
10
19
  <<: *defaults
@@ -12,6 +12,9 @@ module Host3270
12
12
  #
13
13
  # def do_quit; exec_command "CLEAR" end
14
14
  #
15
+ # def do_erase; exec_command "ERASE EOF" end
16
+ #
17
+ # # If you add your static screen you must add it in the navigation method to define how to manage it
15
18
  # def destination_list
16
19
  # [
17
20
  # :company_menu,
@@ -21,6 +24,12 @@ module Host3270
21
24
  # :back]
22
25
  # end
23
26
  #
27
+ # # Use navigation method to move through the static screens
28
+ # # Options:
29
+ # # :cics => ButlerMainframe::Settings.cics,
30
+ # # :user => ButlerMainframe::Settings.user,
31
+ # # :password => ButlerMainframe::Settings.password,
32
+ # # :raise_on_abend => false raise an exception if an abend is occured
24
33
  # def navigate destination, options={}
25
34
  # options = {
26
35
  # :cics => ButlerMainframe::Settings.cics,
@@ -28,32 +37,50 @@ module Host3270
28
37
  # :password => ButlerMainframe::Settings.password,
29
38
  # :raise_on_abend => false
30
39
  # }.merge(options)
31
- # attempts_number = 10
32
- # transactions_after_cics = ['tra1','tra2']
40
+ # attempts_number = ButlerMainframe::Settings.navigation_iterations
41
+ # transactions_cics = ButlerMainframe::Settings.transactions_cics
33
42
  #
34
43
  # raise "Destination #{destination} not valid, please use: #{destination_list.join(', ')}" unless destination_list.include? destination
35
44
  # bol_found = nil
36
45
  # attempts_number.times do
37
46
  # if abend?
38
47
  # options[:raise_on_abend] ? raise(catch_abend) : do_quit
48
+ # elsif company_menu?
49
+ # case destination
50
+ # when :cics_selection,
51
+ # :session_login then
52
+ # do_quit
53
+ # when :back then
54
+ # do_quit
55
+ # bol_found = true; break
56
+ # when :next then
57
+ # company_menu
58
+ # bol_found = true; break
59
+ # when :company_menu then bol_found = true; break
60
+ # else
61
+ # # Every other destination is forward
62
+ # company_menu
63
+ # end
39
64
  # elsif cics?
40
65
  # case destination
41
66
  # when :cics_selection,
42
67
  # :session_login then
43
- # write "cesf logoff", :y => 1, :x => 1
68
+ # write ButlerMainframe::Settings.logoff_cics, :y => 1, :x => 1
44
69
  # do_enter
45
70
  # when :back then
46
- # write "cesf logoff", :y => 1, :x => 1
71
+ # write ButlerMainframe::Settings.logoff_cics, :y => 1, :x => 1
47
72
  # do_enter
48
73
  # bol_found = true; break
49
74
  # when :next then
50
- # write transactions_after_cics[0], :y => 1, :x => 1
75
+ # write transactions_cics[:main_application], :y => 1, :x => 1
51
76
  # do_enter
52
77
  # bol_found = true; break
53
- # when :company_menu then write transactions_after_cics[1], :y => 1, :x => 1
78
+ # when :company_menu then
79
+ # write transactions_cics[:company_menu], :y => 1, :x => 1
80
+ # do_enter
54
81
  # else
55
- # #Se siamo nel cics avviamo direttamente il life
56
- # write transactions_after_cics[0], :y => 1, :x => 1
82
+ # #If we are in CICS with blank screen start the first transaction
83
+ # write transactions_cics[:main_application], :y => 1, :x => 1
57
84
  # do_enter
58
85
  # end
59
86
  # elsif cics_selection?
@@ -82,15 +109,19 @@ module Host3270
82
109
  # else
83
110
  # # If we do not know where we are...
84
111
  # case destination
85
- # when :back then
86
- # # ...we can try to go back
112
+ # when :session_login then
113
+ # # ...to come back to the first screen we surely have to go back
114
+ # go_back
115
+ # when :back then
116
+ # # ...we can try to go back (not all the screen may go back in the same way)
87
117
  # go_back
88
118
  # bol_found = true; break
89
119
  # when :next then
90
120
  # # ...but we dont know how to move forward
121
+ # raise "Define how to go forward in the navigation method on generic function module"
91
122
  # else
92
123
  # # We unlock the position with both commands to be sure that they are managed by all maps cics
93
- # go_back; do_quit
124
+ # raise "Destination #{destination} not defined in the current screen"
94
125
  # end
95
126
  # end
96
127
  # wait_session
@@ -99,15 +130,19 @@ module Host3270
99
130
  # raise "It was waiting #{destination} map instead of: #{screen_title(:rows => 2).strip}" unless bol_found
100
131
  # end
101
132
  #
133
+ # # Check if we are the first blank cics screen
102
134
  # def cics?
103
135
  # scan(:y1 => 1, :x1 => 1, :y2 => 22, :x2 => 80).strip.empty?
104
136
  # end
105
137
  #
106
- # #Login to mainframe
138
+ # # Check if we are on the login mainframe screen
107
139
  # def session_login?
108
- # /EMSP00/i === screen_title
140
+ # /#{ButlerMainframe::Settings.session_login_tag}/i === screen_title
109
141
  # end
110
142
  #
143
+ # # Login to mainframe
144
+ # # param1 user
145
+ # # param2 password [sensible data]
111
146
  # def session_login user, password
112
147
  # puts "Starting session login..." if @debug
113
148
  # wait_session
@@ -118,22 +153,41 @@ module Host3270
118
153
  # do_enter
119
154
  # end
120
155
  #
121
- # # We need a label to know when we are on the cics selection map, usually ibm use EMSP01
156
+ # # Check the label to know when we are on the cics selection map
122
157
  # def cics_selection?
123
- # /EMSP01/i === screen_title
158
+ # /#{ButlerMainframe::Settings.cics_selection_tag}/i === screen_title
124
159
  # end
125
160
  #
126
161
  # # On this map, we have to select the cics environment
162
+ # # param1 cics usually is a number
127
163
  # def cics_selection cics
128
164
  # puts "Starting selezione_cics..." if @debug
129
165
  # wait_session
130
- # raise "It was waiting cics selezion map instead of: #{screen_title}, messaggio: #{catch_message}" unless cics_selection?
166
+ # raise "It was waiting cics selezion map instead of: #{screen_title}, message: #{catch_message}" unless cics_selection?
131
167
  # write cics, :y => 23, :x => 14
132
168
  # do_enter
133
169
  # wait_session 1
134
170
  # end
135
171
  #
172
+ # # Check the label to know when we are on the cics selection map
173
+ # def company_menu?
174
+ # /#{ButlerMainframe::Settings.company_menu_tag}/i === screen_title
175
+ # end
136
176
  #
177
+ # # On this map, we have to select the cics environment
178
+ # # param1 cics usually is a number
179
+ # def company_menu
180
+ # puts "Starting company menu..." if @debug
181
+ # wait_session
182
+ # raise "It was waiting company menu map instead of: #{screen_title}, message: #{catch_message}" unless company_menu?
183
+ # write "01", :y => 24, :x => 43
184
+ # do_enter
185
+ # end
186
+ #
187
+ # # Get the message line usually in the bottom of the screen
188
+ # # You can define which rows provide the message:
189
+ # # :first_row => 22,
190
+ # # :last_row => 23
137
191
  # def catch_message options={}
138
192
  # options = {
139
193
  # :first_row => 22,
@@ -141,12 +195,19 @@ module Host3270
141
195
  # }.merge(options)
142
196
  # scan(:y1 => options[:first_row], :x1 => 1, :y2 => options[:last_row], :x2 => 80).gsub(/\s+/, " ").strip
143
197
  # end
198
+ #
199
+ # # Get the abend message
144
200
  # def catch_abend
145
201
  # scan(:y1 => 23, :x1 => 1, :y2 => 23, :x2 => 80)
146
202
  # end
203
+ #
204
+ # # Check if there was a malfunction on the mainframe
147
205
  # def abend?
148
206
  # /DFHA/i === catch_abend
149
207
  # end
208
+ #
209
+ # # Get the title usually the first row
210
+ # # You can change default option :rows to get more lines starting from the first
150
211
  # def screen_title options={}
151
212
  # options = {
152
213
  # :rows => 1
@@ -12,6 +12,7 @@ module Host3270
12
12
 
13
13
  def do_erase; exec_command "ERASE EOF" end
14
14
 
15
+ # If you add your static screen you must add it in the navigation method to define how to manage it
15
16
  def destination_list
16
17
  [
17
18
  :company_menu,
@@ -21,6 +22,12 @@ module Host3270
21
22
  :back]
22
23
  end
23
24
 
25
+ # Use navigation method to move through the static screens
26
+ # Options:
27
+ # :cics => ButlerMainframe::Settings.cics,
28
+ # :user => ButlerMainframe::Settings.user,
29
+ # :password => ButlerMainframe::Settings.password,
30
+ # :raise_on_abend => false raise an exception if an abend is occured
24
31
  def navigate destination, options={}
25
32
  options = {
26
33
  :cics => ButlerMainframe::Settings.cics,
@@ -28,35 +35,54 @@ module Host3270
28
35
  :password => ButlerMainframe::Settings.password,
29
36
  :raise_on_abend => false
30
37
  }.merge(options)
31
- attempts_number = 10
32
- transactions_after_cics = ['tra1','tra2']
38
+ attempts_number = ButlerMainframe::Settings.navigation_iterations
39
+ transactions_cics = ButlerMainframe::Settings.transactions_cics
33
40
 
34
41
  raise "Destination #{destination} not valid, please use: #{destination_list.join(', ')}" unless destination_list.include? destination
42
+
43
+ puts "Navigating to #{destination}" if @debug
35
44
  bol_found = nil
36
45
  attempts_number.times do
37
46
  if abend?
47
+ puts "Navigate: abend" if @debug
38
48
  options[:raise_on_abend] ? raise(catch_abend) : do_quit
49
+ elsif company_menu?
50
+ puts "Navigating to #{destination} from company menu" if @debug
51
+ case destination
52
+ when :cics_selection,
53
+ :session_login then
54
+ do_quit
55
+ when :back then
56
+ do_quit
57
+ bol_found = true; break
58
+ when :next then
59
+ company_menu
60
+ bol_found = true; break
61
+ when :company_menu then bol_found = true; break
62
+ else
63
+ # Every other destination is forward
64
+ company_menu
65
+ end
39
66
  elsif cics?
67
+ puts "Navigating to #{destination} from cics" if @debug
40
68
  case destination
41
69
  when :cics_selection,
42
70
  :session_login then
43
- write "cesf logoff", :y => 1, :x => 1
44
- do_enter
71
+ execute_cics ButlerMainframe::Settings.logoff_cics
45
72
  when :back then
46
- write "cesf logoff", :y => 1, :x => 1
47
- do_enter
73
+ execute_cics ButlerMainframe::Settings.logoff_cics
48
74
  bol_found = true; break
49
75
  when :next then
50
- write transactions_after_cics[0], :y => 1, :x => 1
51
- do_enter
76
+ execute_cics transactions_cics[:main_application]
52
77
  bol_found = true; break
53
- when :company_menu then write transactions_after_cics[1], :y => 1, :x => 1
78
+ when :company_menu then
79
+ execute_cics transactions_cics[:company_menu]
54
80
  else
55
- #Se siamo nel cics avviamo direttamente il life
56
- write transactions_after_cics[0], :y => 1, :x => 1
57
- do_enter
81
+ #If we are in CICS with blank screen start the first transaction
82
+ execute_cics transactions_cics[:main_application]
58
83
  end
59
84
  elsif cics_selection?
85
+ puts "Navigating to #{destination} from cics selection" if @debug
60
86
  case destination
61
87
  when :cics_selection then bol_found = true; break
62
88
  when :session_login then exec_command("PF3")
@@ -70,6 +96,7 @@ module Host3270
70
96
  cics_selection options[:cics] if options[:cics]
71
97
  end
72
98
  elsif session_login?
99
+ puts "Navigating to #{destination} from session login" if @debug
73
100
  case destination
74
101
  when :session_login,
75
102
  :back then bol_found = true; break
@@ -80,17 +107,22 @@ module Host3270
80
107
  session_login options[:user], options[:password] if options[:user] && options[:password]
81
108
  end
82
109
  else
110
+ puts "Navigating to #{destination} from unknown screen" if @debug
83
111
  # If we do not know where we are...
84
112
  case destination
85
- when :back then
86
- # ...we can try to go back
113
+ when :session_login then
114
+ # ...to come back to the first screen we surely have to go back
115
+ go_back
116
+ when :back then
117
+ # ...we can try to go back (not all the screen may go back in the same way)
87
118
  go_back
88
119
  bol_found = true; break
89
120
  when :next then
90
121
  # ...but we dont know how to move forward
122
+ raise "Define how to go forward in the navigation method on generic function module"
91
123
  else
92
124
  # We unlock the position with both commands to be sure that they are managed by all maps cics
93
- go_back; do_quit
125
+ raise "Destination #{destination} not defined in the current screen"
94
126
  end
95
127
  end
96
128
  wait_session
@@ -99,15 +131,19 @@ module Host3270
99
131
  raise "It was waiting #{destination} map instead of: #{screen_title(:rows => 2).strip}" unless bol_found
100
132
  end
101
133
 
134
+ # Check if we are the first blank cics screen
102
135
  def cics?
103
136
  scan(:y1 => 1, :x1 => 1, :y2 => 22, :x2 => 80).strip.empty?
104
137
  end
105
138
 
106
- #Login to mainframe
139
+ # Check if we are on the login mainframe screen
107
140
  def session_login?
108
- /EMSP00/i === screen_title
141
+ /#{ButlerMainframe::Settings.session_login_tag}/i === screen_title
109
142
  end
110
143
 
144
+ # Login to mainframe
145
+ # param1 user
146
+ # param2 password [sensible data]
111
147
  def session_login user, password
112
148
  puts "Starting session login..." if @debug
113
149
  wait_session
@@ -118,22 +154,41 @@ module Host3270
118
154
  do_enter
119
155
  end
120
156
 
121
- # We need a label to know when we are on the cics selection map, usually ibm use EMSP01
157
+ # Check the label to know when we are on the cics selection map
122
158
  def cics_selection?
123
- /EMSP01/i === screen_title
159
+ /#{ButlerMainframe::Settings.cics_selection_tag}/i === screen_title
124
160
  end
125
161
 
126
162
  # On this map, we have to select the cics environment
163
+ # param1 cics usually is a number
127
164
  def cics_selection cics
128
165
  puts "Starting selezione_cics..." if @debug
129
166
  wait_session
130
- raise "It was waiting cics selezion map instead of: #{screen_title}, messaggio: #{catch_message}" unless cics_selection?
167
+ raise "It was waiting cics selezion map instead of: #{screen_title}, message: #{catch_message}" unless cics_selection?
131
168
  write cics, :y => 23, :x => 14
132
169
  do_enter
133
170
  wait_session 1
134
171
  end
135
172
 
173
+ # Check the label to know when we are on the cics selection map
174
+ def company_menu?
175
+ /#{ButlerMainframe::Settings.company_menu_tag}/i === screen_title
176
+ end
177
+
178
+ # On this map, we have to select the cics environment
179
+ # param1 cics usually is a number
180
+ def company_menu
181
+ puts "Starting company menu..." if @debug
182
+ wait_session
183
+ raise "It was waiting company menu map instead of: #{screen_title}, message: #{catch_message}" unless company_menu?
184
+ write "01", :y => 24, :x => 43
185
+ do_enter
186
+ end
136
187
 
188
+ # Get the message line usually in the bottom of the screen
189
+ # You can define which rows provide the message:
190
+ # :first_row => 22,
191
+ # :last_row => 23
137
192
  def catch_message options={}
138
193
  options = {
139
194
  :first_row => 22,
@@ -141,18 +196,30 @@ module Host3270
141
196
  }.merge(options)
142
197
  scan(:y1 => options[:first_row], :x1 => 1, :y2 => options[:last_row], :x2 => 80).gsub(/\s+/, " ").strip
143
198
  end
199
+
200
+ # Get the abend message
144
201
  def catch_abend
145
202
  scan(:y1 => 23, :x1 => 1, :y2 => 23, :x2 => 80)
146
203
  end
204
+
205
+ # Check if there was a malfunction on the mainframe
147
206
  def abend?
148
207
  /DFHA/i === catch_abend
149
208
  end
209
+
210
+ # Get the title usually the first row
211
+ # You can change default option :rows to get more lines starting from the first
150
212
  def screen_title options={}
151
213
  options = {
152
214
  :rows => 1
153
215
  }.merge(options)
154
216
  scan(:y1 => 1, :x1 => 1, :y2 => options[:rows], :x2 => 80)
155
217
  end
218
+
219
+ def execute_cics name
220
+ write name, :y => 1, :x => 2
221
+ do_enter
222
+ end
156
223
  end
157
224
 
158
225
  end
@@ -12,37 +12,37 @@ module ButlerMainframe
12
12
  def sub_create_object options={}
13
13
  str_obj = 'PASSPORT.System'
14
14
  puts "#{Time.now.strftime "%H:%M:%S"} Creating object #{str_obj}..." if @debug == :full
15
- @action = WIN32OLE.new(str_obj)
16
- @screen = @action.Sessions(@session_tag).Screen if sub_object_created?
15
+ @action[:ole] = WIN32OLE.new(str_obj)
16
+ @screen = @action[:ole].Sessions(@session_tag).Screen if sub_object_created?
17
17
  end
18
18
 
19
19
  # Check is session is started
20
20
  def sub_object_created?
21
- res = @action && @action.Sessions(@session_tag)
21
+ res = @action[:ole] && @action[:ole].Sessions(@session_tag)
22
22
  puts "#{Time.now.strftime "%H:%M:%S"} Terminal successfully detected" if @debug == :full && res
23
23
  res
24
24
  end
25
25
 
26
26
  # Check is session is operative
27
27
  def sub_object_ready?
28
- res = @action.Sessions(@session_tag).Connected == -1
28
+ res = @action[:ole].Sessions(@session_tag).Connected == -1
29
29
  puts "#{Time.now.strftime "%H:%M:%S"} Session ready" if @debug == :full && res
30
30
  res
31
31
  end
32
32
 
33
33
  def sub_name
34
- "#{@action.Name} #{@action.Sessions(@session_tag).Name}"
34
+ "#{@action[:ole].Name} #{@action[:ole].Sessions(@session_tag).Name}"
35
35
  end
36
36
 
37
37
  def sub_fullname
38
- "#{sub_name} #{@action.Sessions(@session_tag).FullName}"
38
+ "#{sub_name} #{@action[:ole].Sessions(@session_tag).FullName}"
39
39
  end
40
40
 
41
41
  #Ends the connection and closes the session
42
42
  def sub_close_session
43
- @action.Sessions(@session_tag).Close
44
- @action.Quit
45
- @action = nil
43
+ @action[:ole].Sessions(@session_tag).Close
44
+ @action[:ole].Quit
45
+ @action[:ole] = nil
46
46
  end
47
47
 
48
48
  #Execute keyboard command like PF1 or PA2 or ENTER ...
@@ -4,6 +4,7 @@ require 'mainframe/host_base'
4
4
  # This class use IBM personal communication
5
5
  # http://www-01.ibm.com/support/knowledgecenter/SSEQ5Y_6.0.0/welcome.html
6
6
  # http://www-01.ibm.com/support/knowledgecenter/SSEQ5Y_6.0.0/com.ibm.pcomm.doc/books/html/host_access08.htm
7
+ # http://www-01.ibm.com/support/knowledgecenter/SSEQ5Y_6.0.0/com.ibm.pcomm.doc/books/html/admin_guide10.htm?lang=en
7
8
  module ButlerMainframe
8
9
  class Host < HostBase
9
10
 
@@ -13,39 +14,41 @@ module ButlerMainframe
13
14
  def sub_create_object options={}
14
15
  str_obj = 'PComm.autECLSession'
15
16
  puts "#{Time.now.strftime "%H:%M:%S"} Creating object #{str_obj}..." if @debug == :full
16
- @action = WIN32OLE.new(str_obj)
17
- @action.SetConnectionByName @session_tag
18
- @space = @action.autECLPS
19
- @screen = @action.autECLOIA
17
+ @action[:ole] = WIN32OLE.new(str_obj)
18
+ @action[:ole].SetConnectionByName @session_tag
19
+ @space = @action[:ole].autECLPS
20
+ @screen = @action[:ole].autECLOIA
20
21
  end
21
22
 
22
23
  # Check is session is started
23
24
  def sub_object_created?
24
- res = @action && @action.CommStarted
25
+ res = @action[:ole] && @action[:ole].CommStarted
25
26
  puts "#{Time.now.strftime "%H:%M:%S"} Terminal successfully detected" if @debug == :full && res
26
27
  res
27
28
  end
28
29
 
29
30
  # Check is session is operative
30
31
  def sub_object_ready?
31
- res = @action.Ready
32
+ res = @action[:ole].Ready
32
33
  puts "#{Time.now.strftime "%H:%M:%S"} Session ready" if @debug == :full && res
33
34
  res
34
35
  end
35
36
 
36
37
  def sub_name
37
- "PComm #{@action.Name}"
38
+ "PComm #{@action[:ole].Name}"
38
39
  end
39
40
 
40
41
  def sub_fullname
41
- "#{sub_name} #{@action.ConnType}"
42
+ "#{sub_name} #{@action[:ole].ConnType}"
42
43
  end
43
44
 
44
45
  #Ends the connection and closes the session
45
46
  def sub_close_session
46
- @action.StopCommunication
47
- @action = nil
48
- Process.kill 9, @pid if @pid
47
+ @action[:ole].StopCommunication
48
+ @action[:ole] = nil
49
+ # See http://www-01.ibm.com/support/knowledgecenter/SSEQ5Y_6.0.0/com.ibm.pcomm.doc/books/html/admin_guide10.htm?lang=en
50
+ Process.spawn "PCOMSTOP /S=#{@session_tag} /q" if @pid
51
+ # Process.kill 9, @pid #Another way is to kill the process but the session start 2nd process pcscm.exe
49
52
  end
50
53
 
51
54
  #Execute keyboard command like PF1 or PA2 or ENTER ...
@@ -92,13 +92,13 @@ module ButlerMainframe
92
92
  :check_protect => true
93
93
  }.merge(options)
94
94
  sub_set_cursor_axes y, x
95
- x_cmd "String(#{text})"
95
+ x_cmd "String(\"#{text}\")", options
96
96
  # TODO
97
97
  # if options[:check_protect]
98
98
  # end
99
99
  end
100
100
 
101
- # Wait text at given coordinates and wait the session is available again
101
+ # Wait text at given coordinates
102
102
  def sub_wait_for_string text, y, x
103
103
  x_cmd "Wait(#{@timeout},InputField)"
104
104
  total_time = 0.0
@@ -109,12 +109,12 @@ module ButlerMainframe
109
109
  # @timeout should be in milliseconds but everything is possible
110
110
  break if total_time >= @timeout
111
111
  end
112
- raise "sub_wait_for_string: string #{text} not found at (#{y}, #{x})" unless sub_scan_row(y, x, text.size) == text
113
- true
112
+ sub_scan_row(y, x, text.size) == text
114
113
  end
115
114
 
116
- def x_cmd cmd
117
- puts "x_cmd in: #{cmd}" if @debug
115
+ # To communicate with executable
116
+ def x_cmd cmd, options={}
117
+ puts "x_cmd in: #{options[:sensible_data] ? ('*' * cmd.size) : cmd}" if @debug == :full
118
118
  @action[:in].print "#{cmd}\n"
119
119
  @action[:in].flush
120
120
 
@@ -123,7 +123,7 @@ module ButlerMainframe
123
123
 
124
124
  line, str_out = '', ''
125
125
  while line = @action[:out].gets.chomp do
126
- puts "x_cmd out: '#{line}'" if @debug
126
+ puts "x_cmd out: '#{line}'" if @debug == :full
127
127
  break if ar_res.include? line
128
128
  str_out << "#{line[6..-1]}" if /^data:\s/ === line
129
129
  end
@@ -14,7 +14,7 @@ module ButlerMainframe
14
14
  :session_tag => ButlerMainframe.configuration.session_tag,
15
15
  :wait => 0.01, #wait screen in seconds
16
16
  :wait_debug => 2, #wait time for debug purpose
17
- :debug => true,
17
+ :debug => false,
18
18
  :browser_path => ButlerMainframe.configuration.browser_path,
19
19
  :session_url => ButlerMainframe.configuration.session_url,
20
20
  :session_path => ButlerMainframe.configuration.session_path,
@@ -31,6 +31,7 @@ module ButlerMainframe
31
31
  @session_tag = options[:session_tag]
32
32
  @close_session = options[:close_session]
33
33
  @timeout = options[:timeout]
34
+ @action = {}
34
35
  @pid = nil
35
36
 
36
37
  create_object options
@@ -87,19 +88,32 @@ module ButlerMainframe
87
88
 
88
89
  # Write text on screen at the coordinates
89
90
  # Based on the parameters provided it writes a line or an area
91
+ # Options:
92
+ # :hook => nil,
93
+ # :y => nil, #row
94
+ # :x => nil, #column
95
+ # :check => true,
96
+ # :raise_error_on_check => true,
97
+ # :sensible_data => nil,
98
+ # :clean_first_chars => nil, # clean x chars before writing a value
99
+ # :erase_field_first => nil # erase first until end of field
90
100
  def write text, options={}
91
101
  options = {
92
102
  :hook => nil,
93
- :y => nil, #riga
94
- :x => nil, #colonna
103
+ :y => nil, #row
104
+ :x => nil, #column
95
105
  :check => true,
96
106
  :raise_error_on_check => true,
97
107
  :sensible_data => nil,
98
- :clean_first_chars => nil # clean x chars before writing a value
108
+ :clean_first_chars => nil, # clean x chars before writing a value
109
+ :erase_field_first => nil # erase first until end of field
99
110
  }.merge(options)
100
111
 
101
112
  y = options[:y]
102
113
  x = options[:x]
114
+ y ||= get_cursor_axes[0]
115
+ x ||= get_cursor_axes[1]
116
+
103
117
  hooked_rows = 2
104
118
  raise "Missing coordinates! y(row)=#{y} x(column)=#{x} " unless x && y
105
119
  raise "Sorry, cannot write null values" unless text
@@ -135,14 +149,14 @@ module ButlerMainframe
135
149
  # It creates the object calling subclass method
136
150
  # It depends on the emulator chosen but typically the object is present after starting the terminal session
137
151
  # These are the options with default values:
138
- # :session => 1,
139
- # :debug => true,
140
- # :browser_path => ButlerMainframe::Settings.browser_path,
141
- # :session_path => ButlerMainframe::Settings.session_path,
152
+ # :session_tag => Fixnum, String or null depending on emulator
153
+ # :debug => boolean
142
154
  def create_object options={}
143
- connection_attempts = 8
144
- seconds_between_attempts = 2
155
+ connection_attempts = 10
156
+ seconds_between_attempts = 1
145
157
 
158
+ # Creating session object for emulators managed by API
159
+ # Some emulator may start session terminal and return a process id in @pid
146
160
  sub_create_object options
147
161
 
148
162
  if sub_object_created?
@@ -151,23 +165,10 @@ module ButlerMainframe
151
165
  # if the terminal is not found then we start it
152
166
  puts "Session #{@session_tag} not found, starting new..." if @debug
153
167
 
154
- executable, args = if options[:browser_path] && !options[:browser_path].empty?
155
- [options[:browser_path], options[:session_url]]
156
- elsif options[:session_path] && !options[:session_path].empty?
157
- [options[:session_path], nil]
158
- else
159
- [nil, nil]
160
- end
161
- raise "Specify an executable in the configuration file!" unless executable
162
-
163
- if /^1.8/ === RUBY_VERSION
164
- Thread.new {system "#{executable} #{args}"}
165
- @pid = $?.pid if $?
166
- else
167
- #It works only on ruby 1.9+
168
- @pid = Process.spawn *[executable, args].compact
169
- end
168
+ # Starting executable, check configuration file
169
+ start_terminal_session options
170
170
 
171
+ # New connection attempts after starting session...
171
172
  puts "Starting session with process id #{@pid}, wait please..." if @debug
172
173
  sleep 2
173
174
  connection_attempts.times do
@@ -179,6 +180,7 @@ module ButlerMainframe
179
180
 
180
181
  raise "Session #{@session_tag} not started. Check the session #{options[:browser_path]} #{options[:session_path]}" unless sub_object_created?
181
182
 
183
+ # Session detected and waiting it become operative
182
184
  unless sub_object_ready?
183
185
  connection_attempts.times do
184
186
  puts "Waiting for the session to be ready..." if @debug
@@ -186,11 +188,12 @@ module ButlerMainframe
186
188
  end
187
189
  end
188
190
 
191
+ # At this stage the session must be operative otherwise raise an exception
189
192
  if sub_object_ready?
190
193
  puts "** Connection established with #{sub_name} **"
191
194
  puts "Session full name: #{sub_fullname}" if @debug == :full
192
195
  else
193
- raise "Connection refused. Check session #{@session_tag} with process id #{@pid}"
196
+ raise "Connection refused. Check session #{@session_tag}#{" with process id #{@pid}" if @pid}!"
194
197
  end
195
198
 
196
199
  rescue
@@ -198,6 +201,31 @@ module ButlerMainframe
198
201
  raise $!
199
202
  end
200
203
 
204
+ # Starting terminal session
205
+ # Options:
206
+ # :browser_path => browser executable path, default value ButlerMainframe::Settings.browser_path (used by web emulator)
207
+ # :session_url => the session url used by browser
208
+ # :session_path => terminal session executable path, default value ButlerMainframe::Settings.session_path
209
+ def start_terminal_session options
210
+ # Check configuration to know emulator starting type
211
+ executable, args = if options[:browser_path] && !options[:browser_path].empty?
212
+ [options[:browser_path], options[:session_url]]
213
+ elsif options[:session_path] && !options[:session_path].empty?
214
+ [options[:session_path], nil]
215
+ else
216
+ [nil, nil]
217
+ end
218
+ raise "Specify an executable in the configuration file!" unless executable
219
+
220
+ if /^1.8/ === RUBY_VERSION
221
+ Thread.new {system "#{executable} #{args}"}
222
+ @pid = $?.pid if $?
223
+ else
224
+ #It works only on ruby 1.9+
225
+ @pid = Process.spawn *[executable, args].compact
226
+ end
227
+ end
228
+
201
229
  #It reads one line on the screen
202
230
  def scan_row y, x, len
203
231
  str = sub_scan_row y, x, len
data/test/test.rake CHANGED
@@ -6,21 +6,107 @@ namespace :butler do
6
6
  desc "Test butler mainframe gem"
7
7
  task(:test) do |task_name, args|
8
8
 
9
+ host = nil
9
10
  begin
10
11
  require 'butler-mainframe'
11
- host = ButlerMainframe::Host.new
12
- str_screen1 = host.scan_page
13
- raise 'host.scan_page' if str_screen1.empty?
14
- host.navigate :next
15
- str_screen2 = host.scan_page
16
- raise 'host.navigate :next' if str_screen1 == str_screen2
17
- host.close_session
12
+ wait_between_iterations = 3
18
13
 
14
+ # SLOW
15
+ simple_iteration :wait => 0.6
16
+ sleep(wait_between_iterations)
17
+
18
+ # MEDIUM
19
+ simple_iteration :wait => 0.08
20
+ sleep(wait_between_iterations)
21
+
22
+ # FAST (default 0.01 atm)
23
+ simple_iteration
24
+ sleep(wait_between_iterations)
25
+
26
+ puts "*** RAKE TESTS COMPLETE SUCCESSFULLY ***"
19
27
  rescue
20
28
  puts $!.message
29
+ puts "--- RAKE TESTS FAILED ---"
21
30
  exit(9)
22
31
  end
23
32
  end
24
33
 
25
34
  end
35
+ end
36
+
37
+ def simple_iteration options={}
38
+ options = {
39
+ :host => nil,
40
+ :wait => nil
41
+
42
+ }.merge(options)
43
+
44
+ puts
45
+ puts "*** START ITERATION TEST WAIT #{options[:wait] || 'DEFAULT'} ***"
46
+ params = {:debug => false, :wait_debug => 0.5}
47
+ params[:wait] = options[:wait] if options[:wait]
48
+ host = options[:host] || ButlerMainframe::Host.new(params)
49
+
50
+ navigate host, :session_login
51
+
52
+ str_screen1 = host.scan_page
53
+ raise 'host.scan_page' if str_screen1.empty?
54
+
55
+ navigate host, :next
56
+ str_screen2 = host.scan_page
57
+ raise 'navigate :next does not pass login screen' if str_screen1 == str_screen2
58
+
59
+ navigate host, :next
60
+ str_screen2 = host.scan_page
61
+ raise 'navigate :next does not pass cics selection' if str_screen1 == str_screen2
62
+
63
+ # Go back until the first screen
64
+ navigate host, :session_login
65
+
66
+ raise "host.scan row failed" unless host.scan(:y => 1, :x => 1, :len => 80).size == 80
67
+ raise "host.scan area failed" unless host.scan(:y1 => 1, :x1 => 1, :y2 => 3, :x2 => 80).size == 240
68
+
69
+ navigate host, :back
70
+
71
+ navigate host, :company_menu
72
+
73
+ navigate host, :next
74
+
75
+ navigate host, :back
76
+
77
+ navigate host, :back
78
+
79
+ navigate host, :company_menu
80
+
81
+ navigate host, :back
82
+
83
+ navigate host, :back
84
+
85
+ navigate host, :session_login
86
+
87
+ navigate host, :cics_selection
88
+
89
+ navigate host, :next
90
+
91
+ navigate host, :back
92
+
93
+ navigate host, :back
94
+
95
+ navigate host, :session_login
96
+
97
+ navigate host, :company_menu
98
+
99
+ navigate host, :cics_selection
100
+
101
+ navigate host, :session_login
102
+
103
+ host.close_session
104
+ end
105
+
106
+ # Easy navigation for rake test
107
+ def navigate host, action
108
+ msg = "Rake Navigate to #{action}: "
109
+ print msg
110
+ host.navigate action
111
+ puts "#{' ' * (40 - msg.size).abs}OK"
26
112
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: butler-mainframe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Mastrodonato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-20 00:00:00.000000000 Z
11
+ date: 2015-10-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: This gem provides a virtual butler which can perform your custom tasks
14
14
  on a 3270 emulator. You just have to choose your emulator (atm only one choice)