osaka 0.4.8 → 0.4.10

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.
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