macos.rb 0.50.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.
- checksums.yaml +7 -0
- data/CHANGELOG +572 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/Rakefile +9 -0
- data/lib/Array/all_but_last.rb +7 -0
- data/lib/MacOS/Apple80211.rb +68 -0
- data/lib/MacOS/Applications.rb +73 -0
- data/lib/MacOS/Defaults/ComAppleDock.rb +153 -0
- data/lib/MacOS/Defaults/NSGlobalDomain/NSUserKeyEquivalents.rb +109 -0
- data/lib/MacOS/Defaults.rb +126 -0
- data/lib/MacOS/DiskUtil.rb +63 -0
- data/lib/MacOS/Dmg.rb +99 -0
- data/lib/MacOS/HardwarePort.rb +79 -0
- data/lib/MacOS/IfConfig.rb +183 -0
- data/lib/MacOS/Keychain.rb +26 -0
- data/lib/MacOS/LaunchControl.rb +36 -0
- data/lib/MacOS/SystemProfiler.rb +119 -0
- data/lib/MacOS/USBFlashDrive.rb +26 -0
- data/lib/MacOS/Users.rb +16 -0
- data/lib/MacOS/VERSION.rb +6 -0
- data/lib/MacOS/VPN.rb +13 -0
- data/lib/MacOS/Voice.rb +40 -0
- data/lib/MacOS.rb +27 -0
- data/lib/Module/alias_methods.rb +7 -0
- data/lib/Ordinal/Array.rb +8 -0
- data/lib/Ordinal.rb +157 -0
- data/lib/Thoran/Array/AllButLast/all_but_last.rb +28 -0
- data/lib/Thoran/Array/LastX/lastX.rb +33 -0
- data/lib/Thoran/Module/AliasMethods/alias_methods.rb +23 -0
- data/lib/Thoran/String/Capture/capture.rb +28 -0
- data/lib/Version.rb +381 -0
- data/macos.rb.gemspec +59 -0
- data/test/MacOS/Apple80211_test.rb +80 -0
- data/test/MacOS/Applications_test.rb +82 -0
- data/test/MacOS/Defaults/ComAppleDock_test.rb +66 -0
- data/test/MacOS/Defaults/NSGlobalDomain/NSUserKeyEquivalents_test.rb +67 -0
- data/test/MacOS/Defaults_test.rb +144 -0
- data/test/MacOS/DiskUtil_test.rb +48 -0
- data/test/MacOS/Dmg_test.rb +69 -0
- data/test/MacOS/HardwarePort_test.rb +46 -0
- data/test/MacOS/IfConfig_test.rb +144 -0
- data/test/MacOS/Keychain_test.rb +22 -0
- data/test/MacOS/LaunchControl_test.rb +40 -0
- data/test/MacOS/SystemProfiler_test.rb +69 -0
- data/test/MacOS/USBFlashDrive_test.rb +20 -0
- data/test/MacOS/Users_test.rb +10 -0
- data/test/MacOS/VPN_test.rb +18 -0
- data/test/MacOS/Voice_test.rb +46 -0
- data/test/MacOS_test.rb +17 -0
- data/test/VERSION_test.rb +19 -0
- data/test/commands_test.rb +50 -0
- data/test/fixtures/airport-I-deprecated.txt +2 -0
- data/test/fixtures/airport-I.txt +17 -0
- data/test/fixtures/defaults-domains.txt +1 -0
- data/test/fixtures/defaults-export-com.apple.dock.xml +47 -0
- data/test/fixtures/defaults-read-NSUserKeyEquivalents.txt +4 -0
- data/test/fixtures/df-k.txt +6 -0
- data/test/fixtures/diskutil-list-plist.xml +104 -0
- data/test/fixtures/dscl-list-users.txt +8 -0
- data/test/fixtures/ifconfig-a.txt +53 -0
- data/test/fixtures/networksetup-listallhardwareports.txt +39 -0
- data/test/fixtures/say-v.txt +8 -0
- data/test/fixtures/system_profiler-SPAirPortDataType.txt +44 -0
- data/test/fixtures/system_profiler-SPSoftwareDataType.txt +14 -0
- data/test/fixtures/system_profiler-SPUSBDataType.txt +58 -0
- data/test/gemspec_test.rb +50 -0
- data/test/helper.rb +18 -0
- metadata +183 -0
data/lib/Version.rb
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# Version.rb
|
|
2
|
+
# Version
|
|
3
|
+
|
|
4
|
+
# 20220213
|
|
5
|
+
# 0.14.0
|
|
6
|
+
|
|
7
|
+
# Description: This class is able to compare strings containing version numbers.
|
|
8
|
+
|
|
9
|
+
# Changes since 0.13:
|
|
10
|
+
# 1. Removed all reference to version_codes.
|
|
11
|
+
|
|
12
|
+
lib_dir = File.dirname(File.expand_path(__FILE__))
|
|
13
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
14
|
+
|
|
15
|
+
require 'Module/alias_methods'
|
|
16
|
+
require 'Ordinal/Array'
|
|
17
|
+
require 'Thoran/String/Capture/capture'
|
|
18
|
+
|
|
19
|
+
class Version
|
|
20
|
+
|
|
21
|
+
module VERSION; STRING = '0.13.0'; end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
|
|
25
|
+
include Enumerable
|
|
26
|
+
include Comparable
|
|
27
|
+
|
|
28
|
+
attr_accessor :version_strings
|
|
29
|
+
|
|
30
|
+
def <<(version)
|
|
31
|
+
self.version_strings ||= []
|
|
32
|
+
self.version_strings << to_version_string(version)
|
|
33
|
+
end
|
|
34
|
+
alias_methods :append, :push, :<<
|
|
35
|
+
|
|
36
|
+
def sorted(*versions)
|
|
37
|
+
versions = versions.flatten
|
|
38
|
+
return_value = versions.collect{|version| Version.new(to_version_string(version))}.sort
|
|
39
|
+
return_value = return_value.collect{|e| e.to_s} if versions.all?{|e| e.is_a?(String)}
|
|
40
|
+
return_value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def latest(*versions)
|
|
44
|
+
sorted(*versions).last
|
|
45
|
+
end
|
|
46
|
+
alias_methods :last, :most_recent, :newest, :highest, :latest
|
|
47
|
+
|
|
48
|
+
def major(version)
|
|
49
|
+
major = to_version_string(version).split('.').first
|
|
50
|
+
major = major.split('-').first if major && hyphenated_part?(version)
|
|
51
|
+
major
|
|
52
|
+
end
|
|
53
|
+
alias_methods :major_version, :major_number, :major_version_number, :major
|
|
54
|
+
|
|
55
|
+
def minor(version)
|
|
56
|
+
minor = to_version_string(version).split('.').second
|
|
57
|
+
minor = minor.split('-').first if minor && hyphenated_part?(version)
|
|
58
|
+
minor
|
|
59
|
+
end
|
|
60
|
+
alias_methods :minor_version, :minor_number, :minor_version_number, :minor
|
|
61
|
+
|
|
62
|
+
def tiny(version)
|
|
63
|
+
tiny = to_version_string(version).split('.').third
|
|
64
|
+
tiny = tiny.split('-').first if tiny && hyphenated_part?(version)
|
|
65
|
+
tiny
|
|
66
|
+
end
|
|
67
|
+
alias_methods :tiny_version, :tiny_number, :tiny_version_number, :tiny
|
|
68
|
+
|
|
69
|
+
def build(version)
|
|
70
|
+
build = to_version_string(version).split('.').fourth
|
|
71
|
+
build = build.split('-').first if build && hyphenated_part?(version)
|
|
72
|
+
build
|
|
73
|
+
end
|
|
74
|
+
alias_methods :build_version, :build_number, :build_version_number, :build
|
|
75
|
+
|
|
76
|
+
def patch(version)
|
|
77
|
+
if hyphenated_part = self.hyphenated_part(version)
|
|
78
|
+
hyphenated_part(version).capture(/^(p\d+)/) # It's unlikely that there would be a p without a number.
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
alias_methods :patch_level, :patch_version, :patch_number, :patch_version_number, :patch
|
|
82
|
+
|
|
83
|
+
def dev(version)
|
|
84
|
+
if hyphenated_part = self.hyphenated_part(version)
|
|
85
|
+
hyphenated_part(version).capture(/^(dev\d*)/)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
alias_methods :dev_version, :dev_number, :dev_version_number, :dev
|
|
89
|
+
alias_methods :development, :development_version, :development_number, :development_version_number, :dev
|
|
90
|
+
|
|
91
|
+
def preview(version)
|
|
92
|
+
if hyphenated_part = self.hyphenated_part(version)
|
|
93
|
+
hyphenated_part.capture(/^(preview\d*)/) # It's possible that there will be a preview without a number.
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
alias_methods :preview_version, :preview_number, :preview_version_number, :preview
|
|
97
|
+
|
|
98
|
+
def release_candidate(version)
|
|
99
|
+
if hyphenated_part = self.hyphenated_part(version)
|
|
100
|
+
hyphenated_part(version).capture(/^(rc\d*)/)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
alias_methods :release_candidate_version, :release_candidate_number, :release_candidate_version_number, :release_candidate
|
|
104
|
+
alias_methods :rc, :rc_version, :rc_number, :rc_version_number, :release_candidate
|
|
105
|
+
|
|
106
|
+
def hyphenated_part(version)
|
|
107
|
+
if to_version_string(version).match(/-/)
|
|
108
|
+
to_version_string(version).split('-').last
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def <=>(version_1, version_2)
|
|
113
|
+
version_1 = Version.new(Version.to_version_string(version_1))
|
|
114
|
+
version_2 = Version.new(Version.to_version_string(version_2))
|
|
115
|
+
if version_1.has_hyphenated_part? || version_2.has_hyphenated_part?
|
|
116
|
+
if (to_a(version_1, false) <=> to_a(version_2, false)) == 0
|
|
117
|
+
if version_1.hyphenated_part == version_2.hyphenated_part
|
|
118
|
+
0
|
|
119
|
+
elsif version_1.hyphenated_part =~ /^dev/
|
|
120
|
+
case version_2.hyphenated_part
|
|
121
|
+
when /^dev/; version_1.hyphenated_part <=> version_2.hyphenated_part
|
|
122
|
+
when /^preview/; -1
|
|
123
|
+
when /^rc/; -1
|
|
124
|
+
when /^p\d+/; -1
|
|
125
|
+
else; -1
|
|
126
|
+
end
|
|
127
|
+
elsif version_1.hyphenated_part =~ /^preview/
|
|
128
|
+
case version_2.hyphenated_part
|
|
129
|
+
when /^preview/; version_1.hyphenated_part <=> version_2.hyphenated_part
|
|
130
|
+
when /^dev/; 1
|
|
131
|
+
when /^rc/; -1
|
|
132
|
+
when /^p\d+/; -1
|
|
133
|
+
else; -1
|
|
134
|
+
end
|
|
135
|
+
elsif version_1.hyphenated_part =~ /^rc/
|
|
136
|
+
case version_2.hyphenated_part
|
|
137
|
+
when /^rc/; version_1.hyphenated_part <=> version_2.hyphenated_part
|
|
138
|
+
when /^dev/; 1
|
|
139
|
+
when /^preview/; 1
|
|
140
|
+
when /^p\d+/; -1
|
|
141
|
+
else; -1
|
|
142
|
+
end
|
|
143
|
+
elsif version_1.hyphenated_part =~ /^p\d+/
|
|
144
|
+
case version_2.hyphenated_part
|
|
145
|
+
when /^p\d+/; version_1.hyphenated_part <=> version_2.hyphenated_part
|
|
146
|
+
when /^dev/; 1
|
|
147
|
+
when /^preview/; 1
|
|
148
|
+
when /^rc/; 1
|
|
149
|
+
else; -1 # This assumes that 2.1.0 != 2.1.0-p0 (ie. 2.1.0-p0 < 2.1.0) and that for all other patch levels that anything with no patch level is superior.
|
|
150
|
+
end
|
|
151
|
+
else
|
|
152
|
+
case version_2.hyphenated_part
|
|
153
|
+
when /^dev/; 1
|
|
154
|
+
when /^preview/; 1
|
|
155
|
+
when /^rc/; 1
|
|
156
|
+
when /^p\d+/; 1
|
|
157
|
+
else; 1
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
to_a(version_1, false) <=> to_a(version_2, false)
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
to_a(version_1) <=> to_a(version_2)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def ===(version_1, version_2)
|
|
169
|
+
version_1_size = to_a(version_1).size
|
|
170
|
+
version_2_size = to_a(version_2).size
|
|
171
|
+
maximum_shared_places = [version_1_size, version_2_size].min
|
|
172
|
+
if version_1_size < version_2_size
|
|
173
|
+
new_version_2 = to_a(version_2).take(maximum_shared_places).join('.')
|
|
174
|
+
self.<=>(version_1, new_version_2).zero? ? true : false
|
|
175
|
+
elsif version_1_size > version_2_size
|
|
176
|
+
new_version_1 = to_a(version_1).take(maximum_shared_places).join('.')
|
|
177
|
+
self.<=>(new_version_1, version_2).zero? ? true : false
|
|
178
|
+
else
|
|
179
|
+
self.<=>(version_1, version_2).zero? ? true : false
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def each
|
|
184
|
+
version_strings.each{|version_string| yield Version.new(version_string)}
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def to_a(version, include_hyphenated = true)
|
|
188
|
+
to_a = [major(version), minor(version), tiny(version), build(version)]
|
|
189
|
+
to_a = to_a + [hyphenated_part(version)] if include_hyphenated
|
|
190
|
+
to_a.compact
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def to_version_string(version)
|
|
194
|
+
version.to_s
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# boolean methods
|
|
198
|
+
|
|
199
|
+
def major?(version)
|
|
200
|
+
major(version) ? true : false
|
|
201
|
+
end
|
|
202
|
+
alias_methods :major_version?, :major_number?, :major_version_number?, :major?
|
|
203
|
+
|
|
204
|
+
def minor?(version)
|
|
205
|
+
minor(version) ? true : false
|
|
206
|
+
end
|
|
207
|
+
alias_methods :minor_version?, :minor_number?, :minor_version_number?, :minor?
|
|
208
|
+
|
|
209
|
+
def tiny?(version)
|
|
210
|
+
tiny(version) ? true : false
|
|
211
|
+
end
|
|
212
|
+
alias_methods :tiny_version?, :tiny_number?, :tiny_version_number?, :tiny?
|
|
213
|
+
|
|
214
|
+
def build?(version)
|
|
215
|
+
build(version) ? true : false
|
|
216
|
+
end
|
|
217
|
+
alias_methods :build_version?, :build_number?, :build_version_number?, :build?
|
|
218
|
+
|
|
219
|
+
def patch?(version)
|
|
220
|
+
patch(version) ? true : false
|
|
221
|
+
end
|
|
222
|
+
alias_methods :patch_level?, :patch_version?, :patch_number?, :patch_version_number?, :patch?
|
|
223
|
+
|
|
224
|
+
def dev?(version)
|
|
225
|
+
dev(version) ? true : false
|
|
226
|
+
end
|
|
227
|
+
alias_methods :dev_version?, :dev_number?, :dev_version_number?, :dev?
|
|
228
|
+
alias_methods :development?, :development_version?, :development_number?, :development_version_number?, :dev?
|
|
229
|
+
|
|
230
|
+
def preview?(version)
|
|
231
|
+
preview(version) ? true : false
|
|
232
|
+
end
|
|
233
|
+
alias_methods :preview_version?, :preview_number?, :preview_version_number?, :preview?
|
|
234
|
+
|
|
235
|
+
def release_candidate?(version)
|
|
236
|
+
release_candidate(version) ? true : false
|
|
237
|
+
end
|
|
238
|
+
alias_methods :release_candidate_version?, :release_candidate_number?, :release_candidate_version_number?, :release_candidate?
|
|
239
|
+
alias_methods :rc?, :rc_version?, :rc_number?, :rc_version_number?, :release_candidate?
|
|
240
|
+
|
|
241
|
+
def hyphenated_part?(version)
|
|
242
|
+
hyphenated_part(version) ? true : false
|
|
243
|
+
end
|
|
244
|
+
alias_methods :has_hyphenated_part?, :hyphenated_part?
|
|
245
|
+
|
|
246
|
+
end # class << self
|
|
247
|
+
|
|
248
|
+
include Comparable
|
|
249
|
+
|
|
250
|
+
attr_accessor :version_string
|
|
251
|
+
|
|
252
|
+
def initialize(version_string = nil)
|
|
253
|
+
@version_string = version_string.to_s
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def major
|
|
257
|
+
self.class.major(version_string)
|
|
258
|
+
end
|
|
259
|
+
alias_methods :major_version, :major_number, :major_version_number, :major
|
|
260
|
+
|
|
261
|
+
def minor
|
|
262
|
+
self.class.minor(version_string)
|
|
263
|
+
end
|
|
264
|
+
alias_methods :minor_version, :minor_number, :minor_version_number, :minor
|
|
265
|
+
|
|
266
|
+
def tiny
|
|
267
|
+
self.class.tiny(version_string)
|
|
268
|
+
end
|
|
269
|
+
alias_methods :tiny_version, :tiny_number, :tiny_version_number, :tiny
|
|
270
|
+
|
|
271
|
+
def build
|
|
272
|
+
self.class.build(version_string)
|
|
273
|
+
end
|
|
274
|
+
alias_methods :build_version, :build_number, :build_version_number, :build
|
|
275
|
+
|
|
276
|
+
def patch
|
|
277
|
+
self.class.patch(version_string)
|
|
278
|
+
end
|
|
279
|
+
alias_methods :patch_level, :patch_version, :patch_number, :patch_version_number, :patch
|
|
280
|
+
|
|
281
|
+
def dev
|
|
282
|
+
self.class.dev(version_string)
|
|
283
|
+
end
|
|
284
|
+
alias_methods :dev_version, :dev_number, :dev_version_number, :dev
|
|
285
|
+
alias_methods :development, :development_version, :development_number, :development_version_number, :dev
|
|
286
|
+
|
|
287
|
+
def preview
|
|
288
|
+
self.class.preview(version_string)
|
|
289
|
+
end
|
|
290
|
+
alias_methods :preview_version, :preview_number, :preview_version_number, :patch
|
|
291
|
+
|
|
292
|
+
def release_candidate
|
|
293
|
+
self.class.release_candidate(version_string)
|
|
294
|
+
end
|
|
295
|
+
alias_methods :release_candidte_level, :release_candidate_version, :release_candidate_number, :release_candidate_version_number, :release_candidate
|
|
296
|
+
alias_methods :rc, :rc_version, :rc_number, :rc_version_number, :release_candidate
|
|
297
|
+
|
|
298
|
+
def hyphenated_part
|
|
299
|
+
patch || dev || preview || release_candidate
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def <=>(other_version)
|
|
303
|
+
self.class.<=>(version_string, other_version)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def ===(other_version)
|
|
307
|
+
self.class.===(version_string, other_version)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def to_a
|
|
311
|
+
self.class.to_a(version_string)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def to_s
|
|
315
|
+
version_string
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def least_significant_version_part_name
|
|
319
|
+
if patch?
|
|
320
|
+
:patch
|
|
321
|
+
elsif build?
|
|
322
|
+
:build
|
|
323
|
+
elsif tiny?
|
|
324
|
+
:tiny
|
|
325
|
+
elsif minor?
|
|
326
|
+
:minor
|
|
327
|
+
else
|
|
328
|
+
:major
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# boolean methods
|
|
333
|
+
|
|
334
|
+
def major?
|
|
335
|
+
self.class.major?(version_string)
|
|
336
|
+
end
|
|
337
|
+
alias_methods :major_version?, :major_number?, :major_version_number?, :major?
|
|
338
|
+
|
|
339
|
+
def minor?
|
|
340
|
+
self.class.minor?(version_string)
|
|
341
|
+
end
|
|
342
|
+
alias_methods :minor_version?, :minor_number?, :minor_version_number?, :minor?
|
|
343
|
+
|
|
344
|
+
def tiny?
|
|
345
|
+
self.class.tiny?(version_string)
|
|
346
|
+
end
|
|
347
|
+
alias_methods :tiny_version?, :tiny_number?, :tiny_version_number?, :tiny?
|
|
348
|
+
|
|
349
|
+
def build?
|
|
350
|
+
self.class.build?(version_string)
|
|
351
|
+
end
|
|
352
|
+
alias_methods :build_version?, :build_number?, :build_version_number?, :build?
|
|
353
|
+
|
|
354
|
+
def patch?
|
|
355
|
+
self.class.patch?(version_string)
|
|
356
|
+
end
|
|
357
|
+
alias_methods :patch_level?, :patch_version?, :patch_number?, :patch_version_number?, :patch?
|
|
358
|
+
|
|
359
|
+
def dev?
|
|
360
|
+
self.class.dev?(version_string)
|
|
361
|
+
end
|
|
362
|
+
alias_methods :dev_version?, :dev_number?, :dev_version_number?, :dev?
|
|
363
|
+
alias_methods :development?, :development_version?, :development_number?, :development_version_number?, :dev?
|
|
364
|
+
|
|
365
|
+
def preview?
|
|
366
|
+
self.class.preview?(version_string) ? true : false
|
|
367
|
+
end
|
|
368
|
+
alias_methods :preview_version?, :preview_number?, :preview_version_number?, :preview?
|
|
369
|
+
|
|
370
|
+
def release_candidate?
|
|
371
|
+
self.class.release_candidate?(version_string) ? true : false
|
|
372
|
+
end
|
|
373
|
+
alias_methods :release_candidate_version?, :release_candidate_number?, :release_candidate_version_number?, :release_candidate?
|
|
374
|
+
alias_methods :rc?, :rc_version?, :rc_number?, :rc_version_number?, :release_candidate?
|
|
375
|
+
|
|
376
|
+
def hyphenated_part?
|
|
377
|
+
patch? || dev? || preview? || release_candidate?
|
|
378
|
+
end
|
|
379
|
+
alias_methods :has_hyphenated_part?, :hyphenated_part?
|
|
380
|
+
|
|
381
|
+
end
|
data/macos.rb.gemspec
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# macos.rb.gemspec
|
|
2
|
+
|
|
3
|
+
require_relative './lib/MacOS/VERSION'
|
|
4
|
+
|
|
5
|
+
class Gem::Specification
|
|
6
|
+
def dependencies=(gems)
|
|
7
|
+
gems.each{|gem| add_dependency(*gem)}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def development_dependencies=(gems)
|
|
11
|
+
gems.each{|gem| add_development_dependency(*gem)}
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Gem::Specification.new do |spec|
|
|
16
|
+
spec.name = 'macos.rb'
|
|
17
|
+
spec.version = MacOS::VERSION
|
|
18
|
+
|
|
19
|
+
spec.summary = "Work with macOS from Ruby."
|
|
20
|
+
spec.description = "Work with macOS from Ruby: network interfaces and hardware ports, the system's version, defaults, disks and disk images, USB flash drives, applications, users, voices, the keychain and launchd."
|
|
21
|
+
|
|
22
|
+
spec.author = 'thoran'
|
|
23
|
+
spec.email = 'code@thoran.com'
|
|
24
|
+
spec.homepage = 'https://github.com/thoran/macos.rb'
|
|
25
|
+
spec.license = 'MIT'
|
|
26
|
+
|
|
27
|
+
spec.metadata = {
|
|
28
|
+
'bug_tracker_uri' => 'https://github.com/thoran/macos.rb/issues',
|
|
29
|
+
'changelog_uri' => 'https://github.com/thoran/macos.rb/blob/master/CHANGELOG',
|
|
30
|
+
'source_code_uri' => 'https://github.com/thoran/macos.rb',
|
|
31
|
+
'documentation_uri' => 'https://github.com/thoran/macos.rb/blob/master/README.md',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
spec.require_paths = ['lib']
|
|
35
|
+
spec.required_ruby_version = '>= 2.7'
|
|
36
|
+
|
|
37
|
+
spec.files = [
|
|
38
|
+
'macos.rb.gemspec',
|
|
39
|
+
Dir['lib/**/*.rb'],
|
|
40
|
+
Dir['test/**/*.rb'],
|
|
41
|
+
Dir['test/fixtures/*'],
|
|
42
|
+
'CHANGELOG',
|
|
43
|
+
'Gemfile',
|
|
44
|
+
'LICENSE',
|
|
45
|
+
'Rakefile',
|
|
46
|
+
'README.md',
|
|
47
|
+
].flatten
|
|
48
|
+
|
|
49
|
+
spec.dependencies = %w{
|
|
50
|
+
CFPropertyList
|
|
51
|
+
plist
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
spec.development_dependencies = %w{
|
|
55
|
+
minitest
|
|
56
|
+
minitest-mock
|
|
57
|
+
rake
|
|
58
|
+
}
|
|
59
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require_relative '../helper'
|
|
2
|
+
require 'MacOS/Apple80211'
|
|
3
|
+
|
|
4
|
+
describe MacOS::Apple80211 do
|
|
5
|
+
let(:wifi){fixture('system_profiler-SPAirPortDataType.txt')}
|
|
6
|
+
|
|
7
|
+
def with_sources(airport, system_profiler = fixture('system_profiler-SPAirPortDataType.txt'), &block)
|
|
8
|
+
MacOS::Apple80211.stub(:airport_i, airport) do
|
|
9
|
+
MacOS::Apple80211.stub(:system_profiler_airport, system_profiler, &block)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "where airport -I reports, as before macOS 14.4" do
|
|
14
|
+
it "reads its attributes" do
|
|
15
|
+
with_sources(fixture('airport-I.txt')) do
|
|
16
|
+
attributes = MacOS::Apple80211.attributes
|
|
17
|
+
_(attributes['BSSID']).must_equal('00:00:5e:00:53:20')
|
|
18
|
+
_(attributes['agrCtlRSSI']).must_equal('-52')
|
|
19
|
+
_(attributes['op mode']).must_equal('station')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "gives the channel, SSID and link authentication" do
|
|
24
|
+
with_sources(fixture('airport-I.txt')) do
|
|
25
|
+
_(MacOS::Apple80211.channel).must_equal('36,80')
|
|
26
|
+
_(MacOS::Apple80211.ssid).must_equal('ExampleNet')
|
|
27
|
+
_(MacOS::Apple80211.link_auth).must_equal('wpa2-psk')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "where airport -I gives only its deprecation, as from macOS 14.4 on" do
|
|
33
|
+
it "reads the current network from system_profiler instead" do
|
|
34
|
+
with_sources(fixture('airport-I-deprecated.txt')) do
|
|
35
|
+
attributes = MacOS::Apple80211.attributes
|
|
36
|
+
_(attributes['PHY Mode']).must_equal('802.11ax')
|
|
37
|
+
_(attributes['Signal / Noise']).must_equal('-52 dBm / -94 dBm')
|
|
38
|
+
_(attributes.keys).wont_include('WARNING')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "gives its channel and security as system_profiler gives them" do
|
|
43
|
+
with_sources(fixture('airport-I-deprecated.txt')) do
|
|
44
|
+
_(MacOS::Apple80211.channel).must_equal('36 (5GHz, 80MHz)')
|
|
45
|
+
_(MacOS::Apple80211.link_auth).must_equal('WPA2 Personal')
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "gives no SSID where macOS redacts the network's name" do
|
|
50
|
+
with_sources(fixture('airport-I-deprecated.txt')) do
|
|
51
|
+
_(MacOS::Apple80211.ssid).must_be_nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "gives the SSID where macOS doesn't redact it" do
|
|
56
|
+
with_sources(fixture('airport-I-deprecated.txt'), wifi.sub(' <redacted>:', ' ExampleNet:')) do
|
|
57
|
+
_(MacOS::Apple80211.ssid).must_equal('ExampleNet')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "gives nothing where there's no current network" do
|
|
62
|
+
with_sources(fixture('airport-I-deprecated.txt'), "Wi-Fi:\n\n Interfaces:\n en0:\n Status: Off\n") do
|
|
63
|
+
_(MacOS::Apple80211.attributes).must_equal({})
|
|
64
|
+
_(MacOS::Apple80211.channel).must_be_nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "where airport isn't there" do
|
|
70
|
+
it "reads system_profiler" do
|
|
71
|
+
missing = ->(command){raise Errno::ENOENT, command}
|
|
72
|
+
MacOS::Apple80211.stub(:`, missing) do
|
|
73
|
+
_(MacOS::Apple80211.airport_i).must_equal('')
|
|
74
|
+
end
|
|
75
|
+
with_sources('') do
|
|
76
|
+
_(MacOS::Apple80211.channel).must_equal('36 (5GHz, 80MHz)')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require_relative '../helper'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
require 'MacOS/Applications'
|
|
5
|
+
|
|
6
|
+
describe MacOS::Applications do
|
|
7
|
+
let(:paths){['/System/Applications/Mail.app', '/Applications/Example App.app']}
|
|
8
|
+
|
|
9
|
+
def with_paths(&block)
|
|
10
|
+
MacOS::Applications.stub(:paths, paths, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "finding an application" do
|
|
14
|
+
it "finds one by its name, with or without .app" do
|
|
15
|
+
with_paths do
|
|
16
|
+
_(MacOS::Applications.path_for('Mail')).must_equal('/System/Applications/Mail.app')
|
|
17
|
+
_(MacOS::Applications.path_for('Example App.app')).must_equal('/Applications/Example App.app')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "knows whether one is installed" do
|
|
22
|
+
with_paths do
|
|
23
|
+
_(MacOS::Applications.installed?('Mail')).must_equal(true)
|
|
24
|
+
_(MacOS::Applications.installed?('Nowhere')).must_equal(false)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "where it looks" do
|
|
30
|
+
def by_directory(&block)
|
|
31
|
+
MacOS::Applications.stub(:in_directory, ->(directory){["#{directory}/In.app"]}) do
|
|
32
|
+
MacOS::Applications.stub(:under_directory, ->(directory){["#{directory}/Under.app"]}, &block)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "looks in the system, local and user domains, the user's own searched all the way down" do
|
|
37
|
+
by_directory do
|
|
38
|
+
_(MacOS::Applications.paths('example')).must_equal(%w{
|
|
39
|
+
/System/Applications/In.app /System/Applications/Utilities/In.app
|
|
40
|
+
/Applications/In.app /Applications/Utilities/In.app
|
|
41
|
+
/Users/example/Applications/Under.app
|
|
42
|
+
})
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "looks in every real user's Applications" do
|
|
47
|
+
by_directory do
|
|
48
|
+
MacOS::Users.stub(:all, %w{another example}) do
|
|
49
|
+
_(MacOS::Applications.all_user_paths).must_equal(%w{/Users/another/Applications/Under.app /Users/example/Applications/Under.app})
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "the applications in a directory" do
|
|
56
|
+
it "finds those directly in it, and those anywhere under it but not inside another application" do
|
|
57
|
+
Dir.mktmpdir do |directory|
|
|
58
|
+
%w{Top.app Sub/Nested.app Top.app/Contents/Helpers/Helper.app}.each{|path| FileUtils.mkdir_p(File.join(directory, path))}
|
|
59
|
+
_(MacOS::Applications.in_directory(directory).map{|path| File.basename(path)}).must_equal(%w{Top.app})
|
|
60
|
+
_(MacOS::Applications.under_directory(directory).map{|path| File.basename(path)}.sort).must_equal(%w{Nested.app Top.app})
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "starting an application" do
|
|
66
|
+
it "opens it, by each of its names" do
|
|
67
|
+
with_paths do
|
|
68
|
+
commands = commands_run_by(MacOS::Applications) do
|
|
69
|
+
%i{start launch open run}.each{|method| MacOS::Applications.send(method, 'Mail')}
|
|
70
|
+
end
|
|
71
|
+
_(commands).must_equal([['open', '/System/Applications/Mail.app']] * 4)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "does nothing for an application which isn't installed" do
|
|
76
|
+
with_paths do
|
|
77
|
+
commands = commands_run_by(MacOS::Applications){_(MacOS::Applications.start('Nowhere')).must_be_nil}
|
|
78
|
+
_(commands).must_equal([])
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require_relative '../../helper'
|
|
2
|
+
require 'MacOS/Defaults/ComAppleDock'
|
|
3
|
+
|
|
4
|
+
describe MacOS::Defaults::ComAppleDock do
|
|
5
|
+
let(:dock){MacOS::Defaults::ComAppleDock}
|
|
6
|
+
|
|
7
|
+
def with_dock(&block)
|
|
8
|
+
MacOS::Defaults::ComAppleDock.stub(:`, fixture('defaults-export-com.apple.dock.xml'), &block)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "writing attributes" do
|
|
12
|
+
it "marks booleans as -bool and integers as -int" do
|
|
13
|
+
_(dock.attribute_write_command('autohide', true)).must_equal('defaults write com.apple.dock autohide -bool true')
|
|
14
|
+
_(dock.attribute_write_command('tilesize', 48)).must_equal('defaults write com.apple.dock tilesize -int 48')
|
|
15
|
+
_(dock.attribute_write_command('orientation', 'left')).must_equal('defaults write com.apple.dock orientation left')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "loads a configuration, adding each persistent app and writing each other attribute" do
|
|
19
|
+
commands = commands_run_by(dock){dock.load('persistent-apps' => ['/Applications/Example.app'], 'autohide' => true)}
|
|
20
|
+
_(commands.size).must_equal(2)
|
|
21
|
+
_(commands.first.first).must_match(%r{\Adefaults write com\.apple\.dock persistent-apps -array-add '<dict>.*file:///Applications/Example\.app}m)
|
|
22
|
+
_(commands.last).must_equal(['defaults write com.apple.dock autohide -bool true'])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "deletes the persistent apps and others, and the recent apps" do
|
|
26
|
+
commands = commands_run_by(dock){dock.delete_all_items}
|
|
27
|
+
_(commands.flatten).must_equal(%w{persistent-apps persistent-others recent-apps}.map{|item| "defaults delete com.apple.dock #{item}"})
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "an app's tile" do
|
|
32
|
+
it "is the app's file URL in the Dock's tile data" do
|
|
33
|
+
_(dock.urlified_app_string('/Applications/Example.app')).must_equal('file:///Applications/Example.app')
|
|
34
|
+
_(dock.app_plist_data('file:///Applications/Example.app')).must_include('<string>file:///Applications/Example.app</string>')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "reading" do
|
|
39
|
+
it "reads the persistent apps back as paths, a user's own with ~ for their home" do
|
|
40
|
+
with_dock do
|
|
41
|
+
_(dock.read('persistent-apps')).must_equal('persistent-apps' => ['/System/Applications/Mail.app/', '~/Applications/Example App.app'])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "reads booleans as yes and no, and other attributes as they are" do
|
|
46
|
+
with_dock do
|
|
47
|
+
_(dock.read('autohide', 'magnification', 'tilesize', 'orientation')).must_equal(
|
|
48
|
+
'autohide' => 'yes', 'magnification' => 'no', 'tilesize' => 48, 'orientation' => 'bottom'
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "leaves out the persistent others and the recent apps" do
|
|
54
|
+
with_dock do
|
|
55
|
+
_(dock.read.keys).wont_include('recent-apps')
|
|
56
|
+
_(dock.read.keys).wont_include('persistent-others')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "writes what it reads as YAML" do
|
|
61
|
+
with_dock do
|
|
62
|
+
_(YAML.load(dock.to_yaml)['tilesize']).must_equal(48)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|