jupyter-ijavascript-utils 1.24.0 → 1.26.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,8 @@ Give it a try here:
75
75
 
76
76
  ## What's New
77
77
 
78
+ * 1.26 - Support for file.writeFile and file.writeJSON to append
79
+ * 1.25 - Additional chain methods and documentation
78
80
  * 1.24 - format.stripHtmlTags, TableGenerator.offset, chain.chainFlatMap, chain.chainFilter
79
81
  * 1.23 - add format.parseNumber and TableGenerator.styleColumn, align group.separateByFields to vega-lite fold transform
80
82
  * 1.22 - make chain iJavaScript aware, but still able to work outside of Jupyter
package/Dockerfile CHANGED
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.24.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.26.0
package/README.md CHANGED
@@ -55,6 +55,8 @@ 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.26 - Support for file.writeFile and file.writeJSON to append
59
+ * 1.25 - Additional chain methods and documentation
58
60
  * 1.24 - format.stripHtmlTags, TableGenerator.offset, chain.chainFlatMap, chain.chainFilter
59
61
  * 1.23 - add format.parseNumber and TableGenerator.styleColumn, align group.separateByFields to vega-lite fold transform
60
62
  * 1.22 - make chain iJavaScript aware, but still able to work outside of Jupyter
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.24.0",
3
+ "version": "1.26.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",
package/src/chain.js CHANGED
@@ -8,17 +8,129 @@
8
8
  * Very helpful for taking values and then progressively working on them,
9
9
  * instead of continually wrapping deeper in method calls.
10
10
  *
11
- * Calling `chain(3)` - gives an object with two properties:
11
+ * Calling `chain(3)` - gives an object you can then chain calls against:
12
12
  *
