manyfest 1.0.47 → 1.0.49

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.47",
3
+ "version": "1.0.49",
4
4
  "description": "JSON Object Manifest for Data Description and Parsing",
5
5
  "main": "source/Manyfest.js",
6
6
  "scripts": {
@@ -46,10 +46,10 @@
46
46
  ]
47
47
  },
48
48
  "dependencies": {
49
- "fable-serviceproviderbase": "^3.0.17"
49
+ "fable-serviceproviderbase": "^3.0.19"
50
50
  },
51
51
  "devDependencies": {
52
- "quackage": "^1.0.51",
52
+ "quackage": "^1.0.59",
53
53
  "typescript": "^5.9.3"
54
54
  },
55
55
  "author": "steven velozo <steven@velozo.com>",
@@ -208,25 +208,27 @@ class ManyfestObjectAddressResolverGetValue
208
208
  // Now get the value for each argument
209
209
  for (let i = 0; i < tmpFunctionArguments.length; i++)
210
210
  {
211
+ // Trim whitespace from the argument (e.g. after comma separation: `Book`, `List` --> ` `List``)
212
+ let tmpArgument = tmpFunctionArguments[i].trim();
211
213
  // Resolve the values for each subsequent entry
212
214
  // Check if the argument value is a string literal or a reference to an address
213
- if ((tmpFunctionArguments[i].length >= 2)
215
+ if ((tmpArgument.length >= 2)
214
216
  &&
215
- ((tmpFunctionArguments[i].charAt(0) == '"')
216
- || (tmpFunctionArguments[i].charAt(0) == "'")
217
- || (tmpFunctionArguments[i].charAt(0) == "`"))
217
+ ((tmpArgument.charAt(0) == '"')
218
+ || (tmpArgument.charAt(0) == "'")
219
+ || (tmpArgument.charAt(0) == "`"))
218
220
  &&
219
- ((tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length-1) == '"')
220
- || (tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length-1) == "'")
221
- || (tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length-1) == "`")))
221
+ ((tmpArgument.charAt(tmpArgument.length-1) == '"')
222
+ || (tmpArgument.charAt(tmpArgument.length-1) == "'")
223
+ || (tmpArgument.charAt(tmpArgument.length-1) == "`")))
222
224
  {
223
225
  // This is a string literal
224
- tmpArgumentValues.push(tmpFunctionArguments[i].substring(1, tmpFunctionArguments[i].length-1));
226
+ tmpArgumentValues.push(tmpArgument.substring(1, tmpArgument.length-1));
225
227
  }
226
228
  else
227
229
  {
228
230
  // This is a hash address
229
- tmpArgumentValues.push(this.getValueAtAddress(tmpRootObject, tmpFunctionArguments[i]));
231
+ tmpArgumentValues.push(this.getValueAtAddress(tmpRootObject, tmpArgument));
230
232
  }
231
233
  }
232
234
 
@@ -440,25 +442,27 @@ class ManyfestObjectAddressResolverGetValue
440
442
  // Now get the value for each argument
441
443
  for (let i = 0; i < tmpFunctionArguments.length; i++)
442
444
  {
445
+ // Trim whitespace from the argument (e.g. after comma separation: `Book`, `List` --> ` `List``)
446
+ let tmpArgument = tmpFunctionArguments[i].trim();
443
447
  // Resolve the values for each subsequent entry
444
448
  // Check if the argument value is a string literal or a reference to an address
445
- if ((tmpFunctionArguments[i].length >= 2)
449
+ if ((tmpArgument.length >= 2)
446
450
  &&
447
- ((tmpFunctionArguments[i].charAt(0) == '"')
448
- || (tmpFunctionArguments[i].charAt(0) == "'")
449
- || (tmpFunctionArguments[i].charAt(0) == "`"))
451
+ ((tmpArgument.charAt(0) == '"')
452
+ || (tmpArgument.charAt(0) == "'")
453
+ || (tmpArgument.charAt(0) == "`"))
450
454
  &&
451
- ((tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length-1) == '"')
452
- || (tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length-1) == "'")
453
- || (tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length-1) == "`")))
455
+ ((tmpArgument.charAt(tmpArgument.length-1) == '"')
456
+ || (tmpArgument.charAt(tmpArgument.length-1) == "'")
457
+ || (tmpArgument.charAt(tmpArgument.length-1) == "`")))
454
458
  {
455
459
  // This is a string literal
456
- tmpArgumentValues.push(tmpFunctionArguments[i].substring(1, tmpFunctionArguments[i].length-1));
460
+ tmpArgumentValues.push(tmpArgument.substring(1, tmpArgument.length-1));
457
461
  }
