jupyter-ijavascript-utils 1.37.0 → 1.40.0

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/DOCS.md CHANGED
@@ -75,6 +75,10 @@ Give it a try here:
75
75
 
76
76
  ## What's New
77
77
 
78
+ * 1.40 - {@link module:array.extract|array.extract} and {@link module:array.applyArrayValues|array.applyArrayValues} to allow for extracting values from arrays, transforming them on a separate process and applying them deeply and safely
79
+ * 1.39 - {@link module:format.extractWords|format.exportWords} - to identify distinct words in strings using unicode character properties
80
+ * 1.38 - {@link module:object.extractObjectProperty|object.extractObjectProperty} / {@link module:object.applyPropertyValue|object.applyPropertyValue} to allow for extracting values from arrays, transforming them on a separate process and applying them back
81
+ * 1.37 - {@link module:format.replaceString|format.replaceString} as convenience for replacing only a single string.
78
82
  * 1.36 - {@link module:format.replaceStrings|format.replaceStrings} to allow for replacement dictionaries and tuplets
79
83
  * 1.35 - {@link module:object.extractObjectProperties|extractObjectProperties} / {@link module:object.extractObjectProperty|extractObjectProperty} - to do horizontal transposes on objects
80
84
  * 1.34 - {@link module:format.mapArrayDomain|format.mapArrayDomain} and add notes in the header of {@link module:random|random} on using non-uniform distributions.
package/Dockerfile CHANGED
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.35.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.40.0
package/README.md CHANGED
@@ -55,6 +55,10 @@ This is not intended to be the only way to accomplish many of these tasks, and a
55
55
 
56
56
  # What's New
57
57
 
58
+ * 1.40 - array.extract and array.applyArrayValue to allow for extracting values from arrays, transforming them on a separate process and applying them back
59
+ * 1.39 - format.exportWords - to identify distinct words in strings using unicode character properties
60
+ * 1.38 - object.extract / object.apply
61
+ * 1.37 - {@link module:format.replaceString|format.replaceString} as convenience for replacing only a single string.
58
62
  * 1.36 - replaceStrings to allow for replacement dictionaries and tuplets
59
63
  * 1.35 - object.extractObjectProperties / object.extractObjectProperty - to do horizontal transposes on objects
60
64
  * 1.34 - format.mapArrayDomain and add notes in to random on using non-uniform distributions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.37.0",
3
+ "version": "1.40.0",
4
4
  "description": "Utilities for working with iJavaScript - a Jupyter Kernel",
5
5
  "homepage": "https://jupyter-ijavascript-utils.onrender.com/",
6
6
  "license": "MIT",
@@ -17,6 +17,7 @@
17
17
  "test:debug": "TZ=UTC node --inspect-brk node_modules/jest/bin/jest.js --runInBand",
18
18
  "test:coverage": "TZ=UTC jest src --collectCoverage",
19
19
  "test:watch:coverage": "TZ=UTC jest src --watch --collectCoverage",
20
+ "doc:taffy": "npm install taffydb && npm run doc",
20
21
  "docs": "npm run doc",
21
22
  "doc": "npm run prep:docdash && node_modules/.bin/jsdoc -c ./jsdoc.json -u ./tutorials ./DOCS.md",
22
23
  "prep:docdash": "cp docResources/docdash/layout.tmpl node_modules/docdash/tmpl/layout.tmpl && rm -rf docResources/notebooks/node_modules",
package/src/array.js CHANGED
@@ -27,6 +27,10 @@ require('./_types/global');
27
27
  * * {@link module:array.pickRows|array.pickRows} - picks a row from a 2d array
28
28
  * * {@link module:array.pickColumns|array.pickColumns} - picks a column from a 2d array
29
29
  * * {@link module:array.pick|array.pick} - picks either/or rows and columns
30
+ * * {@link module:array.extract|array.extract} - synonym to array.pick to pick either a row or column from an array
31
+ * * Applying a value
32
+ * * {@link module:array.applyArrayValue|array.applyArrayValue} - applies a value deeply into an array safely
33
+ * * {@link module:array.applyArrayValues|array.applyArrayValues} - applies a value / multiple values deeply into an array safely
30
34
  * * Understanding Values
31
35
  * * {@link module:array.isMultiDimensional|array.isMultiDimensional} - determines if an array is multi-dimensional
32
36
  *
