manyfest 1.0.8 → 1.0.9

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.
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "optOut": false,
3
- "lastUpdateCheck": 1666021392557
3
+ "lastUpdateCheck": 1666107934926
4
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "manyfest",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
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,57 @@ 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
+ // This allows the magic filtration with elucidator configuration
255
+ // TODO: We could pass more state in (e.g. parent address, object, etc.)
256
+ // TODO: Discuss this metaprogramming AT LENGTH
257
+ let tmpFilterState = (
258
+ {
259
+ Record: pRecord,
260
+ keepRecord: true
261
+ });
262
+
263
+ // This is about as complex as it gets.
264
+ // TODO: Optimize this so it is only initialized once.
265
+ // TODO: That means figuring out a healthy pattern for passing in state to this
266
+ tmpPrecedent.addPattern('{<~~', '~~>}',
267
+ (pInstructionHash) =>
268
+ {
269
+ // This is for internal config on the solution steps. Right now config is not shared across steps.
270
+ if (this.elucidatorSolverState.hasOwnProperty(pInstructionHash))
271
+ {
272
+ tmpFilterState.SolutionState = this.elucidatorSolverState[pInstructionHash];
273
+ }
274
+ this.elucidatorSolver.solveInternalOperation('Custom', pInstructionHash, tmpFilterState);
275
+ });
276
+
277
+ tmpPrecedent.parseString(tmpAddress);
278
+
279
+ // It is expected that the operation will mutate this to some truthy value
280
+ return tmpFilterState.keepRecord;
281
+ }
282
+ else
283
+ {
284
+ return true;
285
+ }
286
+ }
287
+
239
288
  // Get the value of an element at an address
240
289
  getValueAtAddress (pObject, pAddress, pParentAddress)
241
290
  {
242
- // Make sure pObject is an object
291
+ // Make sure pObject (the object we are meant to be recursing) is an object (which could be an array or object)
243
292
  if (typeof(pObject) != 'object') return undefined;
244
- // Make sure pAddress is a string
293
+ // Make sure pAddress (the address we are resolving) is a string
245
294
  if (typeof(pAddress) != 'string') return undefined;
295
+ // Stash the parent address for later resolution
246
296
  let tmpParentAddress = "";
247
297
  if (typeof(pParentAddress) == 'string')
248
298
  {
@@ -337,7 +387,19 @@ class ManyfestObjectAddressResolver
337
387
  return false;
338
388
  }
339
389
 
340
- return pObject[tmpBoxedPropertyName];
390
+ let tmpInputArray = pObject[tmpBoxedPropertyName];
391
+ let tmpOutputArray = [];
392
+ for (let i = 0; i < tmpInputArray.length; i++)
393
+ {
394
+ // The filtering is complex but allows config-based metaprogramming directly from schema
395
+ let tmpKeepRecord = this.checkFilters(pAddress, tmpInputArray[i]);
396
+ if (tmpKeepRecord)
397
+ {
398
+ tmpOutputArray.push(tmpInputArray[i]);
399
+ }
400
+ }
401
+
402
+ return tmpOutputArray;
341
403
  }
342
404
  // The object has been flagged as an object set, so treat it as such
343
405
  else if (tmpObjectTypeMarkerIndex > 0)
@@ -456,7 +518,8 @@ class ManyfestObjectAddressResolver
456
518
  for (let i = 0; i < tmpArrayProperty.length; i++)
457
519
  {
458
520
  let tmpPropertyParentAddress = `${tmpParentAddress}[${i}]`;
459
- let tmpValue = this.getValueAtAddress(pObject[tmpBoxedPropertyName][i], tmpNewAddress, tmpPropertyParentAddress);;
521
+ let tmpValue = this.getValueAtAddress(pObject[tmpBoxedPropertyName][i], tmpNewAddress, tmpPropertyParentAddress);
522
+
460
523
  tmpContainerObject[`${tmpPropertyParentAddress}.${tmpNewAddress}`] = tmpValue;
461
524
  }
462
525
 
@@ -486,8 +549,14 @@ class ManyfestObjectAddressResolver
486
549
  for (let i = 0; i < tmpObjectPropertyKeys.length; i++)
487
550
  {
488
551
  let tmpPropertyParentAddress = `${tmpParentAddress}.${tmpObjectPropertyKeys[i]}`;
489
- let tmpValue = this.getValueAtAddress(pObject[tmpObjectPropertyName][tmpObjectPropertyKeys[i]], tmpNewAddress, tmpPropertyParentAddress);;
490
- tmpContainerObject[`${tmpPropertyParentAddress}.${tmpNewAddress}`] = tmpValue;
552
+ let tmpValue = this.getValueAtAddress(pObject[tmpObjectPropertyName][tmpObjectPropertyKeys[i]], tmpNewAddress, tmpPropertyParentAddress);
553
+
554
+ // The filtering is complex but allows config-based metaprogramming directly from schema
555
+ let tmpKeepRecord = this.checkFilters(pAddress, tmpValue);
556
+ if (tmpKeepRecord)
557
+ {
558
+ tmpContainerObject[`${tmpPropertyParentAddress}.${tmpNewAddress}`] = tmpValue;
559
+ }
491
560
  }
492
561
 
493
562
  return tmpContainerObject;
@@ -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,86 @@
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
+ 'We should be able to access sets of properties from objects with 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
+ 'files[]{<~~ThumbnailFilesOnly~~>}': {Name:'Thumbnail Files', Hash:'ThumbnailFiles'},
41
+ 'metadata.creator': {Name:'Creator', Hash:'Creator'},
42
+ 'metadata{}': {Name:'Metadata', Hash:'Metadata'}
43
+ },
44
+ Solvers:
45
+ {
46
+ 'ThumbnailFilesOnly':
47
+ {
48
+ "Description":
49
+ {
50
+ "Operation": "ThumbnailFilesOnly",
51
+ "Synopsis": "Filter files down to just thumbnails"
52
+ },
53
+ "Config": { "SearchTerm": "Thumbnail" },
54
+ "Steps":
55
+ [
56
+ {
57
+ "Namespace": "Logic",
58
+ "Instruction": "if",
59
+
60
+ "InputHashAddressMap":
61
+ {
62
+ "leftValue": "Record.format",
63
+ "rightValue": "SolutionState.Config.SearchTerm",
64
+ "comparator": "=="
65
+ },
66
+ "OutputHashAddressMap": { "truthinessResult":"keepRecord" }
67
+ }
68
+ ]
69
+ }
70
+ }
71
+ });
72
+ let tmpThumbnailFiles = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'ThumbnailFiles');
73
+ Expect(tmpThumbnailFiles).to.be.an('array');
74
+ // There are 7 thumbnail files in the set.
75
+ Expect(tmpThumbnailFiles.length).to.equal(7);
76
+ let tmpFiles = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'FileSet');
77
+ Expect(tmpFiles).to.be.an('array');
78
+ // There are 7 thumbnail files in the set.
79
+ Expect(tmpFiles.length).to.equal(17);
80
+ fTestComplete();
81
+ }
82
+ );
83
+ }
84
+ );
85
+ }
86
+ );