osaka 0.4.8 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +7 -0
  3. data/README.rdoc +1 -1
  4. data/Rakefile +12 -2
  5. data/lib/osaka.rb +6 -0
  6. data/lib/osaka/calculator.rb +1 -1
  7. data/lib/osaka/commandrunner.rb +17 -0
  8. data/lib/osaka/defaultssystem.rb +28 -0
  9. data/lib/osaka/keynote.rb +8 -6
  10. data/lib/osaka/keynoteflow.rb +10 -2
  11. data/lib/osaka/launchservices.rb +29 -0
  12. data/lib/osaka/location.rb +1 -1
  13. data/lib/osaka/mailmergeflow.rb +1 -1
  14. data/lib/osaka/numbers.rb +1 -1
  15. data/lib/osaka/osakaexpectations.rb +65 -61
  16. data/lib/osaka/pages.rb +35 -26
  17. data/lib/osaka/preview.rb +1 -1
  18. data/lib/osaka/remotecontrol.rb +57 -47
  19. data/lib/osaka/scriptrunner.rb +2 -11
  20. data/lib/osaka/textedit.rb +1 -1
  21. data/lib/osaka/typicalapplication.rb +10 -4
  22. data/lib/osaka/typicalfinderdialog.rb +1 -1
  23. data/lib/osaka/typicalopendialog.rb +22 -18
  24. data/lib/osaka/typicalprintdialog.rb +1 -1
  25. data/lib/osaka/typicalsavedialog.rb +1 -1
  26. data/lib/osaka/version.rb +1 -1
  27. data/{osaka.gemfile → osaka.gemspec} +0 -0
  28. data/spec/assets/document.pdf +0 -0
  29. data/spec/calculator_spec.rb +5 -5
  30. data/spec/defaultssystem_spec.rb +30 -0
  31. data/spec/integration_calculator_spec.rb +7 -7
  32. data/spec/integration_keynote_spec.rb +24 -11
  33. data/spec/integration_numbers_spec.rb +2 -2
  34. data/spec/integration_pages_numbers_mail_merge_spec.rb +9 -9
  35. data/spec/integration_preview_spec.rb +16 -0
  36. data/spec/integration_textedit_spec.rb +5 -5
  37. data/spec/keynote_flows_spec.rb +52 -31
  38. data/spec/keynote_spec.rb +24 -14
  39. data/spec/launchservices_spec.rb +63 -0
  40. data/spec/location_spec.rb +13 -13
  41. data/spec/mailmergeflow_spec.rb +13 -13
  42. data/spec/numbers_spec.rb +10 -10
  43. data/spec/osakaexpectations_spec.rb +3 -3
  44. data/spec/pages_spec.rb +80 -61
  45. data/spec/preview_spec.rb +5 -5
  46. data/spec/remotecontrol_spec.rb +65 -43
  47. data/spec/scriptrunner_spec.rb +22 -22
  48. data/spec/textedit_spec.rb +3 -3
  49. data/spec/typicalapplication_spec.rb +119 -108
  50. data/spec/typicalfinderdialog_spec.rb +2 -2
  51. data/spec/typicalopendialog_spec.rb +41 -35
  52. data/spec/typicalprintdialog_spec.rb +5 -5
  53. data/spec/typicalsavedialog_spec.rb +10 -10
  54. metadata +51 -47
data/lib/osaka/preview.rb CHANGED
@@ -23,4 +23,4 @@ module Osaka
23
23
  end
24
24
 
25
25
  end
26
- end
26
+ end
@@ -5,25 +5,25 @@ module Osaka
5
5
 
6
6
  class InvalidLocation < RuntimeError
7
7
  end
8
-
8
+
9
9
  class RemoteControl
10
-
10
+
11
11
  attr_reader :name
12
12
  attr_accessor :base_location
13
-
13
+
14
14
  def initialize(name, base_location = Location.new(""))
15
15
  @name = name
16
16
  @base_location = base_location
17
17
  end
18
-
18
+
19
19
  def ==(obj)
20
20
  @name == obj.name && base_location == obj.base_location
21
21
  end
22
-
22
+
23
23
  def tell(command)
24
24
  ScriptRunner::execute("tell application \"#{@name}\"; #{command}; end tell")
25
25
  end
26
-
26
+
27
27
  def system_event!(event)
28
28
  ScriptRunner::execute("tell application \"System Events\"; tell process \"#{@name}\"; #{event}; end tell; end tell")
29
29
  end
@@ -31,27 +31,27 @@ module Osaka
31
31
  def running?