@@ -150,6 +154,7 @@ module.exports.peekLast = function peekLast(targetArray, defaultVal = null) {
150
154
  * @param {Array} array2d - 2d array to pick from [row][column]
151
155
  * @param {...Number} rowIndices - Indexes of the row to return, [0...length-1]
152
156
  * @returns - Array with only those rows
157
+ * @see {@link module:array.pick|array.pick} - pick either rows or columns
153
158
  * @example
154
159
  * data = [
155
160
  * ['john', 23, 'purple'],
@@ -179,6 +184,7 @@ module.exports.pickRows = function pickRows(array2d, ...rowIndices) {
179
184
  * @param {Array} array2d - 2d array to pick from [row][column]
180
185
  * @param {...any} columns - Indexes of the columns to pick the values from: [0...row.length-1]
181
186
  * @returns - Array with all rows, and only those columns
187
+ * @see {@link module:array.pick|array.pick} - pick either rows or columns
182
188
  * @example
183
189
  * data = [
184
190
  * ['john', 23, 'purple'],
@@ -210,8 +216,9 @@ module.exports.pickColumns = function pickColumns(array2d, ...columns) {
210
216
  * @param {Number[]} [options.rows = null] - indices of the rows to pick
211
217
  * @param {Number[]} [options.columns = null] - indices of the columns to pick.
212
218
  * @returns {Array} - 2d array of only the rows and columns chosen.
213
- * @see {@link module:Array.pickRows} - picking rows
214
- * @see {@link module:Array.pickColumns} - picking columns
219
+ * @see {@link module:array.pickRows|array.pickRows} - picking rows
220
+ * @see {@link module:array.pickColumns|array.pickColumns} - picking columns
221
+ * @see {@link module:array.applyArrayValues|array.applyArrayValues} - applies a value / multiple values deeply into an array safely
215
222
  * @returns - 2dArray of the columns and rows requested
216
223
  * @example
217
224
  * data = [
@@ -250,6 +257,206 @@ module.exports.pick = function pick(array2d, options) {
250
257
  return results;
251
258
  };
252
259
 
260
+ /**
261
+ * Convenience function for picking specific rows and columns from a 2d array.
262
+ *
263
+ * Alias of {@link module:array.pick|array.pick}
264
+ *
265
+ * Please also see [Danfo.js](https://danfo.jsdata.org/) for working with DataFrames.
266
+ *
267
+ * @param {Array} array2d - 2d array to pick from [row][column]
268
+ * @param {Object} options - options on which to pick
269
+ * @param {Number[]} [options.rows = null] - indices of the rows to pick
270
+ * @param {Number[]} [options.columns = null] - indices of the columns to pick.
271
+ * @returns {Array} - 2d array of only the rows and columns chosen.
272
+ * @see {@link module:array.pickRows} - picking rows
273
+ * @see {@link module:array.pickColumns} - picking columns
274
+ * @returns - 2dArray of the columns and rows requested
275
+ * @example
276
+ * data = [
277
+ * ['john', 23, 'purple'],
278
+ * ['jane', 32, 'red'],
279
+ * ['ringo', 27, 'green']
280
+ * ];
281
+ *
282
+ * utils.array.pick(data, {rows: [0, 1]});
283
+ * //-- [['john', 23, 'purple'], ['jane', 32, 'red']];
284
+ *
285
+ * utils.array.pick(data, {columns: [0, 2]});
286
+ * //-- [['john', 'purple'], ['jane', 'red'], ['ringo', 'green']];
287
+ *
288
+ * utils.array.pick(data, {rows:[0, 1], columns:[0, 2]});
289
+ * //-- [['john', 'purple'], ['jane', 'red']];
290
+ */
291
+ module.exports.extract = module.exports.pick;
292
+
293
+ /**
294
+ * Applies deeply onto an array safely - in-place using dot-notation paths
295
+ * even if the child paths don't exist.
296
+ *
297
+ * While tthis can be as simple as safely applying a value even if targetObj may be null
298
+ *
299
+ * ```
300
+ * targetObj = [1, 2, null, 4, 5];
301
+ *
302
+ * utils.object.applyPropertyValue(targetObj, '[2]', 3);
303
+ * // [1, 2, 3, 4, 5]
304
+ * // equivalent to targetObj[2] = 3;
305
+ * ```
306
+ *
307
+ * This is much more safely working with deeply nested objects
308
+ *
309
+ * ```
310
+ * targetObj = [{
311
+ * name: 'john smith',
312
+ * class: {
313
+ * name: 'ECON_101',
314
+ * professor: {
315
+ * last_name: 'Winklemeyer'
316
+ * }
317
+ * }
318
+ * }];
319
+ *
320
+ * utils.object.applyPropertyValue(targetObj, '[0].class.professor.first_name', 'René');
321
+ * // [{
322
+ * // name: 'john smith',
323
+ * // class: {
324
+ * // name: 'ECON_101',
325
+ * // professor: {
326
+ * // last_name: 'Winklemeyer',
327
+ * // first_name: 'René' // <- Added
328
+ * // }
329
+ * // }
330
+ * // }];
331
+ * ```
332
+ *
333
+ * or creating intermediary objects along the path - if they did not exist first.
334
+ *
335
+ * ```
336
+ * targetObj = [{
337
+ * name: 'john smith'
338
+ * }];
339
+ * utils.object.applyPropertyValue(targetObj, '[0].class.professor.first_name', 'René');
340
+ * [{
341
+ * name: 'john smith',
342
+ * class: {
343
+ * professor: {
344
+ * first_name: 'René'
345
+ * }
346
+ * }
347
+ * }];
348
+ * ```
349
+ *
350
+ * @param {Array} collection - array to apply the value to
351
+ * @param {string} path - dot notation path to set the value, ex: 'geo', or 'states[0].prop'
352
+ * @param {any} value - value to set
353
+ * @returns {Array} - the base array
354
+ * @see {@link module:array.pick|array.pick} - to pick a row or column into an array
355
+ * @see {@link module:array.applyArrayValues|array.applyArrayValues} - applies an array safely and deeply onto another array of values
356
+ */
357
+ module.exports.applyArrayValue = function applyArrayValue(collection, path, value) {
358
+ // const signature = 'applyArrayValue(collection, path, value)';
359
+
360
+ if (!collection) return collection;
361
+ if (!path) return collection;
362
+
363
+ const cleanPath = String(path)
364
+ .replace(/\[/g, '.')
365
+ .replace(/\]/g, '.')
366
+ .replace(/[.]+/g, '.')
367
+ .replace(/^[.]+/, '')
368
+ .replace(/[.]$/, '');
369
+
370
+ const splitPath = cleanPath.split('.');
371
+ const terminalIndex = splitPath.length - 1;
372
+
373
+ return splitPath
374
+ .reduce((currentVal, prop, currentIndex) => {
375
+ //-- can no longer occur
376
+ // if (!prop) throw Error(`${signature}:Unable to set value with path:${path}`);
377
+
378
+ const isLeaf = currentIndex === terminalIndex;
379
+ if (isLeaf) {
380
+ // eslint-disable-next-line no-param-reassign
381
+ currentVal[prop] = value;
382
+ // if (value === undefined) {
383
+ // delete currentVal[prop];
384
+ // } else {
385
+ // currentVal[prop] = value;
386
+ // }
387
+ return collection;
388
+ }
389
+ //-- not a leaf
390
+ if (!currentVal[prop]) {
391
+ // eslint-disable-next-line no-param-reassign
392
+ currentVal[prop] = {};
393
+ }
394
+ return currentVal[prop];
395
+ }, collection);
396
+ };
397
+
398
+ /**
399
+ * Converse from the extractPropertyValue, this takes a value / set of values
400
+ * and applies the values for each index in the collection.
401
+ *
402
+ * for example:
403
+ *
404
+ * ```
405
+ * weather = [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
406
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
407
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }];
408
+ *
409
+ * cities = utils.object.extractObjectProperty('city');
410
+ * // ['Seattle', 'New York', 'Chicago'];
411
+ *
412
+ * //-- async process to geocode
413
+ * geocodedCities = geocodeCity(cities);
414
+ * // [{ city: 'Seattle', state: 'WA', country: 'USA' },
415
+ * // { city: 'New York', state: 'NY', country: 'USA' },
416
+ * // { city: 'Chicago', state: 'IL', country: 'USA' }]
417
+ *
418
+ * utils.applyArrayValues(weather, 'geo', geocodedCities);
419
+ * // [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87, geo: { city: 'Seattle', state: 'WA', country: 'USA' } },
420
+ * // { id: 3, city: 'New York', month: 'Apr', precip: 3.94, geo: { city: 'New York', state: 'NY', country: 'USA' } },
421
+ * // { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62, geo: { city: 'Chicago', state: 'IL', country: 'USA' } }];
422
+ *
423
+ * Note that traditional [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
424
+ * works best for if you are working with objects completely in memory.
425
+ *
426
+ * But this helps quite a bit if the action of mapping / transforming values
427
+ * needs to be separate from the extraction / application of values back.
428
+ *
429
+ * @param {Array} collection - array to apply the value to on each index
430
+ * @param {string} path - dot notation path to set the value within each index, ex: 'geo', or 'states[0].prop'
431
+ * @param {any} value - the value that should be set at that path.
432
+ * @returns {Object}
433
+ * @see {@link module:array.applyArrayValue|array.applyArrayValue} - to apply a single value to a single object
434
+ * @see {@link module:array.pick|array.pick} - to pick a row or column into an array
435
+ */
436
+ module.exports.applyArrayValues = function applyArrayValues(collection, path, valueList) {
437
+ // const signature = 'applyValue(objectList, path, valueList)';
438
+ if (!collection || !path) {
439
+ //-- do nothing
440
+ return collection;
441
+ }
442
+
443
+ const cleanCollection = Array.isArray(collection) ? collection : [collection];
444
+ const cleanValueList = Array.isArray(valueList) ? valueList : Array(cleanCollection.length).fill(valueList);
445
+
446
+ // if (cleanCollection.length !== cleanValueList) throw Error(
447
+ // `${signature}: objectList.length[${cleanCollection.length}] does not match valueList.length[${cleanValueList.length}]`
448
+ // );
449
+ const minLength = Math.min(cleanCollection.length, cleanValueList.length);
450
+
451
+ for (let i = 0; i < minLength; i += 1) {
452
+ const obj = cleanCollection[i];
453
+ const val = cleanValueList[i];
454
+ ArrayUtils.applyArrayValue(obj, path, val);
455
+ }
456
+
457
+ return collection;
458
+ };
459
+
253
460
  /**
254
461
  * Creates an array of a specific size and default value
255
462
  *
package/src/format.js CHANGED
@@ -20,6 +20,9 @@
20
20
  * * {@link module:format.consoleLines|format.consoleLines(...)} - same as limit lines, only console.logs the string out.
21
21
  * * {@link module:format.wordWrap|format.wordWrap(str, options)} - breaks apart string by line length
22
22
  * * {@link module:format.lineCount|format.lineCount(str, options)} - counts the number of lines in a string
23
+ * * Replacing values in Strings
24
+ * * {@link module:format.replaceString|format.replaceString(string, stringTupletsOrMap)} - applies replace from a collection of [matcher, replacement] tuplets or map based on key-> values
25
+ * * {@link module:format.replaceStrings|format.replaceStrings(stringsArray, stringTupletsOrMap)} - applies replaceString on an array of values
23
26
  * * Formatting Time
24
27
  * * {@link module:format.millisecondDuration|format.millisecondDuration}
25
28
  * * Mapping Values
@@ -38,6 +41,8 @@
38
41
  * * {@link module:format.parseNumber|format.parseNumber(val, locale)} - converts a value to a number
39
42
  * * Identifying values
40
43
  * * {@link module:format.isEmptyValue|format.isEmptyValue} - determine if a value is not 'empty'
44
+ * * Extracting values
45
+ * * {@link module:format.extractWords|format.extractWords} - to extract the words from a string
41
46
  *
42
47
  * @module format
43
48
  * @exports format
@@ -1214,6 +1219,7 @@ module.exports.lineCount = function lineCount(str, newlineCharacter = '\n') {
1214
1219
  * // 'and jill came tumbling after' ]
1215
1220
  *
1216
1221
  * //-- or use tuplets of [find, replace] with regular expressions
1222
+ * //-- and strings not in tuplets are simply removed.
1217
1223
  * replaceValues = [['jack', 'john'], [/\s+jill/i, ' ringo'], ' down'];
1218
1224
  * utils.format.replaceStrings(targetStrings, replaceValues);
1219
1225
  * expected = [
@@ -1267,3 +1273,71 @@ module.exports.replaceStrings = function replaceStrings(targetStr, stringTuplets
1267
1273
  ? result
1268
1274
  : result.replace(replaceSearch, replaceWith), stringToClean));
1269
1275
  };
1276
+
1277
+ /**
1278
+ * Conveience function for calling {@link module:format.replaceStrings|format.replaceStrings} -
1279
+ * giving and receiving a single value (instead of an array of values).
1280
+ * @param {String} targetStr - A Single string to replace values on.
1281
+ * @param {Array|Map<string|RegExp,string>} stringTupletsOrMap - [[search, replace]] or Map<String|RegExp,String>
1282
+ * @returns {string} - the targetStr with the values replaced
1283
+ * @see {@link module:format.replaceStrings}
1284
+ */
1285
+ module.exports.replaceString = function replaceString(targetStr, stringTupletsOrMap) {
1286
+ if (Array.isArray(targetStr)) {
1287
+ throw Error('replaceString(targetStr, stringTupletsOrMap): targetStr was sent an array - please use replaceStrings instead');
1288
+ }
1289
+ return FormatUtils.replaceStrings([targetStr], stringTupletsOrMap)[0];
1290
+ };
1291
+
1292
+ /**
1293
+ * Identify an array of words each from a string.
1294
+ *
1295
+ * Note that this uses the new [Unicode Properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape)
1296
+ * using `\p{L}` to identify characters based on unicode properties.
1297
+ *
1298
+ * For example:
1299
+ *
1300
+ * ```
1301
+ * strs = 'I am Modern "major-general".';
1302
+ * FormatUtils.extractWords(strs);
1303
+ * // ['I', 'am', 'Modern', 'major', 'general'];
1304
+ * ```
1305
+ *
1306
+ * you can also include additional characters that will no longer be considered word boundaries.
1307
+ *
1308
+ * ```
1309
+ * strs = 'I am Modern "major-general".';
1310
+ * FormatUtils.extractWords(strs);
1311
+ * // ['I', 'am', 'Modern', 'major-general'];
1312
+ * ```
1313
+ *
1314
+ * arrays of strings are also supported
1315
+ *
1316
+ * ```
1317
+ * strs = ['letras mayúsculas de tamaño igual a las minúsculas',
1318
+ * 'الاختراع، ومايكل هارت'];
1319
+ * FormatUtils.extractWords(strs);
1320
+ * // [ 'letras', 'mayúsculas', 'de', 'tamaño', 'igual', 'a', 'las', 'minúsculas', 'الاختراع', 'ومايكل', 'هارت'];
1321
+ * ```
1322
+ *
1323
+ * @param {String|String[]} strToExtractFrom - collection of strings to extract words from
1324
+ * @param {String} additionalNonBreakingCharacters - each char in this string will not be treated as a word boundry
1325
+ * @returns {string[]} - collection of words
1326
+ */
1327
+ module.exports.extractWords = function extractWords(strToExtractFrom, additionalNonBreakingCharacters) {
1328
+ if (!strToExtractFrom) return strToExtractFrom;
1329
+
1330
+ const cleanNonBreaking = additionalNonBreakingCharacters
1331
+ ? additionalNonBreakingCharacters
1332
+ : '';
1333
+
1334
+ const regex = cleanNonBreaking
1335
+ ? new RegExp(`[${additionalNonBreakingCharacters}\\p{L}]+`, 'gu')
1336
+ : /\p{L}+/ug; // -- old attempt/[A-Za-zÀ-ÖØ-öø-ÿ]+/g;
1337
+
1338
+ const cleanStrings = Array.isArray(strToExtractFrom)
1339
+ ? strToExtractFrom
1340
+ : [strToExtractFrom];
1341
+
1342
+ return cleanStrings.reduce((result, str) => [...result, ...((str || '').match(regex) || [])], []);
1343
+ };
package/src/object.js CHANGED
@@ -13,29 +13,33 @@ const FormatUtils = require('./format');
13
13
  * * {@link module:object.generateSchema|generateSchema()} - generate a schema / describe properties of a list of objects
14
14
  * * {@link module:object.findWithoutProperties|findWithoutProperties()} - find objects without ALL the properties specified
15
15
  * * {@link module:object.findWithoutProperties|findWithProperties()} - find objects with any of the properties specified
16
- * * {@link module:object.setPropertyDefaults|setPropertyDefaults()} - sets values for objects that don't currently have the property
17
16
  * * {@link module:object.propertyValueSample|propertyValueSample(collection)} - finds non-empty values for all properties found in the collection
18
- * * Manipulating objects
19
- * * {@link module:object.assign|objAssign(object, property, value)} - Applies properties to an object in functional programming style.
20
- * * {@link module:object.assignEntities|objAssignEntities(object, [property, value])} - Applies properties to an object using Array values - [key,value]
21
- * * {@link module:object.augment|augment(object, augmentFn)} - Applies properties to an object similar to Map
22
- * * {@link module:object.augmentInherit|augmentInherit(object, augmentFn)} - Applies properties to a collection of objects, 'remembering' the last value - useful for 1d to *D lists.
23
- * * {@link module:object.selectObjectProperties|selectObjectProperties()} - keep only specific properties
24
- * * {@link module:object.filterObjectProperties|filterObjectProperties()} - remove specific properties
25
- * * {@link module:object.mapProperties|mapProperties(collection, fn, ...properties)} - map multiple properties at once (like parseInt, or toString)
26
- * * {@link module:object.formatProperties|formatProperties(collection, propertyTranslation)} - map specific properties (ex: toString, toNumber, etc)
27
- * * {@link module:object.union|union(objectList1, objectList2)} - Unites the properties of two collections of objects.
28
17
  * * Fetch child properties from related objects
29
18
  * * {@link module:object.fetchObjectProperty|fetchObjectProperty(object, string)} - use dot notation to bring a child property onto a parent
30
19
  * * {@link module:object.fetchObjectProperties|fetchObjectProperties(object, string[])} - use dot notation to bring multiple child properties onto a parent
31
20
  * * {@link module:object.join|join(array, index, map, fn)} - join a collection against a map by a given index
32
21
  * * {@link module:object.joinProperties|join(array, index, map, ...fields)} - join a collection, and copy properties over from the mapped object.
22
+ * * Fetch values safely
33
23
  * * {@link module:object.propertyFromList|propertyFromList(array, propertyName)} - fetches a specific property from all objects in a list
34
24
  * * {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} - extracts a property or fn across all objects in list.
35
25
  * * {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} - extracts multiple propertie or fn across all objects in list.
26
+ * * Apply deep values safely
27
+ * * {@link module:object.assign|objAssign(object, property, value)} - Applies properties to an object in functional programming style.
28
+ * * {@link module:object.augment|augment(object, augmentFn)} - Applies properties to an object similar to Map
29
+ * * {@link module:object.assignEntities|objAssignEntities(object, [property, value])} - Applies properties to an object using Array values - [key,value]
30
+ * * {@link module:object.setPropertyDefaults|setPropertyDefaults()} - sets values for objects that don't currently have the property
31
+ * * {@link module:object.applyPropertyValue|object.applyPropertyValue} - safely apply a value deeply and safely
32
+ * * {@link module:object.applyPropertyValues|object.applyPropertyValues} - apply an array of values safely and deeply against a list of objects.
33
+ * * Manipulating objects
34
+ * * {@link module:object.augmentInherit|augmentInherit(object, augmentFn)} - Applies properties to a collection of objects, 'remembering' the last value - useful for 1d to *D lists.
35
+ * * {@link module:object.selectObjectProperties|selectObjectProperties()} - keep only specific properties
36
+ * * {@link module:object.filterObjectProperties|filterObjectProperties()} - remove specific properties
37
+ * * {@link module:object.mapProperties|mapProperties(collection, fn, ...properties)} - map multiple properties at once (like parseInt, or toString)
38
+ * * {@link module:object.formatProperties|formatProperties(collection, propertyTranslation)} - map specific properties (ex: toString, toNumber, etc)
39
+ * * {@link module:object.union|union(objectList1, objectList2)} - Unites the properties of two collections of objects.
36
40
  * * Rename properties
37
41
  * * {@link module:object.cleanProperties|cleanProperties()} - correct inaccessible property names in a list of objects - in place
38
- * * * {@link module:object.cleanProperties2|cleanProperties2()} - correct inaccessible property names in a list of objects - on a cloned list
42
+ * * {@link module:object.cleanProperties2|cleanProperties2()} - correct inaccessible property names in a list of objects - on a cloned list
39
43
  * * {@link module:object.cleanPropertyNames|cleanPropertyNames()} - create a translation of inaccessible names to accessible ones
40
44
  * * {@link module:object.cleanPropertyName|cleanPropertyName()} - create a translation of a specific property name to be accessible.
41
45
  * * {@link module:object.renameProperties|renameProperties()} - Use a translation from old property names to new ones
@@ -498,46 +502,6 @@ module.exports.filterObjectProperties = function filterObjectProperties(list, pr
498
502
  });
499
503
  };
500
504
 
501
- /**
502
- * Options for fetching object properties
503
- * @typedef {Object} FetchObjectOptions
504
- * @property {Boolean} safeAccess - whether to safely access, even if the path cannot be found
505
- * @property {Boolean} append - whether to only return the properties (default) or append
506
- */
507
-
508
- /**
509
- * Fetches multiple properties from an object or list of objects.
510
- * @param {Object | Object[]} list - collection of objects to reduce
511
- * @param {Map<String,any>} propertyNames - Object with the keys as the properties
512
- * and the values using dot notation to access related records and properties
513
- * (ex: {parentName: 'somePropertyObject.parent.parent.name', childName: 'child.Name'})
514
- * @param {FetchObjectOptions} options - {@link module:object~FetchObjectOptions|See FetchObjectOptions}
515
- * @returns {Object[]} - objects with the properties resolved
516
- * (ex: {parentname, childName, etc.})
517
- */
518
- module.exports.fetchObjectProperties = function fetchObjectProperties(list, propertyNames, options = {}) {
519
- const {
520
- //-- whether to safely access even if object path cannot be found
521
- safeAccess = false,
522
-
523
- //-- whether to fetch only those specific properties, or append to the object
524
- append = false
525
- } = options;
526
-
527
- if (!list) return [];
528
- const targetList = Array.isArray(list) ? list : [list];
529
-
530
- const props = Object.getOwnPropertyNames(propertyNames);
531
-
532
- return targetList.map((obj) => {
533
- const result = append ? { ...obj } : {};
534
- props.forEach((prop) => {
535
- result[prop] = ObjectUtils.fetchObjectProperty(obj, propertyNames[prop], safeAccess);
536
- });
537
- return result;
538
- });
539
- };
540
-
541
505
  /**
542
506
  * Similar to a transpose, this finds all the values of a particular property
543
507
  * within a list of objects.
@@ -558,7 +522,9 @@ module.exports.fetchObjectProperties = function fetchObjectProperties(list, prop
558
522
  * @param {Function | String} propertyOrFn - Name of the property or accessor function
559
523
  * @returns {Array} - single array the values stored in propertyOrFn across all objects in objectList.
560
524
  * @see {@link module:aggregate.unique|unique()} to see all the unique values stored
561
- * @see {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} to see the values extracted into a single object / horizontal transpose.
525
+ * @see {@link module:object.extractObjectProperties|object.extractObjectProperties} - to extract into array vectors
526
+ * @see {@link module:object.fetchObjectProperty|object.fetchObjectProperty} - to extract a deep value and optionally throw if not found
527
+ * @see {@link module:object.applyPropertyValue|object.applyPropertyValue} - to apply a single value to a single object using dot notation safely
562
528
  */
563
529
  module.exports.extractObjectProperty = function extractObjectProperty(list, propertyOrFn) {
564
530
  let cleanList = !list ? [] : Array.isArray(list) ? list : [list];
@@ -600,6 +566,8 @@ module.exports.extractObjectProperty = function extractObjectProperty(list, prop
600
566
  * @param {Map<Function | String>} propertyOrFnMap - Name of the property or accessor function
601
567
  * @returns {Object} - Object with the keys in the map as properties - extracting the values across all in list.
602
568
  * @see {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} to see all the values stored for a single property.
569
+ * @see {@link module:object.applyPropertyValues|object.applyPropertyValues} - to safely and deeply apply the list of values extracted to a list of objects.
570
+ * @see {@link module:object.fetchObjectProperties} - to fetch multiple properties at once into objects
603
571
  */
604
572
  module.exports.extractObjectProperties = function extractObjectProperties(list, propertyOrFnMap) {
605
573
  let propertyEntries = [];
@@ -625,36 +593,266 @@ module.exports.extractObjectProperties = function extractObjectProperties(list,
625
593
  const results = {};
626
594
  propertyEntries.forEach(([propertyName, propertyOrFn]) => {
627
595
  results[propertyName] = ObjectUtils.extractObjectProperty(list, propertyOrFn || propertyName);
628
- // const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
629
- // results[propertyName] = cleanList.map(fn);
630
596
  });
631
597
 
632
598
  return results;
633
599
  };
634
600
 
601
+ /**
602
+ * Options for fetching object properties
603
+ * @typedef {Object} FetchObjectOptions
604
+ * @property {Boolean} safeAccess - whether to safely access, even if the path cannot be found
605
+ */
606
+
607
+ /**
608
+ * Fetches multiple properties from an object or list of objects.
609
+ *
610
+ * ```
611
+ * testObj = {
612
+ * name: 'john',
613
+ * courses: [{ name: 'econ-101' }]
614
+ * }
615
+ * utils.object.fetchObjectProperty(testObj,
616
+ * { 'courseName': 'courses[0].?name', personName: 'name' });
617
+ * // { courseName: 'econ-101', personName: 'john' }
618
+ * ```
619
+ *
620
+ * @param {Object | Object[]} list - collection of objects to reduce
621
+ * @param {Object<String,any>} propertyNames - Object with the keys as as properties to return,
622
+ * and the values using dot notation to access related records and properties
623
+ * (ex: {parentName: 'somePropertyObject.parent.parent.name', childName: 'child.Name'})
624
+ * @param {FetchObjectOptions} options - {@link module:object~FetchObjectOptions|See FetchObjectOptions}
625
+ * @returns {Object[]} - objects with the properties resolved
626
+ * (ex: {parentname, childName, etc.})
627
+ * @see {@link module:object.fetchObjectProperty|object.fetchObjectProperty} - to safely fetch a single value
628
+ * @see {@link module:object.applyPropertyValues|object.applyPropertyValues} - to safely and deeply apply the list of values extracted to a list of objects.
629
+ * @see {@link module:object.extractObjectProperties|object.extractObjectProperties} - to extract into array vectors instead of objects
630
+ */
631
+ module.exports.fetchObjectProperties = function fetchObjectProperties(list, propertyNames, options = {}) {
632
+ if (!list) return [];
633
+ const targetList = Array.isArray(list) ? list : [list];
634
+
635
+ const {
636
+ //-- whether to fetch only those specific properties, or append to the object
637
+ append = false
638
+ } = options;
639
+
640
+ const props = Object.getOwnPropertyNames(propertyNames);
641
+
642
+ return targetList.map((obj) => {
643
+ const result = append ? { ...obj } : {};
644
+ props.forEach((prop) => {
645
+ result[prop] = ObjectUtils.fetchObjectProperty(obj, propertyNames[prop], options);
646
+ });
647
+ return result;
648
+ });
649
+ };
650
+
635
651
  /**
636
652
  * Accesses a property using a string
653
+ *
654
+ * ```
655
+ * testObj = {
656
+ * name: 'john',
657
+ * courses: [{ name: 'econ-101' }]
658
+ * }
659
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].?name');
660
+ * // 'econ-101'
661
+ * ```
662
+ *
663
+ * note that the options allow for safe property access
664
+ *
665
+ * ```
666
+ * testObj = {
667
+ * name: 'john',
668
+ * courses: [{ name: 'econ-101' }]
669
+ * }
670
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].courseId');
671
+ * // throws an error
672
+ *
673
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].?courseId');
674
+ * // null - because of optional condition operator
675
+ *
676
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].courseId', { safeAccess: true });
677
+ * // null - because of the safe access option
678
+ * ```
679
+ *
637
680
  * @param {Object} obj - object to access the properties on
638
681
  * @param {String} propertyAccess - dot notation for the property to access
639
682
  * (ex: `parent.obj.Name`)
640
683
  * @param {FetchObjectOptions} options - {@link module:object~FetchObjectOptions|See FetchObjectOptions}
641
684
  * @returns {any} - the value accessed at the end ofthe property chain
685
+ * @see {@link module:object.fetchObjectProperties} - to fetch multiple properties at once into objects
686
+ * @see {@link module:object.extractObjectProperty|object.extractObjectProperty} - to safely extract a deep value without options
687
+ * @see {@link module:object.applyPropertyValue|object.applyPropertyValue} - to apply a single value to a single object using dot notation safely
642
688
  */
643
- module.exports.fetchObjectProperty = function fetchObjectProperty(obj, propertyAccess, safeAccess) {
689
+ module.exports.fetchObjectProperty = function fetchObjectProperty(obj, propertyAccess, options = {}) {
644
690
  if (!obj || !propertyAccess) return null;
691
+ const {
692
+ //-- whether to safely access even if object path cannot be found
693
+ safeAccess = false
694
+ } = options;
645
695
 
646
- //-- @TODO - should we be safe or support elvis operators?
647
- return propertyAccess.split('.')
696
+ const cleanPropertyAccess = String(propertyAccess)
697
+ .replace(/\[/g, '.')
698
+ .replace(/\]/g, '.')
699
+ .replace(/[.]+/g, '.')
700
+ .replace(/^[.]+/, '')
701
+ .replace(/[.]$/, '');
702
+
703
+ return cleanPropertyAccess.split('.')
648
704
  .reduce((currentVal, prop) => {
705
+ let isElvis = false;
706
+ let cleanProp = prop;
707
+ if (prop && prop.length > 0 && prop[0] === '?') {
708
+ isElvis = true;
709
+ cleanProp = prop.slice(1);
710
+ }
649
711
  if (currentVal) {
650
- return currentVal[prop];
651
- } else if (safeAccess || prop[0] === '?') {
712
+ return currentVal[cleanProp];
713
+ } else if (safeAccess || isElvis) {
652
714
  return null;
653
715
  }
654
716
  throw Error(`Invalid property ${propertyAccess} [${prop}] does not exist - safeAccess:${safeAccess}`);
655
717
  }, obj);
656
718
  };
657
719
 
720
+ /**
721
+ * Applies a target value onto a source object in-place safely - using dot-notation paths.
722
+ *
723
+ * This can be as simple as safely applying a value even if targetObj may be null
724
+ * ```
725
+ * targetObj = { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 };
726
+ * utils.object.applyPropertyValue(targetObj, 'state', 'WA');
727
+ * // { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87, state: 'WA };
728
+ * ```
729
+ *
730
+ * working with deeply nested objects
731
+ * ```
732
+ * targetObj = { name: 'john smith', class: { name: 'ECON_101', professor: { last_name: 'Winklemeyer' }} };
733
+ * utils.object.applyPropertyValue(targetObj, 'class.professor.first_name', 'René');
734
+ * // { name: 'john smith', class: { name: 'ECON_101', professor: { last_name: 'Winklemeyer', first_name: 'René' }} };
735
+ * ```
736
+ *
737
+ * or safely working with arrays of values
738
+ * ```
739
+ * targetObj = { name: 'john smith', classes: [{ name: 'ECON_101' }] };
740
+ * utils.object.applyPropertyValue(targetObj, 'classes[0].grade', 'A');
741
+ * // { name: 'john smith', classes: [{ name: 'ECON_101', grade: 'A' }] };
742
+ * ```
743
+ *
744
+ * @param {Object} obj - object to apply the value to
745
+ * @param {string} path - dot notation path to set the value, ex: 'geo', or 'states[0].prop'
746
+ * @param {any} value - value to set
747
+ * @returns {Object} - the object the value was applied to
748
+ * @see {@link module:object.applyPropertyValues|object.applyPropertyValues} - to safely and deeply apply the list of values extracted to a list of objects.
749
+ * @see {@link module:object.extractObjectProperty|object.extractObjectProperty} - to safely extract a deep value
750
+ * @see {@link module:object.fetchObjectProperty|object.fetchObjectProperty} - to extract a deep value and optionally throw if not found
751
+ */
752
+ module.exports.applyPropertyValue = function applyPropertyValue(obj, path, value) {
753
+ // const signature = 'applyPropertyValue(obj, path, value)';
754
+
755
+ if (!obj) return obj;
756
+ if (!path) return obj;
757
+
758
+ const cleanPath = String(path)
759
+ .replace(/\[/g, '.')
760
+ .replace(/\]/g, '.')
761
+ .replace(/[.]+/g, '.')
762
+ .replace(/^[.]+/, '')
763
+ .replace(/[.]$/, '');
764
+
765
+ const splitPath = cleanPath.split('.');
766
+ const terminalIndex = splitPath.length - 1;
767
+
768
+ return splitPath
769
+ .reduce((currentVal, prop, currentIndex) => {
770
+ //-- can no longer occur
771
+ // if (!prop) throw Error(`${signature}:Unable to set value with path:${path}`);
772
+
773
+ const isLeaf = currentIndex === terminalIndex;
774
+ if (isLeaf) {
775
+ // eslint-disable-next-line no-param-reassign
776
+ currentVal[prop] = value;
777
+ // if (value === undefined) {
778
+ // delete currentVal[prop];
779
+ // } else {
780
+ // currentVal[prop] = value;
781
+ // }
782
+ return obj;
783
+ }
784
+ //-- not a leaf
785
+ if (!currentVal[prop]) {
786
+ // eslint-disable-next-line no-param-reassign
787
+ currentVal[prop] = {};
788
+ }
789
+ return currentVal[prop];
790
+ }, obj);
791
+ };
792
+
793
+ /**
794
+ * Opposite of the extractObjectProperty, this takes a value / set of values
795
+ * and applies them along a given path on each of the target objects.
796
+ *
797
+ * for example:
798
+ *
799
+ * ```
800
+ * weather = [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
801
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
802
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }];
803
+ *
804
+ * cities = utils.object.extractObjectProperty('city');
805
+ * // ['Seattle', 'New York', 'Chicago'];
806
+ *
807
+ * //-- async process to geocode
808
+ * geocodedCities = geocodeCity(cities);
809
+ * // [{ city: 'Seattle', state: 'WA', country: 'USA' },
810
+ * // { city: 'New York', state: 'NY', country: 'USA' },
811
+ * // { city: 'Chicago', state: 'IL', country: 'USA' }]
812
+ *
813
+ * utils.applyPropertyValues(weather, 'geo', geocodedCities);
814
+ * [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87, geo: { city: 'Seattle', state: 'WA', country: 'USA' } },
815
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94, geo: { city: 'New York', state: 'NY', country: 'USA' } },
816
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62, geo: { city: 'Chicago', state: 'IL', country: 'USA' } }];
817
+ *
818
+ * Note that traditional [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
819
+ * works best for if you are working with objects completely in memory.
820
+ *
821
+ * But this helps quite a bit if the action of mapping / transforming values
822
+ * needs to be separate from the extraction / application of values back.
823
+ *
824
+ * @param {Object} obj - object to apply the value to
825
+ * @param {string} path - dot notation path to set the value, ex: 'geo', or 'states[0].prop'
826
+ * @param {any} value - the value that should be set at that path.
827
+ * @returns {Object}
828
+ * @see {@link module:object.applyPropertyValue} - to apply a single value to a single object
829
+ * @see {@link module:object.fetchObjectProperties} - to fetch multiple properties at once into objects
830
+ * @see {@link module:object.extractObjectProperties|object.extractObjectProperties} - to extract properties into array vectors instead of objects
831
+ */
832
+ module.exports.applyPropertyValues = function applyPropertyValues(objectList, path, valueList) {
833
+ // const signature = 'applyPropertyValues(objectList, path, valueList)';
834
+ if (!objectList || !path) {
835
+ //-- do nothing
836
+ return objectList;
837
+ }
838
+
839
+ const cleanObjectList = Array.isArray(objectList) ? objectList : [objectList];
840
+ const cleanValueList = Array.isArray(valueList) ? valueList : Array(cleanObjectList.length).fill(valueList);
841
+
842
+ // if (cleanObjectList.length !== cleanValueList) throw Error(
843
+ // `${signature}: objectList.length[${cleanObjectList.length}] does not match valueList.length[${cleanValueList.length}]`
844
+ // );
845
+ const minLength = Math.min(cleanObjectList.length, cleanValueList.length);
846
+
847
+ for (let i = 0; i < minLength; i += 1) {
848
+ const obj = cleanObjectList[i];
849
+ const val = cleanValueList[i];
850
+ ObjectUtils.applyPropertyValue(obj, path, val);
851
+ }
852
+
853
+ return objectList;
854
+ };
855
+
658
856
  /**
659
857
  * Translates specific properties to a new value on an object, or collection of objects.
660
858
  *
package/src/plantuml.js CHANGED
@@ -214,7 +214,7 @@ module.exports.render = function render(plantUMLText, plantUMLOptions) {
214
214
  const targetURL = this.generateURL(plantUMLText, plantUMLOptions);
215
215
  if (showURL || debug) console.log(`url:${targetURL}`);
216
216
 
217
- const result = await fetch(targetURL);
217
+ const result = await global.fetch(targetURL);
218
218
 
219
219
  if (format === 'png') {
220
220
  const buf = await result.buffer();