@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,772 @@
1
+ /*
2
+ * file_manager.nlg
3
+ * frameworks/storage
4
+ *
5
+ * File manager for working with contents of a folder.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2020-2021 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * FileManager
16
+ *
17
+ * The file manager provides an interface between the a storage client and the contents of an
18
+ * underlying folder, ensuring a consistent view of the file state with integrated caching and source
19
+ * control operations, and high-level functions for searching, indexing (one day), and managing the
20
+ * contents of the folder. The return value is the file manager object.
21
+ *
22
+ */
23
+
24
+ closure FileManager(fs, options, local files, watch, opqueue, willgit, hadgit) {
25
+ files = new(object, this)
26
+ files.fs = fs
27
+ files.nodes = [] // array of nodes, one per file or folder
28
+ files.stores = {} // file storage sessions
29
+ files.loadPending = new(nonce) // delay callers while working
30
+ watch = commonWatching.Watchable()
31
+ files.watch = watch.watch // watch for file changes
32
+ files.unWatch = watch.unwatch // stop watching for file changes
33
+
34
+ // compareNodes
35
+ //
36
+ // Compare path objects for sorting.
37
+ //
38
+ function compareNodes(a, b) {
39
+ if a.path < b.path
40
+ return (-1)
41
+ else if a.path > b.path
42
+ return (1)
43
+ 0
44
+ }
45
+
46
+ // pathIndex
47
+ //
48
+ // Find the specified path in the nodes list using a binary search on the sorted nodes, returning
49
+ // the index of the node or false.
50
+ //
51
+ function pathIndex(sought, local upper, lower, z) {
52
+ upper = files.nodes.length
53
+ if upper == 0
54
+ return (false) // not found
55
+ lower = z = 1
56
+ loop {
57
+ z = (upper + lower) / 2
58
+ if sought <= files.nodes[z-1].path
59
+ upper = z - 1
60
+ if sought >= files.nodes[z-1].path
61
+ lower = z + 1
62
+ if upper < lower
63
+ break
64
+ }
65
+ if lower - upper > 1
66
+ return (z - 1)
67
+ false
68
+ }
69
+
70
+ // flatten
71
+ //
72
+ // Flatten the specified tree into an array of path objects. This accomodates the possibility
73
+ // that a node name is empty. In compliance with DBtables filesystem mapping, this uses pathsep
74
+ // as the pathname for an empty directory. So "a" is a file in the root, "/a" is a file in the
75
+ // empty-named directory in the root, and "a//b" is in the empty directory inside of "a", etc.
76
+ //
77
+ function flatten(tree, local nodes, pathsep) {
78
+ nodes = []
79
+ if !tree
80
+ return(nodes) // no tree to enumerate
81
+ pathsep = tree.data.pathsep
82
+ function addPaths(parent, node, local item, items, child) { // add nodes for tree children, if any
83
+ if parent == "" { // either root or empty directory
84
+ if node.name == ""
85
+ parent = pathsep // node in empty-named directory
86
+ else
87
+ parent = node.name } // node in root
88
+ else if parent == pathsep
89
+ parent = parent.concat(node.name) // child of empty directory
90
+ else if parent
91
+ parent = parent.concat(pathsep, node.name) // deeper than root
92
+ else
93
+ parent = "" // root itself
94
+ item = {
95
+ path: parent
96
+ name: node.name
97
+ }
98
+ if node.deleted
99
+ item.deleted = node.deleted // copy the deleted flag
100
+ nodes.push(item)
101
+ if !node.children
102
+ return (item)
103
+ items = [] // children
104
+ if array(node.children) { // e.g. "symlink" if not array
105
+ for child in node.children
106
+ if child.name.charAt(0) != "." { // ### should check hidden flag
107
+ child = addPaths(parent, child) // child becomes new node/subtree
108
+ if child
109
+ items.push(child) }
110
+ }
111
+ item.children = items.sort(compareNodes.proc)
112
+ item
113
+ } (false, tree)
114
+ nodes
115
+ }
116
+
117
+ // difftree
118
+ //
119
+ // Update our nodes array with a new tree, returning the differences.
120
+ //
121
+ function difftree(updated, local original, added, deleted, changed, odex, udex, store) {
122
+ original = files.nodes
123
+ added = []
124
+ deleted = []
125
+ changed = []
126
+ updated = updated.sort(compareNodes.proc)
127
+ odex = udex = 0
128
+ while odex < original.length || udex < updated.length {
129
+ if odex >= original.length || original[odex].path > updated[udex].path
130
+ added.push(updated[udex++])
131
+ else if udex >= updated.length || original[odex].path < updated[udex].path {
132
+ store = files.stores[original[odex].path]
133
+ if store
134
+ store.deleted = true
135
+ deleted.push(original[odex++])
136
+ }
137
+ else {
138
+ if original[odex].meta
139
+ updated[udex].meta = original[odex].meta // meta copied over
140
+ if original[odex].gitstat
141
+ updated[udex].gitstat = original[odex].gitstat // gitstat copied over
142
+ if original[odex].info != updated[udex].info // info is change items to compare
143
+ changed.push(original[odex])
144
+ ++odex
145
+ ++udex
146
+ }
147
+ }
148
+ files.nodes = updated
149
+ if added.length > 0 || deleted.length > 0 {
150
+ { // return changes dictionary
151
+ added: added
152
+ deleted: deleted
153
+ changed: changed
154
+ }
155
+ }
156
+ }
157
+
158
+ // parentPath
159
+ //
160
+ // Return the parent path of the specified node, or "" for the root.
161
+ //
162
+ function parentPath(node) {
163
+ path = node.path
164
+ if path.slice(-1) == files.pathsep
165
+ path = path.slice(0,-1)
166
+ path.split(files.pathsep).slice(0,-1).join(files.pathsep)
167
+ }
168
+
169
+ // opBeginOrReschedule
170
+ //
171
+ // Begin an asynchronous operation if we're idle, or schedule it to be performed when we're
172
+ // done with the current operation.
173
+ //
174
+ function opBeginOrReschedule(newproc) {
175
+
176
+ function opExecute(proc, local pending, result) {
177
+ files.loadPending.reset()
178
+ pending = new(nonce)
179
+ call(proc, function(error, data) {
180
+ pending.signal(list(error, data))
181
+ })
182
+ result = pending.wait()
183
+ files.loadPending.signal(result)
184
+ }
185
+
186
+ if opqueue
187
+ opqueue.push(newproc) // already have a runner
188
+ else {
189
+ opqueue = [newproc]
190
+ future(function() { // schedule a runner
191
+ while opqueue.length > 0 {
192
+ opExecute(opqueue.0)
193
+ opqueue.shift()
194
+ }
195
+ opqueue = false
196
+ }).run()
197
+ }
198
+ }
199
+
200
+ // refreshDir
201
+ //
202
+ // Asynchronously refresh the specified directory, enumerating its children and updating our
203
+ // records. This emits change notifications to our watchers.
204
+ //
205
+ closure refreshDir(parpath) {
206
+ opBeginOrReschedule(closure(callback) {
207
+ files.fs.util.treeDirUpdate("", parpath, files.tree, function(error, changes, local updated) {
208
+ if error { // need better error reporting
209
+ ErrorDebuglog("FileManager.refreshDir failed", error)
210
+ return (callback(error))
211
+ }
212
+ updated = flatten(files.tree)
213
+ changes = difftree(updated)
214
+ if changes
215
+ watch.notify("folder", changes)
216
+ callback(false, files.nodes)
217
+ })
218
+ })
219
+ }
220
+
221
+ // load
222
+ //
223
+ // Initialize our knowledge of the project by loading a tree of the files and folders. This can
224
+ // require some time so it takes place asynchronously once started.
225
+ //
226
+ files.load = closure load() {
227
+ update()
228
+ }
229
+
230
+ // update
231
+ //
232
+ // Update our project storage tree and tell the watchers about any changes.
233
+ //
234
+ files.update = closure update() {
235
+ opBeginOrReschedule(closure(callback) {
236
+ files.fs.tree("", false, function (error, tree, local updated, changes) {
237
+ if error { // need better error reporting
238
+ ErrorDebuglog("FileManager.update failed", error)
239
+ return (callback(error))
240
+ }
241
+ files.tree = tree
242
+ files.pathsep = tree.data.pathsep
243
+ updated = flatten(tree)
244
+ changes = difftree(updated)
245
+ if changes
246
+ watch.notify("folder", changes)
247
+ schedGitRefresh() // schedule the initial git update
248
+ callback(false, files.nodes)
249
+ })
250
+ })
251
+ }
252
+
253
+ // fileList
254
+ //
255
+ // Return an array of files in the project by local pathname. The first element is the root and
256
+ // will have a children array for tree-structured traversal.
257
+ // Each path object has:
258
+ // {
259
+ // path: -- relative pathname string
260
+ // name: -- just our node name
261
+ // [children:] -- array of child path objects, if a directory
262
+ // }
263
+ //
264
+ files.fileList = function fileList() {
265
+ files.nodes
266
+ }
267
+
268
+ // waitFileList
269
+ //
270
+ // Wait until the filelist is loaded, and then callback or return.
271
+ //
272
+ files.waitFileList = closure waitFileList(callback) {
273
+ if callback
274
+ future(function() { apply(callback, files.loadPending.wait()) }).run()
275
+ else
276
+ files.loadPending.wait()
277
+ }
278
+
279
+ // findConfig
280
+ //
281
+ // Make a find configuration dictionary defining a search pattern and parameters. This can be
282
+ // duplicated or modified freely.
283
+ //
284
+ files.findConfig = function findConfig() {
285
+ { // ### these should come from files.fs (?)
286
+ pattern: ""
287
+ fixed: true
288
+ ignoreCase: true
289
+ returnText: true
290
+ }
291
+ }
292
+
293
+ // findRegex
294
+ //
295
+ // Return a RegExp for the specified findConfig, or false if invalid.
296
+ //
297
+ files.findRegex = function findRegex(config, global, local pattern, flags, matchRE) { // ### move to files.fs (?)
298
+ pattern = config.pattern
299
+ flags = ""
300
+ if config.fixed
301
+ pattern = pattern.replace(RegExp("\\.|\\0|\\\\|\\[|\\||\\(|\\)|\\*|\\+|\\?|\\{", "g"), function(match) {
302
+ "\\".concat(match) // escape all special RegExp characters
303
+ })
304
+ if global
305
+ flags = "g"
306
+ if config.ignoreCase
307
+ flags = flags.concat("i")
308
+ try {
309
+ matchRE = RegExp(pattern, flags)
310
+ } catch {
311
+ if true {
312
+ debuglog("files.findRegex: invalid pattern", pattern)
313
+ return (false)
314
+ }
315
+ }
316
+ matchRE
317
+ }
318
+
319
+ // findLines
320
+ //
321
+ // Find matching lines for the specified findConfig. This returns a standard result tuple, which
322
+ // on success will provide an output dictionary listing the matching files and lines. The output
323
+ // dictionary is structured as follows:
324
+ // {
325
+ // config: <findConfig> -- the configuration we used for the search
326
+ // files: [ -- array of files having results
327
+ // { node: <local> -- file node
328
+ // lines: [] } ] -- array of integer line numbers in that file, zero-based
329
+ // }
330
+ // What we really want is indexed search, but since there isn't time for that right now this will
331
+ // use grepLines or dirSearch if they are available, and otherwise read the files and apply the
332
+ // find pattern to each line.
333
+ //
334
+ files.findLines = closure findLines(config, local startMS, output, fileCount, foundFileCount, lineCount) {
335
+
336
+ // processGrep
337
+ // Apply the specified search method to our directory tree.
338
+ function processGrep(method, local curfile, curlines, error, line, lines) {
339
+ fileCount = files.nodes.length
340
+ `(error, lines) = method("", config)
341
+ if error
342
+ debuglog("search failed:", ErrorString(error))
343
+ else {
344
+ function startFile(path) {
345
+ if path {
346
+ curfile = files.nodes[pathIndex(path)]
347
+ if !curfile
348
+ debuglog("files.findLines: unknown path", path) }
349
+ else
350
+ curfile = false
351
+ curlines = []
352
+ }
353
+ function doneFile() {
354
+ if curfile && curlines.length > 0 {
355
+ output.files.push({
356
+ node: curfile
357
+ lines: curlines
358
+ })
359
+ ++foundFileCount }
360
+ }
361
+ startFile()
362
+ for line in lines {
363
+ ++lineCount
364
+ if line.0 != curfile.path {
365
+ doneFile()
366
+ startFile(line.0)
367
+ }
368
+ curlines.push([line.1, line.2])
369
+ }
370
+ doneFile() }
371
+ }
372
+
373
+ startMS = milliseconds()
374
+ output = {
375
+ config: config
376
+ files: []
377
+ }
378
+ fileCount = 0
379
+ foundFileCount = 0
380
+ lineCount = 0
381
+
382
+ if files.fs.grepLines
383
+ processGrep(files.fs.grepLines)
384
+ else if files.fs.dirSearch
385
+ processGrep(files.fs.dirSearch)
386
+ else {
387
+ asyncArray(files.nodes, 5, closure(node, index, elements, local error, lines) {
388
+ ++fileCount
389
+ `(error, lines) = files.fs.readLines(node, config)
390
+ if error
391
+ debuglog("cannot search", node, ErrorString(error))
392
+ else if lines.length > 0 {
393
+ ++foundFileCount
394
+ output.files.push({
395
+ node: node
396
+ lines: lines
397
+ })
398
+ lineCount += lines.length
399
+ }
400
+ }).wait()
401
+ }
402
+ output.elapsedMS = milliseconds()-startMS // elapsed time in milliseconds
403
+ output.foundCount = lineCount // number of lines containing the pattern
404
+ output.foundFileCount = foundFileCount // number of files containing the pattern
405
+ output
406
+ }
407
+
408
+ // gitRefresh
409
+ //
410
+ // Refresh the git status of the project folder, and return a list of the changes.
411
+ //
412
+ files.gitRefresh = closure gitRefresh() {
413
+ if !files.fs.gitStatus
414
+ return // no git checking available
415
+ opBeginOrReschedule(closure(callback) {
416
+ willgit = true
417
+ files.fs.gitStatus(false, function (error, statlist, local changed, delgit, unknownFiles, unknownFileStatus, changes, classID) {
418
+ if error {
419
+ files.gitvalid = false
420
+ return (callback(error))
421
+ }
422
+ files.gitvalid = true
423
+
424
+ // setParentStatus
425
+ // Set the git status of the parents of a node. This is not used to clear status.
426
+
427
+ function setParentStatus(node, local parpath, parnode) {
428
+ parpath = parentPath(node)
429
+ if parpath != "" {
430
+ hadgit[parpath] = 1
431
+ delgit[parpath] = undefined
432
+ parnode = files.nodes[pathIndex(parpath)]
433
+ if parnode.gitstat != node.gitstat && parnode.gitstat != "**" {
434
+ if !parnode.gitstat
435
+ parnode.gitstat = node.gitstat // directory status becomes child status
436
+ else
437
+ parnode.gitstat = "**" // directory has diverse child status
438
+ changed.push(parnode) }
439
+ setParentStatus(parnode) } } // must visit all parents each time to ensure hadgit/delgit are complete
440
+
441
+ // setStatus
442
+ // Set or clear the status of a node.
443
+
444
+ function setStatus(path, status, local node) {
445
+ if (node = files.nodes[pathIndex(path)]) {
446
+ if node.gitstat != status {
447
+ node.gitstat = status
448
+ changed.push(node) }
449
+ if status // if no status then parent will be set directly
450
+ setParentStatus(node) } // always visit all parents
451
+ else {
452
+ unknownFiles.push(path)
453
+ unknownFileStatus[path] = status }
454
+ }
455
+
456
+ changed = []
457
+ unknownFiles = []
458
+ unknownFileStatus = {}
459
+ delgit = hadgit
460
+ hadgit = {}
461
+ willgit = false
462
+ for item in statlist {
463
+ hadgit[item.path] = 1
464
+ delgit[item.path] = undefined
465
+ setStatus(item.path, item.status)
466
+ }
467
+ if unknownFiles.length > 0 { // add any deleted files that git knows about
468
+ files.fs.util.treeAddFiles(unknownFiles, files.tree)
469
+ changes = difftree(flatten(files.tree))
470
+ for item in changes.added { // update node and parent status
471
+ item.gitstat = unknownFileStatus[item.path]
472
+ hadgit[item.path] = 1
473
+ delgit[item.path] = undefined
474
+ setParentStatus(item) }
475
+ }
476
+ if changes
477
+ classID = "folder"
478
+ else {
479
+ classID = "git" // status up date only
480
+ changes = { }
481
+ }
482
+ for item in delgit {
483
+ if item.deleted { // file that previously had deleted status is now gone
484
+ if !changes.deleted
485
+ changes.deleted = []
486
+ changes.deleted.push(item) } // not actually deleted at this time, but filtered in UI
487
+ else
488
+ setStatus(item, false) }
489
+ if changed.length > 0
490
+ changes.changed = changed
491
+ if changes.added.length > 0 || changes.changed.length > 0
492
+ watch.notify(classID, changes)
493
+ callback(false, files.nodes)
494
+ })
495
+ })
496
+ }
497
+
498
+ // schedGitRefresh
499
+ //
500
+ // Schedule a git refresh. This ensures we only do one refresh per time interval.
501
+ //
502
+ files.schedGitRefresh = function schedGitRefresh() {
503
+ if !willgit {
504
+ willgit = true
505
+ future(gitRefresh, 1000) // one scheduled refresh per 1000 msec
506
+ }
507
+ }
508
+
509
+ // path2node
510
+ //
511
+ // Return the node for a path, or false if not in the tree.
512
+ //
513
+ files.path2node = function path2node(path) {
514
+ files.nodes[pathIndex(path)]
515
+ }
516
+
517
+ // delete
518
+ //
519
+ // Delete a file or directory.
520
+ //
521
+ files.delete = function delete(node, local parpath, options, result) {
522
+ parpath = parentPath(node)
523
+ if node.children
524
+ options = { recursive: true }
525
+ result = files.fs.delete(node.path, options)
526
+ refreshDir(parpath)
527
+ result
528
+ }
529
+
530
+ // duplicate
531
+ //
532
+ // Duplicate a file or directory.
533
+ //
534
+ files.duplicate = function duplicate(node, local parpath, result) {
535
+ parpath = parentPath(node)
536
+ result = files.fs.util.duplicate(node.path)
537
+ refreshDir(parpath)
538
+ result
539
+ }
540
+
541
+ // newFile
542
+ //
543
+ // Create a new empty file with an appropriate default name.
544
+ //
545
+ files.newFile = function newFile(node, local parpath, result) {
546
+ if node.children
547
+ parpath = node.path // create file in selected folder
548
+ else
549
+ parpath = parentPath(node) // create file in same folder as selected file
550
+ result = files.fs.util.newFile(parpath)
551
+ refreshDir(parpath)
552
+ result
553
+ }
554
+
555
+ // newFolder
556
+ //
557
+ // Create a new directory with an appropriate default name.
558
+ //
559
+ files.newFolder = function newFolder(node, local parpath, result) {
560
+ if node.children
561
+ parpath = node.path // create folder in selected folder
562
+ else
563
+ parpath = parentPath(node) // create folder in same folder as selected file
564
+ result = files.fs.util.newFolder(parpath)
565
+ refreshDir(parpath)
566
+ result
567
+ }
568
+
569
+ // rename
570
+ //
571
+ // Rename a file or directory to the new name.
572
+ //
573
+ files.rename = function rename(node, newname, local parpath, result) {
574
+ parpath = parentPath(node)
575
+ result = files.fs.copy(node.path, newname, { move: true })
576
+ refreshDir(parpath)
577
+ result
578
+ }
579
+
580
+ // reveal
581
+ //
582
+ // Reveal a file or directory in the host shell.
583
+ //
584
+ files.reveal = function reveal(node) {
585
+ files.fs.reveal(node.path)
586
+ }
587
+
588
+ // store
589
+ //
590
+ // Return a fimaStore object for the specified file, creating it if needed.
591
+ //
592
+ files.store = function store(node) {
593
+ if files.stores[node.path]
594
+ return
595
+ files.stores[node.path] = fimaStore(files, path2node(node.path))
596
+ }
597
+
598
+ // stored
599
+ //
600
+ // Return a fimaStore object for the specified file if it already exists.
601
+ //
602
+ files.stored = function stored(node) {
603
+ files.stores[node.path]
604
+ }
605
+
606
+ // annotate
607
+ //
608
+ // Annotate a node with an editor annotations array.
609
+ //
610
+ files.annotate = function annotate(path, annotations, local node) {
611
+ node = path2node(path)
612
+ if node {
613
+ node.annotations = annotations
614
+ watch.notify("annotations", { changed: [node] })
615
+ }
616
+ }
617
+
618
+ // clearAnnotations
619
+ //
620
+ // Clear editor annotations from all nodes.
621
+ //
622
+ files.clearAnnotations = function clearAnnotations(local changes, node) {
623
+ changes = []
624
+ for node in files.nodes
625
+ if node.annotations {
626
+ node.annotations = false
627
+ changes.push(node)
628
+ }
629
+ if changes.length > 0
630
+ watch.notify("annotations", { changed: changes })
631
+ }
632
+
633
+ // finis
634
+
635
+ files
636
+ };
637
+
638
+
639
+ /*
640
+ * fimaStore
641
+ *
642
+ * A fimaStore object is responsible for storage services for a particular file being edited.
643
+ * This includes reading and writing the contents to and from the edit buffer, updating on changes or
644
+ * determining that a conflict exists between changes in storage and editor, etc. A fimaStore should
645
+ * be referenced independent of file location, so that someday it will be possible to accommodate
646
+ * renames and path changes without interrupting the editing.
647
+ *
648
+ */
649
+
650
+ closure fimaStore(fm, node, local store) {
651
+ store = new(object, this)
652
+ store.fm = fm
653
+ store.fs = fm.fs
654
+ store.uuid = UUID()
655
+ store.name = node.name
656
+ store.path = node.path
657
+ store.deleted = node.deleted
658
+
659
+ // hashData
660
+ //
661
+ // Hash data of unknown type.
662
+ //
663
+ function hashData(data, local hash) {
664
+ try {
665
+ hash = HashMD5(data)
666
+ } catch {
667
+ if true
668
+ hash = 0x5fa5 // this data is not hashable
669
+ }
670
+ hash
671
+ }
672
+
673
+ // node
674
+ //
675
+ // Return the store's node.
676
+
677
+ store.node = function getnode() {
678
+ node
679
+ }
680
+
681
+ // read
682
+ //
683
+ // Read contents of a file into a string, updating our hash record of the stored version.
684
+ //
685
+ store.read = closure read(options, callback) {
686
+ store.fs.readFile(store.path, options, function(error, data, local content) {
687
+ if !error {
688
+ store.storageHash = hashData(data)
689
+ content = { hash: store.storageHash }
690
+ if string(data)
691
+ content.text = data
692
+ else
693
+ content.binary = data }
694
+ callback(error, content)
695
+ })
696
+ }
697
+
698
+ // write
699
+ //
700
+ // Write a file with contents of a string, updating our hash record of the stored version.
701
+ //
702
+ store.write = closure write(data, options, callback) {
703
+ if !options
704
+ options = { }
705
+ options.md5 = store.storageHash
706
+ store.fs.writeFile(store.path, data, options, function(error, result) {
707
+ if !error
708
+ store.storageHash = result = hashData(data) // ### get this from result?
709
+ fm.schedGitRefresh()
710
+ callback(error, result)
711
+ })
712
+ }
713
+
714
+ // exists
715
+ //
716
+ // True iff the file still exists in the tree.
717
+ //
718
+ store.exists = function exists() {
719
+ store.fm.path2node(store.path)
720
+ }
721
+
722
+ // gitLoadFileHead
723
+ //
724
+ // Get the contents of the file at the git Head, or report an error. This
725
+ //
726
+ store.gitLoadFileHead = closure gitLoadFileHead(callback) {
727
+ store.fs.gitLog(store.path, function (error, loglist, local data, content) {
728
+ if error && store.fm.gitvalid
729
+ callback(Error("gitLoadFileHead failed", error))
730
+ else if !(loglist.length >= 1)
731
+ callback(false, { }) // no revisions to report
732
+ else {
733
+ `(error, data) = store.fs.gitShowFile(store.path, loglist.0.hash)
734
+ if error
735
+ callback(Error("gitLoadFileHead couldn't retrieve revision", loglist.0.hash, error))
736
+ else {
737
+ store.storageHashRight = hashData(data)
738
+ content = { hash: store.storageHashRight }
739
+ if string(data)
740
+ content.text = data
741
+ else
742
+ content.binary = data
743
+ callback(false, content) }
744
+ }
745
+ })
746
+ }
747
+
748
+ // finis
749
+
750
+ store
751
+ };
752
+
753
+
754
+ /*
755
+ * fimaInit
756
+ *
757
+ * Initialize the file manager component.
758
+ *
759
+ */
760
+
761
+ function fimaInit(local manifest) {
762
+
763
+ manifest = `(FileManager, fimaStore, fimaInit)
764
+
765
+ Naan.module.build(module.id, "fileManager", function(modobj, compobj) {
766
+ require("./storage.nlg")
767
+ commonWatching = require("../common/watching.nlg")
768
+ compobj.manifest = manifest
769
+ module.exports.FileManager = FileManager
770
+ })
771
+
772
+ }();