458
462
  else
459
463
  {
460
464
  // This is a hash address
461
- tmpArgumentValues.push(this.getValueAtAddress(tmpRootObject, tmpFunctionArguments[i]));
465
+ tmpArgumentValues.push(this.getValueAtAddress(tmpRootObject, tmpArgument));
462
466
  }
463
467
  }
464
468
 
@@ -70,16 +70,32 @@ class ManyfestObjectAddressSetValue
70
70
  // MyValues[`Cost`]
71
71
  //
72
72
  // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
73
+ //
74
+ // Chained bracket elements look like this:
75
+ // MyValues[SubObject]['Property']
76
+ // MyValues[0]['Name']
77
+ //
78
+ // When we encounter chained brackets, resolve the first bracket pair and recurse with the remainder.
79
+ //
80
+ // Bracket-at-zero elements look like this (from chained bracket recursion):
81
+ // ['Property']
82
+ // [0]
83
+ //
73
84
  // The requirements to detect a boxed element are:
74
- // 1) The start bracket is after character 0
75
- if ((tmpBracketStartIndex > 0)
85
+ // 1) The start bracket exists
86
+ if ((tmpBracketStartIndex >= 0)
76
87
  // 2) The end bracket has something between them
77
88
  && (tmpBracketStopIndex > tmpBracketStartIndex)
78
89
  // 3) There is data
79
90
  && (tmpBracketStopIndex - tmpBracketStartIndex > 1))
80
91
  {
81
- // The "Name" of the Object contained too the left of the bracket
82
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
92
+ // Check for any remaining address after the closing bracket (chained brackets or dot paths)
93
+ let tmpRemainingAddress = pAddress.substring(tmpBracketStopIndex + 1);
94
+ if (tmpRemainingAddress.length > 0 && tmpRemainingAddress.charAt(0) === '.')
95
+ {
96
+ // Strip leading dot separator (e.g. [SubObject].Property --> Property)
97
+ tmpRemainingAddress = tmpRemainingAddress.substring(1);
98
+ }
83
99
 
84
100
  // The "Reference" to the property within it, either an array element or object property
85
101
  let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
@@ -87,6 +103,52 @@ class ManyfestObjectAddressSetValue
87
103
  let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
88
104
  let tmpIndexIsNumeric = !isNaN(tmpBoxedPropertyNumber);
89
105
 
106
+ // When the bracket is at position 0, there is no property name prefix -- we are
107
+ // accessing a property or index directly on the current object. This happens when
108
+ // chained bracket resolution recurses with a remainder like ['Property'].
109
+ if (tmpBracketStartIndex === 0)
110
+ {
111
+ if (tmpIndexIsNumeric)
112
+ {
113
+ // Array access at position 0: [0], [1], etc.
114
+ if (!Array.isArray(pObject))
115
+ {
116
+ return false;
117
+ }
118
+ while (pObject.length < (tmpBoxedPropertyNumber + 1))
119
+ {
120
+ pObject.push({});
121
+ }
122
+ if (tmpRemainingAddress.length > 0)
123
+ {
124
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyNumber], tmpRemainingAddress, pValue);
125
+ }
126
+ pObject[tmpBoxedPropertyNumber] = pValue;
127
+ return true;
128
+ }
129
+ else
130
+ {
131
+ // Named property access at position 0: ['Property'], ["Name"], etc.
132
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
133
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
134
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
135
+
136
+ if (tmpRemainingAddress.length > 0)
137
+ {
138
+ if (!(tmpBoxedPropertyReference in pObject) || typeof(pObject[tmpBoxedPropertyReference]) !== 'object')
139
+ {
140
+ pObject[tmpBoxedPropertyReference] = {};
141
+ }
142
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyReference], tmpRemainingAddress, pValue);
143
+ }
144
+ pObject[tmpBoxedPropertyReference] = pValue;
145
+ return true;
146
+ }
147
+ }
148
+
149
+ // The "Name" of the Object contained to the left of the bracket
150
+ let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
151
+
90
152
  if (pObject[tmpBoxedPropertyName] == null)
