plist4r 0.0.0 → 0.1.0

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.
@@ -0,0 +1,66 @@
1
+
2
+ require 'plist4r/backend'
3
+
4
+ class Plist4r::PlistCache
5
+ def initialize plist, *args, &blk
6
+ @plist = plist
7
+ @backend = Backend.new plist, *args, &blk
8
+ end
9
+
10
+ def checksum
11
+ @plist.to_hash.hash
12
+ end
13
+
14
+ def last_checksum
15
+ @checksum
16
+ end
17
+
18
+ def update_checksum
19
+ @checksum = @plist.to_hash.hash
20
+ end
21
+
22
+ def needs_update
23
+ checksum != last_checksum
24
+ end
25
+
26
+ def to_xml
27
+ if needs_update
28
+ update_checksum
29
+ @xml = @backend.call :to_xml
30
+ else
31
+ @xml
32
+ end
33
+ end
34
+
35
+ def to_binary
36
+ if needs_update
37
+ update_checksum
38
+ @binary = @backend.call :to_binary
39
+ else
40
+ @binary
41
+ end
42
+ end
43
+
44
+ def to_next_step
45
+ if needs_update
46
+ update_checksum
47
+ @next_step = @backend.call :to_next_step
48
+ else
49
+ @next_step
50
+ end
51
+ end
52
+
53
+ def open
54
+ @backend.call :load
55
+ update_checksum
56
+ end
57
+
58
+ def save
59
+ if needs_update
60
+ update_checksum
61
+ @backend.call :save
62
+ else
63
+ true
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,59 @@
1
+
2
+ require 'plist4r/mixin/data_methods'
3
+
4
+ class Plist4r::PlistType
5
+ include ::Plist4r::DataMethods
6
+
7
+ def initialize opts, *args, &blk
8
+ raise unless opts[:hash]
9
+ @hash = @orig = opts[:hash]
10
+ end
11
+ end
12
+
13
+ class Plist4r::ArrayDict
14
+ include DataMethods
15
+
16
+ def initialize orig, index=nil, &blk
17
+ @orig = orig
18
+ if index
19
+ @enclosing_block = self.class.to_s.snake_case + "[#{index}]"
20
+ @orig = @orig[index]
21
+ else
22
+ @enclosing_block = self.class.to_s.snake_case
23
+ end
24
+ puts "@orig = #{@orig.inspect}"
25
+ puts "@enclosing_block = #{@enclosing_block}"
26
+
27
+ @block = blk
28
+ @hash = ::ActiveSupport::OrderedHash.new
29
+ puts "@hash = #{@hash}"
30
+
31
+ instance_eval(&@block) if @block
32
+ puts "@hash = #{@hash}"
33
+ end
34
+
35
+ def hash
36
+ @hash
37
+ end
38
+
39
+ def select *keys
40
+ keys.each do |k|
41
+ @hash[k] = @orig[k]
42
+ end
43
+ end
44
+
45
+ def unselect *keys
46
+ keys.each do |k|
47
+ @hash.delete k
48
+ end
49
+ end
50
+
51
+ def unselect_all
52
+ @hash = ::ActiveSupport::OrderedHash.new
53
+ end
54
+
55
+ def select_all
56
+ @hash = @orig
57
+ end
58
+ end
59
+
File without changes
@@ -0,0 +1,572 @@
1
+
2
+
3
+
4
+ require 'plist4r/plist_type'
5
+
6
+ module Plist4r
7
+ class PlistType::Launchd < PlistType
8
+
9
+ def valid_keys
10
+ {
11
+ :string => %w[Label UserName GroupName LimitLoadToSessionType Program RootDirectory WorkingDirectory StandardInPath StandardOutPath StandardErrorPath],
12
+ :bool => %w[Disabled EnableGlobbing EnableTransactions OnDemand RunAtLoad InitGroups StartOnMount Debug WaitForDebugger AbandonProcessGroup HopefullyExitsFirst HopefullyExitsLast LowPriorityIO LaunchOnlyOnce],
13
+ :integer => %w[Umask TimeOut ExitTimeOut ThrottleInterval StartInterval Nice],
14
+ :array_of_strings => %w[LimitLoadToHosts LimitLoadFromHosts ProgramArguments WatchPaths QueueDirectories],
15
+ :complex_keys => %w[inetdCompatibility KeepAlive EnvironmentVariables StartCalendarInterval SoftResourceLimits, HardResourceLimits MachServices Sockets]
16
+ }
17
+ end
18
+
19
+
20
+ # :call-seq:
21
+ # inetdCompatibility({:wait => true})
22
+ # inetdCompatibility -> hash or nil
23
+ #
24
+ # inetd_compatibility <hash>
25
+ # The presence of this key specifies that the daemon expects to be run as if it were launched from inetd.
26
+ #
27
+ # :wait <boolean>
28
+ # This flag corresponds to the "wait" or "nowait" option of inetd. If true, then the listening socket is passed via the standard in/out/error file descriptors.
29
+ # If false, then accept(2) is called on behalf of the job, and the result is passed via the standard in/out/error descriptors.
30
+ def inetd_compatibility value=nil
31
+ key = "inetdCompatibility"
32
+ case value
33
+ when Hash
34
+ if value[:wait]
35
+ @hash[key] = value[:wait]
36
+ else
37
+ raise "Invalid value: #{method_name} #{value.inspect}. Should be: #{method_name} :wait => true|false"
38
+ end
39
+ when nil
40
+ @hash[key]
41
+ else
42
+ raise "Invalid value: #{method_name} #{value.inspect}. Should be: #{method_name} :wait => true|false"
43
+ end
44
+ end
45
+
46
+ class KeepAlive < ArrayDict
47
+ def valid_keys
48
+ {
49
+ :bool => %w[SuccessfulExit NetworkState],
50
+ :hash_of_bools => %w[PathState OtherJobEnabled]
51
+ }
52
+ end
53
+ end
54
+
55
+ # :call-seq:
56
+ # keep_alive(true)
57
+ # keep_alive(false)
58
+ # keep_alive { block_of_keys }
59
+ # keep_alive => true, false, Hash, or nil
60
+ #
61
+ # keep_alive <boolean or block of keys>
62
+ # This optional key is used to control whether your job is to be kept continuously running or to let demand and conditions control the invocation. The default is
63
+ # false and therefore only demand will start the job. The value may be set to true to unconditionally keep the job alive. Alternatively, a dictionary of conditions
64
+ # may be specified to selectively control whether launchd keeps a job alive or not. If multiple keys are provided, launchd ORs them, thus providing maximum flexibil-
65
+ # ity to the job to refine the logic and stall if necessary. If launchd finds no reason to restart the job, it falls back on demand based invocation. Jobs that exit
66
+ # quickly and frequently when configured to be kept alive will be throttled to converve system resources.
67
+ #
68
+ # keep_alive do
69
+ #
70
+ # successful_exit <boolean>
71
+ # If true, the job will be restarted as long as the program exits and with an exit status of zero. If false, the job will be restarted in the inverse condi-
72
+ # tion. This key implies that "RunAtLoad" is set to true, since the job needs to run at least once before we can get an exit status.
73
+ #
74
+ # network_state <boolean>
75
+ # If true, the job will be kept alive as long as the network is up, where up is defined as at least one non-loopback interface being up and having IPv4 or IPv6
76
+ # addresses assigned to them. If false, the job will be kept alive in the inverse condition.
77
+ #
78
+ # path_state <hash of booleans>
79
+ # Each key in this dictionary is a file-system path. If the value of the key is true, then the job will be kept alive as long as the path exists. If false, the
80
+ # job will be kept alive in the inverse condition. The intent of this feature is that two or more jobs may create semaphores in the file-system namespace.
81
+ #
82
+ # other_job_enabled <hash of booleans>
83
+ # Each key in this dictionary is the label of another job. If the value of the key is true, then this job is kept alive as long as that other job is enabled.
84
+ # Otherwise, if the value is false, then this job is kept alive as long as the other job is disabled. This feature should not be considered a substitute for
85
+ # the use of IPC.
86
+ #
87
+ # end
88
+ #
89
+ # Example:
90
+ #
91
+ # keep_alive do
92
+ # successful_exit true
93
+ # network_state false
94
+ # end
95
+ #
96
+ def keep_alive value=nil, &blk
97
+ key = "KeepAlive"
98
+
99
+ case value
100
+ when TrueCass, FalseClass
101
+ @hash[key] = value
102
+ when nil
103
+ if blk
104
+ @hash[key] ||= ::ActiveSupport::OrderedHash.new
105
+ @hash[key] = ::LaunchdPlistStructs::KeepAlive.new(@hash[key],&blk).hash
106
+ else
107
+ @hash[key]
108
+ end
109
+ else
110
+ raise "Invalid value: #{method_name} #{value.inspect}. Should be: #{method_name} true|false, or #{method_name} { block }"
111
+ end
112
+ end
113
+
114
+ # :call-seq:
115
+ # environment_variables({"VAR1" => "VAL1", "VAR2" => "VAL2"})
116
+ # environment_variables -> hash or nil
117
+ #
118
+ # environment_variables <hash of strings>
119
+ # This optional key is used to specify additional environmental variables to be set before running the job.
120
+ def environment_variables value=nil, &blk
121
+ key = "EnvironmentVariables"
122
+ case value
123
+ when Hash
124
+ value.each do |k,v|
125
+ unless k.class == String
126
+ raise "Invalid key: #{method_name}[#{k.inspect}]. Should be of type String"
127
+ end
128
+ unless v.class == String
129
+ raise "Invalid value: #{method_name}[#{k.inspect}] = #{v.inspect}. Should be of type String"
130
+ end
131
+ end
132
+ @hash[key] = value
133
+ when nil
134
+ @hash[key]
135
+ else
136
+ raise "Invalid value: #{method_name} #{value.inspect}. Should be: #{method_name} { hash_of_strings }"
137
+ end
138
+ end
139
+
140
+ class StartCalendarInterval < ArrayDict
141
+ def valid_keys
142
+ { :integer => %w[Minute Hour Day Weekday Month] }
143
+ end
144
+ end
145
+
146
+ # :call-seq:
147
+ # start_calendar_interval(array_index=nil) { block_of_keys }
148
+ # start_calendar_interval -> array or nil
149
+ #
150
+ # start_calendar_interval <array_index=nil> <block of keys>
151
+ # This optional key causes the job to be started every calendar interval as specified. Missing arguments are considered to be wildcard. The semantics are much like
152
+ # crontab(5). Unlike cron which skips job invocations when the computer is asleep, launchd will start the job the next time the computer wakes up. If multiple
153
+ # intervals transpire before the computer is woken, those events will be coalesced into one event upon wake from sleep.
154
+ #
155
+ # start_calendar_interval index=nil do
156
+ #
157
+ # Minute <integer>
158
+ # The minute on which this job will be run.
159
+ #
160
+ # Hour <integer>
161
+ # The hour on which this job will be run.
162
+ #
163
+ # Day <integer>
164
+ # The day on which this job will be run.
165
+ #
166
+ # Weekday <integer>
167
+ # The weekday on which this job will be run (0 and 7 are Sunday).
168
+ #
169
+ # Month <integer>
170
+ # The month on which this job will be run.
171
+ #
172
+ # end
173
+ #
174
+ # Example:
175
+ #
176
+ # start_calendar_interval 0 do
177
+ # hour 02
178
+ # minute 05
179
+ # day 06
180
+ # end
181
+ #
182
+ # start_calendar_interval 1 do
183
+ # hour 02
184
+ # minute 05
185
+ # day 06
186
+ # end
187
+ #
188
+ def start_calendar_interval index=nil, &blk
189
+ key = "StartCalendarInterval"
190
+ unless [Fixnum,NilClass].include? index.class
191
+ raise "Invalid index: #{method_name} #{index.inspect}. Should be: #{method_name} <integer>"
192
+ end
193
+ if blk
194
+ @hash[key] ||= []
195
+ h = ::LaunchdPlistStructs::StartCalendarInterval.new(@hash[key],index,&blk).hash
196
+ if index
197
+ @hash[key][index] = h
198
+ else
199
+ @hash[key] << h
200
+ end
201
+ else
202
+ @hash[key]
203
+ end
204
+ end
205
+
206
+ class ResourceLimits < ArrayDict
207
+ def valid_keys
208
+ { :integer => %w[Core CPU Data FileSize MemoryLock NumberOfFiles NumberOfProcesses ResidentSetSize Stack] }
209
+ end
210
+ end
211
+
212
+ # :call-seq:
213
+ # soft_resource_limits { block_of_keys }
214
+ # soft_resource_limits -> hash or nil
215
+ #
216
+ # soft_resource_limits <block of keys>
217
+ # Resource limits to be imposed on the job. These adjust variables set with setrlimit(2). The following keys apply:
218
+ #
219
+ # soft_resource_limits do
220
+ #
221
+ # Core <integer>
222
+ # The largest size (in bytes) core file that may be created.
223
+ #
224
+ # CPU <integer>
225
+ # The maximum amount of cpu time (in seconds) to be used by each process.
226
+ #
227
+ # Data <integer>
228
+ # The maximum size (in bytes) of the data segment for a process; this defines how far a program may extend its break with the sbrk(2) system call.
229
+ #
230
+ # FileSize <integer>
231
+ # The largest size (in bytes) file that may be created.
232
+ #
233
+ # MemoryLock <integer>
234
+ # The maximum size (in bytes) which a process may lock into memory using the mlock(2) function.
235
+ #
236
+ # NumberOfFiles <integer>
237
+ # The maximum number of open files for this process. Setting this value in a system wide daemon will set the sysctl(3) kern.maxfiles (SoftResourceLimits) or
238
+ # kern.maxfilesperproc (HardResourceLimits) value in addition to the setrlimit(2) values.
239
+ #
240
+ # NumberOfProcesses <integer>
241
+ # The maximum number of simultaneous processes for this user id. Setting this value in a system wide daemon will set the sysctl(3) kern.maxproc (SoftResource-
242
+ # Limits) or kern.maxprocperuid (HardResourceLimits) value in addition to the setrlimit(2) values.
243
+ #
244
+ # ResidentSetSize <integer>
245
+ # The maximum size (in bytes) to which a process's resident set size may grow. This imposes a limit on the amount of physical memory to be given to a process;
246
+ # if memory is tight, the system will prefer to take memory from processes that are exceeding their declared resident set size.
247
+ #
248
+ # Stack <integer>
249
+ # The maximum size (in bytes) of the stack segment for a process; this defines how far a program's stack segment may be extended. Stack extension is performed
250
+ # automatically by the system.
251
+ #
252
+ # end
253
+ #
254
+ # Example:
255
+ #
256
+ # soft_resource_limits do
257
+ # NumberOfProcesses 4
258
+ # NumberOfFiles 512
259
+ # end
260
+ #
261
+ def soft_resource_limits value=nil, &blk
262
+ key = "SoftResourceLimits"
263
+ if blk
264
+ @hash[key] ||= ::ActiveSupport::OrderedHash.new
265
+ @hash[key] = ::LaunchdPlistStructs::ResourceLimits.new(@hash[key],&blk).hash
266
+ else
267
+ @hash[key]
268
+ end
269
+ end
270
+
271
+ # :call-seq:
272
+ # hard_resource_limits { block_of_keys }
273
+ # hard_resource_limits -> hash or nil
274
+ #
275
+ # hard_resource_limits <block of keys>
276
+ # Resource limits to be imposed on the job. These adjust variables set with setrlimit(2). The following keys apply:
277
+ #
278
+ # hard_resource_limits do
279
+ #
280
+ # Core <integer>
281
+ # The largest size (in bytes) core file that may be created.
282
+ #
283
+ # CPU <integer>
284
+ # The maximum amount of cpu time (in seconds) to be used by each process.
285
+ #
286
+ # Data <integer>
287
+ # The maximum size (in bytes) of the data segment for a process; this defines how far a program may extend its break with the sbrk(2) system call.
288
+ #
289
+ # FileSize <integer>
290
+ # The largest size (in bytes) file that may be created.
291
+ #
292
+ # MemoryLock <integer>
293
+ # The maximum size (in bytes) which a process may lock into memory using the mlock(2) function.
294
+ #
295
+ # NumberOfFiles <integer>
296
+ # The maximum number of open files for this process. Setting this value in a system wide daemon will set the sysctl(3) kern.maxfiles (SoftResourceLimits) or
297
+ # kern.maxfilesperproc (HardResourceLimits) value in addition to the setrlimit(2) values.
298
+ #
299
+ # NumberOfProcesses <integer>
300
+ # The maximum number of simultaneous processes for this user id. Setting this value in a system wide daemon will set the sysctl(3) kern.maxproc (SoftResource-
301
+ # Limits) or kern.maxprocperuid (HardResourceLimits) value in addition to the setrlimit(2) values.
302
+ #
303
+ # ResidentSetSize <integer>
304
+ # The maximum size (in bytes) to which a process's resident set size may grow. This imposes a limit on the amount of physical memory to be given to a process;
305
+ # if memory is tight, the system will prefer to take memory from processes that are exceeding their declared resident set size.
306
+ #
307
+ # Stack <integer>
308
+ # The maximum size (in bytes) of the stack segment for a process; this defines how far a program's stack segment may be extended. Stack extension is performed
309
+ # automatically by the system.
310
+ #
311
+ # end
312
+ #
313
+ # Example:
314
+ #
315
+ # hard_resource_limits do
316
+ # NumberOfProcesses 4
317
+ # NumberOfFiles 512
318
+ # end
319
+ #
320
+ def hard_resource_limits value=nil, &blk
321
+ key = "HardResourceLimits"
322
+ if blk
323
+ @hash[key] ||= ::ActiveSupport::OrderedHash.new
324
+ @hash[key] = ::LaunchdPlistStructs::ResourceLimits.new(@hash[key],&blk).hash
325
+ else
326
+ @hash[key]
327
+ end
328
+ end
329
+
330
+ class MachServices < ArrayDict
331
+ class MachService < ArrayDict
332
+ def valid_keys
333
+ { :bool => %w[ResetAtClose HideUntilCheckIn] }
334
+ end
335
+ end
336
+
337
+ def add service, value=nil, &blk
338
+ if value
339
+ @hash[service] = value
340
+ set_or_return :bool, service, value
341
+ elsif blk
342
+ @hash[service] = ::ActiveSupport::OrderedHash.new
343
+ @hash[service] = ::LaunchdPlistStructs::MachServices::MachService.new(@hash[service],&blk).hash
344
+ else
345
+ @orig
346
+ end
347
+ end
348
+ end
349
+
350
+ # :call-seq:
351
+ # mach_services { block }
352
+ # mach_services -> hash or nil
353
+ #
354
+ # mach_services <dictionary of booleans or a dictionary of dictionaries>
355
+ # This optional key is used to specify Mach services to be registered with the Mach bootstrap sub-system. Each key in this dictionary should be the name of service
356
+ # to be advertised. The value of the key must be a boolean and set to true. Alternatively, a dictionary can be used instead of a simple true value.
357
+ #
358
+ # ResetAtClose <boolean>
359
+ # If this boolean is false, the port is recycled, thus leaving clients to remain oblivious to the demand nature of job. If the value is set to true, clients
360
+ # receive port death notifications when the job lets go of the receive right. The port will be recreated atomically with respect to bootstrap_look_up() calls,
361
+ # so that clients can trust that after receiving a port death notification, the new port will have already been recreated. Setting the value to true should be
362
+ # done with care. Not all clients may be able to handle this behavior. The default value is false.
363
+ #
364
+ # HideUntilCheckIn <boolean>
365
+ # Reserve the name in the namespace, but cause bootstrap_look_up() to fail until the job has checked in with launchd.
366
+ #
367
+ # Finally, for the job itself, the values will be replaced with Mach ports at the time of check-in with launchd.
368
+ #
369
+ # Example:
370
+ #
371
+ # mach_services do
372
+ # add "com.apple.afpfs_checkafp", true
373
+ # end
374
+ #
375
+ # mach_services do
376
+ # add "com.apple.AppleFileServer" do
377
+ # hide_until_check_in true
378
+ # reset_at_close false
379
+ # end
380
+ # end
381
+ #
382
+ def mach_services value=nil, &blk
383
+ key = "MachServices"
384
+ if blk
385
+ @hash[key] ||= ::ActiveSupport::OrderedHash.new
386
+ @hash[key] = ::LaunchdPlistStructs::MachServices.new(@hash[key],&blk).hash
387
+ else
388
+ @hash[key]
389
+ end
390
+ end
391
+
392
+ class Sockets < ArrayDict
393
+ class Socket < ArrayDict
394
+ def valid_keys
395
+ {
396
+ :string => %w[SockType SockNodeName SockServiceName SockFamily SockProtocol SockPathName SecureSocketWithKey MulticastGroup],
397
+ :bool => %w[SockPassive],
398
+ :integer => %w[SockPathMode],
399
+ :bool_or_string_or_array_of_strings => %w[Bonjour]
400
+ }
401
+ end
402
+ end
403
+
404
+ def add_socket_to_dictionary key, &blk
405
+ @hash[key] = ::LaunchdPlistStructs::Sockets::Socket.new(@hash[key],&blk).hash
406
+ end
407
+
408
+ def add_socket_to_array key, index, &blk
409
+ @orig[key] = [] unless @orig[key].class == Array
410
+ @hash[key] ||= []
411
+ @hash[key][index] = ::LaunchdPlistStructs::Sockets::Socket.new(@orig[key],index,&blk).hash
412
+ end
413
+
414
+ def add_socket plicity, key, index=nil, &blk
415
+ select_all
416
+ if plicity == :implicit
417
+ if index && @orig[key].class != Array
418
+ raise "Implicit \"Listeners\" socket already exists, and is of a different type (not array of hashes). Override with: socket \"Listeners\" #{index} &blk"
419
+ elsif @orig[key]
420
+ raise "Implicit \"Listeners\" socket already exists, value: #{orig[key]}. Override with: socket \"Listeners\" &blk"
421
+ end
422
+ end
423
+ if index
424
+ add_socket_to_array key, index, &blk
425
+ else
426
+ add_socket_to_dictionary key, &blk
427
+ end
428
+ end
429
+ end
430
+
431
+ # :call-seq:
432
+ # sockets(socket_key="Listeners") { block_of_keys }
433
+ # sockets(socket_key="Listeners", socket_index) { block_of_keys }
434
+ # sockets -> hash or nil
435
+ #
436
+ # socket <array_index=nil> <block of keys>
437
+ # socket <dictionary... OR dictionary + array index...>
438
+ #
439
+ # Please See: http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/LaunchOnDemandDaemons.html
440
+ # for more information about how to properly use the Sockets feature
441
+ #
442
+ # This optional key is used to specify launch on demand sockets that can be used to let launchd know when to run the job. The job must check-in to get a copy of the
443
+ # file descriptors using APIs outlined in launch(3). The keys of the top level Sockets dictionary can be anything. They are meant for the application developer to
444
+ # use to differentiate which descriptors correspond to which application level protocols (e.g. http vs. ftp vs. DNS...). At check-in time, the value of each Sockets
445
+ # dictionary key will be an array of descriptors. Daemon/Agent writers should consider all descriptors of a given key to be to be effectively equivalent, even though
446
+ # each file descriptor likely represents a different networking protocol which conforms to the criteria specified in the job configuration file.
447
+ # The parameters below are used as inputs to call getaddrinfo(3).
448
+ #
449
+ # socket "socket_key" do
450
+ #
451
+ # SockType <string>
452
+ # This optional key tells launchctl what type of socket to create. The default is "stream" and other valid values for this key are "dgram" and "seqpacket"
453
+ # respectively.
454
+ #
455
+ # SockPassive <boolean>
456
+ # This optional key specifies whether listen(2) or connect(2) should be called on the created file descriptor. The default is true ("to listen").
457
+ #
458
+ # SockNodeName <string>
459
+ # This optional key specifies the node to connect(2) or bind(2) to.
460
+ #
461
+ # SockServiceName <string>
462
+ # This optional key specifies the service on the node to connect(2) or bind(2) to.
463
+ #
464
+ # SockFamily <string>
465
+ # This optional key can be used to specifically request that "IPv4" or "IPv6" socket(s) be created.
466
+ #
467
+ # SockProtocol <string>
468
+ # This optional key specifies the protocol to be passed to socket(2). The only value understood by this key at the moment is "TCP".
469
+ #
470
+ # SockPathName <string>
471
+ # This optional key implies SockFamily is set to "Unix". It specifies the path to connect(2) or bind(2) to.
472
+ #
473
+ # SecureSocketWithKey <string>
474
+ # This optional key is a variant of SockPathName. Instead of binding to a known path, a securely generated socket is created and the path is assigned to the
475
+ # environment variable that is inherited by all jobs spawned by launchd.
476
+ #
477
+ # SockPathMode <integer>
478
+ # This optional key specifies the mode of the socket. Known bug: Property lists don't support octal, so please convert the value to decimal.
479
+ #
480
+ # Bonjour <boolean or string or array of strings>
481
+ # This optional key can be used to request that the service be registered with the mDNSResponder(8). If the value is boolean, the service name is inferred from
482
+ # the SockServiceName.
483
+ #
484
+ # MulticastGroup <string>
485
+ # This optional key can be used to request that the datagram socket join a multicast group. If the value is a hostname, then getaddrinfo(3) will be used to
486
+ # join the correct multicast address for a given socket family. If an explicit IPv4 or IPv6 address is given, it is required that the SockFamily family also be
487
+ # set, otherwise the results are undefined.
488
+ #
489
+ # end
490
+ #
491
+ # NOTE:
492
+ # Sockets is a complex structure. If you are manupilating an existing Sockets entry, then you must fully specify the key you want to modify.
493
+ # so `socket do` should be written as `socket "Listeners" do`, (and in the case of an array of sockets) `socket 0 do` should be written as `socket "Listeners" 0 do`
494
+ #
495
+ # Examples:
496
+ #
497
+ # # scenario 1:
498
+ # socket do
499
+ # sock_service_name "netbios-ssn"
500
+ # end
501
+ # # => Result: Ok. The default "Listeners" toplevel key is generated implicitly.
502
+ #
503
+ # # scenario 2:
504
+ # socket do
505
+ # sock_service_name "netbios-ssn"
506
+ # end
507
+ # socket do
508
+ # sock_service_name "netbios"
509
+ # bonjour ['smb']
510
+ # end
511
+ # # => Result: Exception error is raise the second time because the "Listeners" key already exists. We can forcefully overwrite this existing sockets key with `socket "Listeners" do`.
512
+ #
513
+ # # scenario 3:
514
+ # socket "netbios-ssn" do
515
+ # sock_service_name "netbios-ssn"
516
+ # end
517
+ # socket "netbios" do
518
+ # sock_service_name "netbios"
519
+ # bonjour ['smb']
520
+ # end
521
+ # => Result: Ok. Each Sockets entry has a unique key.
522
+ #
523
+ # # scenario 4:
524
+ # socket 0 do
525
+ # sock_service_name "netbios-ssn"
526
+ # end
527
+ # socket 1 do
528
+ # sock_service_name "netbios"
529
+ # bonjour ['smb']
530
+ # end
531
+ # # => Result: Ok. Each Sockets entry has a unique array index. The array of all these sockets is held within the default "Listeners" key (implicit array or sockets).
532
+ #
533
+ # # scenario 5:
534
+ # socket do
535
+ # sock_service_name "netbios-ssn"
536
+ # end
537
+ # socket[0] do
538
+ # sock_service_name "netbios"
539
+ # bonjour ['smb']
540
+ # end
541
+ # # => Result: Exception error. because we cant mix and match types with the implicit "Listeners" key. If in doubt then avoid using the arrays.
542
+ #
543
+ def socket index_or_key=nil, index=nil, &blk
544
+ key = "Sockets"
545
+ if blk
546
+ @hash[key] ||= ::ActiveSupport::OrderedHash.new
547
+ sockets = ::LaunchdPlistStructs::Sockets.new(@hash[key]).hash
548
+
549
+ case index_or_key
550
+ when nil
551
+ sockets.add_socket :implicit, "Listeners", &blk
552
+ when String
553
+ socket_key = index_or_key
554
+ unless index.class == Fixnum
555
+ raise "Invalid sockect index: #{method_name} #{socket_key} #{index.inspect}. Should be: #{method_name} <socket_key> <socket_index> &blk"
556
+ end
557
+ socket_index = index
558
+ sockets.add_socket :explicit, socket_key, socket_index, &blk
559
+ when Fixnum
560
+ socket_index = index_or_key
561
+ sockets.add_socket :implicit, "Listeners", socket_index, &blk
562
+ else
563
+ raise "Invalid socket key: #{method_name} #{index_or_key.inspect}. Should be: #{method_name} <socket_key> &blk"
564
+ end
565
+ @hash[key] = sockets
566
+ else
567
+ @hash[key]
568
+ end
569
+ end
570
+ end
571
+ end
572
+