manyfest 1.0.8 → 1.0.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "manyfest",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "JSON Object Manifest for Data Description and Parsing",
|
|
5
5
|
"main": "source/Manyfest.js",
|
|
6
6
|
"scripts": {
|
|
@@ -58,7 +58,10 @@
|
|
|
58
58
|
"vinyl-buffer": "^1.0.1",
|
|
59
59
|
"vinyl-source-stream": "^2.0.0"
|
|
60
60
|
},
|
|
61
|
-
"dependencies": {
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"elucidator": "^1.0.2",
|
|
63
|
+
"precedent": "^1.0.6"
|
|
64
|
+
},
|
|
62
65
|
"author": "steven velozo <steven@velozo.com>",
|
|
63
66
|
"license": "MIT",
|
|
64
67
|
"bugs": {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @author <steven@velozo.com>
|
|
4
4
|
*/
|
|
5
5
|
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
|
+
let libPrecedent = require('precedent');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Object Address Resolver
|
|
@@ -19,6 +20,8 @@ let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
|
19
20
|
* be kind and rewind... meaning please keep the codebase less
|
|
20
21
|
* terse and more verbose so humans can comprehend it.
|
|
21
22
|
*
|
|
23
|
+
* TODO: Once we validate this pattern is good to go, break these out into
|
|
24
|
+
* three separate modules.
|
|
22
25
|
*
|
|
23
26
|
* @class ManyfestObjectAddressResolver
|
|
24
27
|
*/
|
|
@@ -29,6 +32,9 @@ class ManyfestObjectAddressResolver
|
|
|
29
32
|
// Wire in logging
|
|
30
33
|
this.logInfo = (typeof(pInfoLog) == 'function') ? pInfoLog : libSimpleLog;
|
|
31
34
|
this.logError = (typeof(pErrorLog) == 'function') ? pErrorLog : libSimpleLog;
|
|
35
|
+
|
|
36
|
+
this.elucidatorSolver = false;
|
|
37
|
+
this.elucidatorSolverState = {};
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
// When a boxed property is passed in, it should have quotes of some
|
|
@@ -236,13 +242,111 @@ class ManyfestObjectAddressResolver
|
|
|
236
242
|
}
|
|
237
243
|
}
|
|
238
244
|
|
|
245
|
+
checkFilters(pAddress, pRecord)
|
|
246
|
+
{
|
|
247
|
+
let tmpPrecedent = new libPrecedent();
|
|
248
|
+
// If we don't copy the string, precedent takes it out for good.
|
|
249
|
+
// TODO: Consider adding a "don't replace" option for precedent
|
|
250
|
+
let tmpAddress = pAddress;
|
|
251
|
+
|
|
252
|
+
if (!this.elucidatorSolver)
|
|
253
|
+
{
|
|
254
|
+
// Again, manage against circular dependencies
|
|
255
|
+
let libElucidator = require('elucidator');
|
|
256
|
+
this.elucidatorSolver = new libElucidator({}, this.logInfo, this.logError);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (this.elucidatorSolver)
|
|
260
|
+
{
|
|
261
|
+
// This allows the magic filtration with elucidator configuration
|
|
262
|
+
// TODO: We could pass more state in (e.g. parent address, object, etc.)
|
|
263
|
+
// TODO: Discuss this metaprogramming AT LENGTH
|
|
264
|
+
let tmpFilterState = (
|
|
265
|
+
{
|
|
266
|
+
Record: pRecord,
|
|
267
|
+
keepRecord: true
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// This is about as complex as it gets.
|
|
271
|
+
// TODO: Optimize this so it is only initialized once.
|
|
272
|
+
// TODO: That means figuring out a healthy pattern for passing in state to this
|
|
273
|
+
tmpPrecedent.addPattern('<<~~', '~~>>',
|
|
274
|
+
(pInstructionHash) =>
|
|
275
|
+
{
|
|
276
|
+
// This is for internal config on the solution steps. Right now config is not shared across steps.
|
|
277
|
+
if (this.elucidatorSolverState.hasOwnProperty(pInstructionHash))
|
|
278
|
+
{
|
|
279
|
+
tmpFilterState.SolutionState = this.elucidatorSolverState[pInstructionHash];
|
|
280
|
+
}
|
|
281
|
+
this.elucidatorSolver.solveInternalOperation('Custom', pInstructionHash, tmpFilterState);
|
|
282
|
+
});
|
|
283
|
+
tmpPrecedent.addPattern('<<~?', '?~>>',
|
|
284
|
+
(pMagicSearchExpression) =>
|
|
285
|
+
{
|
|
286
|
+
if (typeof(pMagicSearchExpression) !== 'string')
|
|
287
|
+
{
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
// This expects a comma separated expression:
|
|
291
|
+
// Some.Address.In.The.Object,==,Search Term to Match
|
|
292
|
+
let tmpMagicComparisonPatternSet = pMagicSearchExpression.split(',');
|
|
293
|
+
|
|
294
|
+
let tmpSearchAddress = tmpMagicComparisonPatternSet[0];
|
|
295
|
+
let tmpSearchComparator = tmpMagicComparisonPatternSet[1];
|
|
296
|
+
let tmpSearchValue = tmpMagicComparisonPatternSet[2];
|
|
297
|
+
|
|
298
|
+
tmpFilterState.ComparisonState = (
|
|
299
|
+
{
|
|
300
|
+
SearchAddress: tmpSearchAddress,
|
|
301
|
+
Comparator: tmpSearchComparator,
|
|
302
|
+
SearchTerm: tmpSearchValue
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
this.elucidatorSolver.solveOperation(
|
|
306
|
+
{
|
|
307
|
+
"Description":
|
|
308
|
+
{
|
|
309
|
+
"Operation": "Simple_If",
|
|
310
|
+
"Synopsis": "Test for "
|
|
311
|
+
},
|
|
312
|
+
"Steps":
|
|
313
|
+
[
|
|
314
|
+
{
|
|
315
|
+
"Namespace": "Logic",
|
|
316
|
+
"Instruction": "if",
|
|
317
|
+
|
|
318
|
+
"InputHashAddressMap":
|
|
319
|
+
{
|
|
320
|
+
// This is ... dynamically assigning the address in the instruction
|
|
321
|
+
// The complexity is astounding.
|
|
322
|
+
"leftValue": `Record.${tmpSearchAddress}`,
|
|
323
|
+
"rightValue": "ComparisonState.SearchTerm",
|
|
324
|
+
"comparator": "ComparisonState.Comparator"
|
|
325
|
+
},
|
|
326
|
+
"OutputHashAddressMap": { "truthinessResult":"keepRecord" }
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
}, tmpFilterState);
|
|
330
|
+
});
|
|
331
|
+
tmpPrecedent.parseString(tmpAddress);
|
|
332
|
+
|
|
333
|
+
// It is expected that the operation will mutate this to some truthy value
|
|
334
|
+
return tmpFilterState.keepRecord;
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
{
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
239
342
|
// Get the value of an element at an address
|
|
240
343
|
getValueAtAddress (pObject, pAddress, pParentAddress)
|
|
241
344
|
{
|
|
242
|
-
// Make sure pObject is an object
|
|
345
|
+
// Make sure pObject (the object we are meant to be recursing) is an object (which could be an array or object)
|
|
243
346
|
if (typeof(pObject) != 'object') return undefined;
|
|
244
|
-
// Make sure pAddress is a string
|
|
347
|
+
// Make sure pAddress (the address we are resolving) is a string
|
|
245
348
|
if (typeof(pAddress) != 'string') return undefined;
|
|
349
|
+
// Stash the parent address for later resolution
|
|
246
350
|
let tmpParentAddress = "";
|
|
247
351
|
if (typeof(pParentAddress) == 'string')
|
|
248
352
|
{
|
|
@@ -337,7 +441,19 @@ class ManyfestObjectAddressResolver
|
|
|
337
441
|
return false;
|
|
338
442
|
}
|
|
339
443
|
|
|
340
|
-
|
|
444
|
+
let tmpInputArray = pObject[tmpBoxedPropertyName];
|
|
445
|
+
let tmpOutputArray = [];
|
|
446
|
+
for (let i = 0; i < tmpInputArray.length; i++)
|
|
447
|
+
{
|
|
448
|
+
// The filtering is complex but allows config-based metaprogramming directly from schema
|
|
449
|
+
let tmpKeepRecord = this.checkFilters(pAddress, tmpInputArray[i]);
|
|
450
|
+
if (tmpKeepRecord)
|
|
451
|
+
{
|
|
452
|
+
tmpOutputArray.push(tmpInputArray[i]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return tmpOutputArray;
|
|
341
457
|
}
|
|
342
458
|
// The object has been flagged as an object set, so treat it as such
|
|
343
459
|
else if (tmpObjectTypeMarkerIndex > 0)
|
|
@@ -456,7 +572,8 @@ class ManyfestObjectAddressResolver
|
|
|
456
572
|
for (let i = 0; i < tmpArrayProperty.length; i++)
|
|
457
573
|
{
|
|
458
574
|
let tmpPropertyParentAddress = `${tmpParentAddress}[${i}]`;
|
|
459
|
-
let tmpValue = this.getValueAtAddress(pObject[tmpBoxedPropertyName][i], tmpNewAddress, tmpPropertyParentAddress)
|
|
575
|
+
let tmpValue = this.getValueAtAddress(pObject[tmpBoxedPropertyName][i], tmpNewAddress, tmpPropertyParentAddress);
|
|
576
|
+
|
|
460
577
|
tmpContainerObject[`${tmpPropertyParentAddress}.${tmpNewAddress}`] = tmpValue;
|
|
461
578
|
}
|
|
462
579
|
|
|
@@ -486,8 +603,14 @@ class ManyfestObjectAddressResolver
|
|
|
486
603
|
for (let i = 0; i < tmpObjectPropertyKeys.length; i++)
|
|
487
604
|
{
|
|
488
605
|
let tmpPropertyParentAddress = `${tmpParentAddress}.${tmpObjectPropertyKeys[i]}`;
|
|
489
|
-
let tmpValue = this.getValueAtAddress(pObject[tmpObjectPropertyName][tmpObjectPropertyKeys[i]], tmpNewAddress, tmpPropertyParentAddress)
|
|
490
|
-
|
|
606
|
+
let tmpValue = this.getValueAtAddress(pObject[tmpObjectPropertyName][tmpObjectPropertyKeys[i]], tmpNewAddress, tmpPropertyParentAddress);
|
|
607
|
+
|
|
608
|
+
// The filtering is complex but allows config-based metaprogramming directly from schema
|
|
609
|
+
let tmpKeepRecord = this.checkFilters(pAddress, tmpValue);
|
|
610
|
+
if (tmpKeepRecord)
|
|
611
|
+
{
|
|
612
|
+
tmpContainerObject[`${tmpPropertyParentAddress}.${tmpNewAddress}`] = tmpValue;
|
|
613
|
+
}
|
|
491
614
|
}
|
|
492
615
|
|
|
493
616
|
return tmpContainerObject;
|
package/source/Manyfest.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
6
|
|
|
7
|
+
let libPrecedent = require('precedent');
|
|
8
|
+
|
|
7
9
|
let libHashTranslation = require('./Manyfest-HashTranslation.js');
|
|
8
10
|
let libObjectAddressResolver = require('./Manyfest-ObjectAddressResolver.js');
|
|
9
11
|
let libObjectAddressGeneration = require('./Manyfest-ObjectAddressGeneration.js');
|
|
@@ -48,6 +50,10 @@ class Manyfest
|
|
|
48
50
|
this.elementAddresses = undefined;
|
|
49
51
|
this.elementHashes = undefined;
|
|
50
52
|
this.elementDescriptors = undefined;
|
|
53
|
+
// This can cause a circular dependency chain, so it only gets initialized if the schema specifically calls for it.
|
|
54
|
+
this.dataSolvers = undefined;
|
|
55
|
+
// So solvers can use their own state
|
|
56
|
+
this.dataSolverState = undefined;
|
|
51
57
|
|
|
52
58
|
this.reset();
|
|
53
59
|
|
|
@@ -73,6 +79,11 @@ class Manyfest
|
|
|
73
79
|
this.elementAddresses = [];
|
|
74
80
|
this.elementHashes = {};
|
|
75
81
|
this.elementDescriptors = {};
|
|
82
|
+
this.dataSolvers = undefined;
|
|
83
|
+
this.dataSolverState = {};
|
|
84
|
+
|
|
85
|
+
this.libElucidator = undefined;
|
|
86
|
+
this.objectAddressResolver.elucidatorSolver = false;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
clone()
|
|
@@ -139,6 +150,35 @@ class Manyfest
|
|
|
139
150
|
{
|
|
140
151
|
this.logError(`(${this.scope}) Error loading object description from manifest object. Property "Descriptors" does not exist in the root of the Manifest object.`, pManifest);
|
|
141
152
|
}
|
|
153
|
+
|
|
154
|
+
// This seems like it would create a circular dependency issue but it only goes as deep as the schema defines Solvers
|
|
155
|
+
if ((pManifest.hasOwnProperty('Solvers')) && (typeof(pManifest.Solvers) == 'object'))
|
|
156
|
+
{
|
|
157
|
+
// There are elucidator solvers passed-in, so we will create one to filter data.
|
|
158
|
+
let libElucidator = require('elucidator');
|
|
159
|
+
// WARNING THESE CAN MUTATE THE DATA
|
|
160
|
+
// The pattern for the solver is: {<~~SolverName~~>} anywhere in a property.
|
|
161
|
+
// Yes, this means your Javascript elements can't have my self-styled jellyfish brackets in them.
|
|
162
|
+
// This does, though, mean we can filter at multiple layers safely.
|
|
163
|
+
// Because these can be put at any address
|
|
164
|
+
// The solver themselves:
|
|
165
|
+
// They are passed-in an object, and the current record is in the Record subobject.
|
|
166
|
+
// Basic operations can just write to the root object but...
|
|
167
|
+
// IF YOU PERMUTE THE Record SUBOBJECT YOU CAN AFFECT RECURSION
|
|
168
|
+
// This is mostly meant for if statements to filter.
|
|
169
|
+
// Basically on aggregation, if a filter is set it will set "keep record" to true and let the solver decide differently.
|
|
170
|
+
this.dataSolvers = new libElucidator(pManifest.Solvers, this.logInfo, this.logError);
|
|
171
|
+
this.objectAddressResolver.elucidatorSolver = this.dataSolvers;
|
|
172
|
+
|
|
173
|
+
// Load the solver state in so each instruction can have internal config
|
|
174
|
+
// TODO: Should this just be a part of the lower layer pattern?
|
|
175
|
+
let tmpSolverKeys = Object.keys(pManifest.Solvers)
|
|
176
|
+
for (let i = 0; i < tmpSolverKeys.length; i++)
|
|
177
|
+
{
|
|
178
|
+
this.dataSolverState[tmpSolverKeys] = pManifest.Solvers[tmpSolverKeys[i]];
|
|
179
|
+
}
|
|
180
|
+
this.objectAddressResolver.elucidatorSolverState = this.dataSolverState;
|
|
181
|
+
}
|
|
142
182
|
}
|
|
143
183
|
|
|
144
184
|
// Serialize the Manifest to a string
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
'Embedded Solvers',
|
|
19
|
+
function()
|
|
20
|
+
{
|
|
21
|
+
setup (()=> {} );
|
|
22
|
+
|
|
23
|
+
suite
|
|
24
|
+
(
|
|
25
|
+
'Set Filtration',
|
|
26
|
+
()=>
|
|
27
|
+
{
|
|
28
|
+
test
|
|
29
|
+
(
|
|
30
|
+
'Magic filters should be magic.',
|
|
31
|
+
(fTestComplete)=>
|
|
32
|
+
{
|
|
33
|
+
let _Manyfest = new libManyfest(
|
|
34
|
+
{
|
|
35
|
+
Scope:'Archive.org',
|
|
36
|
+
Descriptors:
|
|
37
|
+
{
|
|
38
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
39
|
+
'files[]<<~?format,==,Thumbnail?~>>': {Name:'Thumbnail Files', Hash:'ThumbnailFiles'},
|
|
40
|
+
'files[]<<~?format,==,Metadata?~>>': {Name:'Metadata Files', Hash:'MetadataFiles'},
|
|
41
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Grab magic filtered thumbnails
|
|
46
|
+
let tmpThumbnailFiles = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'ThumbnailFiles');
|
|
47
|
+
Expect(tmpThumbnailFiles).to.be.an('array');
|
|
48
|
+
// There are 7 thumbnail files in the set.
|
|
49
|
+
Expect(tmpThumbnailFiles.length).to.equal(7);
|
|
50
|
+
|
|
51
|
+
// Grab magic filtered etadataFiles
|
|
52
|
+
let tmpMetadataFiles = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'MetadataFiles');
|
|
53
|
+
Expect(tmpMetadataFiles).to.be.an('array');
|
|
54
|
+
// There are 3 metadata files in the set.
|
|
55
|
+
Expect(tmpMetadataFiles.length).to.equal(3);
|
|
56
|
+
|
|
57
|
+
let tmpFiles = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'FileSet');
|
|
58
|
+
Expect(tmpFiles).to.be.an('array');
|
|
59
|
+
// There are 17 total files in the set.
|
|
60
|
+
Expect(tmpFiles.length).to.equal(17);
|
|
61
|
+
|
|
62
|
+
fTestComplete();
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
test
|
|
66
|
+
(
|
|
67
|
+
'We should be able to access sets of properties from objects with schema.',
|
|
68
|
+
(fTestComplete)=>
|
|
69
|
+
{
|
|
70
|
+
let _Manyfest = new libManyfest(
|
|
71
|
+
{
|
|
72
|
+
Scope:'Archive.org',
|
|
73
|
+
Descriptors:
|
|
74
|
+
{
|
|
75
|
+
'files[]': {Name:'Files', Hash:'FileSet'},
|
|
76
|
+
'files[].size': {Name:'FileSizes', Hash:'FileSizes'},
|
|
77
|
+
'files[]<<~~ThumbnailFilesOnly~~>>': {Name:'Thumbnail Files', Hash:'ThumbnailFiles'},
|
|
78
|
+
'metadata.creator': {Name:'Creator', Hash:'Creator'},
|
|
79
|
+
'metadata{}': {Name:'Metadata', Hash:'Metadata'}
|
|
80
|
+
},
|
|
81
|
+
Solvers:
|
|
82
|
+
{
|
|
83
|
+
'ThumbnailFilesOnly':
|
|
84
|
+
{
|
|
85
|
+
"Description":
|
|
86
|
+
{
|
|
87
|
+
"Operation": "ThumbnailFilesOnly",
|
|
88
|
+
"Synopsis": "Filter files down to just thumbnails"
|
|
89
|
+
},
|
|
90
|
+
"Config": { "SearchTerm": "Thumbnail" },
|
|
91
|
+
"Steps":
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
"Namespace": "Logic",
|
|
95
|
+
"Instruction": "if",
|
|
96
|
+
|
|
97
|
+
"InputHashAddressMap":
|
|
98
|
+
{
|
|
99
|
+
"leftValue": "Record.format",
|
|
100
|
+
"rightValue": "SolutionState.Config.SearchTerm",
|
|
101
|
+
"comparator": "=="
|
|
102
|
+
},
|
|
103
|
+
"OutputHashAddressMap": { "truthinessResult":"keepRecord" }
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
let tmpThumbnailFiles = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'ThumbnailFiles');
|
|
110
|
+
Expect(tmpThumbnailFiles).to.be.an('array');
|
|
111
|
+
// There are 7 thumbnail files in the set.
|
|
112
|
+
Expect(tmpThumbnailFiles.length).to.equal(7);
|
|
113
|
+
let tmpFiles = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'FileSet');
|
|
114
|
+
Expect(tmpFiles).to.be.an('array');
|
|
115
|
+
// There are 7 thumbnail files in the set.
|
|
116
|
+
Expect(tmpFiles.length).to.equal(17);
|
|
117
|
+
fTestComplete();
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
);
|