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