@naanlang/naan 1.0.3 → 1.0.6

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 (44) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +8 -6
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +30 -6
  5. package/dist/naan.min.js +4 -4
  6. package/frameworks/browser/https_request.nlg +120 -0
  7. package/frameworks/browser/sworker.js +29 -39
  8. package/frameworks/browser/terminals.nlg +6 -4
  9. package/frameworks/browser/workers.nlg +13 -6
  10. package/frameworks/client/apiclient.nlg +1 -1
  11. package/frameworks/client/psm_client.nlg +80 -32
  12. package/frameworks/common/common.nlg +104 -26
  13. package/frameworks/common/preferences.nlg +1 -0
  14. package/frameworks/common/watching.nlg +1 -0
  15. package/frameworks/node/apiserver.nlg +9 -5
  16. package/frameworks/node/filesystem.nlg +89 -31
  17. package/frameworks/node/gitter.nlg +27 -6
  18. package/frameworks/node/https_request.nlg +119 -0
  19. package/frameworks/node/node.nlg +1 -0
  20. package/frameworks/node/psm_server.nlg +29 -7
  21. package/frameworks/project/build.nlg +13 -7
  22. package/frameworks/project/proj_cliser.nlg +11 -3
  23. package/frameworks/project/proj_console.nlg +13 -4
  24. package/frameworks/project/proj_folder.nlg +10 -1
  25. package/frameworks/project/proj_lambda.nlg +13 -4
  26. package/frameworks/project/proj_static.nlg +11 -3
  27. package/frameworks/project/projects.nlg +102 -20
  28. package/lib/browser/env_web.js +32 -8
  29. package/lib/core/naanlib.js +4 -4
  30. package/package.json +2 -1
  31. package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -0
  32. package/plugins/serviceAws/aws/aws-sdk.min.js +2 -88
  33. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +7 -6
  34. package/plugins/serviceAws/aws_dynamo.nlg +128 -45
  35. package/plugins/serviceAws/aws_lambda.nlg +11 -4
  36. package/plugins/serviceAws/aws_s3.nlg +44 -14
  37. package/plugins/serviceAws/dbt_aws.nlg +29 -36
  38. package/plugins/serviceAws/psm_aws.nlg +21 -16
  39. package/plugins/serviceAws/serviceAws.nlg +6 -4
  40. package/test/harness.nlg +3 -1
  41. package/test/test_01_core.nlg +5 -5
  42. package/test/test_02_context.nlg +4 -4
  43. package/test/test_03_jsinterop.nlg +9 -3
  44. package/plugins/serviceAws/aws_cognito.nlg +0 -76
