@naanlang/naan 1.3.2 → 1.4.1

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 (56) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/index.js +25 -15
  4. package/dist/naan.min.js +5 -5
  5. package/frameworks/browser/sworker.js +23 -23
  6. package/frameworks/browser/terminals.nlg +26 -11
  7. package/frameworks/browser/ws_client.nlg +1 -1
  8. package/frameworks/client/psm_client.nlg +3 -1
  9. package/frameworks/client/relaycon.nlg +3 -2
  10. package/frameworks/common/common.nlg +5 -2
  11. package/frameworks/common/preferences.nlg +1 -1
  12. package/frameworks/node/apiserver.nlg +3 -3
  13. package/frameworks/node/filesystem.nlg +5 -4
  14. package/frameworks/node/psm_server.nlg +1 -1
  15. package/frameworks/node/shell.nlg +134 -0
  16. package/frameworks/node/worker.nlg +104 -46
  17. package/frameworks/project/build.nlg +1 -2
  18. package/frameworks/project/jszip.js +0 -0
  19. package/frameworks/project/projects.nlg +1 -1
  20. package/frameworks/running/debugnub.nlg +102 -12
  21. package/frameworks/running/debugutil.nlg +30 -13
  22. package/frameworks/running/executors.nlg +64 -28
  23. package/frameworks/running/running.nlg +85 -3
  24. package/frameworks/running/sourcecode.nlg +3 -1
  25. package/frameworks/running/taskexec.nlg +24 -4
  26. package/frameworks/storage/dbt_pouch.nlg +30 -17
  27. package/frameworks/storage/file_manager.nlg +1 -1
  28. package/frameworks/storage/psm.nlg +11 -1
  29. package/frameworks/storage/psm_dbtables.nlg +21 -22
  30. package/frameworks/storage/resources.nlg +12 -4
  31. package/frameworks/storage/storage.nlg +2 -2
  32. package/lib/browser/env_web.js +6 -6
  33. package/lib/browser/env_webworker.js +2 -0
  34. package/lib/core/naanlib.js +5 -5
  35. package/lib/env_nodeworker.js +2 -0
  36. package/package.json +1 -1
  37. package/plugins/gitClient/gitClient.nlg +3 -3
  38. package/plugins/gitClient/gitutils.nlg +2 -2
  39. package/plugins/nodeLib/node_https.nlg +2 -2
  40. package/plugins/openSearch/openSearch.nlg +2 -2
  41. package/plugins/serviceAws/lambda_naan_control.nlg +3 -3
  42. package/plugins/serviceAws/psm_aws.nlg +4 -2
  43. package/plugins/serviceAws/serviceAws.nlg +1 -1
  44. package/plugins/serviceGC/gc_api.nlg +5 -5
  45. package/plugins/serviceGitHub/gitops.nlg +4 -3
  46. package/plugins/serviceGitHub/psm_github.nlg +1 -1
  47. package/plugins/serviceGitHub/serviceGitHub.nlg +1 -1
  48. package/plugins/serviceGitLab/gitops.nlg +2 -1
  49. package/plugins/serviceGitLab/psm_gitlab.nlg +2 -2
  50. package/plugins/serviceGitLab/serviceGitLab.nlg +1 -1
  51. package/plugins/serviceYC/yc_vm.nlg +1 -1
  52. package/test/harness.nlg +9 -9
  53. package/test/test_01_core.nlg +24 -2
  54. package/test/test_02_context.nlg +2 -2
  55. package/test/test_07_lingo.nlg +3 -3
  56. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +0 -33
@@ -36,7 +36,7 @@
36
36
  *
37
37
  */
38
38
 
39
- var CurrentCacheName = "Naanlang-1.3.2+1";
39
+ var CurrentCacheName = "Naanlang-1.4.1+1";
40
40
 
41
41
 
42
42
  //
@@ -92,18 +92,18 @@ var terminated; // flag that this service worker
92
92
  var pubVersion = event.data.hereIsMyVersion;
