@naanlang/naan 1.0.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 (98) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +46 -0
  3. package/dist/env_web.js +431 -0
  4. package/dist/naan.min.js +73 -0
  5. package/frameworks/browser/browser.nlg +30 -0
  6. package/frameworks/browser/pouchdb/pouchdb.min.js +7 -0
  7. package/frameworks/browser/spark-md5/spark-md5.min.js +1 -0
  8. package/frameworks/browser/sworker.js +192 -0
  9. package/frameworks/browser/terminals.nlg +787 -0
  10. package/frameworks/browser/workers.nlg +386 -0
  11. package/frameworks/client/apiclient.nlg +183 -0
  12. package/frameworks/client/client.nlg +29 -0
  13. package/frameworks/client/psm_client.nlg +572 -0
  14. package/frameworks/common/common.nlg +464 -0
  15. package/frameworks/common/preferences.nlg +131 -0
  16. package/frameworks/common/repltools.nlg +100 -0
  17. package/frameworks/common/watching.nlg +97 -0
  18. package/frameworks/node/apiserver.nlg +399 -0
  19. package/frameworks/node/filesystem.nlg +1012 -0
  20. package/frameworks/node/gitter.nlg +234 -0
  21. package/frameworks/node/node.nlg +81 -0
  22. package/frameworks/node/psm_server.nlg +465 -0
  23. package/frameworks/node/worker.nlg +560 -0
  24. package/frameworks/project/build.nlg +1229 -0
  25. package/frameworks/project/jszip.js +30 -0
  26. package/frameworks/project/jszip.nlg +87 -0
  27. package/frameworks/project/proj_cliser.nlg +139 -0
  28. package/frameworks/project/proj_console.nlg +137 -0
  29. package/frameworks/project/proj_folder.nlg +143 -0
  30. package/frameworks/project/proj_lambda.nlg +137 -0
  31. package/frameworks/project/proj_static.nlg +138 -0
  32. package/frameworks/project/project.nlg +29 -0
  33. package/frameworks/project/projects.nlg +587 -0
  34. package/frameworks/project/uglifyjs.js +1 -0
  35. package/frameworks/project/uglifyjs.nlg +79 -0
  36. package/frameworks/running/debugcom.nlg +163 -0
  37. package/frameworks/running/debugnub.nlg +565 -0
  38. package/frameworks/running/debugutil.nlg +510 -0
  39. package/frameworks/running/executors.nlg +767 -0
  40. package/frameworks/running/running.nlg +151 -0
  41. package/frameworks/running/sourcecode.nlg +346 -0
  42. package/frameworks/running/taskexec.nlg +196 -0
  43. package/frameworks/storage/dbt_pouch.nlg +773 -0
  44. package/frameworks/storage/file_manager.nlg +772 -0
  45. package/frameworks/storage/psm.nlg +326 -0
  46. package/frameworks/storage/psm_dbtables.nlg +814 -0
  47. package/frameworks/storage/resources.nlg +445 -0
  48. package/frameworks/storage/storage.nlg +52 -0
  49. package/lib/browser/env_web.js +431 -0
  50. package/lib/browser/env_webworker.js +146 -0
  51. package/lib/browser/require.js +1653 -0
  52. package/lib/core/naanlib.js +73 -0
  53. package/lib/env_node.js +282 -0
  54. package/lib/env_nodeworker.js +165 -0
  55. package/lib/node_repl.js +25 -0
  56. package/lib/node_repl_init.nlg +52 -0
  57. package/package.json +13 -0
  58. package/plugins/gitClient/clientops.nlg +317 -0
  59. package/plugins/gitClient/gitClient.nlg +63 -0
  60. package/plugins/gitClient/gitutils.nlg +1331 -0
  61. package/plugins/gitClient/minimatch.js +945 -0
  62. package/plugins/gitClient/minimatch_license.txt +15 -0
  63. package/plugins/gitClient/minimatch_readme.md +208 -0
  64. package/plugins/nodeLib/nodeLib.nlg +26 -0
  65. package/plugins/nodeLib/node_cp.nlg +65 -0
  66. package/plugins/nodeLib/node_fs.nlg +137 -0
  67. package/plugins/nodeLib/node_https.nlg +172 -0
  68. package/plugins/nodeLib/node_util.nlg +180 -0
  69. package/plugins/serviceAws/aws/aws-sdk.min.js +88 -0
  70. package/plugins/serviceAws/aws/lambda_rest_index.js +122 -0
  71. package/plugins/serviceAws/aws/lambda_rest_init.nlg +58 -0
  72. package/plugins/serviceAws/aws/lambda_ws_index.js +498 -0
  73. package/plugins/serviceAws/aws/lambda_ws_init.nlg +49 -0
  74. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +122 -0
  75. package/plugins/serviceAws/aws_cognito.nlg +76 -0
  76. package/plugins/serviceAws/aws_dynamo.nlg +509 -0
  77. package/plugins/serviceAws/aws_lambda.nlg +95 -0
  78. package/plugins/serviceAws/aws_s3.nlg +134 -0
  79. package/plugins/serviceAws/aws_utils.nlg +86 -0
  80. package/plugins/serviceAws/dbt_aws.nlg +769 -0
  81. package/plugins/serviceAws/lambda_naan_control.nlg +175 -0
  82. package/plugins/serviceAws/psm_aws.nlg +258 -0
  83. package/plugins/serviceAws/serviceAws.nlg +38 -0
  84. package/plugins/serviceGitHub/gitops.nlg +522 -0
  85. package/plugins/serviceGitHub/psm_github.nlg +205 -0
  86. package/plugins/serviceGitHub/serviceGitHub.nlg +65 -0
  87. package/plugins/serviceGitLab/gitops.nlg +528 -0
  88. package/plugins/serviceGitLab/psm_gitlab.nlg +205 -0
  89. package/plugins/serviceGitLab/serviceGitLab.nlg +65 -0
  90. package/test/harness.nlg +312 -0
  91. package/test/node_test.js +33 -0
  92. package/test/test_01_core.nlg +768 -0
  93. package/test/test_02_context.nlg +269 -0
  94. package/test/test_03_jsinterop.nlg +137 -0
  95. package/test/test_04_numerics.nlg +450 -0
  96. package/test/test_05_strings.nlg +63 -0
  97. package/test/test_06_lingoparse.nlg +500 -0
  98. package/test/test_07_lingo.nlg +623 -0