@@ -0,0 +1,120 @@
1
+ /*
2
+ * https_browser.nlg
3
+ *
4
+ * Https operations for the browser.
5
+ *
6
+ * column positioning: // // !
7
+ *
8
+ * Copyright (c) 2021-2022 by Richard C. Zulch
9
+ *
10
+ */
11
+
12
+
13
+ /*
14
+ * HttpsApiRequest
15
+ *
16
+ * Perform an API request from the browser. This operates the same as HttpsApiRequest for NodeJS.
17
+ *
18
+ * Options:
19
+ * {
20
+ * method: <string> // HTTP method (defaults to GET/PUT)
21
+ * query: <dictionary> // query variables added to the URL
22
+ * putdata: <data> // data to put
23
+ * contentType: <string> // sets Content-Type header, otherwise auto-determined
24
+ * encoding: <string> // "binary" to receive binary data as arrayBuffer
25
+ * range: <string> // sets range header
26
+ * headers: [`(key, value)] // add header(s) to the request, overriding defaults
27
+ * }
28
+ *
29
+ */
30
+
31
+ closure HttpsApiRequest(url, options,
32
+ local urlq, fetchOptions, putdata, error, response, contentType, content) {
33
+ if !options
34
+ options = { }
35
+ if options.query
36
+ urlq = url.concat(EncodeQuery("?", options.query))
37
+ else
38
+ urlq = url
39
+ putdata = options.putdata
40
+ fetchOptions = {
41
+ mode: 'cors'
42
+ cache: 'no-cache'
43
+ credentials: 'same-origin'
44
+ headers: { }
45
+ redirect: 'follow'
46
+ referrerPolicy: 'no-referrer'
47
+ }
48
+ if options.method
49
+ fetchOptions.method = options.method
50
+ else if putdata
51
+ fetchOptions.method = "POST"
52
+ else
53
+ fetchOptions.method = "GET"
54
+ if putdata {
55
+ if options.contentType
56
+ fetchOptions.headers['Content-Type'] = options.contentType
57
+ else if jsTypedArray(options.putdata)
58
+ fetchOptions.headers['Content-Type'] = "application/octet-stream"
59
+ else if member(typeof(options.putdata), `(dictionary, array, xobject)) {
60
+ fetchOptions.headers['Content-Type'] = "application/json"
61
+ `(error, putdata) = JsonStringify(options.putdata)
62
+ if error
63
+ return (list(Error("HttpsApiRequest encode:", url)))
64
+ }
65
+ fetchOptions.body = putdata
66
+ }
67
+ if options.range
68
+ fetchOptions.headers.Range = options.range
69
+ if array(options.headers)
70
+ for item in options.headers
71
+ fetchOptions.headers[item.0] = item.1
72
+ //
73
+ // perform the fetch and return when complete
74
+ //
75
+ `(error, response) = await(js.w.fetch(urlq, fetchOptions))
76
+ if error
77
+ return (list(Error("HttpsApiRequest fetch failed:", error, url)))
78
+ contentType = response.headers.get("content-type")
79
+ if response.status < 200 || response.status >= 300
80
+ return (list(Error("HttpsApiRequest status:", url, response.status, {
81
+ status: response.status
82
+ })))
83
+ if contentType.startsWith("application/json") {
84
+ `(error, content) = await(response.text())
85
+ if !error
86
+ `(error, content) = JsonParse(content)
87
+ if error
88
+ error = Error("HttpsApiRequest decode:", url, error)
89
+ else
90
+ content = new(content)
91
+ if array(content) { // deencapsulate to get API result
92
+ content = totuple(content)
93
+ error = content.0
94
+ content = content.1
95
+ }
96
+ }
97
+ else if options.encoding == "binary"
98
+ `(error, content) = await(response.arrayBuffer())
99
+ else
100
+ `(error, content) = await(response.text())
101
+ list(error, content)
102
+ };
103
+
104
+
105
+ /*
106
+ * https_browserInit
107
+ *
108
+ * Initialize HTTPS request operations for browsers.
109
+ *
110
+ */
111
+
112
+ function https_browserInit(local manifest) {
113
+ manifest = `(HttpsApiRequest, https_browserInit)
114
+
115
+ Naan.module.build(module.id, "https_request", function(modobj, compobj) {
116
+ require("./browser.nlg")
117
+ compobj.manifest = manifest
118
+ modobj.exports.HttpsApiRequest = HttpsApiRequest
119
+ })
120
+ }();
@@ -27,10 +27,8 @@
27
27
  * knowledge of which ones are responding. If a request takes longer than a second to respond then
28
28
  * this executes a validate operation, which eliminates any clients that have gone away and rejects
29
29
  * their pending operations.
30
- * One potential problem is stale caches, where a new publisher (e.g. NaanIDE) or new version
31
- * arises but this worker is still serving old data. The solution is that every publisher provides
32
- * its ID and version along with the mesage port. If that is different from the previous version then
33
- * the old caches are cleared and data re-fetched.
30
+ * One potential problem is stale caches. When an older version is discovered then we notify the
31
+ * client and invalidate the old cache.
34
32
  *
35
33
  * column positioning: // // !
36
34
  *
@@ -38,7 +36,7 @@
38
36
  *
39
37
  */
40
38
 
41
- var CurrentCacheName = "Naanlang 1.0.3-1";
39
+ var CurrentCacheName = "Naanlang-1.0.6-2";
42
40
 
43
41
 
44
42
  //
@@ -76,8 +74,8 @@ var waitingForInit = []; // initialization functions, or
76
74
  return; // already done
77
75
  var waiters = waitingForInit;
78
76
  waitingForInit = false; // no more waiters allowed
79
- for (var waiter in waiters)
80
- waiter(); // call each waiter
77
+ for (var wdex in waiters)
78
+ waiters[wdex](); // call each waiter
81
79
  }
82
80
 
83
81
  // self.onmessage events
@@ -91,18 +89,13 @@ var waitingForInit = []; // initialization functions, or
91
89
  var pubVersion = event.data.hereIsMyVersion;
92
90
  var sourceId = event.source.id; // client id of sender of message
93
91
  msgPorts[sourceId] = msgport;
