ranger-compiler 3.2.0 → 3.5.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +2463 -0
  2. package/LICENSE +28 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +1651 -1895
  5. package/dist/Lang.rgr +10946 -5852
  6. package/dist/README.md +3 -2
  7. package/dist/api.d.ts +1548 -27
  8. package/dist/api.js +70572 -38295
  9. package/dist/git-http.mjs +126 -0
  10. package/dist/lib/ACEEditor.rgr +2 -0
  11. package/dist/lib/Ajax.rgr +2 -0
  12. package/dist/lib/CmdParams.rgr +2 -0
  13. package/dist/lib/Crypto.rgr +2 -0
  14. package/dist/lib/DOMLib.rgr +2 -0
  15. package/dist/lib/Engine3D.rgr +2 -0
  16. package/dist/lib/ImmutableVector.rgr +2 -0
  17. package/dist/lib/IndexedDB.rgr +2 -0
  18. package/dist/lib/IsoDate/DateMath.rgr +2 -0
  19. package/dist/lib/IsoDate/IsoCalendar.rgr +2 -0
  20. package/dist/lib/IsoDate/IsoDateParse.rgr +2 -0
  21. package/dist/lib/IsoDateLib.rgr +2 -0
  22. package/dist/lib/JSON.rgr +4906 -48
  23. package/dist/lib/JinxProcess.rgr +2 -0
  24. package/dist/lib/RangerProcess.rgr +2 -0
  25. package/dist/lib/Regex/RegexMatch.rgr +2 -0
  26. package/dist/lib/RegexLib.rgr +2 -0
  27. package/dist/lib/SQL.rgr +2 -0
  28. package/dist/lib/ServiceLib.rgr +2 -0
  29. package/dist/lib/Shell.rgr +326 -0
  30. package/dist/lib/Storage.rgr +2 -0
  31. package/dist/lib/Time.rgr +2 -0
  32. package/dist/lib/Timers.rgr +2 -0
  33. package/dist/lib/TypedArrays.rgr +2 -0
  34. package/dist/lib/ViewLib.rgr +2 -0
  35. package/dist/lib/WebLib.rgr +2 -0
  36. package/dist/lib/WebServerLib.rgr +2 -0
  37. package/dist/lib/apple/AppleAppBuilder.rgr +572 -0
  38. package/dist/lib/apple/AppleAppSpec.rgr +201 -0
  39. package/dist/lib/apple/AppleDevice.rgr +295 -0
  40. package/dist/lib/apple/AppleDeviceDoctor.rgr +453 -0
  41. package/dist/lib/apple/AppleSigning.rgr +379 -0
  42. package/dist/lib/apple/AppleSimulator.rgr +248 -0
  43. package/dist/lib/apple/AppleTarget.rgr +185 -0
  44. package/dist/lib/apple/AppleToolchain.rgr +458 -0
  45. package/dist/lib/apple/README.md +311 -0
  46. package/dist/lib/apple/apple_test.rgr +669 -0
  47. package/dist/lib/core/README.md +251 -0
  48. package/dist/lib/core/RgBase.rgr +313 -0
  49. package/dist/lib/core/RgNum.rgr +653 -0
  50. package/dist/lib/core/RgText.rgr +680 -0
  51. package/dist/lib/core/RgU32.rgr +309 -0
  52. package/dist/lib/ranger-dir.rgr +2 -0
  53. package/dist/lib/shell_test.rgr +193 -0
  54. package/dist/lib/stdlib.rgr +1176 -665
  55. package/dist/lib/stdops.rgr +2 -0
  56. package/dist/lib/zip/Inflate.rgr +675 -0
  57. package/dist/lib/zip/ZipBuffer.rgr +358 -0
  58. package/dist/package.json +1 -1
  59. package/dist/rgrc.js +76333 -40647
  60. package/dist/stdops.rgr +2 -0
  61. package/package.json +1121 -339