32
32
  ScriptRunner::execute("tell application \"System Events\"; (name of processes) contains \"#{@name}\"; end tell").strip == "true"
33
33
  end
34
-
34
+
35
35
  def print_warning(action, message)
36
36
  puts "Osaka WARNING while doing #{action}: #{message}"
37
37
  end
38
-
38
+
39
39
  def check_output(output, action)
40
40
  print_warning(action, output) unless output.empty?
41
41
  output
42
42
  end
43
-
43
+
44
44
  def activate
45
45
  check_output( tell("activate"), "activate" )
46
46
  end
47
-
47
+
48
48
  def launch
49
49
  check_output( tell("launch"), "launch" )
50
50
  end
51
-
51
+
52
52
  def quit
53
53
  keystroke("q", :command)
54
- end
54
+ end
55
55
 
56
56
  def system_event(event)
57
57
  activate
@@ -65,34 +65,34 @@ module Osaka
65
65
  def not_exists?(location)
66
66
  system_event!("not exists #{construct_location(location)}").strip == "true"
67
67
  end
68
-
68
+
69
69
  def wait_until(locations, action)
70
-
70
+
71
71
  begin
72
- Timeout::timeout(5) {
72
+ Timeout::timeout(10) {
73
73
  while(true)
74
- locations.flatten.each { |location|
74
+ locations.flatten.each { |location|
75
75
  return location if yield location
76
76
  }
77
77
  action.() unless action.nil?
78
78
  end
79
79
  }
80
80
  rescue Exception
81
- raise Osaka::TimeoutError, "Timed out while waiting for: #{locations.to_s}"
81
+ raise Osaka::TimeoutError, "Timed out while waiting for: #{locations.join(", ")}"
82
82
  end
83
83
  end
84
-
84
+
85
85
  def wait_until_exists(*locations, &action)
86
86
  activate
87
87
  wait_until_exists!(*locations, &action)
88
88
  end
89
-
89
+
90
90
  def wait_until_exists!(*locations, &action)
91
91
  wait_until(locations, action) { |location|
92
92
  exists?(location)
93
93
  }
94
94
  end
95
-
95
+
96
96
  alias until_exists wait_until_exists
97
97
  alias until_exists! wait_until_exists!
98
98
 
@@ -110,29 +110,29 @@ module Osaka
110
110
  alias until_not_exists wait_until_not_exists
111
111
  alias until_not_exists! wait_until_not_exists!
112
112
 
113
-
113
+
114
114
  def construct_modifier_statement(modifier_keys)
115
115
  modified_key_string = [ modifier_keys ].flatten.collect! { |mod_key| mod_key.to_s + " down"}.join(", ")
116
116
  modifier_command = " using {#{modified_key_string}}" unless modifier_keys.empty?
117
117
  modifier_command
118
118
  end
119
-
119
+
120
120
  def construct_key_statement(key_keys)
121
121
  return "return" if key_keys == :return
122
122
  "\"#{key_keys}\""
123
123
  end
124
-
124
+
125
125
  def keystroke!(key, modifier_keys = [])
126
126
  check_output( system_event!("keystroke #{construct_key_statement(key)}#{construct_modifier_statement(modifier_keys)}"), "keystroke")
127
127
  self
128
128
  end
129
-
129
+
130
130
  def keystroke(key, modifier_keys = [])
131
131
  activate
132
132
  focus
133
133
  (modifier_keys == []) ? keystroke!(key) : keystroke!(key, modifier_keys)
134
134
  end
135
-
135
+
136
136
  def click!(element)
137
137
  # Click seems to often output stuff, but it doesn't have much meaning so we ignore it.
138
138
  system_event!("click #{construct_location(element)}")
@@ -143,19 +143,19 @@ module Osaka
143
143
  activate
144
144
  click!(element)
145
145
  end
146
-
146
+
147
147
  def click_menu_bar(menu_item, menu_name)
148
148
  activate
149
149
  menu_bar_location = at.menu_bar_item(menu_name).menu_bar(1)
150
- click!(menu_item + at.menu(1) + menu_bar_location)
150
+ click!(menu_item + at.menu(1) + menu_bar_location)
151
151
  end
152
-
152
+
153
153
  def set!(element, location, value)
154
154
  encoded_value = (value.class == String) ? "\"#{value}\"" : value.to_s
155
155
  check_output( system_event!("set #{element}#{construct_prefixed_location(location)} to #{encoded_value}"), "set")
156
156
  end
157
-
158
- def focus
157
+
158
+ def focus
159
159
  if (base_location.to_s.empty? || not_exists?(base_location))
160
160
  currently_active_window = window_list[0]
161
161
  currently_active_window ||= ""
@@ -164,17 +164,17 @@ module Osaka
164
164
 
165
165
  focus!
166
166
  end
167
-
167
+
168
168
  def focus!
169
169
  system_event!("set value of attribute \"AXMain\" of #{base_location.top_level_element} to true") unless base_location.top_level_element.to_s.empty?
170
170
  end
171
-
171
+
172
172
  def form_location_with_window(location)
173
173
  new_location = Location.new(location)
174
174
  new_location += @base_location unless new_location.has_top_level_element?
175
175
  new_location
176
176
  end
177
-
177
+
178
178
  def construct_location(location)
179
179
  form_location_with_window(location).to_s
180
180
  end
@@ -182,7 +182,7 @@ module Osaka
182
182
  def construct_prefixed_location(location)
183
183
  form_location_with_window(location).as_prefixed_location
184
184
  end
185
-
185
+
186
186
  def get!(element, location = "")
187
187
  system_event!("get #{element}#{construct_prefixed_location(location)}").strip
188
188
  end
@@ -190,7 +190,7 @@ module Osaka
190
190
  def get_app!(element)
191
191
  system_event!("get #{element}").strip
192
192
  end
193
-
193
+
194
194
  def attributes(location = "")
195
195
  attributelist = get!("attributes", location)
196
196
  attributelist.split("of application process #{name}").collect { |attribute|
@@ -202,24 +202,32 @@ module Osaka
202
202
  activate
203
203
  set!(element, location, value)
204
204
  end
205
-
205
+
206
206
  def window_list
207
207
  windows = get_app!("windows").strip.split(',')
208
208
  windows.collect { |window|
209
209
  window[7...window =~ / of application process/].strip
210
210
  }
211
- end
212
-
211
+ end
212
+
213
+ def standard_window_list
214
+ window_list.collect { |window|
215
+ if get!("subrole", at.window(window)) == "AXStandardWindow"
216
+ window
217
+ end
218
+ }.compact
219
+ end
220
+
213
221
  def set_current_window(window_name)
214
222
  @base_location = at.window(window_name)
215
223
  end
216
-
224
+
217
225
  def current_window_name
218
226
  matchdata = @base_location.to_s.match(/window "(.*)"/)
219
227
  return "" if matchdata.nil? || matchdata[1].nil?
220
228
  matchdata[1]
221
229
  end
222
-
230
+
223
231
  def current_window_invalid?(window_list)
224
232
  @base_location.to_s.empty? || window_list.index(current_window_name).nil?
225
233
  end
@@ -233,21 +241,23 @@ module Osaka
233
241
  :lion
234
242
  when /^10.8.*/
235
243
  :mountain_lion
244
+ when /^10.10.*/
245
+ :yosemite
236
246
  else
237
247
  :other
238
248
  end
239
-
249
+
240
250
  end
241
-
251
+
242
252
  def mac_version_string
243
253
  mac_version
244
254
  @mac_version_string
245
255
  end
246
-
247
-
256
+
257
+
248
258
  def convert_mac_version_string_to_symbol(version_string)
249
-
259
+
250
260
  end
251
-
261
+
252
262
  end
253
- end
263
+ end
@@ -5,9 +5,6 @@ module Osaka
5
5
  class ScriptRunnerError < RuntimeError
6
6
  end
7
7
 
8
- class SystemCommandFailed < RuntimeError
9
- end
10
-
11
8
  class TimeoutError < RuntimeError
12
9
  end
13
10
 
@@ -70,7 +67,7 @@ module Osaka
70
67
 
71
68
  output = ""
72
69
  begin
73
- output = do_system("osascript#{escaped_commands}")
70
+ output = CommandRunner::run("osascript#{escaped_commands}")
74
71
  rescue Osaka::SystemCommandFailed => ex
75
72
  if ex.message =~ /assistive devices/
76
73
  puts <<-eom
@@ -94,14 +91,8 @@ module Osaka
94
91
  end
95
92
 
96
93
  def self.execute_file(scriptName, parameters = "")
97
- do_system("osascript #{scriptName} #{parameters}".strip)
94
+ CommandRunner::run("osascript #{scriptName} #{parameters}".strip)
98
95
  end
99
96
 
100
- private
101
- def self.do_system(command)
102
- output = `#{command} 2>&1`
103
- raise Osaka::SystemCommandFailed, "message" + output unless $?.success?
104
- output
105
- end
106
97
  end
107
98
  end
@@ -17,4 +17,4 @@ module Osaka
17
17
  end
18
18
 
19
19
  end
20
- end
20
+ end
@@ -10,6 +10,9 @@ module Osaka
10
10
  end
11
11
  end
12
12
 
13
+ class ApplicationWindowsMustBeClosed < StandardError
14
+ end
15
+
13
16
  class TypicalApplication
14
17
 
15
18
  attr_accessor :control
@@ -70,7 +73,7 @@ module Osaka
70
73
  control.activate
71
74
  latest_window_list = original_window_list = control.window_list
72
75
  yield
73
- while (original_window_list == latest_window_list)
76
+ while ((latest_window_list - original_window_list).size == 0)
74
77
  latest_window_list = control.window_list
75
78
  end
76
79
  (latest_window_list - original_window_list)[0]
@@ -112,7 +115,7 @@ module Osaka
112
115
 
113
116
  def duplicate_and_close_original
114
117
  new_instance = duplicate
115
- close
118
+ close
116
119
  @control = new_instance.control
117
120
  end
118
121
 
@@ -195,7 +198,10 @@ module Osaka
195
198
  dialog = create_dialog(TypicalOpenDialog, dialog_location)
196
199
  dialog.set_folder(File.dirname(filename))
197
200
  dialog.select_file(File.basename(filename))
198
-
201
+ end
202
+
203
+ def raise_error_on_open_standard_windows(error_message)
204
+ raise Osaka::ApplicationWindowsMustBeClosed, error_message if ! control.standard_window_list.empty?
199
205
  end
200
206
  end
201
- end
207
+ end
@@ -17,4 +17,4 @@ module Osaka
17
17
  end
18
18
  end
19
19
 
20
- end
20
+ end
@@ -1,31 +1,35 @@
1
1
  # encoding: utf-8
2
2
  module Osaka
3
-
3
+
4
4
  class OpenDialogCantSelectFile < StandardError
5
5
  end
6
-
6
+
7
7
  class TypicalOpenDialog < TypicalFinderDialog
8
-
8
+
9
9
  def file_list_location
10
- at.outline(1).scroll_area(2).splitter_group(1).group(1)
10
+ if [:snow_leopard, :lion, :mountain_lion, :mavericks].include? control.mac_version
11
+ at.outline(1).scroll_area(2).splitter_group(1).group(1)
12
+ else
13
+ at.outline(1).scroll_area(1).splitter_group(1).splitter_group(1).group(1)
14
+ end
11
15
  end
12
-
16
+
13
17
  def text_field_location_from_row(row)
14
18
  at.text_field(1).ui_element(1).row(row) + file_list_location
15
19
  end
16
-
20
+
17
21
  def static_field_location_from_row(row)
18
- at.static_text(1).ui_element(1).row(row) + file_list_location
22
+ at.static_text(1).ui_element(1).row(row) + file_list_location
19
23
  end
20
-
24
+
21
25
  def greyed_out?(row)
22
26
  !control.exists?(text_field_location_from_row(row))
23
27
  end
24
-
28
+
25
29
  def click_open
26
30
  control.click(at.button("Open"))
27
31
  end
28
-
32
+
29
33
  def field_location_from_row(row)
30
34
  if (greyed_out?(row))
31
35
  static_field_location_from_row(row)
@@ -33,23 +37,23 @@ module Osaka
33
37
  text_field_location_from_row(row)
34
38
  end
35
39
  end
36
-
40
+
37
41
  def select_file_by_row(row)
38
42
  raise(OpenDialogCantSelectFile, "Tried to select a file, but it either doesn't exist or is greyed out") if (greyed_out?(row))
39
43
  control.set!("selected", at.row(row) + file_list_location, true)
40
44
  end
41
-
45
+
42
46
  def amount_of_files_in_list
43
47
  amount_of_rows = control.get!("rows", file_list_location)
44
48
  amount_match_data = amount_of_rows.match(/.*row (\d) of/)
45
- amount_match_data.nil? ? 0 : amount_match_data[1].to_i
49
+ amount_match_data.nil? ? 0 : amount_match_data[1].to_i
46
50
  end
47
-
51
+
48
52
  def filename_at(row)
49
53
  control.get!("value", field_location_from_row(row))
50
54
  end
51
-
52
- def select_file(filename)
55
+
56
+ def select_file(filename)
53
57
  amount_of_files_in_list.times() { |row|
54
58
  if filename_at(row+1) == filename
55
59
  select_file_by_row(row+1)
@@ -58,6 +62,6 @@ module Osaka
58
62
  end
59
63
  }
60
64
  end
61
- end
65
+ end
62
66
 
63
- end
67
+ end