91
153
  {
92
154
  if (tmpIndexIsNumeric)
@@ -132,8 +194,13 @@ class ManyfestObjectAddressSetValue
132
194
  pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference] = {};
133
195
  }
134
196
 
135
- // Return the value in the property
136
- //TODO: For cases where we have chained [][] properties, this needs to recurse somehow
197
+ // If there is remaining address after this bracket pair, recurse into the resolved subobject
198
+ if (tmpRemainingAddress.length > 0)
199
+ {
200
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpRemainingAddress, pValue);
201
+ }
202
+
203
+ // No remaining address -- set the value directly
137
204
  pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference] = pValue;
138
205
  return true;
139
206
  }
@@ -145,6 +212,12 @@ class ManyfestObjectAddressSetValue
145
212
  pObject[tmpBoxedPropertyName].push({});
146
213
  }
147
214
 
215
+ // If there is remaining address after this bracket pair, recurse into the resolved element
216
+ if (tmpRemainingAddress.length > 0)
217
+ {
218
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpRemainingAddress, pValue);
219
+ }
220
+
148
221
  pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber] = pValue;
149
222
  return true;
150
223
  }
@@ -173,13 +246,49 @@ class ManyfestObjectAddressSetValue
173
246
  //
174
247
  // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
175
248
  // The requirements to detect a boxed element are:
176
- // 1) The start bracket is after character 0
177
- if ((tmpBracketStartIndex > 0)
249
+ // 1) The start bracket exists
250
+ if ((tmpBracketStartIndex >= 0)
178
251
  // 2) The end bracket has something between them
179
252
  && (tmpBracketStopIndex > tmpBracketStartIndex)
180
253
  // 3) There is data
181
254
  && (tmpBracketStopIndex - tmpBracketStartIndex > 1))
182
255
  {
256
+ // When the bracket is at position 0, there is no property name prefix -- we are
257
+ // accessing a property or index directly on the current object. This happens when
258
+ // chained bracket resolution recurses with a segment like ['Nested'].
259
+ if (tmpBracketStartIndex === 0)
260
+ {
261
+ let tmpBoxedPropertyReference = tmpSubObjectName.substring(1, tmpBracketStopIndex).trim();
262
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
263
+
264
+ if (!isNaN(tmpBoxedPropertyNumber))
265
+ {
266
+ // Array access: [0], [1], etc.
267
+ if (!Array.isArray(pObject))
268
+ {
269
+ return false;
270
+ }
271
+ while (pObject.length < (tmpBoxedPropertyNumber + 1))
272
+ {
273
+ pObject.push({});
274
+ }
275
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyNumber], tmpNewAddress, pValue);
276
+ }
277
+ else
278
+ {
279
+ // Named property access: ['Nested'], ["Name"], etc.
280
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
281
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
282
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
283
+
284
+ if (!(tmpBoxedPropertyReference in pObject) || typeof(pObject[tmpBoxedPropertyReference]) !== 'object')
285
+ {
286
+ pObject[tmpBoxedPropertyReference] = {};
287
+ }
288
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyReference], tmpNewAddress, pValue);
289
+ }
290
+ }
291
+
183
292
  let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
184
293
 
185
294
  let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
@@ -187,6 +296,30 @@ class ManyfestObjectAddressSetValue
187
296
  let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
188
297
  let tmpIndexIsNumeric = !isNaN(tmpBoxedPropertyNumber);
189
298
 
