@naanlang/naan 1.0.2 → 1.0.5
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.
- package/LICENSE.md +1 -1
- package/README.md +10 -8
- package/bin/index.js +2 -2
- package/dist/env_web.js +172 -22
- package/dist/naan.min.js +6 -6
- package/frameworks/browser/https_request.nlg +120 -0
- package/frameworks/browser/sworker.js +299 -91
- package/frameworks/browser/terminals.nlg +28 -13
- package/frameworks/browser/workers.nlg +27 -12
- package/frameworks/client/apiclient.nlg +17 -4
- package/frameworks/client/psm_client.nlg +184 -81
- package/frameworks/common/common.nlg +105 -25
- package/frameworks/common/preferences.nlg +1 -0
- package/frameworks/common/watching.nlg +1 -0
- package/frameworks/node/apiserver.nlg +31 -15
- package/frameworks/node/filesystem.nlg +230 -46
- package/frameworks/node/gitter.nlg +27 -6
- package/frameworks/node/https_request.nlg +119 -0
- package/frameworks/node/node.nlg +1 -0
- package/frameworks/node/psm_server.nlg +113 -37
- package/frameworks/project/build.nlg +40 -23
- package/frameworks/project/proj_cliser.nlg +11 -3
- package/frameworks/project/proj_console.nlg +13 -4
- package/frameworks/project/proj_folder.nlg +10 -1
- package/frameworks/project/proj_lambda.nlg +13 -4
- package/frameworks/project/proj_static.nlg +11 -3
- package/frameworks/project/projects.nlg +128 -40
- package/frameworks/running/debugnub.nlg +2 -5
- package/frameworks/running/executors.nlg +1 -0
- package/frameworks/storage/csv.nlg +120 -0
- package/frameworks/storage/dbt_pouch.nlg +41 -25
- package/frameworks/storage/psm.nlg +108 -28
- package/frameworks/storage/psm_dbtables.nlg +7 -3
- package/frameworks/storage/resources.nlg +30 -2
- package/lib/browser/env_web.js +170 -20
- package/lib/browser/env_webworker.js +1 -1
- package/lib/browser/require.js +1 -1
- package/lib/core/naanlib.js +6 -6
- package/package.json +2 -1
- package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -0
- package/plugins/serviceAws/aws/aws-sdk.min.js +2 -88
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +8 -7
- package/plugins/serviceAws/aws_dynamo.nlg +715 -148
- package/plugins/serviceAws/aws_dynextra.nlg +294 -0
- package/plugins/serviceAws/aws_lambda.nlg +11 -4
- package/plugins/serviceAws/aws_s3.nlg +44 -14
- package/plugins/serviceAws/dbt_aws.nlg +58 -45
- package/plugins/serviceAws/psm_aws.nlg +61 -40
- package/plugins/serviceAws/serviceAws.nlg +7 -4
- package/plugins/serviceGitHub/psm_github.nlg +41 -25
- package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
- package/test/harness.nlg +3 -1
- package/test/test_01_core.nlg +5 -5
- package/test/test_02_context.nlg +4 -4
- package/test/test_03_jsinterop.nlg +9 -3
- package/plugins/serviceAws/aws_cognito.nlg +0 -76
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2016-
|
|
9
|
+
* Copyright (c) 2016-2022 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -23,6 +23,7 @@ closure ProjectManager(locate, track, dbname, local error, proj) {
|
|
|
23
23
|
`(error, proj) = storageResources.ResourceManager(dbname, "Projects/")
|
|
24
24
|
if error
|
|
25
25
|
return(list(error))
|
|
26
|
+
proj.configLocal = proj.vault("configlocal") // local config storage
|
|
26
27
|
proj.locate = locate // storage locations
|
|
27
28
|
proj.track = track // execution tracking
|
|
28
29
|
proj.types = []
|
|
@@ -150,40 +151,36 @@ closure projConnector(projman, projtype, local connector, watch) {
|
|
|
150
151
|
connector.label = projtype.classLabel
|
|
151
152
|
connector.vault = projman.vault(projtype.classID)
|
|
152
153
|
watch = projman.watchable(connector)
|
|
153
|
-
|
|
154
|
-
// hashResourceID
|
|
155
|
-
//
|
|
156
|
-
// Make a stable hash based on the resource credentials that we can persist or share between
|
|
157
|
-
// different domains accessing the same resource.
|
|
158
|
-
connector.hashResourceID = function hashResourceID(resource, local ident) {
|
|
159
|
-
ident = resource.name.concat(resource.locationID, resource.path)
|
|
160
|
-
HashSHA256(ident)
|
|
161
|
-
}
|
|
162
154
|
|
|
163
155
|
// findResource
|
|
164
156
|
//
|
|
165
|
-
// Find a resource with the specified contents, returning the resID or false.
|
|
166
|
-
// is: if added, would the specified contents be redundant to an existing resource?
|
|
157
|
+
// Find a resource with the specified contents, returning the resID or false.
|
|
167
158
|
connector.findResource = function findResource(resource, local resID, resInfo) {
|
|
168
159
|
for resID in listResources().1 {
|
|
169
160
|
resInfo = access(resID).1
|
|
170
|
-
if resource.name == resInfo.name
|
|
171
|
-
&& resource.locationID == resInfo.locationID
|
|
172
|
-
&& resource.path == resInfo.path
|
|
161
|
+
if ((!resource.name || resource.name == resInfo.name)
|
|
162
|
+
&& (!resource.locationID || resource.locationID == resInfo.locationID)
|
|
163
|
+
&& (!resource.path || resource.path == resInfo.path))
|
|
173
164
|
return (resID)
|
|
174
165
|
}
|
|
175
166
|
false
|
|
176
167
|
}
|
|
177
168
|
|
|
178
|
-
//
|
|
169
|
+
// writeResource
|
|
179
170
|
//
|
|
180
|
-
// Add credentials for a named resource.
|
|
171
|
+
// Add or update credentials for a named resource. If no resID is specified this adds a new
|
|
172
|
+
// resource; otherwise it updates an existing one. The resource is a dictionary comprising these
|
|
173
|
+
// keys:
|
|
181
174
|
// name: <string> - the user visible name of the project
|
|
182
175
|
// locationID: <string> - resID of location resource where this project resides
|
|
183
176
|
// path: <string> - path to project within location
|
|
184
|
-
// Error, if non-false, is a dictionary of field
|
|
185
|
-
|
|
177
|
+
// The return value is an (error, resID) tuple. Error, if non-false, is a dictionary of field
|
|
178
|
+
// keys and error diagnostic strings.
|
|
179
|
+
connector.writeResource = function writeResource(resource, resID,
|
|
180
|
+
local errors, error, data, change) {
|
|
181
|
+
// badField - test for a bad string
|
|
186
182
|
function badField(str) { !string(str) || str.trim().length == 0 }
|
|
183
|
+
|
|
187
184
|
errors = { }
|
|
188
185
|
if badField(resource.name)
|
|
189
186
|
errors.name = "invalid name"
|
|
@@ -197,19 +194,42 @@ closure projConnector(projman, projtype, local connector, watch) {
|
|
|
197
194
|
if length(errors) > 0
|
|
198
195
|
error = errors
|
|
199
196
|
else {
|
|
200
|
-
resID
|
|
201
|
-
|
|
197
|
+
if resID {
|
|
198
|
+
`(error, data) = connector.vault.updateResource(resID, resource)
|
|
199
|
+
change = { changed: [resID] }
|
|
200
|
+
} else {
|
|
201
|
+
resID = UUID()
|
|
202
|
+
`(error, data) = connector.vault.addResource(resID, resource)
|
|
203
|
+
change = { added: [resID] }
|
|
204
|
+
}
|
|
202
205
|
if data {
|
|
203
|
-
watch.notify(projtype.classID,
|
|
204
|
-
data = resID
|
|
206
|
+
watch.notify(projtype.classID, change)
|
|
207
|
+
data = resID
|
|
208
|
+
}
|
|
205
209
|
}
|
|
206
210
|
list(error, data)
|
|
207
211
|
}
|
|
208
212
|
|
|
213
|
+
// addResource
|
|
214
|
+
//
|
|
215
|
+
// Add a new resource, returning a standard error tuple per writeResource.
|
|
216
|
+
connector.addResource = function addResource(resource) {
|
|
217
|
+
writeResource(resource)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// updateResource
|
|
221
|
+
//
|
|
222
|
+
// Update an existing resource, returning a standard error tuple per writeResource.
|
|
223
|
+
connector.updateResource = function updateResource(resID, resource) {
|
|
224
|
+
writeResource(resource, resID)
|
|
225
|
+
}
|
|
226
|
+
|
|
209
227
|
// deleteResource
|
|
210
228
|
//
|
|
211
229
|
// Delete information for a project. The return value is (error, result) tuple.
|
|
212
|
-
connector.deleteResource = function deleteResource(resID, local
|
|
230
|
+
connector.deleteResource = function deleteResource(resID, local localID) {
|
|
231
|
+
localID = resID.concat("-data")
|
|
232
|
+
projman.configLocal.deleteResource(localID) // remove local storage if there is any
|
|
213
233
|
connector.vault.deleteResource(resID)
|
|
214
234
|
watch.notify(projtype.classID, { deleted: [resID] })
|
|
215
235
|
list(false, { ok: true})
|
|
@@ -276,7 +296,7 @@ closure projConnector(projman, projtype, local connector, watch) {
|
|
|
276
296
|
*
|
|
277
297
|
* Nide.proj/
|
|
278
298
|
* nide_cliser.cfg - JSON definition of the project (see below)
|
|
279
|
-
* NaanIDE_version.txt - [default] substitution file defining 1.0.
|
|
299
|
+
* NaanIDE_version.txt - [default] substitution file defining 1.0.5-1 etc.
|
|
280
300
|
* NaanIDE_version_builds.txt - [default] contains current build number as decimal string
|
|
281
301
|
* build/ - [optional] default build output location
|
|
282
302
|
* NaanIDE.lic - [optional] defines Zulch Laboratories, Inc. and other license info
|
|
@@ -289,6 +309,10 @@ closure projConnector(projman, projtype, local connector, watch) {
|
|
|
289
309
|
* build: <build instructions dictionary>
|
|
290
310
|
* }
|
|
291
311
|
*
|
|
312
|
+
* A project configuration also stores local data that is not in any file, which is useful for
|
|
313
|
+
* items like accounts & locations, or credentials that should not be "on disk". Project data from
|
|
314
|
+
* the .cfg file is available in config.configDict, while local data is in config.localDict.
|
|
315
|
+
*
|
|
292
316
|
* Given a new projConfig instance, you can either load (which fails of the project is not valid)
|
|
293
317
|
* or create (which fails if the project already exists.) The load operation will update the where
|
|
294
318
|
* argument with the actual name and type found. Unless other noted the methods all return standard
|
|
@@ -299,9 +323,11 @@ closure projConnector(projman, projtype, local connector, watch) {
|
|
|
299
323
|
proj_configDataFolder = "Nide.proj/";
|
|
300
324
|
proj_cliserConfigFile = "nide_cliser.cfg";
|
|
301
325
|
|
|
302
|
-
closure projConfig(projman, where, local procon, fs, configpath) {
|
|
326
|
+
closure projConfig(projman, where, projID, local procon, fs, configpath) {
|
|
303
327
|
procon = new(object, this)
|
|
304
328
|
procon.name = where.name
|
|
329
|
+
if projID
|
|
330
|
+
procon.localID = projID.concat("-data")
|
|
305
331
|
|
|
306
332
|
// validate
|
|
307
333
|
//
|
|
@@ -359,6 +385,15 @@ closure projConfig(projman, where, local procon, fs, configpath) {
|
|
|
359
385
|
return (list(Error("can't parse project configuration", where.path, exception))) }
|
|
360
386
|
procon.where_name = where.name // copy name to config
|
|
361
387
|
procon.where_typeID = where.typeID // copy typeID to config
|
|
388
|
+
if procon.localID {
|
|
389
|
+
`(error, data) = projman.configLocal.accessResource(procon.localID)
|
|
390
|
+
if data
|
|
391
|
+
procon.localDict = data
|
|
392
|
+
else {
|
|
393
|
+
procon.localDict = { }
|
|
394
|
+
projman.configLocal.addResource(procon.localID, procon.localDict)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
362
397
|
list(false, procon.configDict)
|
|
363
398
|
}
|
|
364
399
|
|
|
@@ -367,6 +402,11 @@ closure projConfig(projman, where, local procon, fs, configpath) {
|
|
|
367
402
|
// Write procon.configDict to our configuration file, returning a result tuple.
|
|
368
403
|
//
|
|
369
404
|
closure writeConfig(local error, info, data) {
|
|
405
|
+
if procon.localID {
|
|
406
|
+
`(error, data) = projman.configLocal.updateResource(procon.localID, procon.localDict)
|
|
407
|
+
if error
|
|
408
|
+
debuglog("projConfig.writeConfig cannot write local storage:", ErrorString(error))
|
|
409
|
+
}
|
|
370
410
|
`(error, data) = fs.writeFile(configpath, JSONstringify(procon.configDict))
|
|
371
411
|
if !error {
|
|
372
412
|
procon.configMD5 = data
|
|
@@ -385,6 +425,8 @@ closure projConfig(projman, where, local procon, fs, configpath) {
|
|
|
385
425
|
// not already be loaded. The provided name overrides any name in the where argument above, but
|
|
386
426
|
// path and locationID must be specified. Returns a standard result tuple.
|
|
387
427
|
//
|
|
428
|
+
// ### we need to update this for optional multiple-configuraiton builds
|
|
429
|
+
//
|
|
388
430
|
procon.create = closure create(name, local error, data, configpath) {
|
|
389
431
|
if procon.loaded
|
|
390
432
|
return (list(Error("Project.create: project already loaded")))
|
|
@@ -440,7 +482,9 @@ closure projConfig(projman, where, local procon, fs, configpath) {
|
|
|
440
482
|
error = Error("project configuration not a directory", where.path)
|
|
441
483
|
else
|
|
442
484
|
`(error, data) = readConfig() }
|
|
443
|
-
if error
|
|
485
|
+
if error.code == "ENOENT"
|
|
486
|
+
return (list(Error("Project <b>".concat(where.name, "</b>:<br>not found at ", where.path))))
|
|
487
|
+
else if error
|
|
444
488
|
return (list(Error("Project.load failed", error)))
|
|
445
489
|
procon.loaded = true
|
|
446
490
|
list(false, procon.configDict)
|
|
@@ -479,15 +523,52 @@ closure projConfig(projman, where, local procon, fs, configpath) {
|
|
|
479
523
|
procon.configDict = newconfig
|
|
480
524
|
`(error, data) = writeConfig()
|
|
481
525
|
}
|
|
482
|
-
|
|
526
|
+
|
|
527
|
+
// stages
|
|
528
|
+
//
|
|
529
|
+
// Return a tuple of stages in the project configuration.
|
|
530
|
+
//
|
|
531
|
+
procon.stages = function stages() {
|
|
532
|
+
procon.configDict."build-rules".*
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// currentStage
|
|
536
|
+
//
|
|
537
|
+
// Validate and return the current stage of the project, or the first one if there isn't a
|
|
538
|
+
// previous selection, or false if no valid stages exist.
|
|
539
|
+
//
|
|
540
|
+
procon.currentStage = function currentStage(local stage, stagelist) {
|
|
541
|
+
stage = procon.localDict.currentStage
|
|
542
|
+
stagelist = stages()
|
|
543
|
+
if stage && !member(stage, stagelist)
|
|
544
|
+
stage = false
|
|
545
|
+
if stagelist && !stage
|
|
546
|
+
stage = stagelist.0
|
|
547
|
+
stage
|
|
548
|
+
}
|
|
549
|
+
|
|
483
550
|
// buildrules
|
|
484
551
|
//
|
|
485
|
-
// Get the
|
|
552
|
+
// Get the build rules for the named stage.
|
|
486
553
|
//
|
|
487
|
-
function buildrules(local rules) {
|
|
488
|
-
rules = procon.configDict."build-rules"
|
|
554
|
+
function buildrules(stage, local rules, common, rule, key) {
|
|
555
|
+
rules = procon.configDict."build-rules"[stage]
|
|
489
556
|
if !rules
|
|
490
557
|
rules = procon.configDict.build // fallback to old config structure
|
|
558
|
+
rules = new(rules)
|
|
559
|
+
common = procon.configDict."build-common"
|
|
560
|
+
if common {
|
|
561
|
+
for rule in common
|
|
562
|
+
if !rules[rule]
|
|
563
|
+
rules[rule] = common[rule] // common rule was not overridden
|
|
564
|
+
else if array(common[rule], rules[rule])
|
|
565
|
+
rules[rule] = common[rule].concat(rules[rule]) // merge common array with rule
|
|
566
|
+
else if dictionary(common[rule], rules[rule]) {
|
|
567
|
+
for key in common[rule] // merge common dictionary with rule
|
|
568
|
+
if !rules[rule][key]
|
|
569
|
+
rules[rule][key] = common[rule][key]
|
|
570
|
+
}
|
|
571
|
+
}
|
|
491
572
|
rules
|
|
492
573
|
}
|
|
493
574
|
|
|
@@ -495,16 +576,18 @@ closure projConfig(projman, where, local procon, fs, configpath) {
|
|
|
495
576
|
//
|
|
496
577
|
// Run the project by building and then launching.
|
|
497
578
|
//
|
|
498
|
-
procon.run = closure run(buildonly, local rules, builder, buildpath, error, buildnum) {
|
|
579
|
+
procon.run = closure run(stage, buildonly, local rules, builder, buildpath, error, buildnum) {
|
|
499
580
|
if !procon.loaded
|
|
500
581
|
return (list(Error("project not loaded")))
|
|
501
|
-
rules = buildrules()
|
|
582
|
+
rules = buildrules(stage)
|
|
502
583
|
if !rules || !rules.products
|
|
503
584
|
return (list(Error("build rules missing or incomplete")))
|
|
504
585
|
buildpath = fs.path.join(proj_configDataFolder, rules.products)
|
|
505
586
|
rules.name = procon.where_name
|
|
506
587
|
rules.typeID = procon.where_typeID
|
|
507
|
-
|
|
588
|
+
if !rules.operation
|
|
589
|
+
rules.operation = "debug"
|
|
590
|
+
`(error, builder) = Builder(rules, rules.operation, fs, "", buildpath)
|
|
508
591
|
if error
|
|
509
592
|
return (list(error))
|
|
510
593
|
`(error, buildnum) = builder.increment()
|
|
@@ -530,16 +613,21 @@ closure projConfig(projman, where, local procon, fs, configpath) {
|
|
|
530
613
|
//
|
|
531
614
|
// Publish the project to its configured location.
|
|
532
615
|
//
|
|
533
|
-
procon.publish = closure publish(local rules, publish, error, outfs, inpath) {
|
|
534
|
-
rules = buildrules()
|
|
535
|
-
publish =
|
|
536
|
-
|
|
616
|
+
procon.publish = closure publish(stage, local rules, where, publish, error, outfs, inpath) {
|
|
617
|
+
rules = buildrules(stage)
|
|
618
|
+
publish = rules.publish
|
|
619
|
+
where = procon.localDict.publish[stage].where
|
|
620
|
+
if !rules || !where
|
|
537
621
|
return (list(Error("no project publish location configured")))
|
|
538
|
-
`(error, outfs) = projman.locate.connect(
|
|
622
|
+
`(error, outfs) = projman.locate.connect(where.locationID, "NideFS", where.path)
|
|
539
623
|
if error
|
|
540
624
|
return (list(Error("can't access publishing filesystem", error)))
|
|
541
625
|
inpath = fs.path.join(proj_configDataFolder, rules.products, publish.input)
|
|
542
|
-
fs.util.deepcopy(inpath, publish.sources, outfs, publish.output
|
|
626
|
+
fs.util.deepcopy(inpath, publish.sources, outfs, publish.output, {
|
|
627
|
+
erase: true
|
|
628
|
+
overwrite: true
|
|
629
|
+
follow: true
|
|
630
|
+
})
|
|
543
631
|
}
|
|
544
632
|
|
|
545
633
|
// close
|
|
@@ -217,7 +217,7 @@ closure DebugNub(local nub, debug, paused) {
|
|
|
217
217
|
// constant string, with corresponding arguments defined as follows:
|
|
218
218
|
//
|
|
219
219
|
// "startpause" - pause at start of evaluating an expression
|
|
220
|
-
// tuple -
|
|
220
|
+
// tuple - dictionary of current step info
|
|
221
221
|
// "breakpoint" - hit a breakpoint
|
|
222
222
|
// where - dictionary
|
|
223
223
|
// value - applied value
|
|
@@ -237,10 +237,7 @@ closure DebugNub(local nub, debug, paused) {
|
|
|
237
237
|
if entry != "steptrap"
|
|
238
238
|
lineStepClear()
|
|
239
239
|
if entry == "startpause" {
|
|
240
|
-
|
|
241
|
-
msg.error = Error("can't debug atom:", arg1)
|
|
242
|
-
else
|
|
243
|
-
msg.expr = arg1
|
|
240
|
+
msg.expr = arg1
|
|
244
241
|
} else if entry == "breakpoint" {
|
|
245
242
|
if breakpointHit(msg, arg1, arg2)
|
|
246
243
|
return
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* csv.nlg
|
|
3
|
+
* Naanlib/frameworks/storage
|
|
4
|
+
*
|
|
5
|
+
* CSV file parsing and generation.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2022 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* csvParse
|
|
16
|
+
*
|
|
17
|
+
* Parse text lines in CSV format into a row array of column arrays. The delimiter defaults to
|
|
18
|
+
* comma, but can be replaced with anything that's not crazy. Normally missing columns are replaced
|
|
19
|
+
* with false, but the empty parameter can be used to override this. For example, specify "" to
|
|
20
|
+
* use an empty string for the empty column.
|
|
21
|
+
* The parse regex is derived from the following Stack Overflow article:
|
|
22
|
+
* https://stackoverflow.com/questions/1293147/example-javascript-code-to-parse-csv-data
|
|
23
|
+
* Please see the answer by Joshua McLauchlan.
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
function csvParse(text, delim, empty, local res, parseRx, quoteRx, result, matches) {
|
|
28
|
+
if !string(text)
|
|
29
|
+
return (false) // don't hang or be stupid
|
|
30
|
+
res = '(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))'
|
|
31
|
+
if delim
|
|
32
|
+
res = res.replace(RegExp(',', 'g'), delim)
|
|
33
|
+
else
|
|
34
|
+
delim = ','
|
|
35
|
+
parseRx = RegExp(res, 'gi')
|
|
36
|
+
quoteRx = RegExp('""', 'g')
|
|
37
|
+
result = [[]]
|
|
38
|
+
while !!(matches = parseRx.exec(text)) {
|
|
39
|
+
if !matches[0]
|
|
40
|
+
break // should never happen, but don't hang
|
|
41
|
+
if matches[1].length > 0 && matches[1] !== delim
|
|
42
|
+
result.push([]) // first column in row
|
|
43
|
+
result[result.length-1].push(cond(
|
|
44
|
+
if matches[2] !== undefined
|
|
45
|
+
matches[2].replace(quoteRx, '"') // quoted string, including ""
|
|
46
|
+
else if matches[3] !== ""
|
|
47
|
+
matches[3] // unquoted, non-empty
|
|
48
|
+
else
|
|
49
|
+
empty // empty column
|
|
50
|
+
))
|
|
51
|
+
}
|
|
52
|
+
result
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* csvPrint
|
|
58
|
+
*
|
|
59
|
+
* Print an array of row arrays of columns to a string, reversing csvParse.
|
|
60
|
+
*
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
function csvPrint(rows, local output, quoter, row, line, delim, col) {
|
|
64
|
+
quoter = csvQuoteGenerator()
|
|
65
|
+
output = ""
|
|
66
|
+
for row in rows {
|
|
67
|
+
delim = line = ""
|
|
68
|
+
for col in row {
|
|
69
|
+
line = line.concat(delim, quoter(col))
|
|
70
|
+
delim = ","
|
|
71
|
+
}
|
|
72
|
+
output = output.concat(line, "\n")
|
|
73
|
+
}
|
|
74
|
+
output
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* csvQuoteGenerator
|
|
80
|
+
*
|
|
81
|
+
* Return a function that quotes a string as needed for a CSV file with the given delimiter. This
|
|
82
|
+
* is a generator because it will likely be needed frequently. Comma is the default delimeter, just
|
|
83
|
+
* like God intended for a *C*sv file.
|
|
84
|
+
*
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
closure csvQuoteGenerator(delimiter, local rxComma, rxQuote) {
|
|
88
|
+
if !delimiter
|
|
89
|
+
delimiter = ','
|
|
90
|
+
rxDelimiter = RegExp('['.concat('"\\', delimiter, ']'))
|
|
91
|
+
rxQuote = RegExp("\"", "g")
|
|
92
|
+
|
|
93
|
+
// csvQuote
|
|
94
|
+
// Quote a string for a CSV file as specified.
|
|
95
|
+
function csvQuote(str) {
|
|
96
|
+
if rxDelimiter.test(str)
|
|
97
|
+
return ("\"".concat(str.replace(rxQuote, "\"\""), "\""))
|
|
98
|
+
return (str)
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
* csvInit
|
|
105
|
+
*
|
|
106
|
+
* Initialize the component.
|
|
107
|
+
*
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
function csvInit(local manifest) {
|
|
111
|
+
manifest = `(csvParse, csvPrint, csvQuoteGenerator, csvInit)
|
|
112
|
+
|
|
113
|
+
Naan.module.build(module.id, "csv", function(modobj, compobj) {
|
|
114
|
+
require("./storage.nlg")
|
|
115
|
+
compobj.manifest = manifest
|
|
116
|
+
module.exports.csvParse = csvParse
|
|
117
|
+
module.exports.csvPrint = csvPrint
|
|
118
|
+
module.exports.csvQuoteGenerator = csvQuoteGenerator
|
|
119
|
+
})
|
|
120
|
+
}();
|
|
@@ -540,36 +540,26 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
540
540
|
type: "password" },
|
|
541
541
|
]
|
|
542
542
|
}
|
|
543
|
-
|
|
544
|
-
// hashResourceID
|
|
545
|
-
//
|
|
546
|
-
// Make a stable hash based on the resource credentials that we can persist or share between
|
|
547
|
-
// different domains accessing the same resource.
|
|
548
|
-
connector.hashResourceID = function hashResourceID(resource, local ident) {
|
|
549
|
-
ident = resource.urlName
|
|
550
|
-
if resource.label
|
|
551
|
-
ident = ident.concat(resource.label)
|
|
552
|
-
HashSHA256(ident)
|
|
553
|
-
}
|
|
554
543
|
|
|
555
544
|
// findResource
|
|
556
545
|
//
|
|
557
|
-
// Find a resource with the specified contents, returning the resID or false.
|
|
558
|
-
// is: if added, would the specified contents be redundant to an existing resource?
|
|
546
|
+
// Find a resource with the specified contents, returning the resID or false.
|
|
559
547
|
connector.findResource = function findResource(resource, local resID, creds) {
|
|
560
548
|
for resID in listResources().1 {
|
|
561
549
|
creds = access(resID).1
|
|
562
|
-
if resource.urlName == creds.urlName
|
|
550
|
+
if ((!resource.urlName ||resource.urlName == creds.urlName)
|
|
563
551
|
&& (!resource.label || resource.label == creds.label)
|
|
564
|
-
&& resource.authName == creds.authName
|
|
552
|
+
&& (!resource.authName || resource.authName == creds.authName))
|
|
565
553
|
return (resID)
|
|
566
554
|
}
|
|
567
555
|
false
|
|
568
556
|
}
|
|
569
557
|
|
|
570
|
-
//
|
|
558
|
+
// writeResource
|
|
571
559
|
//
|
|
572
|
-
// Add credentials for a named resource.
|
|
560
|
+
// Add or update credentials for a named resource. If no resID is specified this adds a new
|
|
561
|
+
// resource; otherwise it updates an existing one. The resource is a dictionary comprising these
|
|
562
|
+
// keys:
|
|
573
563
|
// label - [optional] label we use for this resource
|
|
574
564
|
// urlName - URL (e.g. "http://domain.com/dbname")
|
|
575
565
|
// - filepath (e.g. "/Users/richardz/dbname") - NodeJS filesystem access
|
|
@@ -579,10 +569,13 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
579
569
|
// hidden - [optional] don't enumerate to user
|
|
580
570
|
// locked - [optional] don't allow user delete
|
|
581
571
|
// existng - [optional] true to enable using existing database
|
|
582
|
-
// The return value is (error,
|
|
583
|
-
// keys and error diagnostic strings.
|
|
584
|
-
connector.
|
|
572
|
+
// The return value is an (error, resID) tuple. Error, if non-false, is a dictionary of field
|
|
573
|
+
// keys and error diagnostic strings.
|
|
574
|
+
connector.writeResource = function writeResource(resource, resID,
|
|
575
|
+
local errors, creds, original, error, data) {
|
|
576
|
+
// badField - test for a bad string
|
|
585
577
|
function badField(str) { !string(str) || str.trim().length == 0 }
|
|
578
|
+
|
|
586
579
|
creds = { urlName: resource.urlName }
|
|
587
580
|
errors = { }
|
|
588
581
|
if resource.label
|
|
@@ -591,6 +584,8 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
591
584
|
creds.label = resource.urlName
|
|
592
585
|
if badField(creds.label)
|
|
593
586
|
errors.label = "invalid label"
|
|
587
|
+
else if !resID && findResource({ label: creds.label })
|
|
588
|
+
errors.label = "duplicate name"
|
|
594
589
|
if badField(resource.urlName)
|
|
595
590
|
errors.urlName = "required field"
|
|
596
591
|
else if !resource.existing {
|
|
@@ -612,15 +607,36 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
612
607
|
creds.hidden = resource.hidden
|
|
613
608
|
if resource.locked
|
|
614
609
|
creds.locked = resource.locked
|
|
615
|
-
resID
|
|
616
|
-
|
|
610
|
+
if resID {
|
|
611
|
+
`(error, data) = connector.vault.updateResource(resID, creds)
|
|
612
|
+
change = { changed: [resID] }
|
|
613
|
+
} else {
|
|
614
|
+
resID = UUID()
|
|
615
|
+
`(error, data) = connector.vault.addResource(resID, creds)
|
|
616
|
+
change = { added: [resID] }
|
|
617
|
+
}
|
|
617
618
|
if data {
|
|
618
|
-
watch.notify(connClassID,
|
|
619
|
-
data = resID
|
|
619
|
+
watch.notify(connClassID, change)
|
|
620
|
+
data = resID
|
|
621
|
+
}
|
|
620
622
|
}
|
|
621
623
|
list(error, data)
|
|
622
624
|
}
|
|
623
|
-
|
|
625
|
+
|
|
626
|
+
// addResource
|
|
627
|
+
//
|
|
628
|
+
// Add a new resource, returning a standard error tuple per writeResource.
|
|
629
|
+
connector.addResource = function addResource(resource) {
|
|
630
|
+
writeResource(resource)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// updateResource
|
|
634
|
+
//
|
|
635
|
+
// Update an existing resource, returning a standard error tuple per writeResource.
|
|
636
|
+
connector.updateResource = function updateResource(resID, resource) {
|
|
637
|
+
writeResource(resource, resID)
|
|
638
|
+
}
|
|
639
|
+
|
|
624
640
|
// getResource
|
|
625
641
|
//
|
|
626
642
|
// Get credentials for a named resource, matching the dictionary semantics of addResource. The
|