osaka 0.4.8 → 0.4.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +7 -0
- data/README.rdoc +1 -1
- data/Rakefile +12 -2
- data/lib/osaka.rb +6 -0
- data/lib/osaka/calculator.rb +1 -1
- data/lib/osaka/commandrunner.rb +17 -0
- data/lib/osaka/defaultssystem.rb +28 -0
- data/lib/osaka/keynote.rb +8 -6
- data/lib/osaka/keynoteflow.rb +10 -2
- data/lib/osaka/launchservices.rb +29 -0
- data/lib/osaka/location.rb +1 -1
- data/lib/osaka/mailmergeflow.rb +1 -1
- data/lib/osaka/numbers.rb +1 -1
- data/lib/osaka/osakaexpectations.rb +65 -61
- data/lib/osaka/pages.rb +35 -26
- data/lib/osaka/preview.rb +1 -1
- data/lib/osaka/remotecontrol.rb +57 -47
- data/lib/osaka/scriptrunner.rb +2 -11
- data/lib/osaka/textedit.rb +1 -1
- data/lib/osaka/typicalapplication.rb +10 -4
- data/lib/osaka/typicalfinderdialog.rb +1 -1
- data/lib/osaka/typicalopendialog.rb +22 -18
- data/lib/osaka/typicalprintdialog.rb +1 -1
- data/lib/osaka/typicalsavedialog.rb +1 -1
- data/lib/osaka/version.rb +1 -1
- data/{osaka.gemfile → osaka.gemspec} +0 -0
- data/spec/assets/document.pdf +0 -0
- data/spec/calculator_spec.rb +5 -5
- data/spec/defaultssystem_spec.rb +30 -0
- data/spec/integration_calculator_spec.rb +7 -7
- data/spec/integration_keynote_spec.rb +24 -11
- data/spec/integration_numbers_spec.rb +2 -2
- data/spec/integration_pages_numbers_mail_merge_spec.rb +9 -9
- data/spec/integration_preview_spec.rb +16 -0
- data/spec/integration_textedit_spec.rb +5 -5
- data/spec/keynote_flows_spec.rb +52 -31
- data/spec/keynote_spec.rb +24 -14
- data/spec/launchservices_spec.rb +63 -0
- data/spec/location_spec.rb +13 -13
- data/spec/mailmergeflow_spec.rb +13 -13
- data/spec/numbers_spec.rb +10 -10
- data/spec/osakaexpectations_spec.rb +3 -3
- data/spec/pages_spec.rb +80 -61
- data/spec/preview_spec.rb +5 -5
- data/spec/remotecontrol_spec.rb +65 -43
- data/spec/scriptrunner_spec.rb +22 -22
- data/spec/textedit_spec.rb +3 -3
- data/spec/typicalapplication_spec.rb +119 -108
- data/spec/typicalfinderdialog_spec.rb +2 -2
- data/spec/typicalopendialog_spec.rb +41 -35
- data/spec/typicalprintdialog_spec.rb +5 -5
- data/spec/typicalsavedialog_spec.rb +10 -10
- metadata +51 -47
data/lib/osaka/preview.rb
CHANGED
data/lib/osaka/remotecontrol.rb
CHANGED
@@ -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(
|
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.
|
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
|
data/lib/osaka/scriptrunner.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
data/lib/osaka/textedit.rb
CHANGED
@@ -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 ==
|
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
|
@@ -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
|
-
|
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
|