299
+ // Check for chained brackets within this segment (e.g. _Object[SubObject]['Nested'])
300
+ // Any remaining address after the first ] within the segment name needs to be
301
+ // prepended to tmpNewAddress so it is not lost during recursion.
302
+ let tmpSegmentRemainder = tmpSubObjectName.substring(tmpBracketStopIndex + 1);
303
+ if (tmpSegmentRemainder.length > 0)
304
+ {
305
+ // Prepend the remainder to the new address
306
+ // e.g. if segment was _Object[SubObject]['Nested'] and newAddress was Deep,
307
+ // then combined address becomes ['Nested'].Deep
308
+ if (tmpNewAddress.length > 0)
309
+ {
310
+ tmpNewAddress = tmpSegmentRemainder + '.' + tmpNewAddress;
311
+ }
312
+ else
313
+ {
314
+ tmpNewAddress = tmpSegmentRemainder;
315
+ }
316
+ // Strip leading dot if present
317
+ if (tmpNewAddress.charAt(0) === '.')
318
+ {
319
+ tmpNewAddress = tmpNewAddress.substring(1);
320
+ }
321
+ }
322
+
190
323
  //if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
191
324
  if (pObject[tmpBoxedPropertyName] == null)
192
325
  {
@@ -324,6 +324,71 @@ suite
324
324
  Expect(_Manyfest.getValueAtAddress(_MockObject, 'Behaviors.SillyFUNCTIONNOTFOUND().Stores[0]')).to.equal(false);
325
325
  Expect(_Manyfest.getValueAtAddress(_MockObject, 'Behaviors.BrokenObject().Stores[0]')).to.equal(false);
326
326
 
327
+ fTestComplete();
328
+ }
329
+ );
330
+ test
331
+ (
332
+ 'Function arguments with whitespace after comma separators',
333
+ (fTestComplete)=>
334
+ {
335
+ let _Manyfest = new libManyfest();
336
+
337
+ let tmpReceivedArgs = [];
338
+ let _MockObject = (
339
+ {
340
+ "Name": "TestValue",
341
+ "Category": "Books",
342
+ "SingleArgFunction": (pValue) => { return `Got: ${pValue}`; },
343
+ "MultiArgFunction": (pArg1, pArg2) => { tmpReceivedArgs = [pArg1, pArg2]; return `${pArg1}:${pArg2}`; },
344
+ "ThreeArgFunction": (pArg1, pArg2, pArg3) => { return `${pArg1}-${pArg2}-${pArg3}`; },
345
+ "Nested":
346
+ {
347
+ "Value": 42,
348
+ "MultiArgMethod": function(pArg1, pArg2) { return `${pArg1}+${pArg2}`; }
349
+ }
350
+ });
351
+
352
+ // Baseline: no spaces after commas (already worked)
353
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'MultiArgFunction("Book","List")'))
354
+ .to.equal('Book:List');
355
+
356
+ // THE BUG: spaces after comma with backtick-delimited string literals
357
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'MultiArgFunction(`Book`, `List`)'))
358
+ .to.equal('Book:List');
359
+
360
+ // Spaces after comma with double-quoted string literals
361
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'MultiArgFunction("Book", "List")'))
362
+ .to.equal('Book:List');
363
+
364
+ // Spaces after comma with single-quoted string literals
365
+ Expect(_Manyfest.getValueAtAddress(_MockObject, "MultiArgFunction('Book', 'List')"))
366
+ .to.equal('Book:List');
367
+
368
+ // Mixed quote styles with spaces
369
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'MultiArgFunction("Book", `List`)'))
370
+ .to.equal('Book:List');
371
+
372
+ // Multiple spaces after comma
373
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'MultiArgFunction(`Book`, `List`)'))
374
+ .to.equal('Book:List');
375
+
376
+ // Spaces with address references (not string literals)
377
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'MultiArgFunction(Name, Category)'))
378
+ .to.equal('TestValue:Books');
379
+
380
+ // Mixed: string literal with space, then address reference with space
381
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'MultiArgFunction("Hello", Name)'))
382
+ .to.equal('Hello:TestValue');
383
+
384
+ // Three arguments with spaces
385
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'ThreeArgFunction(`A`, `B`, `C`)'))
386
+ .to.equal('A-B-C');
387
+
388
+ // Non-terminal function path with spaced arguments
389
+ Expect(_Manyfest.getValueAtAddress(_MockObject, 'Nested.MultiArgMethod(`X`, `Y`)'))
390
+ .to.equal('X+Y');
391
+
327
392
  fTestComplete();