94
- var newCacheName = "Naanlang".concat("-", pubID, "-", pubVersion);
95
- console.log("[1.0.3-1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
- if (CurrentCacheName != newCacheName) {
97
- for (var srcid in msgPorts)
98
- msgPorts[srcid].postMessage({
99
- id: "upgrade",
100
- previous: "1.0.3-1",
101
- version: pubVersion
102
- });
103
- }
104
- ClearCaches(newCacheName)
105
- .then(Reaper); // clean up obsolete info
92
+ console.log("[1.0.6-2] received new msgport for", sourceId, pubID, "-", pubVersion);
93
+ if (pubVersion != "1.0.6-2")
94
+ msgport.postMessage({ // notify new version available
95
+ id: "upgrade",
96
+ version: "1.0.6-2"
97
+ });
98
+ Reaper(); // clean up obsolete info
106
99
 
107
100
  // msgport.onmessage events
108
101
  //
@@ -114,7 +107,7 @@ var waitingForInit = []; // initialization functions, or
114
107
  if (msg.id == "response")
115
108
  processResponse(msg);
116
109
  else if (msg.id == "text")
117
- console.log("[1.0.3-1] msg received:", msg.text);
110
+ console.log("[1.0.6-2] msg received:", msg.text);
118
111
  };
119
112
 
120
113
  // send text to IDE log
@@ -125,7 +118,7 @@ var waitingForInit = []; // initialization functions, or
125
118
  // don't clutter the log
126
119
  msgport.postMessage({
127
120
  id: "text",
128
- text: "port received by 1.0.3-1",
121
+ text: "port received by 1.0.6-2",
129
122
  });
130
123
  */
131
124
  if (--pending === 0)
@@ -142,13 +135,13 @@ var waitingForInit = []; // initialization functions, or
142
135
  includeUncontrolled: true
143
136
  }).then(function(clientList) {
144
137
  clientList.every(function(client) {
145
- console.log("[1.0.3-1] requesting new msgport for", client.id);
138
+ console.log("[1.0.6-2] requesting new msgport for", client.id);
146
139
  ++pending;
147
140
  client.postMessage({ // tell client(s) we need this fetch source
148
141
  msg: "Naan_need_fetch_port",
149
142
  });
150
143
  });
151
- setTimeout(doneInit, 1000); // release fetches
144
+ setTimeout(doneInit, 10000); // release fetches
152
145
  });
153
146
 
154
147
  // processResponse