93
93
  var sourceId = event.source.id; // client id of sender of message
94
94
  msgPorts[sourceId] = msgport;
95
- console.log("[1.3.2+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
- if (pubVersion != "1.3.2+1") {
95
+ console.log("[1.4.1+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
+ if (pubVersion != "1.4.1+1") {
97
97
  if (upgradeMsgPorts) { // if not activated
98
- console.log("[1.3.2+1] update pending for", sourceId);
98
+ console.log("[1.4.1+1] update pending for", sourceId);
99
99
  upgradeMsgPorts[sourceId] = msgport;
100
100
  }
101
101
  else {
102
102
  msgport.postMessage({ // notify new version available
103
103
  id: "upgrade",
104
- version: "1.3.2+1"
104
+ version: "1.4.1+1"
105
105
  });
106
- console.log("[1.3.2+1] update sent for:", sourceId);
106
+ console.log("[1.4.1+1] update sent for:", sourceId);
107
107
  }
108
108
  }
109
109
  Reaper(); // clean up obsolete info
@@ -134,10 +134,10 @@ var terminated; // flag that this service worker
134
134
  msgport.onmessage = function(msg) {
135
135
  msg = msg.data;
136
136
  if (msg.id == "skipWaiting") { // try to activate this SW now
137
- console.log("[1.3.2+1] attempting skipWaiting");
137
+ console.log("[1.4.1+1] attempting skipWaiting");
138
138
  self.skipWaiting();
139
139
  } else if (msg.id == "terminate") { // termiante this SW now
140
- console.log("[1.3.2+1] terminated");
140
+ console.log("[1.4.1+1] terminated");
141
141
  terminate();
142
142
  } else if (msg.id == "abortURL") { // abort an I/O transaction
143
143
  var href = new URL(msg.url).href;
@@ -150,7 +150,7 @@ var terminated; // flag that this service worker
150
150
  } else if (msg.id == "response") // response from fetch request
151
151
  processResponse(msg);
152
152
  else if (msg.id == "text") // just log some text
153
- console.log("[1.3.2+1] msg received:", msg.text);
153
+ console.log("[1.4.1+1] msg received:", msg.text);
154
154
  };
155
155
 
156
156
  // send text to IDE log
@@ -161,7 +161,7 @@ var terminated; // flag that this service worker
161
161
  // don't clutter the log
162
162
  msgport.postMessage({
163
163
  id: "text",
164
- text: "port received by 1.3.2+1",
164
+ text: "port received by 1.4.1+1",
165
165
  });
166
166
  */
167
167
  if (--pending === 0)
@@ -178,7 +178,7 @@ var terminated; // flag that this service worker
178
178
  includeUncontrolled: true
179
179
  }).then(function(clientList) {
180
180
  clientList.every(function(client) {
181
- console.log("[1.3.2+1] requesting new msgport for", client.id);
181
+ console.log("[1.4.1+1] requesting new msgport for", client.id);
182
182
  ++pending;
183
183
  client.postMessage({ // tell client(s) we need this fetch source
184
184
  msg: "Naan_need_fetch_port",
@@ -224,12 +224,12 @@ function Reaper() {
224
224
  clients[clientList[clidex].id] = clientList[clidex];
225
225
  for (var sourceId in msgPorts)
226
226
  if (!clients[sourceId]) {
227
- console.log("[1.3.2+1] source gone:", sourceId);
227
+ console.log("[1.4.1+1] source gone:", sourceId);
228
228
  delete msgPorts[sourceId]; // no longer a source
229
229
  }
230
230
  for (var clientId in fetchPorts)
231
231
  if (!clients[clientId]) {
232
- console.log("[1.3.2+1] client gone:", clientId);
232
+ console.log("[1.4.1+1] client gone:", clientId);
233
233
  delete fetchPorts[clientId]; // no longer a client
234
234
  }
235
235
  for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
@@ -263,16 +263,16 @@ function ClearCaches() {
263
263
  return (Promise.all(
264
264
  cacheNames.map(function(cacheName) {
265
265
  if (cacheName != CurrentCacheName) {
266
- console.log('[1.3.2+1] deleting old cache:', cacheName);
266
+ console.log('[1.4.1+1] deleting old cache:', cacheName);
267
267
  return (caches.delete(cacheName));
268
268
  }
269
269
  })
270
270
  ));
271
271
  }).then(function() { // claim all clients
272
- console.log('[1.3.2+1] claiming clients');
272
+ console.log('[1.4.1+1] claiming clients');
273
273
  return (self.clients.claim());
274
274
  }).then(function() {
275
- console.log('[1.3.2+1] clients claimed');
275
+ console.log('[1.4.1+1] clients claimed');
276
276
  return (Promise.resolve(true));
277
277
  });
278
278
  return (promise);
@@ -315,7 +315,7 @@ function GetClientResponse(event, urlpath) {
315
315
  msgport.postMessage({
316
316
  id: "fetch",
317
317
  seq: seqno,
318
- version: "1.3.2+1",
318
+ version: "1.4.1+1",
319
319
  request: {
320
320
  method: event.request.method,
321
321
  url: event.request.url
@@ -363,7 +363,7 @@ function GetClientResponse(event, urlpath) {
363
363
  */
364
364
 
365
365
  self.addEventListener('install', function(event) {
366
- console.log("[1.3.2+1] install");
366
+ console.log("[1.4.1+1] install");
367
367
  self.skipWaiting();
368
368
  });
369
369
 
@@ -376,20 +376,20 @@ self.addEventListener('install', function(event) {
376
376
  */
377
377
 
378
378
  self.addEventListener('activate', function(event) {
379
- console.log("[1.3.2+1] activate");
379
+ console.log("[1.4.1+1] activate");
380
380
  self.clients.matchAll({ // for debugging, list controlled clients
381
381
  includeUncontrolled: true
382
382
  }).then(function(clientList) {
383
383
  var urls = clientList.map(function(client) {
384
384
  return (client.url);
385
385
  });
386
- console.log('[1.3.2+1] matching clients:', urls.join(', '));
386
+ console.log('[1.4.1+1] matching clients:', urls.join(', '));
387
387
  });
388
388
  var promise = ClearCaches().then(function() {
389
389
  for (var sourceId in upgradeMsgPorts) {
390
390
  upgradeMsgPorts[sourceId].postMessage({ // notify new version available
391
391
  id: "upgrade",
392
- version: "1.3.2+1"
392
+ version: "1.4.1+1"
393
393
  });
394
394
  }
395
395
  upgradeMsgPorts = false;
@@ -421,7 +421,7 @@ self.addEventListener('fetch', function(event) {
421
421
  var promise;
422
422
  var url = new URL(request.url);
423
423
  var nocache = request.method != "GET"
424
- || url.search.length !== 0 && url.searchParams.get("naanver") !== "7ea12b22a1787cca6650606cb36818d6"
424
+ || url.search.length !== 0 && url.searchParams.get("naanver") !== "348dfbc247edf982d7b9f50b51d55de4"
425
425
  || request.headers.get('range');
426
426
  if (url.pathname.startsWith("/run/")) {
427
427
  nocache = true;
@@ -455,7 +455,7 @@ self.addEventListener('fetch', function(event) {
455
455
  statusText: "Request Cancelled"
456
456
  }));
457
457
  }
458
- console.log("[1.3.2+1] fetch failed", request.url, e);
458
+ console.log("[1.4.1+1] fetch failed", request.url, e);
459
459
  return (new Response(undefined, {
460
460
  status: 404,
461
461
  statusText: "Fetch Failed"
@@ -15,7 +15,7 @@
15
15
  *
16
16
  */
17
17
 
18
- workers;
18
+ gWorkers;
19
19
 
20
20
 
21
21
  /*
@@ -431,9 +431,9 @@ closure TermHost(track, api, name, options, local target, nlregex, xtarg)
431
431
 
432
432
  closure TermLocal(track, name, options, local target, nlregex, listener)
433
433
  {
434
- global(js, window, workers)
435
- if !dictionary(workers)
436
- workers = {}
434
+ global(js, window, gWorkers)
435
+ if !dictionary(gWorkers)
436
+ gWorkers = {}
437
437
  options = merge({
438
438
  type: "Local"
439
439
  }, options)
@@ -456,14 +456,15 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
456
456
  // ### tell target we are closing (if needed)
457
457
  })
458
458
  listener = window
459
+ target.guid = 1
459
460
  } else {
460
461
  if not window.Worker
461
462
  return (list(Error("Local environment does not support workers")))
462
463
  target.worker = xnew(window.Worker, "env_webworker.js")
463
464
  listener = target.worker
464
465
  target.guid = UUID()
465
- workers[target.guid] = target
466
466
  }
467
+ gWorkers[target.guid] = target
467
468
 
468
469
  // onmessage
469
470
  // A message was received from the worker.
@@ -551,6 +552,8 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
551
552
  id: "start",
552
553
  state: false // resume state, if we have any
553
554
  altcmd: options.startup.initcmds
555
+ workerID: target.workerID
556
+ workerGUID: target.guid
554
557
  })
555
558
  target.started = true
556
559
  } else if msg.id == "targetsend" {
@@ -667,10 +670,21 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
667
670
  };
668
671
 
669
672
 
673
+ /*
674
+ * WorkerConnect
675
+ *
676
+ * Return the existing web worker for a workerGUID, or false if it does not exist.
677
+ *
678
+ */
679
+
680
+ function WorkerConnect(workerGUID) {
681
+ gWorkers[workerGUID]
682
+ };
683
+
684
+
670
685
  /*
671
686
  * termMainProxy
672
687
  *
673
- * column positioning: // // !
674
688
  * Make a browser proxy representing the main thread. This runs inside the worker and allows it
675
689
  * to do remote execution with Main.
676
690
  *
@@ -736,7 +750,7 @@ closure WorkerToMain(local nideRunning, track) {
736
750
  error = Error("WorkerToMain only available in browser workers")
737
751
  return (list(error))
738
752
  }
739
- nideRunning = require("frameworks/running/executors.nlg")
753
+ nideRunning = require("naanlib:frameworks/running/executors.nlg")
740
754
  track = nideRunning.ExecutorTracker()
741
755
  track.register(mainProxy, "proxy")
742
756
  track.spawn("proxy") // returns result tuple
@@ -769,7 +783,7 @@ closure findIDEtarget(track, name, workerID, naancont, local target) {
769
783
  */
770
784
 
771
785
  closure termIDE(track, name, workerID, naancont, title, local target, connected) {
772
- global(workers)
786
+ global(js, gWorkers)
773
787
  target = findIDEtarget(track, name, workerID, naancont)
774
788
  if target { // new naancont on existing target
775
789
  target.updateController(naancont)
@@ -790,7 +804,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
790
804
  //
791
805
  function replyHook(data) {
792
806
  if data._guid
793
- workers[data._guid].worker.postMessage({
807
+ gWorkers[data._guid].worker.postMessage({
794
808
  id: "targetreceive"
795
809
  data: data
796
810
  })
@@ -969,14 +983,15 @@ closure termInstallVirtualWatcher(track, naancont) {
969
983
  */
970
984
 
971
985
  function termInit(local manifest) {
972
- manifest = `(TermHost, TermLocal, termMainProxy, WorkerToMain, findIDEtarget, termIDE,
973
- termInstallVirtualWatcher, termInit)
986
+ manifest = `(TermHost, TermLocal, WorkerConnect, termMainProxy, WorkerToMain, findIDEtarget,
987
+ termIDE, termInstallVirtualWatcher, termInit)
974
988
 
975
989
  Naan.module.build(module.id, "terminals", function(modobj, compobj) {
976
990
  require("./browser.nlg")
977
991
  compobj.manifest = manifest
978
992
  modobj.exports.TermLocal = TermLocal
979
993
  modobj.exports.TermHost = TermHost
994
+ modobj.exports.WorkerConnect = WorkerConnect
980
995
  modobj.exports.WorkerToMain = WorkerToMain
981
996
  })
982
997
  } ();
@@ -51,7 +51,7 @@ closure WebSocket(options, local wsock) {
51
51
 
52
52
  // onopen
53
53
  // The WebSocket connection is now open.
54
- wsock.ws.onopen = closure onopen(e, local spawner) {
54
+ wsock.ws.onopen = closure onopen(e) {
55
55
  pending.signal(list(false, { ok: true }))
56
56
  options.onopen(e)
57
57
  }
@@ -430,7 +430,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
430
430
  */
431
431
 
432
432
  closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
433
- global()
433
+ global(App)
434
434
  connClassID = "NodeFS"
435
435
  connector = new(object, this)
436
436
  connector.psm = psm
@@ -625,6 +625,8 @@ closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
625
625
  //
626
626
  // Connect to the specified service.
627
627
  connector.connect = function connect(resID, service, args, local error, creds, api, rootpath) {
628
+ if !resID || args && !tuple(args)
629
+ return (list(Error("invalid arguments")))
628
630
  `(error, creds) = connector.access(resID)
629
631
  if !error {
630
632
  if creds == "HostFS"
@@ -43,7 +43,7 @@ wsMod;; // websocket module
43
43
 
44
44
  closure RelayCon(host, secret, options, local relay, nlregex)
45
45
  {
46
- global(execsMod, wsMod)
46
+ global(App, execsMod, wsMod)
47
47
  relay = new(object, this)
48
48
  options = merge({
49
49
  name: "NaanIDE Server"
@@ -85,7 +85,7 @@ closure RelayCon(host, secret, options, local relay, nlregex)
85
85
  })
86
86
  result = relay.wscon.connect()
87
87
  if !result.0
88
- sendControl("spawn", { name: "NaanIDE Server" }) // ensure worker is connected
88
+ sendControl("spawn", { name: options.name }) // ensure worker is connected
89
89
  else if options.debug
90
90
  ErrorDebuglog("WebSocket connect failed:", result.0)
91
91
  result
@@ -301,6 +301,7 @@ function hocoInit(local manifest) {
301
301
  manifest = `(RelayCon, hocoInit)
302
302
 
303
303
  Naan.module.build(module.id, "relaycon", function(modobj, compobj) {
304
+ require("./client.nlg")
304
305
  execsMod = require("../running/executors.nlg")
305
306
  if js.w
306
307
  wsMod = require("../browser/ws_client.nlg")
@@ -247,7 +247,9 @@ macro LoadComponentOnDemand(functionKeys, componentPath, local modobj, key) {
247
247
  componentPath = eval(componentPath)
248
248
  if name(functionKeys)
249
249
  functionKeys = list(functionKeys)
250
- for key in functionKeys
250
+ for key in functionKeys {
251
+ if modobj.exports[key]
252
+ continue // already defined
251
253
  closure (key, path, local exproc) {
252
254
  function loadComponent args { // called when exported function first called
253
255
  exproc = require(path)[key]
@@ -257,6 +259,7 @@ macro LoadComponentOnDemand(functionKeys, componentPath, local modobj, key) {
257
259
  }
258
260
  modobj.exports[key] = loadComponent
259
261
  } (key, componentPath)
262
+ }
260
263
  true
261
264
  };
262
265
 
@@ -274,7 +277,7 @@ macro LoadComponentOnDemand(functionKeys, componentPath, local modobj, key) {
274
277
  * LiveImport() function to a module's exports.
275
278
  *
276
279
  * To use LiveImport, call from the importing module as follows:
277
- * require("frameworks/common/common.nlg").LiveImport(<fixproc>);
280
+ * require("naanlib:frameworks/common/common.nlg").LiveImport(<fixproc>);
278
281
  * The optional <fixproc> is called after the imports in case the foreign module wants to fix up
279
282
  * anything after the import:
280
283
  * fixproc(exporterMod);
@@ -88,7 +88,7 @@ closure Preferences(nicc, dbname, local prefs, error, data) {
88
88
  folder = "plugins/"
89
89
  path = folder.concat("plugins.cfg")
90
90
  pending = new(nonce)
91
- nicc.api.readFile(path, function(error, req, event, local config) {
91
+ nicc.api.readFile(path, function(error, req, event) {
92
92
  if error
93
93
  ErrorDebuglog("Can't read ".concat(path), error)
94
94
  else
@@ -30,7 +30,7 @@
30
30
  */
31
31
 
32
32
  closure APIServer(options, local server, chroot) {
33
- global(JSpath, js, bodyParser, express, psmServerMod, workerMod)
33
+ global(App, JSpath, js, bodyParser, express, psmServerMod, workerMod)
34
34
  apisLoadLibs()
35
35
  server = new(object, this)
36
36
  server.app = express()
@@ -116,7 +116,7 @@ closure APIServer(options, local server, chroot) {
116
116
  return (list(error))
117
117
  }
118
118
  server.hostfs = hostfs
119
- workerMod.WorkerController(server)
119
+ server.workco = workerMod.WorkerController(server)
120
120
  list(false, port)
121
121
  }
122
122
 
@@ -550,7 +550,7 @@ closure WebSocketServer(server, local websocks) {
550
550
  */
551
551
 
552
552
  closure WesCon(server, websocks, socket, request, local wescon) {
553
- global()
553
+ global(js)
554
554
  wescon = new(object, this)
555
555
  wescon.socket = socket
556
556
  wescon.info = {
@@ -1137,6 +1137,11 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1137
1137
  })
1138
1138
  spawn.on("close", function(code) {
1139
1139
  output.exitCode = code // zero on success; null on killed
1140
+ completed(false, output)
1141
+ })
1142
+ spawn.on("exit", function(code, signal) {
1143
+ output.exitCode = code // if exited
1144
+ output.signalName = signal // if terminated
1140
1145
  })
1141
1146
  spawn.stderr.on("error", function(err) {
1142
1147
  completed(Error("stderr stream failed:", err))
@@ -1155,8 +1160,6 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1155
1160
  call(options.errproc, output)
1156
1161
  if linerOut
1157
1162
  linerErr = false // let end-of-out do the completion
1158
- else
1159
- completed(false, output)
1160
1163
  })
1161
1164
  linerErr.on("readable", function (local line) {
1162
1165
  if options.errproc
@@ -1179,8 +1182,6 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1179
1182
  call(options.outproc)
1180
1183
  if linerErr
1181
1184
  linerOut = false // let end-of-err do the completion
1182
- else
1183
- completed(false, output)
1184
1185
  })
1185
1186
  linerOut.on("readable", function (local line) {
1186
1187
  if options.outproc
@@ -525,7 +525,7 @@ function MakeServerPSM(api, name, local importPSM, error, path, psm) {
525
525
  if !error {
526
526
  App.psm = psm
527
527
  psmsFsConnector(App.psm, api)
528
- require("frameworks/storage/dbt_pouch.nlg").DbConnector(App.psm) // Pouch databases as filesystems
528
+ require("naanlib:frameworks/storage/dbt_pouch.nlg").DbConnector(App.psm) // Pouch databases as filesystems
529
529
  } else
530
530
  ErrorDebuglog("Cannot create PSM", error)
531
531
  App.psm
@@ -0,0 +1,134 @@
1
+ /*
2
+ * shell.nlg
3
+ * Naanlib/frameworks/node
4
+ *
5
+ * Shell access and utilities for the host OS.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2024 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * ShellConnect
16
+ *
17
+ * Executing this installs itself in the current dialect to provide a REPL shell escape, returning a
18
+ * standard result tuple. This sends lines starting with ! to the shell with "bash -c". The result is
19
+ * output as text and also assigned to the $$ variable in the active namespace.
20
+ *
21
+ * Options:
22
+ * {
23
+ * fs: <filesystem> // optional: the filesystem object to use
24
+ * cwd: <string> // optional: Current Working Directory
25
+ * derive: <string> // optional: dialect to derive from, or "" for no !
26
+ * }
27
+ *
28
+ */
29
+
30
+ closure ShellConnect(options, local shob, error, fs, xparser, shelly) {
31
+ global(js, compress)
32
+ shob = new(object, this)
33
+
34
+ // readline
35
+ // read one line of text from the source
36
+ function readline(source, local cc, output) {
37
+ output = ""
38
+ while cc = source.readchar() {
39
+ output = output.concat(cc)
40
+ if cc == "\n"
41
+ break }
42
+ output
43
+ }
44
+
45
+ // exec
46
+ //
47
+ // Execute a shell command with optional current-working-directory.
48
+ //
49
+ shob.exec = closure exec(cmd, args, cwd, local pending, report, xlparams) {
50
+ pending = new(nonce)
51
+ report = {
52
+ stdout: []
53
+ stderr: []
54
+ }
55
+ xlparams = {
56
+ cmdargs: args
57
+ outproc: function(output, liner, kill, local line) {
58
+ while line = liner.read() {
59
+ report.stdout.push(line)
60
+ printline("\x1b[33m${line}\x1b[0m") // amber for stdout
61
+ }
62
+ }
63
+ errproc: function(output, liner, kill, local line) {
64
+ while line = liner.read() {
65
+ report.stderr.push(line)
66
+ printline("\x1b[36m${line}\x1b[0m") // cyan for stderr
67
+ }
68
+ }
69
+ }
70
+ if cwd
71
+ xlparams.execops = { cwd: cwd }
72
+ else if options.cwd
73
+ xlparams.execops = { cwd: options.cwd }
74
+ fs.execLines(cmd, xlparams, function(error, result) {
75
+ evalactive(list(`assign, `(compress, "$$"), report)) // assign to $$ in active namespace
76
+ pending.signal(list(error, merge(result, report)))
77
+ })
78
+ pending.wait()
79
+ }
80
+
81
+ // shellparser
82
+ // intercept command-line parsing and look for a !shell escape
83
+ function shellparser(source, grammar, operators, local token) {
84
+ token = source.tokenread()
85
+ if token.atom == `! {
86
+ source.tokenlast(true) // "consume" the ! token
87
+ text = readline(source)
88
+ source.tokenpush({ // don't show any output
89
+ atom: `;;
90
+ loc: tuple(0, text.length)
91
+ })
92
+ exec("bash", ["-c", text], js.d) // default directory: js.d
93
+ }
94
+ else {
95
+ source.tokenpush(token)
96
+ xparser(source, grammar, operators)
97
+ }
98
+ }
99
+
100
+ if options.fs
101
+ fs = options.fs
102
+ else
103
+ `(error, fs) = Filesystem("") // error not possible
104
+ if options.derive != "" { // install hook
105
+ if string(options.derive)
106
+ shelly = Dialect.derive(options.derive)
107
+ else
108
+ shelly = Dialect
109
+ xparser = shelly.parser
110
+ shelly.parser = shellparser
111
+ }
112
+
113
+ // finis
114
+ shob
115
+ };
116
+
117
+
118
+ /*
119
+ * shInit
120
+ *
121
+ * Initialize the node module.
122
+ *
123
+ *
124
+ */
125
+
126
+ function shInit(local manifest) {
127
+ manifest = `(ShellConnect, shInit)
128
+
129
+ Naan.module.build(module.id, "shell", function(modobj, compobj) {
130
+ require("./filesystem.nlg")
131
+ compobj.manifest = manifest
132
+ modobj.exports.ShellConnect = ShellConnect
133
+ })
134
+ } ();