328
393
  }
329
394
  );
@@ -195,8 +195,7 @@ suite
195
195
  fTestComplete();
196
196
  }
197
197
  );
198
- //TODO: known broken case for multiple bracketed properties
199
- test.skip
198
+ test
200
199
  (
201
200
  'Indexed subobject are settable double brackets',
202
201
  (fTestComplete)=>
@@ -211,6 +210,79 @@ suite
211
210
  fTestComplete();
212
211
  }
213
212
  );
213
+ test
214
+ (
215
+ 'Chained brackets with double-quoted inner bracket',
216
+ (fTestComplete)=>
217
+ {
218
+ let _Manyfest = new libManyfest({});
219
+ let _SimpleObject = {};
220
+ _Manyfest.setValueAtAddress(_SimpleObject, `_Object[SubObject]["Property"]`, '456');
221
+ Expect(_Manyfest.getValueAtAddress(_SimpleObject, `_Object.SubObject.Property`))
222
+ .to.equal('456');
223
+ Expect(_SimpleObject._Object.SubObject.Property)
224
+ .to.equal('456');
225
+ fTestComplete();
226
+ }
227
+ );
228
+ test
229
+ (
230
+ 'Chained brackets with quoted outer and quoted inner',
231
+ (fTestComplete)=>
232
+ {
233
+ let _Manyfest = new libManyfest({});
234
+ let _SimpleObject = {};
235
+ _Manyfest.setValueAtAddress(_SimpleObject, `_Object["SubObject"]['Property']`, '789');
236
+ Expect(_Manyfest.getValueAtAddress(_SimpleObject, `_Object.SubObject.Property`))
237
+ .to.equal('789');
238
+ Expect(_SimpleObject._Object.SubObject.Property)
239
+ .to.equal('789');
240
+ fTestComplete();
241
+ }
242
+ );
243
+ test
244
+ (
245
+ 'Chained bracket then dot path',
246
+ (fTestComplete)=>
247
+ {
248
+ let _Manyfest = new libManyfest({});
249
+ let _SimpleObject = {};
250
+ _Manyfest.setValueAtAddress(_SimpleObject, `_Object[SubObject]['Nested'].Deep`, 'abc');
251
+ Expect(_Manyfest.getValueAtAddress(_SimpleObject, `_Object.SubObject.Nested.Deep`))
252
+ .to.equal('abc');
253
+ Expect(_SimpleObject._Object.SubObject.Nested.Deep)
254
+ .to.equal('abc');
255
+ fTestComplete();
256
+ }
257
+ );
258
+ test
259
+ (
260
+ 'Array index then chained bracket',
261
+ (fTestComplete)=>
262
+ {
263
+ let _Manyfest = new libManyfest({});
264
+ let _SimpleObject = { Items: [{}, {}, {}] };
265
+ _Manyfest.setValueAtAddress(_SimpleObject, `Items[1]['Name']`, 'Second');
266
+ Expect(_Manyfest.getValueAtAddress(_SimpleObject, `Items[1].Name`))
267
+ .to.equal('Second');
268
+ Expect(_SimpleObject.Items[1].Name)
269
+ .to.equal('Second');
270
+ fTestComplete();
271
+ }
272
+ );
273
+ test
274
+ (
275
+ 'Bracket at position zero (direct property access)',
276
+ (fTestComplete)=>
277
+ {
278
+ let _Manyfest = new libManyfest({});
279
+ let _SimpleObject = {};
280
+ _Manyfest.setValueAtAddress(_SimpleObject, `['DirectBracket']`, 'direct');
281
+ Expect(_SimpleObject.DirectBracket)
282
+ .to.equal('direct');
283
+ fTestComplete();
284
+ }
285
+ );
214
286
  }
215
287
  );
216
288
  }
package/.babelrc DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "presets": ["@babel/preset-env"]
3
- }
package/.browserslistrc DELETED
@@ -1 +0,0 @@
1
- > 0.01%
File without changes