13
13
  * * {@link ChainContainer#close|.close()} - gets the value of the current chain
14
14
  * * {@link ChainContainer#chain|.chain(function)} - where it is passed the value, and returns a new Chain with that value.
15
- * * {@link ChainContainer#chainMap|.chainMap(function)} - where it treats value as an array, and maps function on every item in the array
16
- * * {@link ChainContainer#chainFlatMap|.chainFlatMap(function)} - where it treats value as an array, and maps function on every item in the array,
17
- * flattening the results
18
- * * {@link ChainContainer#chainFilter|.chainFilter(function)} - where it treats the value as an array, and filters values based on the result
19
- * * {@link ChainContainer#chainReduce|.chainReduce(function, initialValue)} - where it treats value as an array, and reduces the value array
20
15
  * * {@link ChainContainer#errorHandler|.errorHandler(fn)} - custom function called if an error is ever thrown
21
16
  * * {@link ChainContainer#debug|.debug()} - console.logs the current value, and continues the chain with that value
17
+ *
18
+ * Along with methods that can iterate on each element, assuming the value in the chain is an Array.
19
+ *
20
+ * * {@link ChainContainer#chainMap|.chainMap(function)} - where it calls the `.map` on value, and applies the function on every item in the array,
21
+ * storing the result from the function. <br /> <b>(Useful for changing values without changing the original object))</b>
22
+ * * {@link ChainContainer#chainForEach|.chainForEach(function)} - where calls `.forEach` on value, and applies the function on every item,
23
+ * without storing the result from the function. <br /> <b>(Useful for changing objects in-place)</b>
24
+ * * {@link ChainContainer#chainFlatMap|.chainFlatMap(function)} - where it calls `.flatMap` on value, and applies the function on every item,
25
+ * flattening the results. <br /> <b>(Useful for expanding an array based on values in the array)</b>
26
+ * * {@link ChainContainer#chainFilter|.chainFilter(function)} - where it calls `.filter` on value, using the function on every item,
27
+ * keeping the item in the list if the function returns true.
28
+ * <br /> <b>(Useful for removing items from an array)</b>
29
+ * * {@link ChainContainer#chainReduce|.chainReduce(function, initialValue)} - where it calls `.reduce` on value, and reduces the value
30
+ * to a single result.
31
+ * <br /> <b>(Useful for reducing the array to a single value <br /> - like a concatenated string or sum total)</b>
32
+ *
33
+ * There may be times you want to run side effects, or replace the value entirely. (This isn't common, but may be useful on occasion)
34
+ *
35
+ * * {@link ChainContainer#execute|.execute(function)} - where it calls a function, but doesn't pass on the result.
36
+ * <br /> (This is useful for side-effects, like writing to files)
37
+ * * {@link ChainContainer#replace|.replace(value)} - replaces the value in the chain with a literal value,
38
+ * regardless of the previous value.
39
+ *
40
+ * For example:
41
+ *
42
+ * ```
43
+ * addTwo = (value) => value + 2;
44
+ *
45
+ * //-- we can always get the value
46
+ * utils.chain(3).close(); // 3
47
+ * ```
48
+ *
49
+ * but this is much easier if we continue to chain it
50
+ *
51
+ * ```
52
+ * addTwo = (value) => value + 2;
53
+ * addTwo(3); // 5
54
+ *
55
+ * utils.chain(3)
56
+ * .chain(addTwo) // (3 + 2)
57
+ * .chain(addTwo) // (5 + 2)
58
+ * .debug() // consoles 7 and passes the value along
59
+ * // define a function inline
60
+ * .chain((value) => value + 3) // (7 + 3)
61
+ * .close()
62
+ *
63
+ * // consoles out value `7`
64
+ * // returns value 10
65
+ * ```
66
+ *
67
+ * Note that we can also map against values in the array
68
+ *
69
+ * ```
70
+ * initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
71
+ * initializeArray(3); // [0, 1, 2]
72
+ *
73
+ * addTwo = (value) => value + 2;
74
+ * addTwo(3); // 5
75
+ *
76
+ * utils.chain(3)
77
+ * .chain(initializeArray) // [0, 1, 2]
78
+ * .chainMap(addTwo) // [2, 3, 4] or [0 + 2, 1 + 2, 2 + 2]
79
+ * .chainMap(addTwo)
80
+ * .close();
81
+ * // [4, 5, 6]
82
+ * ```
83
+ *
84
+ * Chain to log results while transforming values
85
+ *
86
+ * ```
87
+ * results = [{ userId: 'abc123' }, { userId: 'xyz987' }];
88
+ *
89
+ * activeUsers = chain(results)
90
+ * .chainMap((record) => users.get(record.userId))
91
+ * .chainForEach(record => record.status = 'active')
92
+ * .chain(records => d3.csv.format(records))
93
+ * .execute(records => utils.file.writeFile('./log', d3.csv.format(records)))
94
+ * .close()
95
+ * ```
96
+ *
97
+ * Or even combine with other utility methods
98
+ *
99
+ * ```
100
+ * badStr = 'I%20am%20the%20very%20model%20of%20a%20modern%20Major'
101
+ * + '-General%0AI\'ve%20information%20vegetable%2C%20animal%2C%20'
102
+ * + 'and%20mineral%0AI%20know%20the%20kings%20of%20England%2C%20'
103
+ * + 'and%20I%20quote%20the%20fights%0AHistorical%0AFrom%20Marath'
104
+ * + 'on%20to%20Waterloo%2C%20in%20order%20categorical';
105
+ *
106
+ * chain(badStr)
107
+ * .chain(decodeURIComponent)
108
+ * .chain(v => v.split('\n'))
109
+ * // .debug() // check the values along the way
110
+ * .chainMap(line => ({ line, length: line.length }))
111
+ * .chain(values => utils.table(values).render());
112
+ * ```
113
+ *
114
+ * this can be more legible than the normal way to write this, <br />
115
+ * especially if you need to troubleshoot the value halfway through.
116
+ *
117
+ * ```
118
+ * utils.table(
119
+ * decodeURIComponent(badStr)
120
+ * .split('\n')
121
+ * .map(line => ({ line, length: line.length }))
122
+ * ).render()
123
+ * ```
124
+ *
125
+ * and it renders out a lovely table like this:
126
+ *
127
+ * line |length
128
+ * -- |--
129
+ * I am the very model of a modern Major-General |45
130
+ * I've information vegetable, animal, and mineral |47
131
+ * I know the kings of England, and I quote the fights|51
132
+ * Historical |10
133
+ * From Marathon to Waterloo, in order categorical |47
22
134
  */
23
135
  class ChainContainer {
24
136
  /**
@@ -134,7 +246,9 @@ class ChainContainer {
134
246
  }
135
247
 
136
248
  /**
137
- * Assuming that value is an array, this maps fn to every value in the array.
249
+ * Assuming that value is an array, this does a
250
+ * [javascript array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
251
+ * and applies `fn` to every value in the array.
138
252
  *
139
253
  * ```
140
254
  * initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
@@ -152,6 +266,8 @@ class ChainContainer {
152
266
  * // [4, 5, 6]
153
267
  * ```
154
268
  *
269
+ * This is in contrast to {@link ChainContainer#chainForEach|chainForEach}
270
+ *
155
271
  * @param {Function} fn - applies function under every index of this.value
156
272
  * @returns {ChainContainer}
157
273
  */
@@ -161,6 +277,39 @@ class ChainContainer {
161
277
  return this.chain((value) => value.map(fn));
162
278
  }
163
279
 
280
+ /**
281
+ * Assuming that value is an array, performs a
282
+ * [javaScript forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
283
+ * against the results.
284
+ *
285
+ * This will run the passed function against every element in the result,
286
+ * without replacing the element with the returned value and makes inline editing simpler.
287
+ *
288
+ * ```
289
+ * list = [{ first: 'john', last: 'doe' }, { first: 'jane', last: 'doe' }];
290
+ * utils.chain(list)
291
+ * .mapForEach((entry) => entry.name = `${entry.first} ${entry.last})
292
+ * .close();
293
+ * // [{ first: 'john', last: 'doe', name: 'john doe' }, { first: 'jane', last: 'doe', name: 'jane doe' }]
294
+ * ```
295
+ *
296
+ * This is in contrast to {@link ChainContainer#chainMap|chainMap}, that replaces the element with the value returned.
297
+ * @param {Function(any):any} fn - function to execute on each element
298
+ * @returns {ChainContainer} - chainable container
299
+ */
300
+ chainForEach(fn) {
301
+ if (!Array.isArray(this.value)
302
+ && !(this.value instanceof Set)
303
+ && !(this.value instanceof Map)
304
+ ) {
305
+ throw Error(`chainForEach expects an array, but was passed:${this.value}`);
306
+ }
307
+
308
+ this.value.forEach(fn);
309
+
310
+ return this;
311
+ }
312
+
164
313
  /**
165
314
  * Assuming that value is an array, performs a
166
315
  * [javaScript flatMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap)
@@ -287,6 +436,28 @@ class ChainContainer {
287
436
  return this;
288
437
  }
289
438
 
439
+ /**
440
+ * Applies a function against the current value, while not passing the results along the chain.
441
+ *
442
+ * ```
443
+ * results = [{ userId: 'abc123' }, { userId: 'xyz987' }];
444
+ *
445
+ * activeUsers = chain(results)
446
+ * .chainMap((record) => users.get(record.userId))
447
+ * .chainForEach(record => record.status = 'active')
448
+ * .chain(records => d3.csv.format(records))
449
+ * .execute(records => utils.file.writeFile('./log', d3.csv.format(records)))
450
+ * .close()
451
+ * ```
452
+ *
453
+ * @param {Function} fn - function to execute against the current value
454
+ * @returns {ChainContainer}
455
+ */
456
+ execute(fn) {
457
+ fn(this.value);
458
+ return this;
459
+ }
460
+
290
461
  /**
291
462
  * Function to call if an error occurs anywhere on the chain.
292
463
  *
@@ -325,6 +496,20 @@ class ChainContainer {
325
496
  return this;
326
497
  }
327
498
 
499
+ /**
500
+ * Normally, you will want to replace the value in the chain
501
+ * based on the current value.
502
+ *
503
+ * This replaces the value regardless, and is rarely used.
504
+ *
505
+ * @param {any} value - new value in the chain.
506
+ * @returns {ChainContainer}
507
+ */
508
+ replace(value) {
509
+ this.value = value;
510
+ return this;
511
+ }
512
+
328
513
  /**
329
514
  * Closes the chain and returns the current value.
330
515
  * @returns {any}
@@ -422,27 +607,42 @@ class ChainContainer {
422
607
  * Very helpful for taking values and then progressively working on them,
423
608
  * instead of continually wrapping deeper in method calls.
424
609
  *
425
- * Calling `chain(3)` - gives an object you can either use the value of, or continue the chain going.
610
+ * Calling `chain(3)` - gives an object you can then chain calls against:
426
611
  *
427
612
  * * {@link ChainContainer#close|.close()} - gets the value of the current chain
428
613
  * * {@link ChainContainer#chain|.chain(function)} - where it is passed the value, and returns a new Chain with that value.
429
- * * {@link ChainContainer#chainMap|.chainMap(function)} - where it treats value as an array, and maps function on every item in the array
430
- * * {@link ChainContainer#chainFlatMap|.chainFlatMap(function)} - where it treats value as an array, and maps function on every item in the array,
431
- * flattening the results
432
- * * {@link ChainContainer#chainFilter|.chainFilter(function)} - where it treats the value as an array, and filters values based on the result
433
- * * {@link ChainContainer#chainReduce|.chainReduce(function, initialValue)} - where it treats value as an array, and reduces the value array
434
614
  * * {@link ChainContainer#errorHandler|.errorHandler(fn)} - custom function called if an error is ever thrown
435
615
  * * {@link ChainContainer#debug|.debug()} - console.logs the current value, and continues the chain with that value
436
616
  *
617
+ * Along with methods that can iterate on each element, assuming the value in the chain is an Array.
618
+ *
619
+ * * {@link ChainContainer#chainMap|.chainMap(function)} - where it calls the `.map` on value, and applies the function on every item in the array,
620
+ * storing the result from the function. <br /> <b>(Useful for changing values without changing the original object))</b>
621
+ * * {@link ChainContainer#chainForEach|.chainForEach(function)} - where calls `.forEach` on value, and applies the function on every item,
622
+ * without storing the result from the function. <br /> <b>(Useful for changing objects in-place)</b>
623
+ * * {@link ChainContainer#chainFlatMap|.chainFlatMap(function)} - where it calls `.flatMap` on value, and applies the function on every item,
624
+ * flattening the results. <br /> <b>(Useful for expanding an array based on values in the array)</b>
625
+ * * {@link ChainContainer#chainFilter|.chainFilter(function)} - where it calls `.filter` on value, using the function on every item,
626
+ * keeping the item in the list if the function returns true.
627
+ * <br /> <b>(Useful for removing items from an array)</b>
628
+ * * {@link ChainContainer#chainReduce|.chainReduce(function, initialValue)} - where it calls `.reduce` on value, and reduces the value
629
+ * to a single result.
630
+ * <br /> <b>(Useful for reducing the array to a single value <br /> - like a concatenated string or sum total)</b>
631
+ *
632
+ * There may be times you want to run side effects, or replace the value entirely. (This isn't common, but may be useful on occasion)
633
+ *
634
+ * * {@link ChainContainer#execute|.execute(function)} - where it calls a function, but doesn't pass on the result.
635
+ * <br /> (This is useful for side-effects, like writing to files)
636
+ * * {@link ChainContainer#replace|.replace(value)} - replaces the value in the chain with a literal value,
637
+ * regardless of the previous value.
638
+ *
437
639
  * For example:
438
640
  *
439
641
  * ```
440
642
  * addTwo = (value) => value + 2;
441
643
  *
442
644
  * //-- we can always get the value
443
- * console.log(
444
- * utils.chain(3).value
445
- * ); // 3
645
+ * utils.chain(3).close(); // 3
446
646
  * ```
447
647
  *
448
648
  * but this is much easier if we continue to chain it
@@ -451,18 +651,16 @@ class ChainContainer {
451
651
  * addTwo = (value) => value + 2;
452
652
  * addTwo(3); // 5
453
653
  *
454
- * console.log(
455
- * utils.chain(3)
456
- * .chain(addTwo) // (3 + 2)
457
- * .chain(addTwo) // (5 + 2)
458
- * .debug() // consoles 7 and passes the value along
459
- * // define a function inline
460
- * .chain((value) => value + 3) // (7 + 3)
461
- * .value
462
- * );
463
- *
464
- * // 7
465
- * // 10
654
+ * utils.chain(3)
655
+ * .chain(addTwo) // (3 + 2)
656
+ * .chain(addTwo) // (5 + 2)
657
+ * .debug() // consoles 7 and passes the value along
658
+ * // define a function inline
659
+ * .chain((value) => value + 3) // (7 + 3)
660
+ * .close()
661
+ *
662
+ * // consoles out value `7`
663
+ * // returns value 10
466
664
  * ```
467
665
  *
468
666
  * Note that we can also map against values in the array
@@ -478,10 +676,23 @@ class ChainContainer {
478
676
  * .chain(initializeArray) // [0, 1, 2]
479
677
  * .chainMap(addTwo) // [2, 3, 4] or [0 + 2, 1 + 2, 2 + 2]
480
678
  * .chainMap(addTwo)
481
- * .value;
679
+ * .close();
482
680
  * // [4, 5, 6]
483
681
  * ```
484
682
  *
683
+ * Chain to log results while transforming values
684
+ *
685
+ * ```
686
+ * results = [{ userId: 'abc123' }, { userId: 'xyz987' }];
687
+ *
688
+ * activeUsers = chain(results)
689
+ * .chainMap((record) => users.get(record.userId))
690
+ * .chainForEach(record => record.status = 'active')
691
+ * .chain(records => d3.csv.format(records))
692
+ * .execute(records => utils.file.writeFile('./log', d3.csv.format(records)))
693
+ * .close()
694
+ * ```
695
+ *
485
696
  * Or even combine with other utility methods
486
697
  *
487
698
  * ```
@@ -499,6 +710,17 @@ class ChainContainer {
499
710
  * .chain(values => utils.table(values).render());
500
711
  * ```
501
712
  *
713
+ * this can be more legible than the normal way to write this, <br />
714
+ * especially if you need to troubleshoot the value halfway through.
715
+ *
716
+ * ```
717
+ * utils.table(
718
+ * decodeURIComponent(badStr)
719
+ * .split('\n')
720
+ * .map(line => ({ line, length: line.length }))
721
+ * ).render()
722
+ * ```
723
+ *
502
724
  * and it renders out a lovely table like this:
503
725
  *
504
726
  * line |length
package/src/file.js CHANGED
@@ -18,8 +18,8 @@ const logger = require('./logger');
18
18
  * (or storing and loading json data)
19
19
  *
20
20
  * * Writing files
21
- * * {@link module:file.writeFile|writeFile(path, string)} - write a file as plain text
22
- * * {@link module:file.writeJSON|writeJSON(path, any)} - write data as JSON
21
+ * * {@link module:file.writeFile|writeFile(path, string)} - write or append to file with plain text
22
+ * * {@link module:file.writeJSON|writeJSON(path, any)} - write or append to a file with objects converted to JSON
23
23
  * * reading files
24
24
  * * {@link module:file.readFile|readFile(path, string)} - read a file as plain text
25
25
  * * {@link module:file.readJSON|readJSON(path, any)} - read data as JSON
@@ -179,11 +179,7 @@ module.exports.readFile = function readFile(filePath, fsOptions = {}) {
179
179
  *
180
180
  * NOTE that this uses `utf-8` as the default encoding
181
181
  *
182
- * @param {string} filePath - path of the file to write
183
- * @param {Object} fsOptions - options to pass for fsRead (ex: { encoding: 'utf-8' })
184
- * @param {string} contents - contents of the file
185
- * @see {@link module:file.readJSON|readJSON(filePath, fsOptions)} - for reading
186
- * @example
182
+ * ```
187
183
  * const weather = [
188
184
  * { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
189
185
  * { id: 0, city: 'Seattle', month: 'Apr', precip: 2.68 },
@@ -199,16 +195,52 @@ module.exports.readFile = function readFile(filePath, fsOptions = {}) {
199
195
  *
200
196
  * const myWeather = utils.file.readJSON('./data/weather.json');
201
197
  * myWeather.length; // 9
198
+ * ```
199
+ *
200
+ * Note, passing `append:true` in the options, will let you append text before writing,
201
+ * useful for dealing with large and complex files.
202
+ *
203
+ * ```
204
+ * weatherEntry1 = { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 };
205
+ * weatherEntry2 = { id: 0, city: 'Seattle', month: 'Apr', precip: 2.68 };
206
+ * weatherEntry3 = { id: 2, city: 'Seattle', month: 'Dec', precip: 5.31 };
207
+ *
208
+ * utils.file.writeJSON('./data/weather2.json', weatherEntry1, { prefix: '[' });
209
+ * utils.file.writeJSON('./data/weather2.json', weatherEntry2, { append: true, prefix: ', ' });
210
+ * utils.file.writeJSON('./data/weather2.json', weatherEntry3, { append: true, prefix: ', ', suffix: ']' });
211
+ *
212
+ * utils.file.readJSON('./data/weather.json');
213
+ *
214
+ * //-- single line shown here on multiple lines for clarity
215
+ * // [{"id":1,"city":"Seattle","month":"Aug","precip":0.87}
216
+ * // ,{"id":0,"city":"Seattle","month":"Apr","precip":2.68}
217
+ * // ,{"id":2,"city":"Seattle","month":"Dec","precip":5.31}]
218
+ *
219
+ * @param {string} filePath - path of the file to write
220
+ * @param {string} contents - contents of the file
221
+ * @param {Object} fsOptions - [nodejs fs writeFileSync, appendFileSync options](https://nodejs.org/api/fs.html)
222
+ * @param {Boolean} fsOptions.append - if true, will append the text to the file
223
+ * @param {Boolean} fsOptions.prefix - string to add before writing the json, like an opening bracket '[' or comma ','
224
+ * @param {Boolean} fsOptions.prefix - string to add before writing the json, like a closing bracket ']'
225
+ * @param {String} fsOptions.encoding - encoding to use when writing the file.
226
+ * @see {@link module:file.readJSON|readJSON(filePath, fsOptions)} - for reading
202
227
  */
203
228
  module.exports.writeJSON = function writeJSON(filePath, contents, fsOptions = {}) {
204
229
  //-- if it isn't desired, simply pass as a string.
205
230
  const jsonContents = JSON.stringify(contents, null, 2);
206
231
  const optionsDefaults = { encoding: 'utf-8' };
207
232
  const cleanedOptions = { ...optionsDefaults, ...fsOptions };
233
+ const isAppend = cleanedOptions.append === true;
234
+ const prefix = cleanedOptions.prefix || '';
235
+ const suffix = cleanedOptions.suffix || '';
208
236
 
209
237
  // const resolvedPath = path.resolve(filePath);
210
238
  try {
211
- fs.writeFileSync(filePath, jsonContents, cleanedOptions);
239
+ if (isAppend) {
240
+ fs.appendFileSync(filePath, prefix + jsonContents + suffix, cleanedOptions);
241
+ } else {
242
+ fs.writeFileSync(filePath, prefix + jsonContents + suffix, cleanedOptions);
243
+ }
212
244
  } catch (err) {
213
245
  logger.error(`unable to write to file: ${filePath}`);
214
246
  }
@@ -219,23 +251,35 @@ module.exports.writeJSON = function writeJSON(filePath, contents, fsOptions = {}
219
251
  *
220
252
  * Note that this uses `utf-8` as the encoding by default
221
253
  *
222
- * @param {string} filePath - path of the file to write
223
- * @param {string} contents - contents of the file
224
- * @see {@link module:file.readFile|readFile(filePath, fsOptions)} - for reading
225
- * @example
254
+ * ```
226
255
  * const myString = `hello`;
227
256
  * utils.file.writeFile('./tmp', myString);
228
257
  * const newString = utils.file.readFile('./tmp');
229
258
  * newString; // 'hello';
259
+ * ```
260
+ *
261
+ * Note, you can append to the file by passing `{append:true}` in the options.
262
+ *
263
+ * @param {string} filePath - path of the file to write
264
+ * @param {string} contents - contents of the file
265
+ * @param {Object} fsOptions - [nodejs fs writeFileSync, appendFileSync options](https://nodejs.org/api/fs.html)
266
+ * @param {Boolean} fsOptions.append - if true, will append the text to the file
267
+ * @param {String} fsOptions.encoding - encoding to use when writing the file.
268
+ * @see {@link module:file.readFile|readFile(filePath, fsOptions)} - for reading
230
269
  */
231
270
  module.exports.writeFile = function writeFile(filePath, contents, fsOptions = {}) {
232
271
  const resolvedPath = path.resolve(filePath);
233
272
  const optionsDefaults = { encoding: 'utf-8' };
234
273
  const cleanedOptions = { ...optionsDefaults, ...fsOptions };
274
+ const isAppend = cleanedOptions.append === true;
235
275
 
236
276
  try {
237
277
  // fsStd.writeFileSync(resolvedPath, contents, { encoding: 'utf-8' });
238
- fs.writeFileSync(resolvedPath, contents, cleanedOptions);
278
+ if (isAppend) {
279
+ fs.appendFileSync(resolvedPath, contents, cleanedOptions);
280
+ } else {
281
+ fs.writeFileSync(resolvedPath, contents, cleanedOptions);
282
+ }
239
283
  } catch (err) {
240
284
  logger.error(`unable to write to file: ${filePath}`);
241
285
  logger.error('err.message', err.message);