@@ -0,0 +1,1012 @@
1
+ /*
2
+ * filesystem.nlg
3
+ * Naanlib/frameworks/node
4
+ *
5
+ * File and directory operations on the host filesystem.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2017-2021 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * Filesystem
16
+ *
17
+ * Access to the NodeJS filesystem. Methods take a callback, which if not provided will cause
18
+ * execution to block until complete. The return value is always a tuple of (error, data).
19
+ *
20
+ */
21
+
22
+ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
23
+ if !rootpath
24
+ return (list(Error("mandatory rootpath missing")))
25
+ else if rootpath == ""
26
+ rootpath = js.g.process.cwd() // NodeJS uses CWD for empty rootpath
27
+ files = new(object, this)
28
+ files.rootpath = rootpath
29
+ files.path = JSpath
30
+
31
+ // commonParents
32
+ //
33
+ // Find the common parents between two paths, or "" if they have nothing in common.
34
+
35
+ function commonParents(short, long, local swap) {
36
+ if short.length > long.length {
37
+ swap = long
38
+ long = short
39
+ short = swap
40
+ }
41
+ loop {
42
+ if long.startsWith(short)
43
+ return (short)
44
+ swap = JSpath.dirname(short)
45
+ if swap == short
46
+ break
47
+ short = swap
48
+ }
49
+ return ("")
50
+ }
51
+
52
+ // localPath
53
+ //
54
+ // Convert a path argument to a local path, returning a standard result tuple. If the path is
55
+ // invalid this will report why. Otherwise this returns the correct localpath, which is the path
56
+ // we use with the underlying API.
57
+
58
+ badCreatePathRegEx = RegExp("[:]|[/\\\\][/\\\\]|[/\\\\]$|(^|[/\\\\])[.][.]?$")
59
+
60
+ closure localPath(path, create, local pattern, testpath) {
61
+ if !path
62
+ return (list(Error("filesystem path false")))
63
+ else if !string(path)
64
+ return (list(Error("filesystem path invalid:", typeof(path))))
65
+ if JSpath.isAbsolute(path)
66
+ path = JSpath.resolve(path) // puts C: in front of \abs\win\path
67
+ else
68
+ path = JSpath.normalize(path) // normalize for current platform
69
+ if JSpath === JSpath.win32
70
+ pattern = "\\:\\" // unchanged and unique on win32
71
+ else
72
+ pattern = "/:/" // unchanged and unique on posix
73
+ if ((testpath = commonParents(path, rootpath)) != "")
74
+ list(false, JSpath.resolve(rootpath, path)) // ### shouldn't be so lax on this test
75
+ else if ((testpath = JSpath.resolve(pattern, path)).indexOf(pattern.slice(0,-1)) < 0)
76
+ list(Error("filesystem path reaches above root:", path))
77
+ else if create && testpath.indexOf(pattern) < 0
78
+ list(Error("filesystem create path cannot be root:", path))
79
+ else if create && badCreatePathRegEx.test(path)
80
+ list(Error("filesystem create path invalid", path))
81
+ else
82
+ list(false, JSpath.resolve(rootpath, path))
83
+ }
84
+
85
+ // winDrives
86
+ //
87
+ // Return an array of the drive letters on windows, or call back with false otherwise.
88
+
89
+ files.winDrives = closure winDrives(callback) {
90
+ if !callback
91
+ return (syncAdapter(winDrives))
92
+ if js.g.process.platform != "win32"
93
+ return (asyncResult(callback, false)) // for unixen, return nothing
94
+ nodecp.execFile("wmic",
95
+ ["logicaldisk", "get", "name"], // get name field for each mapped drive
96
+ { }, // execution options
97
+ function(err, stdout, stderr) {
98
+ if stdout && string(stdout)
99
+ callback(false, new(stdout.split("\r\r\n")
100
+ .filter(function(value){RegExp("[A-Za-z]:").test(value)})
101
+ .map(function(value){value.trim()})))
102
+ else
103
+ callback(Error("exec(wmic) failed", err))
104
+ }
105
+ )
106
+ }
107
+
108
+ // testHidden
109
+ //
110
+ // Test if the specified file or directory is hidden. On Windows this executes the attrib utility
111
+ // to obtain the hidden attribute.
112
+
113
+ files.testHidden = closure testHidden(path, callback, local darwinHiddenDirs, found, error, localpath) {
114
+ if !callback
115
+ return (syncAdapter(testHidden, path))
116
+ `(error, localpath) = localPath(path)
117
+ if error
118
+ return (asyncResult(callback, error))
119
+ darwinHiddenDirs = {
120
+ "/bin": true,
121
+ "/cores": true,
122
+ "/dev": true,
123
+ "/etc": true,
124
+ "/home": true,
125
+ "/net": true,
126
+ "/network": true,
127
+ "/private": true,
128
+ "/sbin": true,
129
+ "/tmp": true,
130
+ "/usr": true,
131
+ "/var": true }
132
+ if js.g.process.platform == "darwin" && darwinHiddenDirs[localpath]
133
+ return (asyncResult(callback, false, true)) // Darwin has specific named hidden folders
134
+ if js.g.process.platform != "win32" {
135
+ found = JSpath.basename(localpath)
136
+ return (asyncResult(callback, false, RegExp("^\\.").test(found))) } // for unixen, leading dot for hidden
137
+ nodecp.execFile("attrib",
138
+ ["/D", localpath], // "/D" to include directories
139
+ { }, // execution options
140
+ function(err, stdout, stderr) {
141
+ if stdout && string(stdout) {
142
+ found = stdout.match(RegExp("^[A-Z ]{11}"))
143
+ callback(false, found[0].indexOf("H") >= 0) } // "H" for hidden
144
+ else
145
+ callback(Error("exec(attrib) failed", err))
146
+ }
147
+ )
148
+ }
149
+
150
+ // readLines
151
+ //
152
+ // Read the contents of a file into an array of lines with the following options:
153
+ // pattern: -- only return lines matching pattern
154
+ // fixed -- pattern is a fixed string, not a regular expression
155
+ // ignoreCase: -- ignore case during match
156
+ // invert: -- return lines that DO NOT match the regex
157
+ // returnText: -- return text of matched lines
158
+ //
159
+
160
+ files.readLines = closure readLines(path, options, callback,
161
+ local localpath, error, instream, pattern, matchRE, liner, lines, lineno) {
162
+ if !callback
163
+ return (syncAdapter(readLines, path, options))
164
+ `(error, localpath) = localPath(path)
165
+ if error
166
+ return (asyncResult(callback, error))
167
+ instream = fs.createReadStream(localpath)
168
+ instream.on("error", function (err) {
169
+ callback(Error("stream failed:", localpath, err))
170
+ })
171
+ liner = NodeStreamLiner()
172
+ instream.pipe(liner)
173
+ liner.on("end", function() {
174
+ callback(false, lines)
175
+ })
176
+ lines = []
177
+ if options.pattern {
178
+ pattern = options.pattern
179
+ if options.fixed
180
+ pattern = pattern.replace(RegExp("\\.|\\0|\\\\|\\[|\\||\\(|\\*|\\+|\\?|\\{"), function(match) {
181
+ "\\".concat(match) // escape all special RegExp characters
182
+ })
183
+ try {
184
+ if options.ignoreCase
185
+ matchRE = RegExp(pattern, "i")
186
+ else
187
+ matchRE = RegExp(pattern)
188
+ } catch {
189
+ if true
190
+ return (callback(Error("search expression error", exception)))
191
+ }
192
+ lineno = 0
193
+ liner.on("readable", function (local line, matched) {
194
+ while (line = liner.read()) {
195
+ ++lineno
196
+ matched = matchRE.test(line)
197
+ if matched && !options.invert || !matched && options.invert
198
+ if options.returnText
199
+ lines.push([lineno, line])
200
+ else
201
+ lines.push(lineno) }
202
+ })
203
+ } else
204
+ liner.on("readable", function (local line) {
205
+ while (line = liner.read())
206
+ lines.push(line)
207
+ })
208
+ }
209
+
210
+ // readFile
211
+ //
212
+ // Read the contents of a file into a string.
213
+
214
+ files.readFile = closure readFile(path, options, callback, local nodeOptions, error, localpath) {
215
+ if !callback
216
+ return (syncAdapter(readFile, path, options))
217
+ `(error, localpath) = localPath(path)
218
+ if error
219
+ return (asyncResult(callback, error))
220
+ nodeOptions = { encoding: "utf8" }
221
+ if options.encoding == "binary"
222
+ nodeOptions.encoding = undefined
223
+ else if options.encoding
224
+ nodeOptions.encoding = options.encoding
225
+ fs.readFile(localpath, nodeOptions, function(err, strdata) {
226
+ if err
227
+ callback(Error("fs.readFile failed:", localpath, err))
228
+ else
229
+ callback(false, strdata)
230
+ })
231
+ }
232
+
233
+ // writeFile
234
+ //
235
+ // Write a string to the specified file, with the following options:
236
+ // md5: <hash> -- existing file content must match hash to overwrite
237
+ // encoding: <type> -- default utf8
238
+ // The non-error result is a dictionary containing the new file contents hash.
239
+
240
+ files.writeFile = closure writeFile(path, data, options, callback, local error, localpath, nodeOptions, md5data, pending, result) {
241
+ if !callback
242
+ return (syncAdapter(writeFile, path, data, options))
243
+ `(error, localpath) = localPath(path)
244
+ if error
245
+ return (asyncResult(callback, error))
246
+ nodeOptions = { encoding: "utf8" }
247
+ if options.encoding
248
+ nodeOptions.encoding = options.encoding
249
+ md5data = HashMD5(data)
250
+ if options.md5 { // read file to verify it is unchanged
251
+ pending = new(nonce)
252
+ fs.readFile(localpath, nodeOptions, function(err, strdata) {
253
+ if err.code == "ENOENT"
254
+ result = false // no file to check
255
+ else
256
+ result = HashMD5(strdata)
257
+ pending.signal(result)
258
+ })
259
+ result = pending.wait() // result is existing hash, or false if none
260
+ if result && options.md5 != result { // current contents don't match check
261
+ if md5data == result
262
+ return (callback(false, { md5: md5data })) // but, oddly, current contents match data
263
+ else
264
+ return (callback(Error("existing file match failed:", { // someone beat us to the write
265
+ path: localpath
266
+ current: result
267
+ check: options.md5
268
+ }))) } }
269
+ fs.writeFile(localpath, data, nodeOptions, function (err) {
270
+ if err
271
+ callback(Error("fs.writeFile failed:", localpath, err))
272
+ else
273
+ callback(false, { md5: md5data })
274
+ })
275
+ }
276
+
277
+ // delete
278
+ //
279
+ // Delete the file at the specified path, or a directory if the options dictionary includes:
280
+ // recursive: true -- "recursively" delete directory and children
281
+
282
+ files.delete = closure delete(path, options, callback, local error, localpath) {
283
+ if !callback
284
+ return (syncAdapter(delete, path, options))
285
+ `(error, localpath) = localPath(path)
286
+ if error
287
+ return (asyncResult(callback, error))
288
+ if options.recursive
289
+ fs.rmdir(localpath, function(err) {
290
+ if err
291
+ callback(Error("fs.rmdir failed:", localpath, err))
292
+ else
293
+ callback(false, { ok: true })
294
+ })
295
+ else
296
+ fs.unlink(localpath, function(err) {
297
+ if err
298
+ callback(Error("fs.unlink failed:", localpath, err))
299
+ else
300
+ callback(false, { ok: true })
301
+ })
302
+ }
303
+
304
+ // info
305
+ //
306
+ // Report info on a file or directory.
307
+
308
+ files.info = closure info(path, callback, local error, localpath) {
309
+ if !callback
310
+ return (syncAdapter(info, path))
311
+ `(error, localpath) = localPath(path)
312
+ if error
313
+ return (asyncResult(callback, error))
314
+ fs.lstat(localpath, function(err, stat, local data, info, nodetype) {
315
+ if err
316
+ return (callback(Error("fs.stat failed:", localpath, err)))
317
+ info = {
318
+ type: "other"
319
+ length: stat.size
320
+ birthtimeMs: stat.birthtimeMs
321
+ birthtime: stat.birthtime
322
+ mtimeMs: stat.mtimeMs
323
+ mtime: stat.mtime
324
+ nodejs_stat: stat }
325
+ `(err, data) = files.testHidden(localpath)
326
+ if data.hidden
327
+ info.hidden = true
328
+ nodetype = mod(stat.mode / 4096, 16)
329
+ if nodetype == 4
330
+ info.type = "directory"
331
+ else if nodetype == 8
332
+ info.type = "file"
333
+ else if nodetype == 10
334
+ info.type = "symlink"
335
+ callback(false, info)
336
+ })
337
+ }
338
+
339
+ // setTimes
340
+ //
341
+ // Set timestamps on a file or directory.
342
+
343
+ files.setTimes = closure setTimes(path, atime, mtime, callback, local error, localpath) {
344
+ if !callback
345
+ return (syncAdapter(setTimes, path, atime, mtime))
346
+ `(error, localpath) = localPath(path)
347
+ if error
348
+ return (asyncResult(callback, error))
349
+ if !numeric(atime, mtime)
350
+ return (asyncResult(callback, Error("invalid arguments:", atime, mtime)))
351
+ localpath = JSpath.resolve(rootpath, localpath)
352
+ fs.utimes(localpath, atime, mtime, function(err, stat) {
353
+ if err
354
+ callback(Error("fs.utimes failed:", localpath, err))
355
+ else
356
+ callback(false, { ok: true})
357
+ })
358
+ }
359
+
360
+ // statfetch
361
+ //
362
+ // Update each element in the input array with info, in parallel if possible.
363
+
364
+ closure statfetch(input, parentpath, local kqdex, filepath, pending) {
365
+ // queuemore
366
+ //
367
+ // Make more requests, or signal when all are completed
368
+
369
+ function queuemore() {
370
+ while kqdex < input.length {
371
+ if pending.active >= 10
372
+ return // too busy
373
+ closure (kdex, local name) { // separate closure to keep record context
374
+ name = input[kdex].name
375
+ if name { // this record is fetchable
376
+ ++pending.active
377
+ filepath = JSpath.join(parentpath, name)
378
+ files.info(filepath, function(error, info) {
379
+ if error
380
+ input[kdex].error = error // this record failed
381
+ else
382
+ input[kdex].info = info // this record succeeded
383
+ --pending.active
384
+ queuemore()
385
+ }) }
386
+ } (kqdex++) // issue one request
387
+ }
388
+ if pending.active == 0
389
+ pending.signal() // no more to do, and all done
390
+ }
391
+
392
+ pending = new(nonce)
393
+ pending.active = 0
394
+ kqdex = 0
395
+ queuemore()
396
+ pending.wait()
397
+ }
398
+
399
+ // dirList
400
+ //
401
+ // Return array of files in a directory if it exists. If options.head is true then this reads
402
+ // info for each file as well.
403
+
404
+ files.dirList = closure dirList(path, options, callback, local error, localpath, output, nodelist, file) {
405
+ if path == "" && js.g.process.platform == "win32" && rootpath == "\\"
406
+ return (files.winDrives(callback)) // top level is drive letters in Windows
407
+ if !callback
408
+ return (syncAdapter(dirList, path, options))
409
+ `(error, localpath) = localPath(path)
410
+ if error
411
+ return (asyncResult(callback, error))
412
+ output = {
413
+ name: JSpath.basename(localpath)
414
+ path: path
415
+ data: {
416
+ platform: js.g.process.platform,
417
+ semantics: cond(if JSpath eq JSpath.win32 "win32", if true "posix")
418
+ pathsep: JSpath.sep
419
+ }
420
+ }
421
+ fs.readdir(localpath, { }, function(err, files) {
422
+ if err
423
+ return (callback(Error("fs.readdir failed:", localpath, err)))
424
+ output.children = []
425
+ for file in files
426
+ output.children.push({
427
+ name: file
428
+ })
429
+ if options.head
430
+ statfetch(output.children, localpath) // read all the info records
431
+ callback(false, output)
432
+ })
433
+ }
434
+
435
+ // mkdir
436
+ //
437
+ // Make a directory at the specified path. If the directory already exists then the result is
438
+ // { exists: true }, otherwise if it was just created it's { created: true}
439
+
440
+ files.mkdir = closure mkdir(path, callback, local error, localpath) {
441
+ if !callback
442
+ return (syncAdapter(mkdir, path))
443
+ `(error, localpath) = localPath(path)
444
+ if error
445
+ return (asyncResult(callback, error))
446
+ fs.mkdir(localpath, { recursive: true }, function(errmkdir, local result) {
447
+ if errmkdir.code == "EEXIST"
448
+ fs.stat(localpath, function(errstat, stat) {
449
+ if errstat || !stat.isDirectory()
450
+ return (callback(Error("mkdir conflict:", errmkdir, localpath)))
451
+ else
452
+ return (callback(false, { exists: true })) // directory already exists
453
+ })
454
+ else if errmkdir
455
+ callback(Error("mkdir failed", errmkdir))
456
+ else
457
+ return (callback(false, { created: true })) // directory just created
458
+ })
459
+ }
460
+
461
+ // tree
462
+ //
463
+ // Create a directory tree to the specified depth by recursive enumeration. The depthlimit is 1
464
+ // to enumerate only the children of the specified path, or false to include all recursive children.
465
+ // Errors are recorded in the resulting tree.
466
+ // Tree starts with the specified path and does not traverse device boundaries that it knows
467
+ // about. You'll need to list the drive letters (Win) and the volumes (Mac) yourself before asking
468
+ // for a tree on one of them.
469
+ //
470
+ // The minimum structure of an actual treedex is the following:
471
+ // {
472
+ // name: <name of this node>
473
+ // data: {
474
+ // <store-specific data definitions, like timestamps, etc.>
475
+ // }
476
+ // parents: <array of node names describing path from this node to root>
477
+ // error: <string> // error that occured while attempting to enumerate children
478
+ // children: <undefined> // definitively no children
479
+ // children: pending // children may exist, but not yet enumerated
480
+ // children: [] // empty array for non-leaf with no children, e.g. empty folder
481
+ // // (if error with empty array then enumeration failed)
482
+ // children: [ // each child node is like us, but no ownerID or parents array
483
+ // {
484
+ // name: <name of the child; must be non-empty and unique among children>
485
+ // iconID: <token identifying the icon to use>
486
+ // data: {
487
+ // <store-specific data definitions>
488
+ // }
489
+ // view: {
490
+ // <optional view-specific data definitions>
491
+ // }
492
+ // error: // as above, for grandchildren
493
+ // children: // as above, for grandchildren
494
+ // },
495
+ // ...
496
+ // ]
497
+ // }
498
+
499
+ files.tree = closure tree(path, depthlimit, callback, local error, localpath, root, pending, queue, data, rootstat) {
500
+ if !callback
501
+ return (syncAdapter(tree, path, depthlimit))
502
+
503
+ // recurTree
504
+ //
505
+ // Recursively build the tree
506
+
507
+ closure recurTree(localpath, depth, parent) {
508
+ if depthlimit && depth >= depthlimit
509
+ return
510
+ ++pending.active
511
+ fs.readdir(localpath, { }, function(err, nodes, local child, node, stat) {
512
+ if err.code == "ENOTDIR"
513
+ parent.children = undefined // we are a leaf (in the wind)
514
+ else if err {
515
+ parent.children = [], // enumeration failed
516
+ parent.error = err.code } // keep lightweight
517
+ else { // we have children
518
+ parent.children = []
519
+ stat = files.info(localpath).1
520
+ if stat {
521
+ if stat.type == "symlink" {
522
+ parent.children = "symlink" // don't follow links
523
+ nodes = false }
524
+ else if stat.nodejs_stat.dev && stat.nodejs_stat.dev != rootstat.dev {
525
+ parent.children = "mountpoint" // don't enter mountpoints
526
+ nodes = false } }
527
+ for node in nodes {
528
+ child = {
529
+ name: node
530
+ children: "pending" // temporary unless depth limit reached
531
+ }
532
+ parent.children.push(child)
533
+ queue.push({
534
+ path: JSpath.join(localpath, child.name),
535
+ depth: depth+1,
536
+ parent: child
537
+ }) } }
538
+ --pending.active
539
+ while queue.length > 0 && pending.active < 20 {
540
+ node = queue.pop()
541
+ recurTree(node.path, node.depth, node.parent) }
542
+ if pending.active == 0 && queue.length == 0
543
+ pending.signal(list(false, root))
544
+ })
545
+ }
546
+
547
+ `(error, localpath) = localPath(path)
548
+ if error
549
+ return (asyncResult(callback, error))
550
+ if not integer(depthlimit)
551
+ depthlimit = false // default is all children
552
+ queue = []
553
+ root = {
554
+ name: JSpath.basename(localpath)
555
+ data: {
556
+ platform: js.g.process.platform,
557
+ semantics: cond(if JSpath eq JSpath.win32 "win32", if true "posix")
558
+ pathsep: JSpath.sep
559
+ }
560
+ }
561
+ root.parents = new(JSpath.dirname(localpath).split(JSpath.sep))
562
+ pending = new(nonce)
563
+ pending.active = 0
564
+ `(error, data) = files.info(localpath)
565
+ if !error {
566
+ rootstat = data.nodejs_stat
567
+ recurTree(localpath, 0, root)
568
+ `(error, data) = pending.wait() // returns `(false, root) when done
569
+ }
570
+ callback(error, data)
571
+ }
572
+
573
+ // dirSearch
574
+ //
575
+ // Search the files in a directory for a pattern.
576
+
577
+ files.dirSearch = closure dirSearch(path, options, callback,
578
+ local error, localpaths, output) {
579
+ if !callback
580
+ return (syncAdapter(dirSearch, path, options))
581
+ `(error, localpath) = localPath(path)
582
+ if error
583
+ return (asyncResult(callback, error))
584
+ output = []
585
+ debuglog("dirSearch begins", localpath)
586
+ asyncArray(localpaths, 10, closure(localpath, index, elements, local error, direnum, node, lines, line) {
587
+ `(error, direnum) = dirList(localpath, { })
588
+ if direnum.children
589
+ for node in direnum.children
590
+ localpaths.push(JSpath.join(localpath, node.name)) // more files or directories to examine
591
+ else {
592
+ `(error, lines) = readLines(localpath, options)
593
+ if error && localpaths.length == 1 {
594
+ error = Error("dirSearch failed:", localpath) // can't access the root
595
+ output = false }
596
+ else
597
+ for line in lines {
598
+ if array(line)
599
+ output.push([localpath].concat(line)) // [localpath, line, text]
600
+ else
601
+ output.push([localpath, line]) // [localpath, line]
602
+ }
603
+ }
604
+ }).wait()
605
+ debuglog(" dirSearched", localpath, error, localpaths.length, output.length)
606
+ callback(error, output)
607
+ }
608
+
609
+ // copy
610
+ //
611
+ // Copy / move / rename a file or directory. The source is the full path of the item. The
612
+ // destination is:
613
+ // - a new name if it has no embedded slashes, or
614
+ // - a new location if it ends in a slash, otherwise
615
+ // - a new pathname for the source if it does not end in a slash.
616
+ // This works by creating a new file or directory of files in the new location and then
617
+ // optionally deleting the files in the existing location. If options.move is true then we
618
+ // delete the source after the copy.
619
+ // Supported options are:
620
+ // {
621
+ // move: <boolean> -- delete after successful copy
622
+ // overwrite: <boolean> -- overwrite existing file, if any
623
+ // }
624
+
625
+ files.copy = closure copy(srcpath, dest, options, callback, local error, localsrc, localdest, pending) {
626
+ if !callback
627
+ return (syncAdapter(copy, srcpath, dest, options))
628
+ `(error, localsrc) = localPath(srcpath)
629
+ if error
630
+ return (asyncResult(callback, error))
631
+ // ### the following localdest doesn't detect existing files in all cases
632
+ if dest.indexOf(JSpath.sep) < 0
633
+ localdest = JSpath.join(JSpath.dirname(localsrc), dest) // new name for existing source in same path location
634
+ else {
635
+ `(error, localdest) = localPath(dest)
636
+ if error
637
+ return (asyncResult(callback, error))
638
+ if dest.slice(-1) == JSpath.sep // new location, else entirely new pathname
639
+ localdest = JSpath.join(localdest, JSpath.basename(srcpath))
640
+ }
641
+ fs.lstat(localdest, closure(err, stat, local cmd, args) {
642
+ if !err && !options.overwrite
643
+ callback(Error("copy destination already exists",
644
+ {
645
+ status: 409
646
+ name: "conflict"
647
+ source: srcpath
648
+ destination: dest
649
+ existing: localdest
650
+ }))
651
+ else {
652
+ if options.move {
653
+ cmd = "mv"
654
+ args = [localsrc, localdest] }
655
+ else {
656
+ cmd = "cp"
657
+ args = ["-pR", localsrc, localdest] } // preserve file modes and attributes; copy directories
658
+ nodecp.execFile(cmd, args,
659
+ { }, // execution options
660
+ function(err, stdout, stderr) {
661
+ if err {
662
+ debuglog("cp failed", err)
663
+ debuglog(" ", srcpath, "|", localsrc, "->", dest, "|", localdest)
664
+ }
665
+ if err
666
+ callback(Error(cmd.concat(" failed"), err, stderr))
667
+ else
668
+ callback(false, { ok: true })
669
+ }
670
+ )
671
+ }
672
+ })
673
+ }
674
+
675
+ // reveal
676
+ //
677
+ // Reveal the specified filesystem path(s) in the MacOS Finder. The path should be a string
678
+ // or array thereof. This returns an array listing any errors. Needs a Windows version too.
679
+
680
+ files.reveal = closure reveal(path, callback, local errors, pathargs, item) {
681
+ if !callback
682
+ return (syncAdapter(reveal, path))
683
+
684
+ function addpath(path, local error, localpath) {
685
+ `(error, localpath) = localPath(path)
686
+ if error
687
+ errors.push(error)
688
+ else
689
+ pathargs.push("file://localhost".concat(localpath))
690
+ }
691
+
692
+ errors = []
693
+ pathargs = ["-R", "--"] // reveal, then stop using '-' for ags
694
+ if array(path)
695
+ for item in path
696
+ addpath(item)
697
+ else
698
+ addpath(path)
699
+ nodecp.execFile("open",
700
+ pathargs, // command-line options
701
+ { }, // execution options
702
+ function(err, stdout, stderr) {
703
+ if err
704
+ callback(Error("open failed", err, stderr))
705
+ else
706
+ callback(false, errors)
707
+ }
708
+ )
709
+ }
710
+
711
+ //
712
+ // minimize
713
+ //
714
+ // Execute the uglifyjs utility on the specified source, returning the result as a standard
715
+ // tuple: `(error, minified)
716
+ //
717
+ files.minimize = closure minimize(input, callback, local child, stdinStream) {
718
+ if !callback
719
+ return (syncAdapter(minimize, input))
720
+ child = nodecp.execFile("uglifyjs",
721
+ ["--compress", "drop_debugger=false", "--mangle", "eval"], // ### uglifyjs options should be parameterized
722
+ function(err, stdout, stderr) {
723
+ debuglog("#minimize:", input.length, "->", stdout.length, "bytes")
724
+ if err
725
+ callback(Error("uglifyjs failed", err, stderr))
726
+ else
727
+ callback(false, stdout)
728
+ }
729
+ )
730
+ child.stdin.on("error", function(err) {
731
+ // make sure error isn't unhandled, so that a failed uglifyjs doesn't
732
+ // kill our process when the stdin write occurs after the pipe closes.
733
+ })
734
+ child.stdin.end(input)
735
+ }
736
+
737
+ //
738
+ // unzip
739
+ //
740
+ // Execute the unzip utility, returning the result as a standard tuple: `(error, result)
741
+ //
742
+ files.unzip = closure unzip(inpath, outpath, callback) {
743
+ if !callback
744
+ return (syncAdapter(unzip, inpath, outpath))
745
+ nodecp.execFile("unzip",
746
+ ["-qo", inpath, "-d", outpath], // quiet; no queries
747
+ { }, // execution options
748
+ function(err, stdout, stderr) {
749
+ if err
750
+ callback(Error("unzip failed", err, stderr))
751
+ else
752
+ callback(false, { ok: true })
753
+ }
754
+ )
755
+ }
756
+
757
+ //
758
+ // touch
759
+ //
760
+ // Execute the touch utility, returning the result as a standard tuple: `(error, result)
761
+ //
762
+ files.touch = closure touch(outpath, callback) {
763
+ if !callback
764
+ return (syncAdapter(touch, outpath))
765
+ nodecp.execFile("touch",
766
+ [outpath],
767
+ { }, // execution options
768
+ function(err, stdout, stderr) {
769
+ if err
770
+ callback(Error("touch failed", err, stderr))
771
+ else
772
+ callback(false, { ok: true })
773
+ }
774
+ )
775
+ }
776
+
777
+ //
778
+ // shellOpen
779
+ //
780
+ // Execute the MacOS open utility, returning a standard tuple: `(error, result)
781
+ //
782
+ files.shellOpen = closure shellOpen(path, args, callback) {
783
+ if !callback
784
+ return (syncAdapter(shellOpen, path, args))
785
+ if args
786
+ args = [path].concat("--args", args) // only add target args if specified
787
+ else
788
+ args = [path]
789
+ nodecp.execFile("open",
790
+ args, // command line args to open
791
+ { }, // execution options
792
+ function(err, stdout, stderr) {
793
+ if err
794
+ callback(Error("shellOpen failed", err, stderr))
795
+ else
796
+ callback(false, { ok: true })
797
+ }
798
+ )
799
+ }
800
+
801
+ // grepLines
802
+ //
803
+ // Return an array of text matches in a file or recursive directory of files with the following
804
+ // options:
805
+ // pattern: -- pattern to find
806
+ // ignoreCase: -- ignore case during match
807
+ // invert: -- return lines that DO NOT match the regex
808
+ // returnText: -- return text of matched lines
809
+ // maxCount: -- integer maximum number of results (default 10,000)
810
+ // grep-specific options:
811
+ // ignoreBinary: -- ignore binary files
812
+ //
813
+ files.grepLines = closure grepLines(path, options, callback,
814
+ local error, localpath, cmdargs, execOptions, grep, result, lineRE, maxcount) {
815
+ if !callback
816
+ return (syncAdapter(grepLines, path, options))
817
+ `(error, localpath) = localPath(path)
818
+ if error
819
+ return (asyncResult(callback, error))
820
+ cmdargs = ["-rn"] // recursively search directories for files; report line numbers
821
+ if options.ignoreCase
822
+ cmdargs.push("-i")
823
+ if options.invert
824
+ cmdargs.push("-v")
825
+ if options.ignoreBinary
826
+ cmdargs.push("-I")
827
+ else
828
+ cmdargs.push("--binary-files=text")
829
+ if options.fixed
830
+ cmdargs.push("-F") // fixed pattern (not a regular expression)
831
+ else
832
+ cmdargs.push("-E") // work like e-grep
833
+ cmdargs.push("-e") // ensure that patterns starting with "-" are accepted
834
+ cmdargs.push(options.pattern)
835
+ cmdargs.push(JSpath.resolve(rootpath, localpath))
836
+ execOptions = { cmdargs: cmdargs }
837
+ result = []
838
+ lineRE = RegExp("([^:]*):([0-9]+):(.*)$") // <path>:<line-number>:<matching-text-line>
839
+ if integer(options.maxCount)
840
+ maxcount = options.maxCount
841
+ else
842
+ maxcount = 5000 // default is 5K lines maximum return
843
+ execOptions.outproc = function(output, liner, kill, local line, match) {
844
+ while (line = liner.read()) {
845
+ if result.length >= maxcount
846
+ return (kill())
847
+ match = line.match(lineRE)
848
+ if match {
849
+ item = [JSpath.relative(localpath, match.1), match.2]
850
+ if options.returnText
851
+ item.push(match.3)
852
+ result.push(item) // relative file path, line number, and (optional) text
853
+ }
854
+ }
855
+ }
856
+ execLines("grep", execOptions, function(error, output) {
857
+ if output.stderr
858
+ debuglog("grep stderr:", output.stderr.join("\n"))
859
+ if !error
860
+ output = result
861
+ callback(error, output)
862
+ })
863
+ }
864
+
865
+ // execLines
866
+ //
867
+ // Execute a command and parse the results into lines, executing the callback when everything
868
+ // has completed. Options are:
869
+ // {
870
+ // cmdargs: -- array of string arguments to the command (see below)
871
+ // execops: -- optional exec options like cwd
872
+ // errproc: -- callback for stderr output lines, or false
873
+ // errLE: -- string or RegExp for stderr line ending
874
+ // outproc: -- optional callback for stdout output lines
875
+ // outLE: -- string or RegExp for stdout line ending
876
+ // output: -- optional output structure
877
+ // }
878
+ // The result is stored in an output structure, which can have:
879
+ // {
880
+ // exitCode: -- status code from execution, or null if killed
881
+ // stderr: -- array of stderr lines if errproc not specified
882
+ // stdout: -- array of stdout lines if outproc not specified
883
+ // }
884
+ // Errorproc and outproc have the same protocol, which is:
885
+ // proc(output, liner, kill)
886
+ // These functions are free to modify output, which is the eventual result of execLines if no
887
+ // overall error occurs. Each time liner is called, the result is either a line of text or false
888
+ // to indicate that there are no more lines available at this time. Calling kill() will terminate
889
+ // execution of the command immediately, setting the output.exitCode to null. When all data is
890
+ // transferred the procs are called one more time with both liner and kill false to give the
891
+ // proc a chance to finalize any output.
892
+ //
893
+ //
894
+
895
+ files.execLines = closure execLines(cmdname, options, callback, local output, arg, execops, spawn, linerErr, linerOut) {
896
+ if !callback
897
+ return (syncAdapter(execLines, cmdname, options))
898
+ if !string(cmdname) || cmdname == ""
899
+ return (asyncResult(callback, Error("invalid command:", typeof(cmdname))))
900
+ if options.output
901
+ output = options.output
902
+ else
903
+ output = {}
904
+ if options.execops
905
+ execops = options.execops
906
+ else
907
+ execops = { }
908
+ spawn = nodecp.spawn(cmdname, options.cmdargs, execops)
909
+
910
+ // completed
911
+ // execute callback only once
912
+ function completed(error, result) {
913
+ if callback {
914
+ callback(error, result)
915
+ callback = false } }
916
+
917
+ // kill
918
+ // terminate the command
919
+ function kill() {
920
+ grep.kill()
921
+ completed(false, output) } // the official end will never come
922
+
923
+ //
924
+ // process pipes and errors
925
+ //
926
+ spawn.stdin.end()
927
+ spawn.on("error", function(err) {
928
+ completed(Error("failed to spawn command ".concat(cmdname), err))
929
+ })
930
+ spawn.on("close", function(code) {
931
+ output.exitCode = code // zero on success; null on killed
932
+ })
933
+ spawn.stderr.on("error", function(err) {
934
+ completed(Error("stderr stream failed:", err))
935
+ })
936
+ spawn.stdout.on("error", function(err) {
937
+ completed(Error("stdout stream failed:", err))
938
+ })
939
+
940
+ //
941
+ // process stderr output
942
+ //
943
+ linerErr = NodeStreamLiner(options.errLE)
944
+ spawn.stderr.pipe(linerErr)
945
+ linerErr.on("end", function() {
946
+ if options.errproc
947
+ call(options.errproc, output)
948
+ if linerOut
949
+ linerErr = false // let end-of-out do the completion
950
+ else
951
+ completed(false, output)
952
+ })
953
+ linerErr.on("readable", function (local line) {
954
+ if options.errproc
955
+ call(options.errproc, output, linerErr, kill)
956
+ else
957
+ while ((line = linerErr.read()) && line.length > 0) {
958
+ if !output.stderr
959
+ output.stderr = [] // only create this if we have real output
960
+ output.stderr.push(line)
961
+ }
962
+ })
963
+
964
+ //
965
+ // process stdout output
966
+ //
967
+ linerOut = NodeStreamLiner(options.outLE)
968
+ spawn.stdout.pipe(linerOut)
969
+ linerOut.on("end", function() {
970
+ if options.outproc
971
+ call(options.outproc)
972
+ if linerErr
973
+ linerOut = false // let end-of-err do the completion
974
+ else
975
+ completed(false, output)
976
+ })
977
+ linerOut.on("readable", function (local line) {
978
+ if options.outproc
979
+ call(options.outproc, output, linerOut, kill)
980
+ else {
981
+ if !output.stdout
982
+ output.stdout = []
983
+ while (line = linerOut.read())
984
+ output.stdout.push(line)
985
+ }
986
+ })
987
+ }
988
+
989
+ // finis
990
+
991
+ list(false, files)
992
+ };
993
+
994
+
995
+ /*
996
+ * filesystemInit
997
+ *
998
+ * Initialize the module.
999
+ *
1000
+ */
1001
+
1002
+ function filesystemInit(local manifest) {
1003
+
1004
+ manifest = `(Filesystem, filesystemInit)
1005
+
1006
+ Naan.module.build(module.id, "filesystem", function(modobj, compobj) {
1007
+ Naan.module.require("./node.nlg")
1008
+ compobj.manifest = manifest
1009
+ modobj.exports.Filesystem = Filesystem
1010
+ })
1011
+
1012
+ }();