manyfest 1.0.6 → 1.0.8
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/.config/code-server/config.yaml +1 -1
- package/.config/configstore/update-notifier-npm.json +1 -1
- package/package.json +1 -1
- package/source/Manyfest-ObjectAddressGeneration.js +123 -0
- package/source/Manyfest-ObjectAddressResolver.js +122 -11
- package/source/Manyfest.js +30 -4
- package/test/Manyfest_Object_ReadSets_tests.js +229 -0
- package/test/Manyfest_Object_Read_tests.js +10 -4
- package/test/Manyfest_Object_SchemaManipulation_tests.js +46 -0
package/package.json
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* @author <steven@velozo.com>
|
|
4
|
+
*/
|
|
5
|
+
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Object Address Generation
|
|
9
|
+
*
|
|
10
|
+
* Automagically generate addresses and properties based on a passed-in object,
|
|
11
|
+
* to be used for easy creation of schemas. Meant to simplify the lives of
|
|
12
|
+
* developers wanting to create schemas without typing a bunch of stuff.
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
|
|
15
|
+
* be extremely clear what is going on in the recursion for
|
|
16
|
+
* each of the three address resolution functions.
|
|
17
|
+
*
|
|
18
|
+
* Although there is some opportunity to repeat ourselves a
|
|
19
|
+
* bit less in this codebase (e.g. with detection of arrays
|
|
20
|
+
* versus objects versus direct properties), it can make
|
|
21
|
+
* debugging.. challenging. The minified version of the code
|
|
22
|
+
* optimizes out almost anything repeated in here. So please
|
|
23
|
+
* be kind and rewind... meaning please keep the codebase less
|
|
24
|
+
* terse and more verbose so humans can comprehend it.
|
|
25
|
+
*
|
|
26
|
+
*
|
|
27
|
+
* @class ManyfestObjectAddressGeneration
|
|
28
|
+
*/
|
|
29
|
+
class ManyfestObjectAddressGeneration
|
|
30
|
+
{
|
|
31
|
+
constructor(pInfoLog, pErrorLog)
|
|
32
|
+
{
|
|
33
|
+
// Wire in logging
|
|
34
|
+
this.logInfo = (typeof(pInfoLog) == 'function') ? pInfoLog : libSimpleLog;
|
|
35
|
+
this.logError = (typeof(pErrorLog) == 'function') ? pErrorLog : libSimpleLog;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// generateAddressses
|
|
39
|
+
//
|
|
40
|
+
// This flattens an object into a set of key:value pairs for *EVERY SINGLE
|
|
41
|
+
// POSSIBLE ADDRESS* in the object. It can get ... really insane really
|
|
42
|
+
// quickly. This is not meant to be used directly to generate schemas, but
|
|
43
|
+
// instead as a starting point for scripts or UIs.
|
|
44
|
+
//
|
|
45
|
+
// This will return a mega set of key:value pairs with all possible schema
|
|
46
|
+
// permutations and default values (when not an object) and everything else.
|
|
47
|
+
generateAddressses (pObject, pBaseAddress, pSchema)
|
|
48
|
+
{
|
|
49
|
+
let tmpBaseAddress = (typeof(pBaseAddress) == 'string') ? pBaseAddress : '';
|
|
50
|
+
let tmpSchema = (typeof(pSchema) == 'object') ? pSchema : {};
|
|
51
|
+
|
|
52
|
+
let tmpObjectType = typeof(pObject);
|
|
53
|
+
|
|
54
|
+
let tmpSchemaObjectEntry = (
|
|
55
|
+
{
|
|
56
|
+
Address: tmpBaseAddress,
|
|
57
|
+
Hash: tmpBaseAddress,
|
|
58
|
+
Name: tmpBaseAddress,
|
|
59
|
+
// This is so scripts and UI controls can force a developer to opt-in.
|
|
60
|
+
InSchema: false
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
switch(tmpObjectType)
|
|
65
|
+
{
|
|
66
|
+
case 'string':
|
|
67
|
+
tmpSchemaObjectEntry.DataType = 'String';
|
|
68
|
+
tmpSchemaObjectEntry.Default = pObject;
|
|
69
|
+
tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
|
|
70
|
+
break;
|
|
71
|
+
case 'number':
|
|
72
|
+
case 'bigint':
|
|
73
|
+
tmpSchemaObjectEntry.DataType = 'Number';
|
|
74
|
+
tmpSchemaObjectEntry.Default = pObject;
|
|
75
|
+
tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
|
|
76
|
+
break;
|
|
77
|
+
case 'undefined':
|
|
78
|
+
tmpSchemaObjectEntry.DataType = 'Any';
|
|
79
|
+
tmpSchemaObjectEntry.Default = pObject;
|
|
80
|
+
tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
|
|
81
|
+
break;
|
|
82
|
+
case 'object':
|
|
83
|
+
if (Array.isArray(pObject))
|
|
84
|
+
{
|
|
85
|
+
tmpSchemaObjectEntry.DataType = 'Array';
|
|
86
|
+
if (tmpBaseAddress != '')
|
|
87
|
+
{
|
|
88
|
+
tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < pObject.length; i++)
|
|
92
|
+
{
|
|
93
|
+
this.generateAddressses(pObject[i], `${tmpBaseAddress}[${i}]`, tmpSchema);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
{
|
|
98
|
+
tmpSchemaObjectEntry.DataType = 'Object';
|
|
99
|
+
if (tmpBaseAddress != '')
|
|
100
|
+
{
|
|
101
|
+
tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
|
|
102
|
+
tmpBaseAddress += '.';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let tmpObjectProperties = Object.keys(pObject);
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < tmpObjectProperties.length; i++)
|
|
108
|
+
{
|
|
109
|
+
this.generateAddressses(pObject[tmpObjectProperties[i]], `${tmpBaseAddress}${tmpObjectProperties[i]}`, tmpSchema);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case 'symbol':
|
|
114
|
+
case 'function':
|
|
115
|
+
// Symbols and functions neither recurse nor get added to the schema
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return tmpSchema;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
module.exports = ManyfestObjectAddressGeneration;
|
|
@@ -93,7 +93,7 @@ class ManyfestObjectAddressResolver
|
|
|
93
93
|
// 2) The end bracket has something between them
|
|
94
94
|
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
95
95
|
// 3) There is data
|
|
96
|
-
&& (tmpBracketStopIndex - tmpBracketStartIndex >
|
|
96
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
97
97
|
{
|
|
98
98
|
// The "Name" of the Object contained too the left of the bracket
|
|
99
99
|
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
|
|
@@ -167,7 +167,7 @@ class ManyfestObjectAddressResolver
|
|
|
167
167
|
// 2) The end bracket has something between them
|
|
168
168
|
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
169
169
|
// 3) There is data
|
|
170
|
-
&& (tmpBracketStopIndex - tmpBracketStartIndex >
|
|
170
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
171
171
|
{
|
|
172
172
|
let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
|
|
173
173
|
|
|
@@ -237,12 +237,17 @@ class ManyfestObjectAddressResolver
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// Get the value of an element at an address
|
|
240
|
-
getValueAtAddress (pObject, pAddress)
|
|
240
|
+
getValueAtAddress (pObject, pAddress, pParentAddress)
|
|
241
241
|
{
|
|
242
242
|
// Make sure pObject is an object
|
|
243
243
|
if (typeof(pObject) != 'object') return undefined;
|
|
244
244
|
// Make sure pAddress is a string
|
|
245
245
|
if (typeof(pAddress) != 'string') return undefined;
|
|
246
|
+
let tmpParentAddress = "";
|
|
247
|
+
if (typeof(pParentAddress) == 'string')
|
|
248
|
+
{
|
|
249
|
+
tmpParentAddress = pParentAddress;
|
|
250
|
+
}
|
|
246
251
|
|
|
247
252
|
// TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
|
|
248
253
|
let tmpSeparatorIndex = pAddress.indexOf('.');
|
|
@@ -253,6 +258,11 @@ class ManyfestObjectAddressResolver
|
|
|
253
258
|
// Check if the address refers to a boxed property
|
|
254
259
|
let tmpBracketStartIndex = pAddress.indexOf('[');
|
|
255
260
|
let tmpBracketStopIndex = pAddress.indexOf(']');
|
|
261
|
+
|
|
262
|
+
// Check for the Object Set Type marker.
|
|
263
|
+
// Note this will not work with a bracket in the same address box set
|
|
264
|
+
let tmpObjectTypeMarkerIndex = pAddress.indexOf('{}');
|
|
265
|
+
|
|
256
266
|
// Boxed elements look like this:
|
|
257
267
|
// MyValues[10]
|
|
258
268
|
// MyValues['Name']
|
|
@@ -266,7 +276,7 @@ class ManyfestObjectAddressResolver
|
|
|
266
276
|
// 2) The end bracket has something between them
|
|
267
277
|
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
268
278
|
// 3) There is data
|
|
269
|
-
&& (tmpBracketStopIndex - tmpBracketStartIndex >
|
|
279
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
270
280
|
{
|
|
271
281
|
// The "Name" of the Object contained too the left of the bracket
|
|
272
282
|
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
|
|
@@ -311,6 +321,37 @@ class ManyfestObjectAddressResolver
|
|
|
311
321
|
return pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber];
|
|
312
322
|
}
|
|
313
323
|
}
|
|
324
|
+
// The requirements to detect a boxed set element are:
|
|
325
|
+
// 1) The start bracket is after character 0
|
|
326
|
+
else if ((tmpBracketStartIndex > 0)
|
|
327
|
+
// 2) The end bracket is after the start bracket
|
|
328
|
+
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
329
|
+
// 3) There is nothing in the brackets
|
|
330
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex == 1))
|
|
331
|
+
{
|
|
332
|
+
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
|
|
333
|
+
|
|
334
|
+
if (!Array.isArray(pObject[tmpBoxedPropertyName]))
|
|
335
|
+
{
|
|
336
|
+
// We asked for a set from an array but it isnt' an array.
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return pObject[tmpBoxedPropertyName];
|
|
341
|
+
}
|
|
342
|
+
// The object has been flagged as an object set, so treat it as such
|
|
343
|
+
else if (tmpObjectTypeMarkerIndex > 0)
|
|
344
|
+
{
|
|
345
|
+
let tmpObjectPropertyName = pAddress.substring(0, tmpObjectTypeMarkerIndex).trim();
|
|
346
|
+
|
|
347
|
+
if (typeof(pObject[tmpObjectPropertyName]) != 'object')
|
|
348
|
+
{
|
|
349
|
+
// We asked for a set from an array but it isnt' an array.
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return pObject[tmpObjectPropertyName];
|
|
354
|
+
}
|
|
314
355
|
else
|
|
315
356
|
{
|
|
316
357
|
// Now is the point in recursion to return the value in the address
|
|
@@ -322,6 +363,7 @@ class ManyfestObjectAddressResolver
|
|
|
322
363
|
let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
|
|
323
364
|
let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
|
|
324
365
|
|
|
366
|
+
// BOXED ELEMENTS
|
|
325
367
|
// Test if the tmpNewAddress is an array or object
|
|
326
368
|
// Check if it's a boxed property
|
|
327
369
|
let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
|
|
@@ -339,7 +381,7 @@ class ManyfestObjectAddressResolver
|
|
|
339
381
|
// 2) The end bracket has something between them
|
|
340
382
|
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
341
383
|
// 3) There is data
|
|
342
|
-
&& (tmpBracketStopIndex - tmpBracketStartIndex >
|
|
384
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
343
385
|
{
|
|
344
386
|
let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
|
|
345
387
|
|
|
@@ -376,15 +418,80 @@ class ManyfestObjectAddressResolver
|
|
|
376
418
|
tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
|
|
377
419
|
tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
|
|
378
420
|
|
|
421
|
+
// Continue to manage the parent address for recursion
|
|
422
|
+
tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpSubObjectName}`;
|
|
379
423
|
// Recurse directly into the subobject
|
|
380
|
-
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress);
|
|
424
|
+
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress, tmpParentAddress);
|
|
381
425
|
}
|
|
382
426
|
else
|
|
383
427
|
{
|
|
428
|
+
// Continue to manage the parent address for recursion
|
|
429
|
+
tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpSubObjectName}`;
|
|
384
430
|
// We parsed a valid number out of the boxed property name, so recurse into the array
|
|
385
|
-
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress);
|
|
431
|
+
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress, tmpParentAddress);
|
|
386
432
|
}
|
|
387
433
|
}
|
|
434
|
+
// The requirements to detect a boxed set element are:
|
|
435
|
+
// 1) The start bracket is after character 0
|
|
436
|
+
else if ((tmpBracketStartIndex > 0)
|
|
437
|
+
// 2) The end bracket is after the start bracket
|
|
438
|
+
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
439
|
+
// 3) There is nothing in the brackets
|
|
440
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex == 1))
|
|
441
|
+
{
|
|
442
|
+
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
|
|
443
|
+
|
|
444
|
+
if (!Array.isArray(pObject[tmpBoxedPropertyName]))
|
|
445
|
+
{
|
|
446
|
+
// We asked for a set from an array but it isnt' an array.
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// We need to enumerate the array and grab the addresses from there.
|
|
451
|
+
let tmpArrayProperty = pObject[tmpBoxedPropertyName];
|
|
452
|
+
// Managing the parent address is a bit more complex here -- the box will be added for each element.
|
|
453
|
+
tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpBoxedPropertyName}`;
|
|
454
|
+
// The container object is where we have the "Address":SOMEVALUE pairs
|
|
455
|
+
let tmpContainerObject = {};
|
|
456
|
+
for (let i = 0; i < tmpArrayProperty.length; i++)
|
|
457
|
+
{
|
|
458
|
+
let tmpPropertyParentAddress = `${tmpParentAddress}[${i}]`;
|
|
459
|
+
let tmpValue = this.getValueAtAddress(pObject[tmpBoxedPropertyName][i], tmpNewAddress, tmpPropertyParentAddress);;
|
|
460
|
+
tmpContainerObject[`${tmpPropertyParentAddress}.${tmpNewAddress}`] = tmpValue;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return tmpContainerObject;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// OBJECT SET
|
|
467
|
+
// Note this will not work with a bracket in the same address box set
|
|
468
|
+
let tmpObjectTypeMarkerIndex = pAddress.indexOf('{}');
|
|
469
|
+
if (tmpObjectTypeMarkerIndex > 0)
|
|
470
|
+
{
|
|
471
|
+
let tmpObjectPropertyName = pAddress.substring(0, tmpObjectTypeMarkerIndex).trim();
|
|
472
|
+
|
|
473
|
+
if (typeof(pObject[tmpObjectPropertyName]) != 'object')
|
|
474
|
+
{
|
|
475
|
+
// We asked for a set from an array but it isnt' an array.
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// We need to enumerate the Object and grab the addresses from there.
|
|
480
|
+
let tmpObjectProperty = pObject[tmpObjectPropertyName];
|
|
481
|
+
let tmpObjectPropertyKeys = Object.keys(tmpObjectProperty);
|
|
482
|
+
// Managing the parent address is a bit more complex here -- the box will be added for each element.
|
|
483
|
+
tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpObjectPropertyName}`;
|
|
484
|
+
// The container object is where we have the "Address":SOMEVALUE pairs
|
|
485
|
+
let tmpContainerObject = {};
|
|
486
|
+
for (let i = 0; i < tmpObjectPropertyKeys.length; i++)
|
|
487
|
+
{
|
|
488
|
+
let tmpPropertyParentAddress = `${tmpParentAddress}.${tmpObjectPropertyKeys[i]}`;
|
|
489
|
+
let tmpValue = this.getValueAtAddress(pObject[tmpObjectPropertyName][tmpObjectPropertyKeys[i]], tmpNewAddress, tmpPropertyParentAddress);;
|
|
490
|
+
tmpContainerObject[`${tmpPropertyParentAddress}.${tmpNewAddress}`] = tmpValue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return tmpContainerObject;
|
|
494
|
+
}
|
|
388
495
|
|
|
389
496
|
// If there is an object property already named for the sub object, but it isn't an object
|
|
390
497
|
// then the system can't set the value in there. Error and abort!
|
|
@@ -395,13 +502,17 @@ class ManyfestObjectAddressResolver
|
|
|
395
502
|
else if (pObject.hasOwnProperty(tmpSubObjectName))
|
|
396
503
|
{
|
|
397
504
|
// If there is already a subobject pass that to the recursive thingy
|
|
398
|
-
|
|
505
|
+
// Continue to manage the parent address for recursion
|
|
506
|
+
tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpSubObjectName}`;
|
|
507
|
+
return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, tmpParentAddress);
|
|
399
508
|
}
|
|
400
509
|
else
|
|
401
510
|
{
|
|
402
511
|
// Create a subobject and then pass that
|
|
512
|
+
// Continue to manage the parent address for recursion
|
|
513
|
+
tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpSubObjectName}`;
|
|
403
514
|
pObject[tmpSubObjectName] = {};
|
|
404
|
-
return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress);
|
|
515
|
+
return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, tmpParentAddress);
|
|
405
516
|
}
|
|
406
517
|
}
|
|
407
518
|
}
|
|
@@ -434,7 +545,7 @@ class ManyfestObjectAddressResolver
|
|
|
434
545
|
// 2) The end bracket has something between them
|
|
435
546
|
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
436
547
|
// 3) There is data
|
|
437
|
-
&& (tmpBracketStopIndex - tmpBracketStartIndex >
|
|
548
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
438
549
|
{
|
|
439
550
|
// The "Name" of the Object contained too the left of the bracket
|
|
440
551
|
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
|
|
@@ -510,7 +621,7 @@ class ManyfestObjectAddressResolver
|
|
|
510
621
|
// 2) The end bracket has something between them
|
|
511
622
|
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
512
623
|
// 3) There is data
|
|
513
|
-
&& (tmpBracketStopIndex - tmpBracketStartIndex >
|
|
624
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
514
625
|
{
|
|
515
626
|
let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
|
|
516
627
|
|
package/source/Manyfest.js
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* @author <steven@velozo.com>
|
|
4
4
|
*/
|
|
5
5
|
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
let libHashTranslation = require('./Manyfest-HashTranslation.js');
|
|
8
|
+
let libObjectAddressResolver = require('./Manyfest-ObjectAddressResolver.js');
|
|
9
|
+
let libObjectAddressGeneration = require('./Manyfest-ObjectAddressGeneration.js');
|
|
8
10
|
let libSchemaManipulation = require('./Manyfest-SchemaManipulation.js');
|
|
9
11
|
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Manyfest object address-based descriptions and manipulations.
|
|
12
15
|
*
|
|
@@ -54,6 +57,7 @@ class Manyfest
|
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
this.schemaManipulations = new libSchemaManipulation(this.logInfo, this.logError);
|
|
60
|
+
this.objectAddressGeneration = new libObjectAddressGeneration(this.logInfo, this.logError);
|
|
57
61
|
|
|
58
62
|
this.hashTranslations = new libHashTranslation(this.logInfo, this.logError);
|
|
59
63
|
}
|
|
@@ -256,13 +260,29 @@ class Manyfest
|
|
|
256
260
|
// Get the value of an element by its hash
|
|
257
261
|
getValueByHash (pObject, pHash)
|
|
258
262
|
{
|
|
259
|
-
|
|
263
|
+
let tmpValue = this.getValueAtAddress(pObject, this.resolveHashAddress(pHash));
|
|
264
|
+
|
|
265
|
+
if (typeof(tmpValue) == 'undefined')
|
|
266
|
+
{
|
|
267
|
+
// Try to get a default if it exists
|
|
268
|
+
tmpValue = this.getDefaultValue(this.getDescriptorByHash(pHash));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return tmpValue;
|
|
260
272
|
}
|
|
261
273
|
|
|
262
274
|
// Get the value of an element at an address
|
|
263
275
|
getValueAtAddress (pObject, pAddress)
|
|
264
276
|
{
|
|
265
|
-
|
|
277
|
+
let tmpValue = this.objectAddressResolver.getValueAtAddress(pObject, pAddress);
|
|
278
|
+
|
|
279
|
+
if (typeof(tmpValue) == 'undefined')
|
|
280
|
+
{
|
|
281
|
+
// Try to get a default if it exists
|
|
282
|
+
tmpValue = this.getDefaultValue(this.getDescriptor(pAddress));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return tmpValue;
|
|
266
286
|
}
|
|
267
287
|
|
|
268
288
|
// Set the value of an element by its hash
|
|
@@ -304,9 +324,10 @@ class Manyfest
|
|
|
304
324
|
for (let i = 0; i < this.elementAddresses.length; i++)
|
|
305
325
|
{
|
|
306
326
|
let tmpDescriptor = this.getDescriptor(this.elementAddresses[i]);
|
|
327
|
+
let tmpValueExists = this.checkAddressExists(pObject, tmpDescriptor.Address);
|
|
307
328
|
let tmpValue = this.getValueAtAddress(pObject, tmpDescriptor.Address);
|
|
308
329
|
|
|
309
|
-
if (typeof(tmpValue) == 'undefined')
|
|
330
|
+
if ((typeof(tmpValue) == 'undefined') || !tmpValueExists)
|
|
310
331
|
{
|
|
311
332
|
// This will technically mean that `Object.Some.Value = undefined` will end up showing as "missing"
|
|
312
333
|
// TODO: Do we want to do a different message based on if the property exists but is undefined?
|
|
@@ -385,6 +406,11 @@ class Manyfest
|
|
|
385
406
|
// Returns a default value, or, the default value for the data type (which is overridable with configuration)
|
|
386
407
|
getDefaultValue(pDescriptor)
|
|
387
408
|
{
|
|
409
|
+
if (typeof(pDescriptor) != 'object')
|
|
410
|
+
{
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
388
414
|
if (pDescriptor.hasOwnProperty('Default'))
|
|
389
415
|
{
|
|
390
416
|
return pDescriptor.Default;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Manyfest
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var Chai = require("chai");
|
|
10
|
+
var Expect = Chai.expect;
|
|
11
|
+
|
|
12
|
+
let libManyfest = require('../source/Manyfest.js');
|
|
13
|
+
|
|
14
|
+
let _SampleDataArchiveOrgFrankenberry = require('./Data-Archive-org-Frankenberry.json');
|
|
15
|
+
|
|
16
|
+
suite
|
|
17
|
+
(
|
|
18
|
+
'Manyfest Object Read of Sets',
|
|
19
|
+
function()
|
|
20
|
+
{
|
|
21
|
+
setup (()=> {} );
|
|
22
|
+
|
|
23
|
+
suite
|
|
24
|
+
(
|
|
25
|
+
'Basic Array Set Reads',
|
|
26
|
+
()=>
|
|
27
|
+
{
|
|
28
|
+
test
|
|
29
|
+
(
|
|
30
|
+
'We should be able to access sets of properties from arrays without schema.',
|
|
31
|
+
(fTestComplete)=>
|
|
32
|
+
{
|
|
33
|
+
let _Manyfest = new libManyfest(
|
|
34
|
+
{
|
|
35
|
+
Scope:'Archive.org',
|
|
36
|
+
Descriptors:
|
|
37
|
+
{
|
|
38
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
39
|
+
'files[].size': {Name:'FileSizes', Hash:'FileSizes'},
|
|
40
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
let tmpFileSet = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'FileSet');
|
|
44
|
+
Expect(Array.isArray(tmpFileSet)).to.equal(true);
|
|
45
|
+
fTestComplete();
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
test
|
|
49
|
+
(
|
|
50
|
+
'We should be able to access arrays with or without boxes on the address.',
|
|
51
|
+
(fTestComplete)=>
|
|
52
|
+
{
|
|
53
|
+
let _Manyfest = new libManyfest(
|
|
54
|
+
{
|
|
55
|
+
Scope:'Archive.org',
|
|
56
|
+
Descriptors:
|
|
57
|
+
{
|
|
58
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
59
|
+
'files[].size': {Name:'FileSizes', Hash:'FileSizes'},
|
|
60
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
let tmpFileSet = _Manyfest.getValueAtAddress(_SampleDataArchiveOrgFrankenberry, 'files[]');
|
|
64
|
+
Expect(Array.isArray(tmpFileSet)).to.equal(true);
|
|
65
|
+
// There are 17 versions of this damn commercial....
|
|
66
|
+
Expect(tmpFileSet.length).to.equal(17);
|
|
67
|
+
let tmpFileSetUnboxed = _Manyfest.getValueAtAddress(_SampleDataArchiveOrgFrankenberry, 'files');
|
|
68
|
+
Expect(Array.isArray(tmpFileSetUnboxed)).to.equal(true);
|
|
69
|
+
fTestComplete();
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
test
|
|
73
|
+
(
|
|
74
|
+
'We should be able to access arrays with a hash.',
|
|
75
|
+
(fTestComplete)=>
|
|
76
|
+
{
|
|
77
|
+
let _Manyfest = new libManyfest(
|
|
78
|
+
{
|
|
79
|
+
Scope:'Archive.org',
|
|
80
|
+
Descriptors:
|
|
81
|
+
{
|
|
82
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
83
|
+
'files[].size': {Name:'FileSizes', Hash:'FileSizes'},
|
|
84
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
let tmpFileSet = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'FileSet');
|
|
88
|
+
Expect(Array.isArray(tmpFileSet)).to.equal(true);
|
|
89
|
+
// There are 17 versions of this damn commercial....
|
|
90
|
+
Expect(tmpFileSet.length).to.equal(17);
|
|
91
|
+
fTestComplete();
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
test
|
|
95
|
+
(
|
|
96
|
+
'Attempting to access non-array properties as arrays should return false.',
|
|
97
|
+
(fTestComplete)=>
|
|
98
|
+
{
|
|
99
|
+
let _Manyfest = new libManyfest(
|
|
100
|
+
{
|
|
101
|
+
Scope:'Archive.org',
|
|
102
|
+
Descriptors:
|
|
103
|
+
{
|
|
104
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
105
|
+
'files[].size': {Name:'FileSizes', Hash:'FileSizes'},
|
|
106
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
let tmpFileSet = _Manyfest.getValueAtAddress(_SampleDataArchiveOrgFrankenberry, 'metadata.creator[]');
|
|
110
|
+
Expect(tmpFileSet).to.equal(false);
|
|
111
|
+
fTestComplete();
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
test
|
|
115
|
+
(
|
|
116
|
+
'Sub-objects in arrays can be parsed and the values will be pulled in magically.',
|
|
117
|
+
(fTestComplete)=>
|
|
118
|
+
{
|
|
119
|
+
let _Manyfest = new libManyfest(
|
|
120
|
+
{
|
|
121
|
+
Scope:'Archive.org',
|
|
122
|
+
Descriptors:
|
|
123
|
+
{
|
|
124
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
125
|
+
'files[].size': {Name:'FileSizes', Hash:'FileSizes'},
|
|
126
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
let tmpFileSet = _Manyfest.getValueAtAddress(_SampleDataArchiveOrgFrankenberry, 'files[].size');
|
|
130
|
+
Expect(tmpFileSet).to.be.an('object');
|
|
131
|
+
Expect(tmpFileSet['files[13].size']).to.equal('31625216');
|
|
132
|
+
fTestComplete();
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
suite
|
|
138
|
+
(
|
|
139
|
+
'Basic Object Set Reads',
|
|
140
|
+
()=>
|
|
141
|
+
{
|
|
142
|
+
test
|
|
143
|
+
(
|
|
144
|
+
'We should be able to access sets of properties from objects with schema.',
|
|
145
|
+
(fTestComplete)=>
|
|
146
|
+
{
|
|
147
|
+
let _Manyfest = new libManyfest(
|
|
148
|
+
{
|
|
149
|
+
Scope:'Archive.org',
|
|
150
|
+
Descriptors:
|
|
151
|
+
{
|
|
152
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
153
|
+
'files[].size': {Name:'FileSizes', Hash:'FileSizes'},
|
|
154
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'},
|
|
155
|
+
'metadata{}': {Name:'Metadata', Hash:'Metadata'}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
let tmpMetadata = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Metadata');
|
|
159
|
+
Expect(tmpMetadata).to.be.an('object');
|
|
160
|
+
fTestComplete();
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
test
|
|
164
|
+
(
|
|
165
|
+
'We should be able to get declared objects without schema.',
|
|
166
|
+
(fTestComplete)=>
|
|
167
|
+
{
|
|
168
|
+
let _Manyfest = new libManyfest(
|
|
169
|
+
{
|
|
170
|
+
Scope:'RandomObject',
|
|
171
|
+
Descriptors:{}
|
|
172
|
+
});
|
|
173
|
+
let tmpData = (
|
|
174
|
+
{
|
|
175
|
+
Name: "Menu",
|
|
176
|
+
Ingredients: {
|
|
177
|
+
"GUID-001": { cost:100, Name:'Carrot' },
|
|
178
|
+
"GUID-002": { cost:102, Name:'Pea' },
|
|
179
|
+
"GUID-003": { cost:13, Name:'Potato' },
|
|
180
|
+
"GUID-004": { cost:1, Name:'Apple' },
|
|
181
|
+
"GUID-005": { cost:190, Name:'Wine' },
|
|
182
|
+
"GUID-006": { cost:2500, Name:'Steak' },
|
|
183
|
+
"GUID-007": { cost:"44", Name:'Gristle' },
|
|
184
|
+
"GUID-008": { cost:2, Name:'Cherry' },
|
|
185
|
+
"GUID-009": { cost:14, Name:'Orange' },
|
|
186
|
+
"GUID-010": { cost:1, Name:'Dandelion' },
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
let tmpMetadata = _Manyfest.getValueAtAddress(tmpData, 'Ingredients{}');
|
|
190
|
+
Expect(tmpMetadata).to.be.an('object');
|
|
191
|
+
fTestComplete();
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
test
|
|
195
|
+
(
|
|
196
|
+
'We should be able to declared object subobjects without schema.',
|
|
197
|
+
(fTestComplete)=>
|
|
198
|
+
{
|
|
199
|
+
let _Manyfest = new libManyfest(
|
|
200
|
+
{
|
|
201
|
+
Scope:'RandomObject',
|
|
202
|
+
Descriptors:{}
|
|
203
|
+
});
|
|
204
|
+
let tmpData = (
|
|
205
|
+
{
|
|
206
|
+
Name: "Menu",
|
|
207
|
+
Ingredients: {
|
|
208
|
+
"GUID-001": { cost:100, Name:'Carrot' },
|
|
209
|
+
"GUID-002": { cost:102, Name:'Pea' },
|
|
210
|
+
"GUID-003": { cost:13, Name:'Potato' },
|
|
211
|
+
"GUID-004": { cost:1, Name:'Apple' },
|
|
212
|
+
"GUID-005": { cost:190, Name:'Wine' },
|
|
213
|
+
"GUID-006": { cost:2500, Name:'Steak' },
|
|
214
|
+
"GUID-007": { cost:"44", Name:'Gristle' },
|
|
215
|
+
"GUID-008": { cost:2, Name:'Cherry' },
|
|
216
|
+
"GUID-009": { cost:14, Name:'Orange' },
|
|
217
|
+
"GUID-010": { cost:1, Name:'Dandelion' },
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
let tmpMetadata = _Manyfest.getValueAtAddress(tmpData, 'Ingredients{}.cost');
|
|
221
|
+
Expect(tmpMetadata).to.be.an('object');
|
|
222
|
+
Expect(tmpMetadata['Ingredients.GUID-003.cost']).to.equal(13);
|
|
223
|
+
fTestComplete();
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
);
|
|
@@ -57,7 +57,7 @@ suite
|
|
|
57
57
|
);
|
|
58
58
|
test
|
|
59
59
|
(
|
|
60
|
-
'
|
|
60
|
+
'Return default values when none are supplied.',
|
|
61
61
|
(fTestComplete)=>
|
|
62
62
|
{
|
|
63
63
|
let animalManyfest = new libManyfest(
|
|
@@ -79,13 +79,19 @@ suite
|
|
|
79
79
|
"Name":"Comfortable Environmental Temperature",
|
|
80
80
|
"NameShort":"Comf Env Temp",
|
|
81
81
|
"Hash":"ComfET",
|
|
82
|
-
"Description":"The most comfortable temperature for this animal to survive in."
|
|
82
|
+
"Description":"The most comfortable temperature for this animal to survive in.",
|
|
83
|
+
"Default": "96.8"
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
});
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
.to.equal(
|
|
88
|
+
Expect(animalManyfest.getValueByHash({MedicalStats: { Temps: { CET:200 }},Name:'Froggy'}, 'ComfET')).to.equal(200);
|
|
89
|
+
Expect(animalManyfest.getValueByHash({MedicalStats: { Temps: { MinET:200 }},Name:'Froggy'}, 'ComfET')).to.equal('96.8');
|
|
90
|
+
Expect(animalManyfest.getValueByHash({MedicalStats: { Temps: { MinET:200 }},Name:'Froggy'}, 'CurrentTemperature')).to.equal(undefined);
|
|
91
|
+
|
|
92
|
+
Expect(animalManyfest.getValueAtAddress({MedicalStats: { Temps: { CET:200 }},Name:'Froggy'}, 'MedicalStats.Temps.CET')).to.equal(200);
|
|
93
|
+
Expect(animalManyfest.getValueAtAddress({MedicalStats: { Temps: { MinET:200 }},Name:'Froggy'}, 'MedicalStats.Temps.CET')).to.equal('96.8');
|
|
94
|
+
Expect(animalManyfest.getValueAtAddress({MedicalStats: { Temps: { MinET:200 }},Name:'Froggy'}, 'MedicalStats.Temps.HighET')).to.equal(undefined);
|
|
89
95
|
|
|
90
96
|
fTestComplete();
|
|
91
97
|
}
|
|
@@ -194,6 +194,52 @@ suite
|
|
|
194
194
|
Expect(_ClonedManyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal('General Mills');
|
|
195
195
|
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal(undefined);
|
|
196
196
|
|
|
197
|
+
fTestComplete();
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
test
|
|
201
|
+
(
|
|
202
|
+
'Schema definition prototypes should be able to be generated from any JSON object shape.',
|
|
203
|
+
(fTestComplete)=>
|
|
204
|
+
{
|
|
205
|
+
let tmpSchemaDescriptors = (
|
|
206
|
+
{
|
|
207
|
+
"a": { "Hash": "a", "Type": "Number" },
|
|
208
|
+
"b": { "Hash": "b", "Type": "Number" },
|
|
209
|
+
"TranslationTable":
|
|
210
|
+
{
|
|
211
|
+
"a": "CarrotCost",
|
|
212
|
+
"b": "AppleCost"
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
let _Manyfest = new libManyfest();
|
|
217
|
+
// Now remap the schema (in-place)
|
|
218
|
+
let tmpSchemaPrototype = _Manyfest.objectAddressGeneration.generateAddressses(tmpSchemaDescriptors);
|
|
219
|
+
|
|
220
|
+
// The schema should be fundamentally altered to point these addresses to the old hashes
|
|
221
|
+
Expect(tmpSchemaPrototype).to.be.an('object');
|
|
222
|
+
|
|
223
|
+
Expect(tmpSchemaPrototype['TranslationTable.a'].DataType).to.equal('String');
|
|
224
|
+
|
|
225
|
+
fTestComplete();
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
test
|
|
229
|
+
(
|
|
230
|
+
'Make a much bigger schema prototype.',
|
|
231
|
+
(fTestComplete)=>
|
|
232
|
+
{
|
|
233
|
+
let _Manyfest = new libManyfest();
|
|
234
|
+
// Now remap the schema (in-place)
|
|
235
|
+
let tmpSchemaPrototype = _Manyfest.objectAddressGeneration.generateAddressses(_SampleDataArchiveOrgFrankenberry);
|
|
236
|
+
|
|
237
|
+
// The schema should be fundamentally altered to point these addresses to the old hashes
|
|
238
|
+
Expect(tmpSchemaPrototype).to.be.an('object');
|
|
239
|
+
|
|
240
|
+
Expect(tmpSchemaPrototype['files_count'].Default).to.equal(17);
|
|
241
|
+
Expect(tmpSchemaPrototype['files_count'].DataType).to.equal('Number');
|
|
242
|
+
|
|
197
243
|
fTestComplete();
|
|
198
244
|
}
|
|
199
245
|
);
|