@@ -0,0 +1,201 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ ; =============================================================================
4
+ ; AppleAppSpec.rgr -- the app, as everything but the code.
5
+ ; =============================================================================
6
+ ;
7
+ ; An .app bundle is a directory with an executable in it and a property list
8
+ ; that explains the executable to the system. Xcode builds that plist from a
9
+ ; project file, a target, a scheme and a build settings sheet; a command line
10
+ ; build has to say it in one place, and this is that place.
11
+ ;
12
+ ; The plist writer is here too, because the keys are not free-form: an iOS app
13
+ ; that omits `CFBundleExecutable` does not launch, one that omits
14
+ ; `UIDeviceFamily` is an iPhone app on an iPad, and one that omits
15
+ ; `UILaunchScreen` is letterboxed into a phone-sized box on every iPad no
16
+ ; matter what its families say. Those three are the ones a hand-built bundle
17
+ ; gets wrong, so they are written from the spec rather than left to a caller.
18
+ ; =============================================================================
19
+
20
+ class AppleAppSpec {
21
+
22
+ ; The bundle and executable name. No spaces -- this is a file name.
23
+ def name:string "RangerApp"
24
+ ; What the home screen shows under the icon.
25
+ def displayName:string "Ranger App"
26
+ ; Reverse DNS, and the identity the simulator installs and launches by.
27
+ def bundleId:string "fi.ranger.app"
28
+ def version:string "1.0"
29
+ def build:string "1"
30
+
31
+ ; Every Swift file that goes into the executable, in no particular order.
32
+ ; For a Ranger app this is the generated .swift plus the host sources.
33
+ def sources:[string]
34
+ ; Files and directories copied into the bundle as they are. A stylesheet, a
35
+ ; font, an icon.
36
+ def resources:[string]
37
+ ; Anything extra for swiftc: -O, -warnings-as-errors, a framework path.
38
+ def swiftFlags:[string]
39
+ ; Frameworks to link that swiftc does not pull in on its own.
40
+ def frameworks:[string]
41
+
42
+ ; Extra Info.plist entries, as already-formatted XML fragments. The escape
43
+ ; hatch for a key this class does not know about.
44
+ def extraPlist:[string]
45
+
46
+ ; The .mobileprovision for a DEVICE build. Ignored for the simulator, which
47
+ ; needs no profile at all. When set, it is copied into the bundle as
48
+ ; `embedded.mobileprovision` and the entitlements it grants are read out of
49
+ ; it and handed to codesign.
50
+ def provisioningProfile:string ""
51
+
52
+ ; Portrait plus both landscapes, which is what a page that reflows wants.
53
+ ; An empty list leaves the key out and lets the system decide.
54
+ def orientations:[string]
55
+
56
+ Constructor () {
57
+ orientations = ([] _:string ( "UIInterfaceOrientationPortrait" "UIInterfaceOrientationLandscapeLeft" "UIInterfaceOrientationLandscapeRight" ))
58
+ }
59
+
60
+ fn addSource:void (path:string) {
61
+ push sources path
62
+ }
63
+
64
+ fn addResource:void (path:string) {
65
+ push resources path
66
+ }
67
+
68
+ fn addFramework:void (name:string) {
69
+ push frameworks name
70
+ }
71
+
72
+ ; The bundle directory name. `.app` is not decoration -- the simulator
73
+ ; installs a directory by that extension and nothing else.
74
+ fn bundleName:string () {
75
+ return (name + ".app")
76
+ }
77
+ }
78
+
79
+ ; The property list, written out. XML rather than JSON or binary because it is
80
+ ; the format every Apple tool reads without conversion, and because a build that
81
+ ; goes wrong is one a person has to be able to read.
82
+ class AppleInfoPlist {
83
+
84
+ ; Escape the five characters an XML text node cannot hold. A display name
85
+ ; with an ampersand in it is not exotic, and an unescaped one makes the
86
+ ; plist unparseable rather than wrong -- the app simply will not install.
87
+ sfn escape:string (s:string) {
88
+ def out:string s
89
+ out = (join (strsplit out "&") "&")
90
+ out = (join (strsplit out "<") "&lt;")
91
+ out = (join (strsplit out ">") "&gt;")
92
+ out = (join (strsplit out "\"") "&quot;")
93
+ return out
94
+ }
95
+
96
+ sfn stringKey:string (key:string value:string) {
97
+ return ("\t<key>" + (AppleInfoPlist.escape(key)) + "</key>\n\t<string>" + (AppleInfoPlist.escape(value)) + "</string>")
98
+ }
99
+
100
+ sfn boolKey:string (key:string value:boolean) {
101
+ def word:string "<false/>"
102
+ if value {
103
+ word = "<true/>"
104
+ }
105
+ return ("\t<key>" + (AppleInfoPlist.escape(key)) + "</key>\n\t" + word)
106
+ }
107
+
108
+ sfn intArrayKey:string (key:string values:[int]) {
109
+ def rows:[string]
110
+ push rows ("\t<key>" + (AppleInfoPlist.escape(key)) + "</key>")
111
+ push rows "\t<array>"
112
+ for values v:int i {
113
+ push rows ("\t\t<integer>" + v + "</integer>")
114
+ }
115
+ push rows "\t</array>"
116
+ return (join rows "\n")
117
+ }
118
+
119
+ sfn stringArrayKey:string (key:string values:[string]) {
120
+ def rows:[string]
121
+ push rows ("\t<key>" + (AppleInfoPlist.escape(key)) + "</key>")
122
+ push rows "\t<array>"
123
+ for values v:string i {
124
+ push rows ("\t\t<string>" + (AppleInfoPlist.escape(v)) + "</string>")
125
+ }
126
+ push rows "\t</array>"
127
+ return (join rows "\n")
128
+ }
129
+
130
+ ; The whole plist for one app on one target.
131
+ sfn build:string (spec:AppleAppSpec target:AppleTarget) {
132
+ def rows:[string]
133
+ push rows "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
134
+ push rows "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
135
+ push rows "<plist version=\"1.0\">"
136
+ push rows "<dict>"
137
+ push rows (AppleInfoPlist.stringKey("CFBundleDevelopmentRegion" "en"))
138
+ ; The one key with no default worth guessing: without it the system has
139
+ ; a directory and no idea which file in it to run.
140
+ push rows (AppleInfoPlist.stringKey("CFBundleExecutable" spec.name))
141
+ push rows (AppleInfoPlist.stringKey("CFBundleIdentifier" spec.bundleId))
142
+ push rows (AppleInfoPlist.stringKey("CFBundleInfoDictionaryVersion" "6.0"))
143
+ push rows (AppleInfoPlist.stringKey("CFBundleName" spec.name))
144
+ push rows (AppleInfoPlist.stringKey("CFBundleDisplayName" spec.displayName))
145
+ push rows (AppleInfoPlist.stringKey("CFBundlePackageType" "APPL"))
146
+ push rows (AppleInfoPlist.stringKey("CFBundleShortVersionString" spec.version))
147
+ push rows (AppleInfoPlist.stringKey("CFBundleVersion" spec.build))
148
+
149
+ def platformName:string "iPhoneOS"
150
+ if (target.isWatch()) {
151
+ platformName = "WatchOS"
152
+ if target.isSimulator {
153
+ platformName = "WatchSimulator"
154
+ }
155
+ } {
156
+ if target.isSimulator {
157
+ platformName = "iPhoneSimulator"
158
+ }
159
+ }
160
+ def platforms:[string]
161
+ push platforms platformName
162
+ push rows (AppleInfoPlist.stringArrayKey("CFBundleSupportedPlatforms" platforms))
163
+
164
+ if (target.isWatch()) {
165
+ ; A standalone watch app -- no paired iPhone app to be an appendage
166
+ ; of, which is what watchOS 6 and later allow and what makes a watch
167
+ ; build from the command line reasonable at all. WKApplication is
168
+ ; what watchOS 7 and later read, WKWatchOnly says there is no
169
+ ; companion, and WKRunsIndependentlyOfCompanionApp is what the
170
+ ; watchOS 6 generation read for the same thing. All three, because
171
+ ; which one is consulted depends on the OS and none of them costs
172
+ ; anything.
173
+ push rows (AppleInfoPlist.boolKey("WKApplication" true))
174
+ push rows (AppleInfoPlist.boolKey("WKWatchOnly" true))
175
+ push rows (AppleInfoPlist.boolKey("WKRunsIndependentlyOfCompanionApp" true))
176
+ push rows (AppleInfoPlist.stringKey("MinimumOSVersion" target.minVersion))
177
+ } {
178
+ push rows (AppleInfoPlist.stringKey("MinimumOSVersion" target.minVersion))
179
+ push rows (AppleInfoPlist.intArrayKey("UIDeviceFamily" target.families))
180
+ if ((array_length spec.orientations) > 0) {
181
+ push rows (AppleInfoPlist.stringArrayKey("UISupportedInterfaceOrientations" spec.orientations))
182
+ push rows (AppleInfoPlist.stringArrayKey("UISupportedInterfaceOrientations~ipad" spec.orientations))
183
+ }
184
+ ; An empty UILaunchScreen dictionary is the modern way to say "this
185
+ ; app has no launch storyboard and is still full screen". Leave it
186
+ ; out and iOS runs the app letterboxed in a phone-sized window on
187
+ ; every iPad, which looks exactly like a layout bug.
188
+ push rows "\t<key>UILaunchScreen</key>"
189
+ push rows "\t<dict/>"
190
+ push rows (AppleInfoPlist.boolKey("UIRequiresFullScreen" false))
191
+ }
192
+
193
+ for spec.extraPlist row:string i {
194
+ push rows row
195
+ }
196
+ push rows "</dict>"
197
+ push rows "</plist>"
198
+ push rows ""
199
+ return (join rows "\n")
200
+ }
201
+ }
@@ -0,0 +1,295 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ ; =============================================================================
4
+ ; AppleDevice.rgr -- the iPhones and iPads that are plugged in.
5
+ ; =============================================================================
6
+ ;
7
+ ; `xcrun devicectl list devices` prints a table. This reads it, and like the
8
+ ; simulator parser it is a PURE FUNCTION over the text: the one part of the
9
+ ; device story that has edge cases is therefore the one part that can be tested
10
+ ; on a machine with nothing plugged into it.
11
+ ;
12
+ ; Devices:
13
+ ; Name Hostname Identifier State Model
14
+ ; ───────────── ──────────────────── ───────────────────────────────────── ────────── ─────
15
+ ; Tero iPhone 00008101-...local 1A2B3C4D-5E6F-7890-ABCD-EF1234567890 connected iPhone 14 Pro (iPhone15,2)
16
+ ;
17
+ ; The fields are found by splitting on runs of TWO OR MORE spaces rather than by
18
+ ; slicing at the separator line's column offsets. Column offsets look tidier and
19
+ ; are wrong: `strlen` counts UTF-16 units on some targets, runes on others and
20
+ ; BYTES on C++, so a device named "Terön iPhone" would slice at three different
21
+ ; places in three languages. Two spaces is two spaces everywhere.
22
+ ;
23
+ ; The IDENTIFIER is what `devicectl --device` wants, and it is not the 40-hex
24
+ ; UDID that older tools used -- it is a UUID that devicectl assigns. Finding it
25
+ ; by shape rather than by column position is also what makes this survive a
26
+ ; column being added, which Xcode has done before.
27
+ ; =============================================================================
28
+
29
+ Import "AppleSimulator.rgr"
30
+
31
+ class AppleDeviceInfo {
32
+
33
+ def name:string ""
34
+ ; What `devicectl --device` takes.
35
+ def identifier:string ""
36
+ ; "connected", "available (paired)", "unavailable"...
37
+ def state:string ""
38
+ def model:string ""
39
+
40
+ Constructor () {
41
+ }
42
+
43
+ ; "connected" means devicectl has a tunnel to the device AT THIS MOMENT.
44
+ fn isConnected:boolean () {
45
+ return ((indexOf state "connected") >= 0)
46
+ }
47
+
48
+ ; "available (paired)" is a device devicectl knows and has no tunnel to
49
+ ; right now. It is NOT an absent device: the tunnel is raised on demand by
50
+ ; the first real operation -- `devicectl device info apps` on a phone in
51
+ ; this state prints "Acquired tunnel connection to device" and then answers
52
+ ; -- so an install onto it works. Refusing it here is the difference
53
+ ; between a build that runs and one that stops on a cable that is plugged
54
+ ; in.
55
+ ;
56
+ ; "unavailable" CONTAINS "available", so the negative is tested first; the
57
+ ; substring test that reads right is the one that is wrong.
58
+ fn isPaired:boolean () {
59
+ if ((indexOf state "unavailable") >= 0) {
60
+ return false
61
+ }
62
+ return ((indexOf state "available") >= 0)
63
+ }
64
+
65
+ ; A device worth handing to devicectl: one with a tunnel, or one that can
66
+ ; raise one.
67
+ fn isUsable:boolean () {
68
+ if (this.isConnected()) {
69
+ return true
70
+ }
71
+ return (this.isPaired())
72
+ }
73
+
74
+ fn describe:string () {
75
+ return (name + " (" + model + ") " + identifier + " -- " + state)
76
+ }
77
+ }
78
+
79
+ class AppleDeviceList {
80
+
81
+ ; Split a table row on runs of two or more spaces, dropping empty pieces.
82
+ sfn columns:[string] (line:string) {
83
+ def out:[string]
84
+ def n:int (strlen line)
85
+ def cur:string ""
86
+ def spaces:int 0
87
+ def i:int 0
88
+ while (i < n) {
89
+ def ch:string (substring line i (i + 1))
90
+ if (ch == " ") {
91
+ spaces = spaces + 1
92
+ } {
93
+ ; One space is inside a field; two ends it.
94
+ if (spaces >= 2) {
95
+ if ((strlen (trim cur)) > 0) {
96
+ push out (trim cur)
97
+ }
98
+ cur = ""
99
+ } {
100
+ if (spaces == 1) {
101
+ cur = (cur + " ")
102
+ }
103
+ }
104
+ spaces = 0
105
+ cur = (cur + ch)
106
+ }
107
+ i = i + 1
108
+ }
109
+ if ((strlen (trim cur)) > 0) {
110
+ push out (trim cur)
111
+ }
112
+ return out
113
+ }
114
+
115
+ sfn parse:[AppleDeviceInfo] (text:string) {
116
+ def found:[AppleDeviceInfo]
117
+ def lines:[string] (strsplit text "\n")
118
+ for lines rawLine:string li {
119
+ def cols:[string] (AppleDeviceList.columns(rawLine))
120
+ def cnt:int (array_length cols)
121
+ if (cnt >= 3) {
122
+ ; The identifier is found by SHAPE, so a new column before or
123
+ ; after it changes nothing. The header row and the separator
124
+ ; row hold no UUID and fall out here.
125
+ def at:int (0 - 1)
126
+ def ci:int 0
127
+ while (ci < cnt) {
128
+ if (at < 0) {
129
+ if (AppleSimulatorList.looksLikeUdid((itemAt cols ci))) {
130
+ at = ci
131
+ }
132
+ }
133
+ ci = ci + 1
134
+ }
135
+ if (at > 0) {
136
+ def dev:AppleDeviceInfo (new AppleDeviceInfo)
137
+ dev.name = (itemAt cols 0)
138
+ dev.identifier = (itemAt cols at)
139
+ if ((at + 1) < cnt) {
140
+ dev.state = (itemAt cols (at + 1))
141
+ }
142
+ def rest:[string]
143
+ def mi:int (at + 2)
144
+ while (mi < cnt) {
145
+ push rest (itemAt cols mi)
146
+ mi = mi + 1
147
+ }
148
+ dev.model = (join rest " ")
149
+ push found dev
150
+ }
151
+ }
152
+ }
153
+ return found
154
+ }
155
+
156
+ ; The device to install onto. `nameWanted` is a substring of the name, or
157
+ ; the identifier itself; "" takes the first usable one, which is the whole
158
+ ; point of a one-command device build.
159
+ ;
160
+ ; A CONNECTED device wins over a merely paired one when both match, because
161
+ ; the connected one needs no tunnel raised and is therefore faster and
162
+ ; surer. A paired one is still returned when it is all there is.
163
+ sfn pick@(optional):AppleDeviceInfo (devices:[AppleDeviceInfo] nameWanted:string) {
164
+ def wanted:string (trim nameWanted)
165
+ def best@(optional):AppleDeviceInfo
166
+ def fallback@(optional):AppleDeviceInfo
167
+ for devices d:AppleDeviceInfo i {
168
+ if (d.isUsable()) {
169
+ def matches:boolean false
170
+ if ((strlen wanted) == 0) {
171
+ matches = true
172
+ } {
173
+ if ((indexOf d.name wanted) >= 0) {
174
+ matches = true
175
+ }
176
+ if (d.identifier == wanted) {
177
+ matches = true
178
+ }
179
+ }
180
+ if matches {
181
+ if (d.isConnected()) {
182
+ if (null? best) {
183
+ best = d
184
+ }
185
+ } {
186
+ if (null? fallback) {
187
+ fallback = d
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ if (!null? best) {
194
+ return best
195
+ }
196
+ return fallback
197
+ }
198
+ }
199
+
200
+ ; =============================================================================
201
+ ; What `xcrun devicectl device info details --device ID` says about one device.
202
+ ; =============================================================================
203
+ ;
204
+ ; The listing that `list devices` prints has one word for the whole story --
205
+ ; "connected", "available (paired)" -- and that word is not enough to tell a
206
+ ; device that cannot be used from one that merely has no tunnel open. `info
207
+ ; details` has the fields that actually decide it:
208
+ ;
209
+ ; ▿ deviceProperties:
210
+ ; • ddiServicesAvailable: false
211
+ ; • developerModeStatus: enabled
212
+ ; • osVersionNumber: 26.3
213
+ ; ▿ connectionProperties:
214
+ ; • pairingState: paired
215
+ ; • tunnelState: unavailable
216
+ ;
217
+ ; `developerModeStatus` is the one a person has to fix on the phone itself, and
218
+ ; `ddiServicesAvailable` is the one that fixes itself the moment anything asks
219
+ ; for a developer service. Telling them apart is the difference between "go
220
+ ; enable a setting" and "wait four seconds".
221
+ ;
222
+ ; Parsed as a PURE FUNCTION over the text, like the listing above it: the
223
+ ; bullets are decoration, so a value is read as whatever follows "key:" on the
224
+ ; line that has it. A field Apple adds, moves or re-indents changes nothing.
225
+ ; =============================================================================
226
+
227
+ class AppleDeviceDetails {
228
+
229
+ ; "enabled", "disabled", ""
230
+ def developerMode:string ""
231
+ ; The developer disk image is mounted and its services can be called.
232
+ def ddiAvailable:boolean false
233
+ ; "connected", "disconnected", "unavailable"
234
+ def tunnelState:string ""
235
+ def pairingState:string ""
236
+ def osVersion:string ""
237
+ def marketingName:string ""
238
+ def udid:string ""
239
+
240
+ Constructor () {
241
+ }
242
+
243
+ fn developerModeEnabled:boolean () {
244
+ return (developerMode == "enabled")
245
+ }
246
+
247
+ ; The text after "key:" on the first line that mentions it, trimmed. "" when
248
+ ; no line does -- which is also the answer for a device that reported the
249
+ ; field empty, and both mean the same thing to every caller here.
250
+ ;
251
+ ; The line is SPLIT on ":" rather than sliced at the offset `indexOf` gives
252
+ ; for the key. The lines have "•" and "▿" in them, and an offset into a
253
+ ; string with multi-byte characters before it is counted in runes on some
254
+ ; targets and in BYTES on others -- so the slice that is right on Node is
255
+ ; off by two per bullet on Go. Splitting asks no arithmetic and is the same
256
+ ; answer everywhere. `AppleDeviceList.columns` avoids the same trap for the
257
+ ; same reason.
258
+ sfn valueFor:string (text:string key:string) {
259
+ def lines:[string] (strsplit text "\n")
260
+ def found:string ""
261
+ def seen:boolean false
262
+ for lines raw:string i {
263
+ if (seen == false) {
264
+ def parts:[string] (strsplit raw ":")
265
+ def cnt:int (array_length parts)
266
+ if (cnt >= 2) {
267
+ if ((indexOf (itemAt parts 0) key) >= 0) {
268
+ seen = true
269
+ ; A value with a ":" in it -- a time, a URL -- keeps it.
270
+ def rest:[string]
271
+ def pi:int 1
272
+ while (pi < cnt) {
273
+ push rest (itemAt parts pi)
274
+ pi = pi + 1
275
+ }
276
+ found = (trim (join rest ":"))
277
+ }
278
+ }
279
+ }
280
+ }
281
+ return found
282
+ }
283
+
284
+ sfn parse:AppleDeviceDetails (text:string) {
285
+ def d:AppleDeviceDetails (new AppleDeviceDetails)
286
+ d.developerMode = (AppleDeviceDetails.valueFor(text "developerModeStatus"))
287
+ d.tunnelState = (AppleDeviceDetails.valueFor(text "tunnelState"))
288
+ d.pairingState = (AppleDeviceDetails.valueFor(text "pairingState"))
289
+ d.osVersion = (AppleDeviceDetails.valueFor(text "osVersionNumber"))
290
+ d.marketingName = (AppleDeviceDetails.valueFor(text "marketingName"))
291
+ d.udid = (AppleDeviceDetails.valueFor(text "udid"))
292
+ d.ddiAvailable = ((AppleDeviceDetails.valueFor(text "ddiServicesAvailable")) == "true")
293
+ return d
294
+ }
295
+ }