jupyter-ijavascript-utils 1.8.5 → 1.9.2

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
@@ -28,7 +28,8 @@ See the [#Installation section for requirements and installation](#install)
28
28
 
29
29
  ## What's New
30
30
 
31
- * 1.8 - add in What can I Do tutorial, and object.join methods
31
+ * 1.9 - allow {@link TableGenerator#transpose|transposing results} on TableGenerator.
32
+ * 1.8 - add in What can I Do tutorial, and {@link module:object.join|object.join methods}
32
33
  * 1.7 - revamp of `animation` method for ijs.htmlScript
33
34
  * 1.6 - add SVG support for rendering SVGs and animations with {@link module:svg}.
34
35
  * 1.5 - Add LaTeX / KaTeX support with {@link module:latex} for rendering Math formulas and PlantUML support for Diagrams
package/Dockerfile ADDED
@@ -0,0 +1,34 @@
1
+ # per https://github.com/n-riesco/ijavascript/issues/273
2
+ # and https://github.com/paulroth3d/jupyter-ijavascript-utils/issues/4
3
+
4
+ # as of today this is python-3.7.1
5
+ FROM jupyter/base-notebook:latest
6
+
7
+ # for nbhosting
8
+ USER root
9
+ COPY start-in-dir-as-uid.sh /usr/local/bin
10
+
11
+ # prerequisites with apt-get
12
+ # we do install python(2) here because
13
+ # some npm build part named gyp still requires it
14
+ RUN apt-get update && apt-get install -y gcc g++ make python
15
+
16
+ # !!! dirty trick!!!
17
+ # original PATH is
18
+ # /opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
19
+ # move conda's path **at the end**
20
+ # so that python resolves in /usr/bin/python(2)
21
+ # but node is still found in conda
22
+ ENV PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/conda/bin"
23
+ USER jovyan
24
+ RUN npm install -g ijavascript
25
+ RUN ijsinstall
26
+
27
+ # for displaying html fragments
28
+ RUN npm install -g jsdom d3
29
+
30
+ # !!! clean up!!!
31
+ USER root
32
+ RUN apt-get autoremove -y python
33
+ ENV PATH="/opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
34
+ USER jovyan
package/README.md CHANGED
@@ -6,6 +6,9 @@
6
6
  <img src="https://img.shields.io/badge/License-MIT-green" />
7
7
  </a>
8
8
  <img src="https://img.shields.io/badge/Coverage-98-green" />
9
+ <a href="https://github.com/paulroth3d/jupyter-ijavascript-utils" alt="npm">
10
+ <img src="https://img.shields.io/badge/npm-%5E1.9.2-red" />
11
+ </a>
9
12
  </p>
10
13
 
11
14
  # Overview
@@ -18,6 +21,7 @@ See documentation at: [https://jupyter-ijavascript-utils.onrender.com/](https://
18
21
 
19
22
  # What's New
20
23
 
24
+ * 1.9 - allow transposing results on TableGenerator.
21
25
  * 1.8 - add in What can I Do tutorial, and object.join methods
22
26
  * 1.7 - revamp of `animation` method to htmlScript
23
27
  * 1.6 - add SVG support for rendering SVGs and animations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.8.5",
3
+ "version": "1.9.2",
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,8 @@ const generateRange = (length, defaultValue) => new Array(length).fill(defaultVa
17
17
 
18
18
  const IJSUtils = require('./ijs');
19
19
 
20
+ const ArrayUtils = require('./array');
21
+
20
22
  const { createSort } = require('./array');
21
23
 
22
24
  /**
@@ -62,29 +64,32 @@ const { createSort } = require('./array');
62
64
  * * change the columns and headers
63
65
  * * {@link TableGenerator#columns|columns(field, field, ...)} - specify fields and order
64
66
  * * {@link TableGenerator#columnsToExclude|columnsToExclude(field, ...)} - specify fields not to show
65
- * * {@link TableGenerator#labels|lables(obj} - labels for field headers
67
+ * * {@link TableGenerator#labels|lables(obj)} - labels for field headers
66
68
  * * augment and change the values (non-destructively)
67
- * * {@link TableGenerator#formatter|formatter(obj} - adjust values of specific fields
68
- * * {@link TableGenerator#formatterFn|formatterFn(fn} - row, column aware adjustment
69
- * * {@link TableGenerator#printOptions|printOptions(object} - options for value rendering
69
+ * * {@link TableGenerator#formatter|formatter(obj)} - adjust values of specific fields
70
+ * * {@link TableGenerator#formatterFn|formatterFn(fn)} - row, column aware adjustment
71
+ * * {@link TableGenerator#printOptions|printOptions(object)} - options for value rendering
70
72
  * * {@link TableGenerator#augment|augment(obj)} - add fields to table
71
73
  * * sort and limit the output
72
- * * {@link TableGenerator#filter|filter(fn))} - determine which rows to include or not
73
- * * {@link TableGenerator#limit|limit(number} - limit only specific # of rows
74
- * * {@link TableGenerator#sortFn|sortFn(fn} - Standard Array sort function
75
- * * {@link TableGenerator#sort|sort(field, field, ...} - sorts by fields, or descending with -
74
+ * * {@link TableGenerator#filter|filter(fn)} - determine which rows to include or not
75
+ * * {@link TableGenerator#limit|limit(number)} - limit only specific # of rows
76
+ * * {@link TableGenerator#sortFn|sortFn(fn)} - Standard Array sort function
77
+ * * {@link TableGenerator#sort|sort(field, field, ...)} - sorts by fields, or descending with '-'
78
+ * * transpose the output
79
+ * * {@link TableGenerator#transpose|transpose()} - transposes the output prior to rendering
76
80
  * * style the table
77
- * * {@link TableGenerator#styleTable|styleTable(string} - css style for the table
78
- * * {@link TableGenerator#styleHeader|styleHeader(string} - css styles for the header row
81
+ * * {@link TableGenerator#styleTable|styleTable(string)} - css style for the table
82
+ * * {@link TableGenerator#styleHeader|styleHeader(string)} - css styles for the header row
79
83
  * * {@link TableGenerator#styleRow|styleRow(fn)} - Function to style rows
80
- * * {@link TableGenerator#styleCell|styleCell(fn} - Function to style cells
84
+ * * {@link TableGenerator#styleCell|styleCell(fn)} - Function to style cells
81
85
  * * generate output
82
- * * {@link TableGenerator#generateHTML|generateHTML(} - returns html table with the results
83
- * * {@link TableGenerator#generateMarkdown|generateMarkdown(} - returns markdown with the results
84
- * * {@link TableGenerator#generateCSV|generateCSV(} - generates a CSV with the results
85
- * * {@link TableGenerator#generateArray|generateArray(} - generates an array for further process
86
+ * * {@link TableGenerator#generateHTML|generateHTML()} - returns html table with the results
87
+ * * {@link TableGenerator#generateMarkdown|generateMarkdown()} - returns markdown with the results
88
+ * * {@link TableGenerator#generateCSV|generateCSV()} - generates a CSV with the results
89
+ * * {@link TableGenerator#generateArray|generateArray()} - generates an array of headers and data for further process
90
+ * * {@link TableGenerator#generateArray2|generateArray2()} - generates a single array for further process
86
91
  * * render in jupyter
87
- * * {@link TableGenerator#render|render(} - renders the results in a table within jupyter
92
+ * * {@link TableGenerator#render|render()} - renders the results in a table within jupyter
88
93
  *
89
94
  */
90
95
  class TableGenerator {
@@ -191,6 +196,12 @@ class TableGenerator {
191
196
  */
192
197
  #styleCell = null;
193
198
 
199
+ /**
200
+ * Whether the data should be output transposed
201
+ * @type {Boolean}
202
+ */
203
+ #isTransposed = false;
204
+
194
205
  /**
195
206
  * Function to format a value for a cell
196
207
  *
@@ -237,6 +248,7 @@ class TableGenerator {
237
248
  this.#styleHeader = '';
238
249
  this.#styleRow = null;
239
250
  this.#styleCell = null;
251
+ this.#isTransposed = false;
240
252
  }
241
253
 
242
254
  //-- GETTER SETTERS
@@ -781,6 +793,53 @@ class TableGenerator {
781
793
  return this;
782
794
  }
783
795
 
796
+ /**
797
+ * Transposes (flips along the diagonal) prior to output.
798
+ *
799
+ * This can be very handy for wide, but short, tables.
800
+ *
801
+ * For example, given the data:
802
+ *
803
+ * ```
804
+ * const data = [
805
+ * { name: 'John', color: 'green', age: 23, hair: 'blond', state: 'IL' },
806
+ * { name: 'Jane', color: 'brown', age: 23, hair: 'blonde', state: 'IL' }
807
+ * ];
808
+ * ```
809
+ *
810
+ * Running normally would give
811
+ *
812
+ * ```
813
+ * new utils.TableGenerator(data)
814
+ * .generateMarkdown();
815
+ * ```
816
+ *
817
+ * name|color|age|hair |state
818
+ * -- |-- |-- |-- |--
819
+ * John|green|23 |blond |IL
820
+ * Jane|brown|23 |blonde|IL
821
+ *
822
+ * Running that transposed flips it.
823
+ *
824
+ * ```
825
+ * new utils.TableGenerator(data)
826
+ * .transpose()
827
+ * .generateMarkdown();
828
+ * ```
829
+ *
830
+ * name |John |Jane
831
+ * -- |-- |--
832
+ * color|green|brown
833
+ * age |23 |23
834
+ * hair |blond|blonde
835
+ * state|IL |IL
836
+ * @returns {TableGenerator}
837
+ */
838
+ transpose() {
839
+ this.#isTransposed = true;
840
+ return this;
841
+ }
842
+
784
843
  //-- Table Generation
785
844
 
786
845
  /**
@@ -833,7 +892,7 @@ class TableGenerator {
833
892
  record: row
834
893
  }));
835
894
 
836
- const headers = keys.map(translateHeader);
895
+ let headers = keys.map(translateHeader);
837
896
  let data = cleanCollection.map(translateData);
838
897
 
839
898
  if (this.#limit < 0) {
@@ -842,6 +901,15 @@ class TableGenerator {
842
901
  data = data.slice(0, this.#limit);
843
902
  }
844
903
 
904
+ if (this.#isTransposed) {
905
+ let transposedResults = [headers, ...data];
906
+ transposedResults = ArrayUtils.transpose(transposedResults);
907
+
908
+ // eslint-disable-next-line prefer-destructuring
909
+ headers = transposedResults[0];
910
+ data = transposedResults.slice(1);
911
+ }
912
+
845
913
  return ({ headers, data });
846
914
  }
847
915
 
@@ -995,8 +1063,10 @@ class TableGenerator {
995
1063
  */
996
1064
 
997
1065
  /**
998
- * Generates an array of objects
1066
+ * Generates an a result set to allow for further processing
999
1067
  *
1068
+ * @see {@link TableGenerator#generateArray2|generateArray2()}
1069
+ * @returns {TableArray}
1000
1070
  * @example
1001
1071
  *
1002
1072
  * dataSet = [{reg:'z', source: 'A', temp: 99},
@@ -1019,14 +1089,44 @@ class TableGenerator {
1019
1089
  * ['A', 100],
1020
1090
  * ]
1021
1091
  * }
1022
- *
1023
- * @returns {TableArray}
1024
1092
  */
1025
- generateArray() {
1093
+ generateArray(returnUnifiedArray = false) {
1026
1094
  const results = this.prepare();
1027
1095
  return results;
1028
1096
  }
1029
1097
 
1098
+ /**
1099
+ * Generates an array of objects in a 2d Array
1100
+ *
1101
+ * NOTE: this can be helpful for needing to transpose results
1102
+ *
1103
+ * @returns {any[][]} - 2d array with both headers and data included
1104
+ * @see {@link TableGenerator#generateArray|generateArray()}
1105
+ * @example
1106
+ *
1107
+ * dataSet = [{reg:'z', source: 'A', temp: 99},
1108
+ * {reg: 'z', source: 'B', temp: 98},
1109
+ * {reg: 'z', source:'A', temp: 100}
1110
+ * ];
1111
+ *
1112
+ * //-- only show the temp and source columns
1113
+ * new TableGenerator(dataSet)
1114
+ * .columnsToExclude('reg') // or .columnsToExclude(['reg'])
1115
+ * .generateArray2();
1116
+ *
1117
+ * //--
1118
+ * [
1119
+ * ['source', 'temp'],
1120
+ * ['A', 99],
1121
+ * ['B', 98],
1122
+ * ['A', 100],
1123
+ * ];
1124
+ */
1125
+ generateArray2() {
1126
+ const results = this.prepare();
1127
+ return [...results.headers, ...results.data];
1128
+ }
1129
+
1030
1130
  /**
1031
1131
  * Renders the html table in the cell results.
1032
1132
  *
package/src/file.js CHANGED
@@ -26,6 +26,8 @@ const logger = require('./logger');
26
26
  * * listing directory
27
27
  * * {@link module:file.pwd|pwd()} - list the current path
28
28
  * * {@link module:file.listFiles|listFiles(path)} - list files in a diven path
29
+ * * checking files exist
30
+ * * {@link module:file.checkFile|checkFile(...paths)} - check if a file at a path exists
29
31
  *
30
32
  * ---
31
33
  *
@@ -299,3 +301,87 @@ module.exports.listFiles = function listFiles(directoryPath) {
299
301
  logger.error(`unable to read directory: ${resolvedPath}`);
300
302
  }
301
303
  };
304
+
305
+ /**
306
+ * Synchronously checks if any of the files provided do not exist.
307
+ *
308
+ * For example:
309
+ *
310
+ * ```
311
+ * //-- these exist
312
+ * // ./data/credentials.env
313
+ * // ./data/results.json
314
+ *
315
+ * if (!utils.file.checkFile('./data/results.json')) {
316
+ * //-- retrieve the results
317
+ * utils.ijs.await(async($$, console) => {
318
+ * results = await connection.query('SELECT XYZ from Contacts');
319
+ * utils.file.write('./data/results.json', results);
320
+ * });
321
+ * } else {
322
+ * results = utils.file.readJSON('./data/results.json');
323
+ * }
324
+ * ```
325
+ *
326
+ * Note, you can also ask for multiple files at once
327
+ *
328
+ * ```
329
+ * utils.file.checkFile(
330
+ * './data/credentials.env',
331
+ * './data/results.json',
332
+ * './data/results.csv'
333
+ * );
334
+ * // false
335
+ * ```
336
+ *
337
+ * or as an array:
338
+ *
339
+ * ```
340
+ * utils.file.checkFile(['./data/credentails.env']);
341
+ * // true
342
+ * ```
343
+ *
344
+ * @param {...String} files - List of file paths to check (can use relative paths, like './') <br />
345
+ * see {@link file:listFiles|listFiles()} or {@link file:pwd|pwd()} to help you)
346
+ * @returns {String[]} - null if all files are found, or array of string paths of files not found
347
+ */
348
+ module.exports.checkFile = function checkFile(...files) {
349
+ //-- allow passing an array of files
350
+ const cleanFiles = files.length === 1 && Array.isArray(files[0])
351
+ ? files[0]
352
+ : files;
353
+
354
+ const resolvedFiles = cleanFiles.map((unresolvedPath) => path.resolve(unresolvedPath));
355
+
356
+ const notFoundFiles = resolvedFiles.map((resolvedPath) => fs.existsSync(resolvedPath)
357
+ ? null
358
+ : resolvedPath);
359
+
360
+ //-- do not filter empty files, as position in array is helpful
361
+ if (notFoundFiles.filter((p) => p).length === 0) {
362
+ return null;
363
+ }
364
+
365
+ return notFoundFiles;
366
+ };
367
+
368
+ /*
369
+ * Execute an async function if any of the files do not exist
370
+ * @param {String[]} filePaths - list of paths of files to check that they exist
371
+ * @param {*} fnIfFailed - async function tha will run - but only if any of the files are not found.
372
+ */
373
+ /*
374
+ module.exports.ifNotExists = async function ifNotExists(filePaths, fnIfFailed) {
375
+ const filesNotFound = FileUtil.checkFile(filePaths);
376
+
377
+ let results;
378
+
379
+ if (filesNotFound) {
380
+ results = await fnIfFailed(filesNotFound);
381
+ } else {
382
+ results = null;
383
+ }
384
+
385
+ return results;
386
+ };
387
+ */
package/src/format.js CHANGED
@@ -9,6 +9,9 @@
9
9
  * * formatting Numbers
10
10
  * * {@link module:format.zeroFill|format.zeroFill} - Pads a number to a specific length
11
11
  * * {@link module:format.divideR|format.divideR} - Divides a number to provide { integer, remainder } - ex: 5/3 as ( 1, remainder 2 )
12
+ * * Formatting Strings
13
+ * * {@link module:format.capitalize|format.capitalize} - Capitalizes only the first character in the string (ex: 'John paul');
14
+ * * {@link module:format.capitalizeAll|format.capitalizeAll} - Capitalizes all the words in a string (ex: 'John Paul')
12
15
  * * Formatting Time
13
16
  * * {@link module:format.millisecondDuration|format.millisecondDuration}
14
17
  * * Mapping Values
@@ -454,3 +457,40 @@ module.exports.clampDomain = function clampDomain(value, [minimum, maximum]) {
454
457
  }
455
458
  return value;
456
459
  };
460
+
461
+ /**
462
+ * Capitalizes the first character of the string.
463
+ *
464
+ * @param {String} str - String to capitalize the first letter only
465
+ * @returns {String} - ex: 'John paul'
466
+ * @see {@link module:format.capitalizeAll|capitalizeAll} - to capitalize all words in a string
467
+ * @example
468
+ * utils.format.capitalize('john'); // 'John'
469
+ * utils.format.capitalize('john doe'); // 'John doe'
470
+ */
471
+ module.exports.capitalize = function capitalize(str) {
472
+ if (!str || str.length === 0) {
473
+ return '';
474
+ }
475
+
476
+ //-- charAt does not work for unicode
477
+ const [first, ...rest] = str;
478
+ return first.toLocaleUpperCase() + rest.join('');
479
+ };
480
+
481
+ /**
482
+ * Capitalizes all words in a string.
483
+ *
484
+ * @param {String} str - String to capitalize
485
+ * @returns {String} - ex: 'John-Paul'
486
+ * @see {@link module:format.capitalizeAll|capitalizeAll} - to capitalize all words in a string
487
+ * @example
488
+ * utils.format.capitalize('john'); // 'John'
489
+ * utils.format.capitalize('john doe'); // 'John Doe'
490
+ * utils.format.capitalize('john-paul'); // 'John-Paul'
491
+ */
492
+ module.exports.capitalizeAll = function capitalizeAll(str) {
493
+ return (str || '').split(/\b/)
494
+ .map(FormatUtils.capitalize)
495
+ .join('');
496
+ };
package/src/ijs.js CHANGED
@@ -135,7 +135,7 @@ module.exports.await = async function ijsAsync(fn) {
135
135
  context.$$.async();
136
136
 
137
137
  try {
138
- const results = fn(context.$$, context.console);
138
+ const results = await fn(context.$$, context.console);
139
139
  context.$$.sendResult(results);
140
140
  } catch (err) {
141
141
  context.console.error('error occurred');
package/src/object.js CHANGED
@@ -9,6 +9,9 @@ const schemaGenerator = require('generate-schema');
9
9
  * * {@link module:object.keys|keys()} - Safely get the keys of an object or list of objects
10
10
  * * {@link module:object.getObjectPropertyTypes|getObjectPropertyTypes()} - describe the properties of a list of objects
11
11
  * * {@link module:object.generateSchema|generateSchema()} - generate a schema / describe properties of a list of objects
12
+ * * {@link module:object.findWithoutProperties|findWithoutProperties()} - find objects without ALL the properties specified
13
+ * * {@link module:object.findWithoutProperties|findWithProperties()} - find objects with any of the properties specified
14
+ * * {@link module:object.setPropertyDefaults|setPropertyDefaults()} - sets values for objects that don't currently have the property
12
15
  * * Manipulating objects
13
16
  * * {@link module:object.objAssign|objAssign()} -
14
17
  * * {@link module:object.objAssignEntities|objAssignEntities()} -
@@ -634,3 +637,154 @@ module.exports.joinProperties = function join(objectArray, indexField, targetMap
634
637
 
635
638
  return ObjectUtils.join(objectArray, indexField, targetMap, joinFn);
636
639
  };
640
+
641
+ /**
642
+ * Finds objects that do not have ALL the properties specified.
643
+ *
644
+ * This can be very helpful in ensuring all objects actually meet a specification and are not missing values.
645
+ *
646
+ * ```
647
+ * const students = [
648
+ * { first: 'john', last: 'doe', age: 23 }, { first: 'jane', last: 'doe', age: 23 }, { first: 'jack', last: 'white', failure: 401 }
649
+ * ];
650
+ *
651
+ * utils.findWithoutProperties(students, 'first', 'last', 'age');
652
+ * // [{ first: 'jack', last: 'white', failure: 401 }]
653
+ *
654
+ * utils.findWithoutProperties(students, 'failure');
655
+ * // [{ first: 'john', last: 'doe', age: 23 }, { first: 'jane', last: 'doe', age: 23 }]
656
+ * ```
657
+ *
658
+ * Please note, that we can check a single object:
659
+ *
660
+ * ```
661
+ * utils.findWithoutProperties(students[0], 'failure');
662
+ * // []
663
+ * ```
664
+ *
665
+ * @param {Object[]} objectsToCheck - the array of objects to check for the properties.
666
+ * @param {...String} propertiesToFind - the list of properties to find within the collection.
667
+ * @returns {Object[]} - Array of objects that are missing at least one of those properties
668
+ * @see {@link module:file.findWithProperties|findWithProperties} - if you want objects that do not have all properties
669
+ **/
670
+ module.exports.findWithoutProperties = function findWithoutProperties(targetObj, ...propertiesToFind) {
671
+ const cleanProperties = propertiesToFind.length > 0 && Array.isArray(propertiesToFind[0])
672
+ ? propertiesToFind[0]
673
+ : propertiesToFind;
674
+
675
+ const cleanTargets = Array.isArray(targetObj)
676
+ ? targetObj
677
+ : [targetObj];
678
+
679
+ const results = [];
680
+
681
+ cleanTargets.forEach((target) => {
682
+ if (cleanProperties.find((prop) => (typeof target[prop]) === 'undefined')) {
683
+ results.push(target);
684
+ }
685
+ });
686
+
687
+ return results;
688
+ };
689
+
690
+ /**
691
+ * Finds objects that have any of the properties specified.
692
+ *
693
+ * This can be very helpful when working with datasets that include mixed data (such as JSON)
694
+ *
695
+ * ```
696
+ * const students = [
697
+ * { first: 'john', last: 'doe' }, { first: 'jane', last: 'doe' }, { first: 'jack', last: 'white', failure: 401 }
698
+ * ];
699
+ *
700
+ * utils.findWithProperties(students, 'failure');
701
+ * // { first: 'jack', last: 'white', failure: 401 }
702
+ * ```
703
+ *
704
+ * Please note, that we can check a single object:
705
+ *
706
+ * ```
707
+ * utils.findWithProperties({ first: 'john', last: 'doe' }, 'failure');
708
+ * // []
709
+ * ```
710
+ *
711
+ * @param {Object[]} objectsToCheck - the array of objects to check for the properties.
712
+ * @param {...String} propertiesToFind - the list of properties to find within the collection.
713
+ * @returns {Object[]} - Array of objects that have at least one of those properties
714
+ * @see {@link module:file.findWithoutProperties|findWithoutProperties} - if you want objects that do not have all properties
715
+ **/
716
+ module.exports.findWithProperties = function findWithProperties(targetObj, ...propertiesToFind) {
717
+ const cleanProperties = propertiesToFind.length > 0 && Array.isArray(propertiesToFind[0])
718
+ ? propertiesToFind[0]
719
+ : propertiesToFind;
720
+
721
+ const cleanTargets = Array.isArray(targetObj)
722
+ ? targetObj
723
+ : [targetObj];
724
+
725
+ const results = [];
726
+
727
+ cleanTargets.forEach((target) => {
728
+ if (cleanProperties.find((prop) => (typeof target[prop]) !== 'undefined')) {
729
+ results.push(target);
730
+ }
731
+ });
732
+
733
+ return results;
734
+ };
735
+
736
+ /**
737
+ * Sets values for objects that don't currently have the property
738
+ *
739
+ * This is very helpful for ensuring that all objects have a property,
740
+ * or setting a value to make it easier to identify that it is 'N/A'
741
+ *
742
+ * Note, that only the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty|ownProperties}
743
+ * on the default object are checked.
744
+ *
745
+ * And values are applied to the target object, only if the property is not on the object (property is undefined)
746
+ *
747
+ * @param {Object[] | Object} targetObject - Object to apply the properties to <br />
748
+ * but ONLY if the object does not have that property (ex: undefined)
749
+ * @param {Object} defaultObj - Object with the properties and defaults applied
750
+ * @param {any} defaultObj.property - the property to check, with the default value assigned
751
+ * @see {@link module:file.findWithoutProperties|findWithoutProperties} - to determine if any objects do not have a set of properties
752
+ * @see {@link module:file.keys|keys} - to get a list of unique properties of all objects in a list.
753
+ * @example
754
+ * const students = [
755
+ * { first: 'john', last: 'doe', birthday: '2002-04-01' },
756
+ * { first: 'jane', last: 'doe', birthday: '2003-05-01' },
757
+ * { first: 'jack', last: 'white', failure: 401 }
758
+ * ];
759
+ *
760
+ * utils.object.setPropertyDefaults(students, {
761
+ * first: '',
762
+ * last: '',
763
+ * birthday: ''
764
+ * });
765
+ *
766
+ * // [
767
+ * // { first: 'john', last: 'doe', birthday: '2002-04-01' },
768
+ * // { first: 'jane', last: 'doe', birthday: '2003-05-01' },
769
+ * // { first: 'jack', last: 'white', birthday: '', failure: 401 }
770
+ * // ];
771
+ */
772
+ module.exports.setPropertyDefaults = function setPropertyDefaults(targetObject, defaultObj) {
773
+ const cleanTargets = Array.isArray(targetObject)
774
+ ? targetObject
775
+ : [targetObject];
776
+
777
+ if (!defaultObj || typeof defaultObj !== 'object') {
778
+ throw Error('object.setPropertyDefaults(targetObject, defaultObject): defaultObject is expected to be an object with properties set to the defaults to apply');
779
+ }
780
+
781
+ const defaultKeys = Object.getOwnPropertyNames(defaultObj);
782
+
783
+ cleanTargets.forEach((target) => {
784
+ defaultKeys.forEach((prop) => {
785
+ if (typeof target[prop] === 'undefined') {
786
+ target[prop] = defaultObj[prop];
787
+ }
788
+ });
789
+ });
790
+ };