run_loop 0.2.1 → 1.0.0.pre3
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 +4 -4
- data/lib/run_loop.rb +4 -1
- data/lib/run_loop/core.rb +94 -46
- data/lib/run_loop/device.rb +22 -0
- data/lib/run_loop/plist_buddy.rb +173 -0
- data/lib/run_loop/sim_control.rb +787 -0
- data/lib/run_loop/version.rb +161 -1
- data/lib/run_loop/xctools.rb +184 -0
- data/scripts/calabash.lldb.erb +4 -0
- metadata +179 -29
- data/.gitignore +0 -22
- data/.gitmodules +0 -3
- data/.irbrc +0 -19
- data/CHANGES.txt +0 -1
- data/Gemfile +0 -4
- data/README.md +0 -7
- data/Rakefile +0 -2
- data/build_to_run_loop.sh +0 -2
- data/docs/intro.md +0 -17
- data/irb.sh +0 -2
- data/run_loop.gemspec +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fbeb69acc86dff93a612295675c56e5c60b9286
|
4
|
+
data.tar.gz: 3d8639044fcbf76a09a8b1dc3ec426ad6dcae537
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 521d737aa170ac8ef6be590b47024aced47db7b8698641a0124b1bc5bff4a6fb7e6207ca01039039cea551e2776c8ae4f55e14ce61f3ad93573a0b6293f36f0e
|
7
|
+
data.tar.gz: 4c22b25f74512d2590efd9a0ee79c7c8d776319672f938215acbae5861ca0163b706311ee05d5569951ec6fc968168d9517b77dd83ebaf558a7ba517e86d9090
|
data/lib/run_loop.rb
CHANGED
data/lib/run_loop/core.rb
CHANGED
@@ -2,6 +2,7 @@ require 'fileutils'
|
|
2
2
|
require 'tmpdir'
|
3
3
|
require 'timeout'
|
4
4
|
require 'json'
|
5
|
+
require 'open3'
|
5
6
|
|
6
7
|
module RunLoop
|
7
8
|
|
@@ -24,6 +25,23 @@ module RunLoop
|
|
24
25
|
SCRIPTS_PATH
|
25
26
|
end
|
26
27
|
|
28
|
+
# @deprecated since 1.0.0
|
29
|
+
# still used extensively in calabash-ios launcher
|
30
|
+
def self.above_or_eql_version?(target_version, xcode_version)
|
31
|
+
if target_version.is_a?(RunLoop::Version)
|
32
|
+
target = target_version
|
33
|
+
else
|
34
|
+
target = RunLoop::Version.new(target_version)
|
35
|
+
end
|
36
|
+
|
37
|
+
if xcode_version.is_a?(RunLoop::Version)
|
38
|
+
xcode = xcode_version
|
39
|
+
else
|
40
|
+
xcode = RunLoop::Version.new(xcode_version)
|
41
|
+
end
|
42
|
+
target >= xcode
|
43
|
+
end
|
44
|
+
|
27
45
|
def self.script_for_key(key)
|
28
46
|
if SCRIPTS[key].nil?
|
29
47
|
return nil
|
@@ -36,7 +54,7 @@ module RunLoop
|
|
36
54
|
Timeout::timeout(1, TimeoutError) do
|
37
55
|
return `#{File.join(scripts_path, 'udidetect')}`.chomp
|
38
56
|
end
|
39
|
-
rescue TimeoutError =>
|
57
|
+
rescue TimeoutError => _
|
40
58
|
`killall udidetect &> /dev/null`
|
41
59
|
end
|
42
60
|
nil
|
@@ -46,6 +64,13 @@ module RunLoop
|
|
46
64
|
before = Time.now
|
47
65
|
ensure_instruments_not_running!
|
48
66
|
|
67
|
+
sim_control ||= options[:sim_control] || RunLoop::SimControl.new
|
68
|
+
xctools ||= options[:xctools] || sim_control.xctools
|
69
|
+
|
70
|
+
# @todo only enable accessibility when there is simulator target
|
71
|
+
# @todo only enable accessibility on the targeted simulator
|
72
|
+
sim_control.enable_accessibility_on_sims({:verbose => true})
|
73
|
+
|
49
74
|
device_target = options[:udid] || options[:device_target] || detect_connected_device || 'simulator'
|
50
75
|
if device_target && device_target.to_s.downcase == 'device'
|
51
76
|
device_target = detect_connected_device
|
@@ -78,11 +103,12 @@ module RunLoop
|
|
78
103
|
|
79
104
|
|
80
105
|
# Compute udid and bundle_dir / bundle_id from options and target depending on Xcode version
|
81
|
-
|
82
|
-
udid, bundle_dir_or_bundle_id = udid_and_bundle_for_launcher(device_target, options)
|
106
|
+
udid, bundle_dir_or_bundle_id = udid_and_bundle_for_launcher(device_target, options, xctools)
|
83
107
|
|
84
108
|
args = options.fetch(:args, [])
|
85
109
|
|
110
|
+
inject_dylib = options.fetch(:inject_dylib, (ENV['CALABASH_USE_DYLIB']=='1'))
|
111
|
+
|
86
112
|
log_file ||= File.join(results_dir, 'run_loop.out')
|
87
113
|
|
88
114
|
if ENV['DEBUG']=='1'
|
@@ -94,6 +120,7 @@ module RunLoop
|
|
94
120
|
puts "log_file=#{log_file}"
|
95
121
|
puts "timeout=#{timeout}"
|
96
122
|
puts "args=#{args}"
|
123
|
+
puts "inject_dylib=#{inject_dylib}"
|
97
124
|
end
|
98
125
|
|
99
126
|
after = Time.now
|
@@ -109,7 +136,8 @@ module RunLoop
|
|
109
136
|
:results_dir => results_dir,
|
110
137
|
:script => script,
|
111
138
|
:log_file => log_file,
|
112
|
-
:args => args)
|
139
|
+
:args => args),
|
140
|
+
xctools)
|
113
141
|
|
114
142
|
log_header("Starting on #{device_target} App: #{bundle_dir_or_bundle_id}")
|
115
143
|
cmd_str = cmd.join(' ')
|
@@ -134,11 +162,38 @@ module RunLoop
|
|
134
162
|
|
135
163
|
uia_timeout = options[:uia_timeout] || (ENV['UIA_TIMEOUT'] && ENV['UIA_TIMEOUT'].to_f) || 10
|
136
164
|
|
165
|
+
raw_lldb_output = nil
|
137
166
|
before = Time.now
|
138
167
|
begin
|
139
168
|
Timeout::timeout(timeout, TimeoutError) do
|
140
169
|
read_response(run_loop, 0, uia_timeout)
|
141
170
|
end
|
171
|
+
if inject_dylib
|
172
|
+
lldb_template_file = File.join(scripts_path,'calabash.lldb.erb')
|
173
|
+
require 'erb'
|
174
|
+
lldb_template = ::ERB.new(File.read(lldb_template_file))
|
175
|
+
lldb_template.filename = lldb_template_file
|
176
|
+
|
177
|
+
cf_bundle_executable = find_cf_bundle_executable(bundle_dir_or_bundle_id)
|
178
|
+
require 'calabash/dylibs'
|
179
|
+
dylib_path_for_target = Calabash::Dylibs.path_to_sim_dylib
|
180
|
+
|
181
|
+
lldb_cmd = lldb_template.result(binding)
|
182
|
+
|
183
|
+
tmpdir = Dir.mktmpdir('lldb_cmd')
|
184
|
+
lldb_script = File.join(tmpdir,'lldb')
|
185
|
+
|
186
|
+
File.open(lldb_script,'w') {|f| f.puts(lldb_cmd)}
|
187
|
+
|
188
|
+
if ENV['DEBUG'] == '1'
|
189
|
+
puts "lldb script #{lldb_script}"
|
190
|
+
end
|
191
|
+
|
192
|
+
raw_lldb_output = `lldb -s #{lldb_script}`
|
193
|
+
if ENV['DEBUG'] == '1'
|
194
|
+
puts raw_lldb_output
|
195
|
+
end
|
196
|
+
end
|
142
197
|
rescue TimeoutError => e
|
143
198
|
if ENV['DEBUG']
|
144
199
|
puts "Failed to launch\n"
|
@@ -150,6 +205,7 @@ module RunLoop
|
|
150
205
|
puts "log_file=#{log_file}"
|
151
206
|
puts "timeout=#{timeout}"
|
152
207
|
puts "args=#{args}"
|
208
|
+
puts "lldb_output=#{raw_lldb_output}" if raw_lldb_output
|
153
209
|
end
|
154
210
|
raise TimeoutError, "Time out waiting for UIAutomation run-loop to Start. \n Logfile #{log_file} \n\n #{File.read(log_file)}\n"
|
155
211
|
end
|
@@ -163,7 +219,16 @@ module RunLoop
|
|
163
219
|
run_loop
|
164
220
|
end
|
165
221
|
|
166
|
-
def self.
|
222
|
+
def self.find_cf_bundle_executable(bundle_dir_or_bundle_id)
|
223
|
+
unless File.directory?(bundle_dir_or_bundle_id)
|
224
|
+
raise "Injecting dylibs currently only works with simulator and app bundles"
|
225
|
+
end
|
226
|
+
info_plist = Dir[File.join(bundle_dir_or_bundle_id,'Info.plist')].first
|
227
|
+
raise "Unable to find Info.plist in #{bundle_dir_or_bundle_id}" if info_plist.nil?
|
228
|
+
`/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "#{info_plist}"`.strip
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.udid_and_bundle_for_launcher(device_target, options, xctools=RunLoop::XCTools.new)
|
167
232
|
bundle_dir_or_bundle_id = options[:app] || ENV['BUNDLE_ID']|| ENV['APP_BUNDLE_PATH'] || ENV['APP']
|
168
233
|
|
169
234
|
unless bundle_dir_or_bundle_id
|
@@ -171,16 +236,21 @@ module RunLoop
|
|
171
236
|
end
|
172
237
|
|
173
238
|
udid = nil
|
174
|
-
|
239
|
+
|
240
|
+
if xctools.xcode_version_gte_51?
|
175
241
|
if device_target.nil? || device_target.empty? || device_target == 'simulator'
|
176
|
-
|
242
|
+
if xctools.xcode_version_gte_6?
|
243
|
+
# the simulator can be either the textual name or the UDID (directory name)
|
244
|
+
device_target = 'iPhone 5 (8.0 Simulator)'
|
245
|
+
else
|
246
|
+
device_target = 'iPhone Retina (4-inch) - Simulator - iOS 7.1'
|
247
|
+
end
|
177
248
|
end
|
178
249
|
udid = device_target
|
179
250
|
|
180
251
|
unless /simulator/i.match(device_target)
|
181
252
|
bundle_dir_or_bundle_id = options[:bundle_id] if options[:bundle_id]
|
182
253
|
end
|
183
|
-
|
184
254
|
else
|
185
255
|
if device_target == 'simulator'
|
186
256
|
|
@@ -210,30 +280,9 @@ module RunLoop
|
|
210
280
|
return udid, bundle_dir_or_bundle_id
|
211
281
|
end
|
212
282
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
return true if x_major.to_i > t_major.to_i
|
217
|
-
return false if x_major.to_i < t_major.to_i
|
218
|
-
#major versions are equal
|
219
|
-
t_minor_i = (t_minor && t_minor.to_i || 0)
|
220
|
-
x_minor_i = (x_minor && x_minor.to_i || 0)
|
221
|
-
return true if x_minor_i > t_minor_i
|
222
|
-
return false if x_minor_i < t_minor_i
|
223
|
-
#minor versions are equal
|
224
|
-
|
225
|
-
t_patch_i = (t_patch && t_patch.to_i || 0)
|
226
|
-
x_patch_i = (x_patch && x_patch.to_i || 0)
|
227
|
-
|
228
|
-
x_patch_i >= t_patch_i
|
229
|
-
end
|
230
|
-
|
231
|
-
def self.xcode_version
|
232
|
-
xcode_build_output = `xcodebuild -version`.split("\n")
|
233
|
-
xcode_build_output.each do |line|
|
234
|
-
match=/^Xcode\s(.*)$/.match(line.strip)
|
235
|
-
return match[1] if match && match.length > 1
|
236
|
-
end
|
283
|
+
# @deprecated 1.0.0 replaced with Xctools#version
|
284
|
+
def self.xcode_version(xctools=XCTools.new)
|
285
|
+
xctools.xcode_version.to_s
|
237
286
|
end
|
238
287
|
|
239
288
|
def self.jruby?
|
@@ -360,7 +409,7 @@ module RunLoop
|
|
360
409
|
end
|
361
410
|
|
362
411
|
def self.instruments_command_prefix(udid, results_dir_trace)
|
363
|
-
instruments_path = 'instruments'
|
412
|
+
instruments_path = 'xcrun instruments'
|
364
413
|
if udid
|
365
414
|
instruments_path = "#{instruments_path} -w \"#{udid}\""
|
366
415
|
end
|
@@ -369,7 +418,7 @@ module RunLoop
|
|
369
418
|
end
|
370
419
|
|
371
420
|
|
372
|
-
def self.instruments_command(options)
|
421
|
+
def self.instruments_command(options, xctools=XCTools.new)
|
373
422
|
udid = options[:udid]
|
374
423
|
results_dir_trace = options[:results_dir_trace]
|
375
424
|
bundle_dir_or_bundle_id = options[:bundle_dir_or_bundle_id]
|
@@ -381,7 +430,7 @@ module RunLoop
|
|
381
430
|
instruments_prefix = instruments_command_prefix(udid, results_dir_trace)
|
382
431
|
cmd = [
|
383
432
|
instruments_prefix,
|
384
|
-
'-t', "\"#{automation_template}\"",
|
433
|
+
'-t', "\"#{automation_template(xctools)}\"",
|
385
434
|
"\"#{bundle_dir_or_bundle_id}\"",
|
386
435
|
'-e', 'UIARESULTSPATH', results_dir,
|
387
436
|
'-e', 'UIASCRIPT', script,
|
@@ -393,21 +442,20 @@ module RunLoop
|
|
393
442
|
cmd
|
394
443
|
end
|
395
444
|
|
396
|
-
def self.automation_template(candidate = ENV['TRACE_TEMPLATE'])
|
445
|
+
def self.automation_template(xctools, candidate = ENV['TRACE_TEMPLATE'])
|
397
446
|
unless candidate && File.exist?(candidate)
|
398
|
-
candidate = default_tracetemplate
|
447
|
+
candidate = default_tracetemplate xctools
|
399
448
|
end
|
400
449
|
candidate
|
401
450
|
end
|
402
451
|
|
403
|
-
def self.default_tracetemplate
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
end
|
410
|
-
File.join(automation_bundle, 'Contents', 'Resources', 'Automation.tracetemplate')
|
452
|
+
def self.default_tracetemplate(xctools=XCTools.new)
|
453
|
+
templates = xctools.instruments :templates
|
454
|
+
templates.delete_if do |path|
|
455
|
+
not path =~ /\/Automation.tracetemplate/
|
456
|
+
end.delete_if do |path|
|
457
|
+
not path =~ /Xcode/
|
458
|
+
end.first.tr("\"", '').strip
|
411
459
|
end
|
412
460
|
|
413
461
|
def self.log(message)
|
@@ -467,7 +515,7 @@ module RunLoop
|
|
467
515
|
result = Core.read_response(run_loop, expected_index)
|
468
516
|
end
|
469
517
|
|
470
|
-
rescue TimeoutError =>
|
518
|
+
rescue TimeoutError => _
|
471
519
|
raise TimeoutError, "Time out waiting for UIAutomation run-loop for command #{cmd}. Waiting for index:#{expected_index}"
|
472
520
|
end
|
473
521
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RunLoop
|
2
|
+
class Device
|
3
|
+
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :version
|
6
|
+
attr_reader :udid
|
7
|
+
|
8
|
+
def initialize(name, version, udid)
|
9
|
+
@name = name
|
10
|
+
|
11
|
+
if version.is_a? String
|
12
|
+
@version = RunLoop::Version.new version
|
13
|
+
else
|
14
|
+
@version = version
|
15
|
+
end
|
16
|
+
|
17
|
+
@udid = udid
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module RunLoop
|
2
|
+
# A class for reading and writing property list values.
|
3
|
+
#
|
4
|
+
# Why not use CFPropertyList? Because it is super wonky. Among its other
|
5
|
+
# faults, it matches Boolean to a string type with 'true/false' values which
|
6
|
+
# is problematic for our purposes.
|
7
|
+
class PlistBuddy
|
8
|
+
|
9
|
+
# Reads key from file and returns the result.
|
10
|
+
# @param [String] key the key to inspect (may not be nil or empty)
|
11
|
+
# @param [String] file the plist to read
|
12
|
+
# @param [Hash] opts options for controlling execution
|
13
|
+
# @option opts [Boolean] :verbose (false) controls log level
|
14
|
+
# @return [String] the value of the key
|
15
|
+
# @raise [ArgumentError] if nil or empty key
|
16
|
+
def plist_read(key, file, opts={})
|
17
|
+
if key.nil? or key.length == 0
|
18
|
+
raise(ArgumentError, "key '#{key}' must not be nil or empty")
|
19
|
+
end
|
20
|
+
cmd = build_plist_cmd(:print, {:key => key}, file)
|
21
|
+
res = execute_plist_cmd(cmd, opts)
|
22
|
+
if res == "Print: Entry, \":#{key}\", Does Not Exist"
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
res
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Checks if the key exists in plist.
|
30
|
+
# @param [String] key the key to inspect (may not be nil or empty)
|
31
|
+
# @param [String] file the plist to read
|
32
|
+
# @param [Hash] opts options for controlling execution
|
33
|
+
# @option opts [Boolean] :verbose (false) controls log level
|
34
|
+
# @return [Boolean] true if the key exists in plist file
|
35
|
+
def plist_key_exists?(key, file, opts={})
|
36
|
+
plist_read(key, file, opts) != nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Replaces or creates the value of key in the file.
|
40
|
+
#
|
41
|
+
# @param [String] key the key to set (may not be nil or empty)
|
42
|
+
# @param [String] type the plist type (used only when adding a value)
|
43
|
+
# @param [String] value the new value
|
44
|
+
# @param [String] file the plist to read
|
45
|
+
# @param [Hash] opts options for controlling execution
|
46
|
+
# @option opts [Boolean] :verbose (false) controls log level
|
47
|
+
# @return [Boolean] true if the operation was successful
|
48
|
+
# @raise [ArgumentError] if nil or empty key
|
49
|
+
def plist_set(key, type, value, file, opts={})
|
50
|
+
default_opts = {:verbose => false}
|
51
|
+
merged = default_opts.merge(opts)
|
52
|
+
|
53
|
+
if key.nil? or key.length == 0
|
54
|
+
raise(ArgumentError, "key '#{key}' must not be nil or empty")
|
55
|
+
end
|
56
|
+
|
57
|
+
cmd_args = {:key => key,
|
58
|
+
:type => type,
|
59
|
+
:value => value}
|
60
|
+
|
61
|
+
if plist_key_exists?(key, file, merged)
|
62
|
+
cmd = build_plist_cmd(:set, cmd_args, file)
|
63
|
+
else
|
64
|
+
cmd = build_plist_cmd(:add, cmd_args, file)
|
65
|
+
end
|
66
|
+
|
67
|
+
res = execute_plist_cmd(cmd, merged)
|
68
|
+
res == ''
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# returns the path to the PlistBuddy executable
|
74
|
+
# @return [String] path to PlistBuddy
|
75
|
+
def plist_buddy
|
76
|
+
'/usr/libexec/PlistBuddy'
|
77
|
+
end
|
78
|
+
|
79
|
+
# Executes cmd as a shell command and returns the result.
|
80
|
+
#
|
81
|
+
# @param [String] cmd shell command to execute
|
82
|
+
# @param [Hash] opts options for controlling execution
|
83
|
+
# @option opts [Boolean] :verbose (false) controls log level
|
84
|
+
# @return [Boolean,String] `true` if command was successful. If :print'ing
|
85
|
+
# the result, the value of the key. If there is an error, the output of
|
86
|
+
# stderr.
|
87
|
+
def execute_plist_cmd(cmd, opts={})
|
88
|
+
default_opts = {:verbose => false}
|
89
|
+
merged = default_opts.merge(opts)
|
90
|
+
|
91
|
+
puts cmd if merged[:verbose]
|
92
|
+
|
93
|
+
res = nil
|
94
|
+
# noinspection RubyUnusedLocalVariable
|
95
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
96
|
+
err = stderr.read
|
97
|
+
std = stdout.read
|
98
|
+
if not err.nil? and err != ''
|
99
|
+
res = err.chomp
|
100
|
+
else
|
101
|
+
res = std.chomp
|
102
|
+
end
|
103
|
+
end
|
104
|
+
res
|
105
|
+
end
|
106
|
+
|
107
|
+
# Composes a PlistBuddy command that can be executed as a shell command.
|
108
|
+
#
|
109
|
+
# @param [Symbol] type should be one of [:print, :set, :add]
|
110
|
+
#
|
111
|
+
# @param [Hash] args_hash arguments used to construct plist command
|
112
|
+
# @option args_hash [String] :key (required) the plist key
|
113
|
+
# @option args_hash [String] :value (required for :set and :add) the new value
|
114
|
+
# @option args_hash [String] :type (required for :add) the new type of the value
|
115
|
+
#
|
116
|
+
# @param [String] file the plist file to interact with (must exist)
|
117
|
+
#
|
118
|
+
# @raise [RuntimeError] if file does not exist
|
119
|
+
# @raise [ArgumentError] when invalid type is passed
|
120
|
+
# @raise [ArgumentError] when args_hash does not include required key/value pairs
|
121
|
+
#
|
122
|
+
# @return [String] a shell-ready PlistBuddy command
|
123
|
+
def build_plist_cmd(type, args_hash, file)
|
124
|
+
|
125
|
+
unless File.exist?(File.expand_path(file))
|
126
|
+
raise(RuntimeError, "plist '#{file}' does not exist - could not read")
|
127
|
+
end
|
128
|
+
|
129
|
+
case type
|
130
|
+
when :add
|
131
|
+
value_type = args_hash[:type]
|
132
|
+
unless value_type
|
133
|
+
raise(ArgumentError, ':value_type is a required key for :add command')
|
134
|
+
end
|
135
|
+
allowed_value_types = ['string', 'bool', 'real', 'integer']
|
136
|
+
unless allowed_value_types.include?(value_type)
|
137
|
+
raise(ArgumentError, "expected '#{value_type}' to be one of '#{allowed_value_types}'")
|
138
|
+
end
|
139
|
+
value = args_hash[:value]
|
140
|
+
unless value
|
141
|
+
raise(ArgumentError, ':value is a required key for :add command')
|
142
|
+
end
|
143
|
+
key = args_hash[:key]
|
144
|
+
unless key
|
145
|
+
raise(ArgumentError, ':key is a required key for :add command')
|
146
|
+
end
|
147
|
+
cmd_part = "\"Add :#{key} #{value_type} #{value}\""
|
148
|
+
when :print
|
149
|
+
key = args_hash[:key]
|
150
|
+
unless key
|
151
|
+
raise(ArgumentError, ':key is a required key for :print command')
|
152
|
+
end
|
153
|
+
cmd_part = "\"Print :#{key}\""
|
154
|
+
when :set
|
155
|
+
value = args_hash[:value]
|
156
|
+
unless value
|
157
|
+
raise(ArgumentError, ':value is a required key for :set command')
|
158
|
+
end
|
159
|
+
key = args_hash[:key]
|
160
|
+
unless key
|
161
|
+
raise(ArgumentError, ':key is a required key for :set command')
|
162
|
+
end
|
163
|
+
cmd_part = "\"Set :#{key} #{value}\""
|
164
|
+
else
|
165
|
+
cmds = [:add, :print, :set]
|
166
|
+
raise(ArgumentError, "expected '#{type}' to be one of '#{cmds}'")
|
167
|
+
end
|
168
|
+
|
169
|
+
"#{plist_buddy} -c #{cmd_part} \"#{file}\""
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|