jupyter-ijavascript-utils 1.9.0 → 1.10.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
@@ -28,6 +28,7 @@ See the [#Installation section for requirements and installation](#install)
28
28
 
29
29
  ## What's New
30
30
 
31
+ * 1.10 - provide {@link module:aggregate.percentile|percentile} (like 50th percentile) aggregates
31
32
  * 1.9 - allow {@link TableGenerator#transpose|transposing results} on TableGenerator.
32
33
  * 1.8 - add in What can I Do tutorial, and {@link module:object.join|object.join methods}
33
34
  * 1.7 - revamp of `animation` method for ijs.htmlScript
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.10.0-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.10 - provide percentile (like 50th percentile) aggregates
21
25
  * 1.9 - allow transposing results on TableGenerator.
22
26
  * 1.8 - add in What can I Do tutorial, and object.join methods
23
27
  * 1.7 - revamp of `animation` method to htmlScript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.9.0",
3
+ "version": "1.10.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",
@@ -27,7 +27,6 @@
27
27
  "jupyter"
28
28
  ],
29
29
  "author": "Paul Roth",
30
- "license": "MIT",
31
30
  "devDependencies": {
32
31
  "babel-eslint": "^10.1.0",
33
32
  "docdash": "^1.2.0",
@@ -44,6 +43,7 @@
44
43
  "fs-extra": "^10.0.1",
45
44
  "generate-schema": "^2.6.0",
46
45
  "node-fetch": "^2.6.5",
46
+ "percentile": "^1.6.0",
47
47
  "pino": "^6.12.0",
48
48
  "pino-pretty": "^5.1.2",
49
49
  "plantuml-encoder": "^1.4.0",
@@ -13,7 +13,7 @@
13
13
 
14
14
  const { printValue } = require('./format');
15
15
 
16
- const generateRange = (length, defaultValue) => new Array(length).fill(defaultValue);
16
+ // const generateRange = (length, defaultValue) => new Array(length).fill(defaultValue);
17
17
 
18
18
  const IJSUtils = require('./ijs');
19
19
 
@@ -452,20 +452,64 @@ class TableGenerator {
452
452
  *
453
453
  * (This is an alternate to {@link formatterFn} or simple `.map()` call on the source data)
454
454
  *
455
+ * **NOTE: Only matching properties on the formatter object are changed - all others are left alone.**
456
+ *
455
457
  * For example:
456
458
  *
457
459
  * ```
458
- * data = [{temp: 98, type: 'F'}, {temp: 99, type: 'F'}, {temp: 100, type: 'F'}];
460
+ * data = [
461
+ * {station: 'A', temp: 98, type: 'F', descr: '0123'},
462
+ * {station: 'A', temp: 99, type: 'F', descr: '0123456'},
463
+ * {station: 'A', temp: 100, type: 'F', descr: '0123456789'}
464
+ * ];
459
465
  *
460
466
  * //-- simple example where the temp property is converted, and type property overwritten
461
467
  * new TableGenerator(data)
462
468
  * .formatter({
469
+ * //-- property 'station' not mentioned, so no change
470
+ *
471
+ * //-- convert temperature to celsius
463
472
  * temp: (value) => (value - 32) * 0.5556,
464
- * type: (value) => 'C'
465
- * })...
473
+ * //-- overwrite type from 'F' to 'C'
474
+ * type: 'C',
475
+ * //-- ellipsify to shorten the description string, if longer than 8 characters
476
+ * descr: (str) => utils.format.ellipsify(str, 8)
477
+ * }).renderMarkdown()
466
478
  * ```
467
479
  *
468
- * Only properties on the object are checked - all others are left alone.
480
+ * station|temp |type|descr
481
+ * -- |-- |-- |--
482
+ * A |36.67 |F |0123
483
+ * A |37.225|F |0123456
484
+ * A |37.781|F |01234567…
485
+ *
486
+ * Note, due to frequent requests, simple datatype conversions can be requested.
487
+ *
488
+ * Only ('String', 'Number', and 'Boolean') are supported
489
+ *
490
+ * ```
491
+ * data = [
492
+ * { propA: ' 8009', propB: 8009, isBoolean: 0},
493
+ * { propA: ' 92032', propB: 92032, isBoolean: 1},
494
+ * { propA: ' 234234', propB: 234234, isBoolean: 1},
495
+ * ];
496
+ *
497
+ * new utils.TableGenerator(data)
498
+ * .formatter({
499
+ * //-- convert Prop A to Number - so render with Locale Number Formatting
500
+ * propA: 'number',
501
+ * //-- conver PropB to String - so render without Locale Number Formatting
502
+ * propB: 'string',
503
+ * //-- render 'True' or 'False'
504
+ * isBoolean: 'boolean'
505
+ * }).renderMarkdown();
506
+ * ```
507
+ *
508
+ * propA|propB|isBoolean
509
+ * -- |-- |--
510
+ * 8,009 |8009 |false
511
+ * 92,032 |92032 |true
512
+ * 234,234 |234234 |true
469
513
  *
470
514
  * @param {Object} obj - object with properties storing arrow functions
471
515
  * @param {Function} obj.PropertyToTranslate - (value) => result
@@ -480,10 +524,25 @@ class TableGenerator {
480
524
 
481
525
  const fnMap = new Map();
482
526
  Object.getOwnPropertyNames(obj).forEach((key) => {
483
- if ((typeof obj[key]) !== 'function') {
484
- throw (Error(`Formatter properties must be functions. [${key}]`));
527
+ if ((typeof obj[key]) === 'string') {
528
+ let fn;
529
+ const str = obj[key].toLowerCase();
530
+ if (str === 'string') {
531
+ fn = (val) => String(val);
532
+ } else if (str === 'number') {
533
+ fn = (val) => Number(val);
534
+ } else if (str === 'boolean') {
535
+ fn = (val) => val ? 'true' : 'false';
536
+ } else {
537
+ throw Error(`TableGenerator.format: property ${key} formatter of ${str} is unsupported. Only (String, Number, Boolean) are supported`);
538
+ }
539
+ fnMap.set(key, fn);
540
+ } else {
541
+ if ((typeof obj[key]) !== 'function') {
542
+ throw (Error(`Formatter properties must be functions. [${key}]`));
543
+ }
544
+ fnMap.set(key, obj[key]);
485
545
  }
486
- fnMap.set(key, obj[key]);
487
546
  });
488
547
 
489
548
  this.#formatterFn = ({ value, property }) => fnMap.has(property)
package/src/aggregate.js CHANGED
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable implicit-arrow-linebreak */
2
2
 
3
+ const Percentile = require('percentile');
4
+
3
5
  const ObjectUtils = require('./object');
4
6
  const FormatUtils = require('./format');
5
7
 
@@ -13,6 +15,8 @@ const FormatUtils = require('./format');
13
15
  *
14
16
  * Types of methods:
15
17
  *
18
+ * * Select a single property
19
+ * * {@link module:aggregate.property|property()} - maps to a single property (often used with other libraries)
16
20
  * * Ranges of values
17
21
  * * {@link module:aggregate.extent|extent()} - returns the min and max of range
18
22
  * * {@link module:aggregate.min|min()} - returns the minimum value of the range
@@ -35,6 +39,17 @@ const FormatUtils = require('./format');
35
39
  * * {@link module:aggregate.sum|sum()} - sum of a collection
36
40
  * * Functional
37
41
  * * {@link module:aggregate.deferCollection|deferCollection(function, bindArg, bindArg, ...)} - bind a function with arguments
42
+ * * Percentile
43
+ * * {@link module:aggregate.percentile|percentile()} - determines the Nth percentile of a field or value
44
+ * * {@link module:aggregate.percentile_01|percentile_01()} - 1th percentile
45
+ * * {@link module:aggregate.percentile_05|percentile_05()} - 5th percentile
46
+ * * {@link module:aggregate.percentile_10|percentile_10()} - 10th percentile
47
+ * * {@link module:aggregate.percentile_25|percentile_25()} - 25th percentile
48
+ * * {@link module:aggregate.percentile_50|percentile_50()} - 50th percentile
49
+ * * {@link module:aggregate.percentile_75|percentile_75()} - 75th percentile
50
+ * * {@link module:aggregate.percentile_90|percentile_90()} - 90th percentile
51
+ * * {@link module:aggregate.percentile_95|percentile_95()} - 95th percentile
52
+ * * {@link module:aggregate.percentile_99|percentile_99()} - 99th percentile
38
53
  *
39
54
  * Please note, there is nothing special for these functions, such as working with {@link SourceMap#reduce|SourceMap.reduce()}
40
55
  *
@@ -225,6 +240,34 @@ module.exports = {};
225
240
  // eslint-disable-next-line no-unused-vars
226
241
  const AggregateUtils = module.exports;
227
242
 
243
+ /**
244
+ * Maps an array of values to a single property.
245
+ *
246
+ * For example:
247
+ *
248
+ * ```
249
+ * const data = [{ record: 'jobA', val: 1 }, { record: 'jobA', val: 2 },
250
+ * { record: 'jobA', val: 3 }, { record: 'jobA', val: 4 },
251
+ * { record: 'jobA', val: 5 }, { record: 'jobA', val: 6 },
252
+ * { record: 'jobA', val: 7 }, { record: 'jobA', val: 8 },
253
+ * { record: 'jobA', val: 9 }, { record: 'jobA', val: 10 }
254
+ * ];
255
+ *
256
+ * utils.object.propertyFromList(data, 'val')
257
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
258
+ *
259
+ * utils.object.propertyFromList(data, (r) => r.val);
260
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
261
+ * ```
262
+ *
263
+ * @param {Object[]} objectArray - Array of Objects to be mapped to a single property / value
264
+ * @param {Function | String} propertyOrFn - Name of the property or Function to return a value
265
+ * @returns {Array} - Array of values
266
+ */
267
+ module.exports.property = function propertyFromList(objectArray, propertyOrFn) {
268
+ return ObjectUtils.propertyFromList(objectArray, propertyOrFn);
269
+ };
270
+
228
271
  /**
229
272
  * Converts an aggregate function to two functions -
230
273
  * one that takes all arguments except the collection
@@ -662,3 +705,157 @@ module.exports.isUnique = function isUnique(collection, accessor) {
662
705
  });
663
706
  return duplicateValue === undefined;
664
707
  };
708
+
709
+ /**
710
+ * Returns a given percentile from a list of objects.
711
+ *
712
+ * **Note: this simply aggregates the values and passes to the [Percentile NPM Package](https://www.npmjs.com/package/percentile)**
713
+ *
714
+ * @param {Object[]} collection - collection of objects
715
+ * @param {Function | String} accessor - function to access the value, string property or null
716
+ * @param {Number} pct - Percentile (either .5 or 50)
717
+ * @returns {Number} - the pct percentile of a property within the collection
718
+ * @example
719
+ * const data = [{ record: 'jobA', val: 1 }, { record: 'jobA', val: 2 },
720
+ * { record: 'jobA', val: 3 }, { record: 'jobA', val: 4 },
721
+ * { record: 'jobA', val: 5 }, { record: 'jobA', val: 6 },
722
+ * { record: 'jobA', val: 7 }, { record: 'jobA', val: 8 },
723
+ * { record: 'jobA', val: 9 }, { record: 'jobA', val: 10 }
724
+ * ];
725
+ *
726
+ * utils.aggregate.percentile(data, 'val', 50) //-- returns 5
727
+ * utils.aggregate.percentile(data, (r) => r.val, 70) //-- returns 7
728
+ */
729
+ module.exports.percentile = function percentile(collection, accessor, pct) {
730
+ const values = ObjectUtils.propertyFromList(collection, accessor);
731
+ const cleanPercentile = pct > 0 && pct < 1
732
+ ? pct * 100
733
+ : pct;
734
+ return Percentile(cleanPercentile, values);
735
+ };
736
+
737
+ /**
738
+ * Returns a hard coded percentage
739
+ *
740
+ * {@link module:aggregate.percentage|See Percentage for more detail}
741
+ *
742
+ * @param {Object[]} collection - collection of objects
743
+ * @param {Function | String} accessor - function to access the value, string property or null
744
+ * @returns {Number} - the percentile of a property within the collection
745
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
746
+ */
747
+ module.exports.percentile_01 = function percentile(collection, accessor) {
748
+ return AggregateUtils.percentile(collection, accessor, 1);
749
+ };
750
+
751
+ /**
752
+ * Returns a hard coded percentage
753
+ *
754
+ * {@link module:aggregate.percentage|See Percentage for more detail}
755
+ *
756
+ * @param {Object[]} collection - collection of objects
757
+ * @param {Function | String} accessor - function to access the value, string property or null
758
+ * @returns {Number} - the percentile of a property within the collection
759
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
760
+ */
761
+ module.exports.percentile_05 = function percentile(collection, accessor) {
762
+ return AggregateUtils.percentile(collection, accessor, 5);
763
+ };
764
+
765
+ /**
766
+ * Returns a hard coded percentage
767
+ *
768
+ * {@link module:aggregate.percentage|See Percentage for more detail}
769
+ *
770
+ * @param {Object[]} collection - collection of objects
771
+ * @param {Function | String} accessor - function to access the value, string property or null
772
+ * @returns {Number} - the percentile of a property within the collection
773
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
774
+ */
775
+ module.exports.percentile_10 = function percentile(collection, accessor) {
776
+ return AggregateUtils.percentile(collection, accessor, 10);
777
+ };
778
+
779
+ /**
780
+ * Returns a hard coded percentage
781
+ *
782
+ * {@link module:aggregate.percentage|See Percentage for more detail}
783
+ *
784
+ * @param {Object[]} collection - collection of objects
785
+ * @param {Function | String} accessor - function to access the value, string property or null
786
+ * @returns {Number} - the percentile of a property within the collection
787
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
788
+ */
789
+ module.exports.percentile_25 = function percentile(collection, accessor) {
790
+ return AggregateUtils.percentile(collection, accessor, 25);
791
+ };
792
+
793
+ /**
794
+ * Returns a hard coded percentage
795
+ *
796
+ * {@link module:aggregate.percentage|See Percentage for more detail}
797
+ *
798
+ * @param {Object[]} collection - collection of objects
799
+ * @param {Function | String} accessor - function to access the value, string property or null
800
+ * @returns {Number} - the percentile of a property within the collection
801
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
802
+ */
803
+ module.exports.percentile_50 = function percentile(collection, accessor) {
804
+ return AggregateUtils.percentile(collection, accessor, 50);
805
+ };
806
+
807
+ /**
808
+ * Returns a hard coded percentage
809
+ *
810
+ * {@link module:aggregate.percentage|See Percentage for more detail}
811
+ *
812
+ * @param {Object[]} collection - collection of objects
813
+ * @param {Function | String} accessor - function to access the value, string property or null
814
+ * @returns {Number} - the percentile of a property within the collection
815
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
816
+ */
817
+ module.exports.percentile_75 = function percentile(collection, accessor) {
818
+ return AggregateUtils.percentile(collection, accessor, 75);
819
+ };
820
+
821
+ /**
822
+ * Returns a hard coded percentage
823
+ *
824
+ * {@link module:aggregate.percentage|See Percentage for more detail}
825
+ *
826
+ * @param {Object[]} collection - collection of objects
827
+ * @param {Function | String} accessor - function to access the value, string property or null
828
+ * @returns {Number} - the percentile of a property within the collection
829
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
830
+ */
831
+ module.exports.percentile_90 = function percentile(collection, accessor) {
832
+ return AggregateUtils.percentile(collection, accessor, 90);
833
+ };
834
+
835
+ /**
836
+ * Returns a hard coded percentage
837
+ *
838
+ * {@link module:aggregate.percentage|See Percentage for more detail}
839
+ *
840
+ * @param {Object[]} collection - collection of objects
841
+ * @param {Function | String} accessor - function to access the value, string property or null
842
+ * @returns {Number} - the percentile of a property within the collection
843
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
844
+ */
845
+ module.exports.percentile_95 = function percentile(collection, accessor) {
846
+ return AggregateUtils.percentile(collection, accessor, 95);
847
+ };
848
+
849
+ /**
850
+ * Returns a hard coded percentage
851
+ *
852
+ * {@link module:aggregate.percentage|See Percentage for more detail}
853
+ *
854
+ * @param {Object[]} collection - collection of objects
855
+ * @param {Function | String} accessor - function to access the value, string property or null
856
+ * @returns {Number} - the percentile of a property within the collection
857
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
858
+ */
859
+ module.exports.percentile_99 = function percentile(collection, accessor) {
860
+ return AggregateUtils.percentile(collection, accessor, 99);
861
+ };
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,10 @@
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')
15
+ * * {@link module:format.ellipsify|format.ellipsify} - Truncates a string if the length is 'too long'
12
16
  * * Formatting Time
13
17
  * * {@link module:format.millisecondDuration|format.millisecondDuration}
14
18
  * * Mapping Values
@@ -454,3 +458,40 @@ module.exports.clampDomain = function clampDomain(value, [minimum, maximum]) {
454
458
  }
455
459
  return value;
456
460
  };
461
+
462
+ /**
463
+ * Capitalizes the first character of the string.
464
+ *
465
+ * @param {String} str - String to capitalize the first letter only
466
+ * @returns {String} - ex: 'John paul'
467
+ * @see {@link module:format.capitalizeAll|capitalizeAll} - to capitalize all words in a string
468
+ * @example
469
+ * utils.format.capitalize('john'); // 'John'
470
+ * utils.format.capitalize('john doe'); // 'John doe'
471
+ */
472
+ module.exports.capitalize = function capitalize(str) {
473
+ if (!str || str.length === 0) {
474
+ return '';
475
+ }
476
+
477
+ //-- charAt does not work for unicode
478
+ const [first, ...rest] = str;
479
+ return first.toLocaleUpperCase() + rest.join('');
480
+ };
481
+
482
+ /**
483
+ * Capitalizes all words in a string.
484
+ *
485
+ * @param {String} str - String to capitalize
486
+ * @returns {String} - ex: 'John-Paul'
487
+ * @see {@link module:format.capitalizeAll|capitalizeAll} - to capitalize all words in a string
488
+ * @example
489
+ * utils.format.capitalize('john'); // 'John'
490
+ * utils.format.capitalize('john doe'); // 'John Doe'
491
+ * utils.format.capitalize('john-paul'); // 'John-Paul'
492
+ */
493
+ module.exports.capitalizeAll = function capitalizeAll(str) {
494
+ return (str || '').split(/\b/)
495
+ .map(FormatUtils.capitalize)
496
+ .join('');
497
+ };
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()} -
@@ -19,6 +22,7 @@ const schemaGenerator = require('generate-schema');
19
22
  * * {@link module:object.fetchObjectProperties|fetchObjectProperties(object, string[])} - use dot notation to bring multiple child properties onto a parent
20
23
  * * {@link module:object.join|join(array, index, map, fn)} - join a collection against a map by a given index
21
24
  * * {@link module:object.joinProperties|join(array, index, map, ...fields)} - join a collection, and copy properties over from the mapped object.
25
+ * * {@link module:object.propertyFromList|propertyFromList(array, propertyName)} - fetches a specific property from all objects in a list
22
26
  * * Rename properties
23
27
  * * {@link module:object.cleanProperties|cleanProperties()} - correct inaccessible property names in a list of objects
24
28
  * * {@link module:object.cleanPropertyNames|cleanPropertyNames()} - create a translation of inaccessible names to accessible ones
@@ -634,3 +638,188 @@ module.exports.joinProperties = function join(objectArray, indexField, targetMap
634
638
 
635
639
  return ObjectUtils.join(objectArray, indexField, targetMap, joinFn);
636
640
  };
641
+
642
+ /**
643
+ * Maps an array of values to a single property.
644
+ *
645
+ * For example:
646
+ *
647
+ * ```
648
+ * const data = [{ record: 'jobA', val: 1 }, { record: 'jobA', val: 2 },
649
+ * { record: 'jobA', val: 3 }, { record: 'jobA', val: 4 },
650
+ * { record: 'jobA', val: 5 }, { record: 'jobA', val: 6 },
651
+ * { record: 'jobA', val: 7 }, { record: 'jobA', val: 8 },
652
+ * { record: 'jobA', val: 9 }, { record: 'jobA', val: 10 }
653
+ * ];
654
+ *
655
+ * utils.object.propertyFromList(data, 'val')
656
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
657
+ *
658
+ * utils.object.propertyFromList(data, (r) => r.val);
659
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
660
+ * ```
661
+ *
662
+ * @param {Object[]} objectArray - Array of Objects to be mapped to a single property / value
663
+ * @param {Function | String} propertyOrFn - Name of the property or Function to return a value
664
+ * @returns {Array} - Array of values
665
+ */
666
+ module.exports.propertyFromList = function propertyFromList(objectArray, propertyOrFn) {
667
+ const cleanArray = Array.isArray(objectArray)
668
+ ? objectArray
669
+ : [];
670
+
671
+ const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
672
+
673
+ return cleanArray.map(fn);
674
+ };
675
+
676
+ /**
677
+ * Finds objects that do not have ALL the properties specified.
678
+ *
679
+ * This can be very helpful in ensuring all objects actually meet a specification and are not missing values.
680
+ *
681
+ * ```
682
+ * const students = [
683
+ * { first: 'john', last: 'doe', age: 23 }, { first: 'jane', last: 'doe', age: 23 }, { first: 'jack', last: 'white', failure: 401 }
684
+ * ];
685
+ *
686
+ * utils.findWithoutProperties(students, 'first', 'last', 'age');
687
+ * // [{ first: 'jack', last: 'white', failure: 401 }]
688
+ *
689
+ * utils.findWithoutProperties(students, 'failure');
690
+ * // [{ first: 'john', last: 'doe', age: 23 }, { first: 'jane', last: 'doe', age: 23 }]
691
+ * ```
692
+ *
693
+ * Please note, that we can check a single object:
694
+ *
695
+ * ```
696
+ * utils.findWithoutProperties(students[0], 'failure');
697
+ * // []
698
+ * ```
699
+ *
700
+ * @param {Object[]} objectsToCheck - the array of objects to check for the properties.
701
+ * @param {...String} propertiesToFind - the list of properties to find within the collection.
702
+ * @returns {Object[]} - Array of objects that are missing at least one of those properties
703
+ * @see {@link module:file.findWithProperties|findWithProperties} - if you want objects that do not have all properties
704
+ **/
705
+ module.exports.findWithoutProperties = function findWithoutProperties(targetObj, ...propertiesToFind) {
706
+ const cleanProperties = propertiesToFind.length > 0 && Array.isArray(propertiesToFind[0])
707
+ ? propertiesToFind[0]
708
+ : propertiesToFind;
709
+
710
+ const cleanTargets = Array.isArray(targetObj)
711
+ ? targetObj
712
+ : [targetObj];
713
+
714
+ const results = [];
715
+
716
+ cleanTargets.forEach((target) => {
717
+ if (cleanProperties.find((prop) => (typeof target[prop]) === 'undefined')) {
718
+ results.push(target);
719
+ }
720
+ });
721
+
722
+ return results;
723
+ };
724
+
725
+ /**
726
+ * Finds objects that have any of the properties specified.
727
+ *
728
+ * This can be very helpful when working with datasets that include mixed data (such as JSON)
729
+ *
730
+ * ```
731
+ * const students = [
732
+ * { first: 'john', last: 'doe' }, { first: 'jane', last: 'doe' }, { first: 'jack', last: 'white', failure: 401 }
733
+ * ];
734
+ *
735
+ * utils.findWithProperties(students, 'failure');
736
+ * // { first: 'jack', last: 'white', failure: 401 }
737
+ * ```
738
+ *
739
+ * Please note, that we can check a single object:
740
+ *
741
+ * ```
742
+ * utils.findWithProperties({ first: 'john', last: 'doe' }, 'failure');
743
+ * // []
744
+ * ```
745
+ *
746
+ * @param {Object[]} objectsToCheck - the array of objects to check for the properties.
747
+ * @param {...String} propertiesToFind - the list of properties to find within the collection.
748
+ * @returns {Object[]} - Array of objects that have at least one of those properties
749
+ * @see {@link module:file.findWithoutProperties|findWithoutProperties} - if you want objects that do not have all properties
750
+ **/
751
+ module.exports.findWithProperties = function findWithProperties(targetObj, ...propertiesToFind) {
752
+ const cleanProperties = propertiesToFind.length > 0 && Array.isArray(propertiesToFind[0])
753
+ ? propertiesToFind[0]
754
+ : propertiesToFind;
755
+
756
+ const cleanTargets = Array.isArray(targetObj)
757
+ ? targetObj
758
+ : [targetObj];
759
+
760
+ const results = [];
761
+
762
+ cleanTargets.forEach((target) => {
763
+ if (cleanProperties.find((prop) => (typeof target[prop]) !== 'undefined')) {
764
+ results.push(target);
765
+ }
766
+ });
767
+
768
+ return results;
769
+ };
770
+
771
+ /**
772
+ * Sets values for objects that don't currently have the property
773
+ *
774
+ * This is very helpful for ensuring that all objects have a property,
775
+ * or setting a value to make it easier to identify that it is 'N/A'
776
+ *
777
+ * Note, that only the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty|ownProperties}
778
+ * on the default object are checked.
779
+ *
780
+ * And values are applied to the target object, only if the property is not on the object (property is undefined)
781
+ *
782
+ * @param {Object[] | Object} targetObject - Object to apply the properties to <br />
783
+ * but ONLY if the object does not have that property (ex: undefined)
784
+ * @param {Object} defaultObj - Object with the properties and defaults applied
785
+ * @param {any} defaultObj.property - the property to check, with the default value assigned
786
+ * @see {@link module:file.findWithoutProperties|findWithoutProperties} - to determine if any objects do not have a set of properties
787
+ * @see {@link module:file.keys|keys} - to get a list of unique properties of all objects in a list.
788
+ * @example
789
+ * const students = [
790
+ * { first: 'john', last: 'doe', birthday: '2002-04-01' },
791
+ * { first: 'jane', last: 'doe', birthday: '2003-05-01' },
792
+ * { first: 'jack', last: 'white', failure: 401 }
793
+ * ];
794
+ *
795
+ * utils.object.setPropertyDefaults(students, {
796
+ * first: '',
797
+ * last: '',
798
+ * birthday: ''
799
+ * });
800
+ *
801
+ * // [
802
+ * // { first: 'john', last: 'doe', birthday: '2002-04-01' },
803
+ * // { first: 'jane', last: 'doe', birthday: '2003-05-01' },
804
+ * // { first: 'jack', last: 'white', birthday: '', failure: 401 }
805
+ * // ];
806
+ */
807
+ module.exports.setPropertyDefaults = function setPropertyDefaults(targetObject, defaultObj) {
808
+ const cleanTargets = Array.isArray(targetObject)
809
+ ? targetObject
810
+ : [targetObject];
811
+
812
+ if (!defaultObj || typeof defaultObj !== 'object') {
813
+ throw Error('object.setPropertyDefaults(targetObject, defaultObject): defaultObject is expected to be an object with properties set to the defaults to apply');
814
+ }
815
+
816
+ const defaultKeys = Object.getOwnPropertyNames(defaultObj);
817
+
818
+ cleanTargets.forEach((target) => {
819
+ defaultKeys.forEach((prop) => {
820
+ if (typeof target[prop] === 'undefined') {
821
+ target[prop] = defaultObj[prop];
822
+ }
823
+ });
824
+ });
825
+ };