@@ -188,12 +181,12 @@ function Reaper() {
188
181
  clients[clientList[clidex].id] = clientList[clidex];
189
182
  for (var sourceId in msgPorts)
190
183
  if (!clients[sourceId]) {
191
- console.log("[1.0.3-1] source gone:", sourceId);
184
+ console.log("[1.0.6-2] source gone:", sourceId);
192
185
  delete msgPorts[sourceId]; // no longer a source
193
186
  }
194
187
  for (var clientId in fetchPorts)
195
188
  if (!clients[clientId]) {
196
- console.log("[1.0.3-1] client gone:", clientId);
189
+ console.log("[1.0.6-2] client gone:", clientId);
197
190
  delete fetchPorts[clientId]; // no longer a client
198
191
  }
199
192
  for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
@@ -218,25 +211,22 @@ function Reaper() {
218
211
  /*
219
212
  * ClearCaches
220
213
  *
221
- * Clear obsolete caches in favor of the new name, returning a promise.
214
+ * Clear obsolete caches, returning a promise.
222
215
  *
223
216
  */
224
217
 
225
- function ClearCaches(newCacheName) {
226
- if (CurrentCacheName == newCacheName)
227
- return (Promise.resolve()); // no change
228
- CurrentCacheName = newCacheName;
218
+ function ClearCaches() {
229
219
  var promise = caches.keys().then(function(cacheNames) { // delete old cache entries
230
220
  return (Promise.all(
231
221
  cacheNames.map(function(cacheName) {
232
- if (cacheName !== CurrentCacheName) {
233
- console.log('[1.0.3-1] deleting old cache:', cacheName);
222
+ if (cacheName != CurrentCacheName) {
223
+ console.log('[1.0.6-2] deleting old cache:', cacheName);
234
224
  return (caches.delete(cacheName));
235
225
  }
236
226
  })
237
227
  ));
238
228
  }).then(function() { // claim all clients
239
- console.log('[1.0.3-1] claiming clients for version', CurrentCacheName);
229
+ console.log('[1.0.6-2] claiming clients');
240
230
  return (self.clients.claim());
241
231
  });
242
232
  return (promise);
@@ -279,7 +269,7 @@ function GetClientResponse(event, urlpath) {
279
269
  msgport.postMessage({
280
270
  id: "fetch",
281
271
  seq: seqno,
282
- version: "1.0.3-1",
272
+ version: "1.0.6-2",
283
273
  request: {
284
274
  method: event.request.method,
285
275
  url: event.request.url
@@ -327,7 +317,7 @@ function GetClientResponse(event, urlpath) {
327
317
  */
328
318
 
329
319
  self.addEventListener('install', function(event) {
330
- console.log("[1.0.3-1] install");
320
+ console.log("[1.0.6-2] install");
331
321
  self.skipWaiting();
332
322
  });
333
323
 
@@ -366,7 +356,7 @@ self.addEventListener('fetch', function(event) {
366
356
  }
367
357
  else
368
358
  promise = fetch(event.request).catch(function (e) {
369
- console.log("[1.0.3-1] fetch failed", e);
359
+ console.log("[1.0.6-2] fetch failed", e);
370
360
  return (new Response(undefined, {
371
361
  status: 404,
372
362
  statusText: "Fetch Failed"
@@ -395,16 +385,16 @@ self.addEventListener('fetch', function(event) {
395
385
  */
396
386
 
397
387
  self.addEventListener('activate', function(event) {
398
- console.log("[1.0.3-1] activate");
388
+ console.log("[1.0.6-2] activate");
399
389
  self.clients.matchAll({ // for debugging, list controlled clients
400
390
  includeUncontrolled: true
401
391
  }).then(function(clientList) {
402
392
  var urls = clientList.map(function(client) {
403
393
  return (client.url);
404
394
  });
405
- console.log('[1.0.3-1] matching clients:', urls.join(', '));
395
+ console.log('[1.0.6-2] matching clients:', urls.join(', '));
406
396
  });
407
- var promise = ClearCaches(CurrentCacheName);
397
+ var promise = ClearCaches();
408
398
  if (event.waitUntil)
409
399
  event.waitUntil(promise);
410
400
  });
@@ -77,7 +77,7 @@ closure TermHost(track, api, name, workerID, options,
77
77
  if !target.written
78
78
  target.term.WriteLn("(reconnected)")
79
79
  }, 1000).run()
80
- else
80
+ else if message.respOp
81
81
  debuglog("unknown host response message:", message.respOp)
82
82
  }
83
83
 
@@ -602,14 +602,17 @@ closure findIDEtarget(track, name, workerID, naancont, local target) {
602
602
  *
603
603
  */
604
604
 
605
- closure termIDE(track, name, workerID, naancont, local target, connected) {
605
+ closure termIDE(track, name, workerID, naancont, title, local target, connected) {
606
606
  target = findIDEtarget(track, name, workerID, naancont)
607
607
  if target { // new naancont on existing target
608
608
  target.updateController(naancont)
609
+ target.title = title
610
+ track.update(target)
609
611
  return (target)
610
612
  }
611
613
  target = runningExecutors.ExecutorBase(track, "Local", name)
612
614
  target.name = name
615
+ target.title = title
613
616
  target.workerID = workerID
614
617
  target.naancont = naancont
615
618
  connected = []
@@ -756,7 +759,6 @@ closure termIDE(track, name, workerID, naancont, local target, connected) {
756
759
  /*
757
760
  * termInstallVirtualWatcher
758
761
  *
759
- * column positioning: // // !
760
762
  * Install a persistent watcher to note when virtual terminals come and go, and manage their
761
763
  * execution target lifetimes.
762
764
  *
@@ -766,7 +768,7 @@ closure termInstallVirtualWatcher(track, naancont) {
766
768
 
767
769
  function watchVsites(msg, local error, target) {
768
770
  if msg.op == "VsiteOpen" {
769
- `(error, target) = termIDE(track, msg.name, "NaanVsite", msg.naancont)
771
+ `(error, target) = termIDE(track, msg.name, "NaanVsite", msg.naancont, msg.title)
770
772
  target.attention()
771
773
  }
772
774
  else if msg.op == "VsiteClose" {
@@ -55,8 +55,13 @@ closure ServiceWorker(pubID, pubVersion, local serv, callback) {
55
55
  version: msg.version
56
56
  })
57
57
  } else if msg.id == "upgrade" {
58
- if msg.version != msg.previous // same version: new install
59
- debuglog("service worker version upgrade to:", msg.version, "from", msg.previous)
58
+ if serv.upgrade != msg.version {
59
+ serv.upgrade = msg.version // only report once
60
+ callback(false, {
61
+ message: "upgrade"
62
+ version: msg.version
63
+ })
64
+ }
60
65
  }
61
66
  else if msg.id == "text"
62
67
  debuglog("sw[".concat(serv.scope, "]: "), msg.text) // handy remote debug capability
@@ -182,7 +187,7 @@ closure ServiceWorker(pubID, pubVersion, local serv, callback) {
182
187
  *
183
188
  */
184
189
 
185
- closure ScopedServiceWorker(pubID, pubVersion, local scoper, result) {
190
+ closure ScopedServiceWorker(pubID, pubVersion, callback, local scoper, result) {
186
191
  scoper = new(object, ScopedServiceWorker)
187
192
  scoper.scopes = {}
188
193
  scoper.serv = ServiceWorker(pubID, pubVersion)
@@ -232,7 +237,8 @@ closure ScopedServiceWorker(pubID, pubVersion, local scoper, result) {
232
237
  response.body = data
233
238
  scoper.serv.msgport.postMessage(response)
234
239
  return
235
- }
240
+ } else if data.message == "upgrade"
241
+ callback(data.version)
236
242
  })
237
243
  if result.0 {
238
244
  ErrorDebuglog("ScopedServiceWorker failed", result)
@@ -308,6 +314,7 @@ closure TrackedScope(track, scope, fs, rootpath, local trope, result) {
308
314
  mimeType = {
309
315
  "css": "text/css",
310
316
  "csv": "text/csv",
317
+ "gif": "image/gif",
311
318
  "htm": "text/html",
312
319
  "html": "text/html",
313
320
  "ico": "image/x-icon",
@@ -365,8 +372,8 @@ closure TrackedScope(track, scope, fs, rootpath, local trope, result) {
365
372
  *
366
373
  */
367
374
 
368
- closure TrackServiceWorkers(track, pubID, pubVersion, local error) {
369
- `(error, serviceWorker) = ScopedServiceWorker(pubID, pubVersion)
375
+ closure TrackServiceWorkers(track, pubID, pubVersion, callback, local error) {
376
+ `(error, serviceWorker) = ScopedServiceWorker(pubID, pubVersion, callback)
370
377
  if error
371
378
  ErrorDebugLog("TrackServiceWorkers: cannot load service worker", error)
372
379
  track.register(TrackedScope, "V-Site")
@@ -37,7 +37,7 @@ closure MakeNideAPIclient(url, guid, local client) {
37
37
  request.addEventListener("error", function reqFailed(event, local errtext) {
38
38
  if request.status == 0
39
39
  errtext = "connectivity"
40
- ellse
40
+ else
41
41
  errtext = "request failed"
42
42
  error = Error(errtext, {
43
43
  status: request.status
@@ -22,6 +22,50 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
22
22
  view = new(object, this)
23
23
  view.api = api
24
24
  view.rootpath = rootpath
25
+
26
+ // data
27
+ //
28
+ // Return the filesystem data, i.e. the tree data field. The minimum information is:
29
+ // platform -- e.g. "darwin" or "win32"
30
+ // semantics -- e.g. "win32" or "posix"
31
+ // pathsep -- e.g. / or \
32
+
33
+ view.data = closure data(callback) {
34
+ if !callback
35
+ return (syncAdapter(data))
36
+ params = {
37
+ path: ""
38
+ op: "tree"
39
+ depthlimit: 0
40
+ }
41
+ api.psmRemote(params, false, function(error, tree) {
42
+ if error
43
+ debuglog("psmcFsView.data: can't get initial filesystem data:", ErrorString(error))
44
+ else {
45
+ view.fsdata = tree.data
46
+ if rootpath == ""
47
+ rootpath = tree.data.pathsep
48
+ }
49
+ callback(error, tree.data)
50
+ })
51
+ }
52
+
53
+ // pathInit
54
+ //
55
+ // Initialize our path module for the applicable path type.
56
+
57
+ closure pathInit(local result) {
58
+ result = view.data()
59
+ if result.0
60
+ pathmod = false
61
+ else {
62
+ if view.fsdata.pathsep == "\\"
63
+ pathmod = JSpath.win32
64
+ else
65
+ pathmod = JSpath.posix
66
+ false
67
+ }
68
+ }
25
69
 
26
70
  // psmRemote
27
71
  //
@@ -30,10 +74,14 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
30
74
  closure psmRemote(path, op, options, putdata, callback, local params, item) {
31
75
  if !callback
32
76
  return (syncAdapter(psmRemote, path, op, options, putdata))
77
+ if !pathmod && pathInit()
78
+ return // failed to initialize path module
33
79
  if !string(path)
34
80
  return (asyncResult(callback, Error("invalid path:", typeof(path))))
81
+ if rootpath != ""
82
+ path = pathmod.resolve(rootpath, path)
35
83
  params = {
36
- path: JSpath.resolve(rootpath, path)
84
+ path: path
37
85
  op: op
38
86
  }
39
87
  for item in options
@@ -59,47 +107,39 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
59
107
 
60
108
  // path
61
109
  //
62
- // The path sub-object remotes calls to the NodeJS path module.
110
+ // Redirect path calls to module chosen for filesystem semantics. It is possible for this to fail
111
+ // on first connection to the filesystem, in which case we return false for the path result.
112
+ // Throwing an error would complicate our callers and testing. This way there's a small chance
113
+ // that a transient error will give a bad path, but that will be detected downstream.
63
114
 
64
115
  view.path = new(object, this)
65
- view.path[".undefined"] = closure path args {
66
- closure (selector, arglist, local error, info) {
67
- if !pathmod {
68
- `(error, data) = view.data()
69
- if error
70
- return (list(error))
71
- if info.pathsep == "\\"
72
- pathmod = JSpath.win32
73
- else
74
- pathmod = JSpath.posix
75
- }
76
- xapply(pathmod, selector, arglist)
77
- } (args.0, args.-1)
116
+ view.path[".undefined"] = function path args {
117
+ if !pathmod
118
+ pathInit()
119
+ xapply(pathmod, args.0, args.-1)
78
120
  }
79
121
 
80
- // data
122
+ // folderPath
81
123
  //
82
- // Return the filesystem data, i.e. the tree data field. The minimum information is:
83
- // platform -- e.g. "darwin" or "win32"
84
- // semantics -- e.g. "win32" or "posix"
85
- // pathsep -- e.g. / or \
86
-
87
- view.data = closure data(callback) {
88
- if !callback
89
- return (syncAdapter(data))
90
- psmRemote("", "tree", { depthlimit: 0 }, false, function(error, tree) {
91
- view.fsdata = tree.data
92
- callback(error, tree.data)
93
- })
124
+ // Return the path of the specified special folder:
125
+ // "HomeDir"
126
+ // "TempDir"
127
+ // "PackageDir"
128
+ // The returned path is relative to our root if within it, or absolute otherwise.
129
+
130
+ view.folderPath = function folderPath(folderID, callback) {
131
+ psmRemote("", "folderPath", {
132
+ folderid: folderID
133
+ }, false, callback)
94
134
  }
95
135
 
96
136
  // readLines
97
137
  //
98
138
  // Read a lines of a file at the specified path with options:
99
- // matchRE: -- JavaScript regex
100
- // ignoreCase: -- ignore case during match
101
- // invert: -- return lines that DO NOT match the regex
102
- // returnText: -- return text of matched lines
139
+ // matchRE: -- JavaScript regex
140
+ // ignoreCase: -- ignore case during match
141
+ // invert: -- return lines that DO NOT match the regex
142
+ // returnText: -- return text of matched lines
103
143
  // When using a matchRE, the return value is an array of [ lineNumber, text ] pairs if
104
144
  // returnText is specified, or just an array of one-based line numbers otherwise.
105
145
  //
@@ -248,6 +288,10 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
248
288
  // Search the specified file or recursive directory.
249
289
 
250
290
  view.dirSearch = function dirSearch(path, options, callback) {
291
+ if options.excludeDirs {
292
+ options = new(options)
293
+ options.excludeDirs = JSONstringify(options.excludeDirs)
294
+ }
251
295
  psmRemote(path, "dirSearch", options, false, callback)
252
296
  }
253
297
 
@@ -317,6 +361,10 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
317
361
  // Grep the specified file or recursive directory.
318
362
 
319
363
  view.grepLines = function grepLines(path, options, callback) {
364
+ if options.excludeDirs {
365
+ options = new(options)
366
+ options.excludeDirs = JSONstringify(options.excludeDirs)
367
+ }
320
368
  psmRemote(path, "grepLines", options, false, callback)
321
369
  }
322
370