bare-script 2.2.18 → 2.3.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.
Files changed (3) hide show
  1. package/lib/library.js +1392 -1030
  2. package/lib/parser.js +1 -1
  3. package/package.json +3 -3
package/lib/library.js CHANGED
@@ -17,1049 +17,1411 @@ import {typeModel} from 'schema-markdown/lib/typeModel.js';
17
17
  export const defaultMaxStatements = 1e9;
18
18
 
19
19
 
20
- // The built-in script functions
21
- export const scriptFunctions = {
22
- //
23
- // Array functions
24
- //
25
-
26
- // $function: arrayCopy
27
- // $group: Array
28
- // $doc: Create a copy of an array
29
- // $arg array: The array to copy
30
- // $return: The array copy
31
- 'arrayCopy': ([array]) => (Array.isArray(array) ? [...array] : []),
32
-
33
- // $function: arrayExtend
34
- // $group: Array
35
- // $doc: Extend one array with another
36
- // $arg array: The array to extend
37
- // $arg array2: The array to extend with
38
- // $return: The extended array
39
- 'arrayExtend': ([array, array2]) => {
40
- if (Array.isArray(array) && Array.isArray(array2)) {
41
- array.push(...array2);
42
- }
43
- return array;
44
- },
45
-
46
- // $function: arrayGet
47
- // $group: Array
48
- // $doc: Get an array element
49
- // $arg array: The array
50
- // $arg index: The array element's index
51
- // $return: The array element
52
- 'arrayGet': ([array, index]) => (Array.isArray(array) ? array[index] ?? null : null),
53
-
54
- // $function: arrayIndexOf
55
- // $group: Array
56
- // $doc: Find the index of a value in an array
57
- // $arg array: The array
58
- // $arg value: The value to find in the array
59
- // $arg index: Optional (default is 0). The index at which to start the search.
60
- // $return: The first index of the value in the array; -1 if not found.
61
- 'arrayIndexOf': ([array, value, index = 0]) => (Array.isArray(array) ? array.indexOf(value, index) : -1),
62
-
63
- // $function: arrayJoin
64
- // $group: Array
65
- // $doc: Join an array with a separator string
66
- // $arg array: The array
67
- // $arg separator: The separator string
68
- // $return: The joined string
69
- 'arrayJoin': ([array, separator]) => (Array.isArray(array) ? array.join(separator) : ''),
70
-
71
- // $function: arrayLastIndexOf
72
- // $group: Array
73
- // $doc: Find the last index of a value in an array
74
- // $arg array: The array
75
- // $arg value: The value to find in the array
76
- // $arg index: Optional (default is the end of the array). The index at which to start the search.
77
- // $return: The last index of the value in the array; -1 if not found.
78
- 'arrayLastIndexOf': ([array, value, index = null]) => (
79
- Array.isArray(array) ? (index === null ? array.lastIndexOf(value) : array.lastIndexOf(value, index)) : -1
80
- ),
81
-
82
- // $function: arrayLength
83
- // $group: Array
84
- // $doc: Get the length of an array
85
- // $arg array: The array
86
- // $return: The array's length; null if not an array
87
- 'arrayLength': ([array]) => (Array.isArray(array) ? array.length : null),
88
-
89
- // $function: arrayNew
90
- // $group: Array
91
- // $doc: Create a new array
92
- // $arg values...: The new array's values
93
- // $return: The new array
94
- 'arrayNew': (values) => values,
95
-
96
- // $function: arrayNewSize
97
- // $group: Array
98
- // $doc: Create a new array of a specific size
99
- // $arg size: Optional (default is 0). The new array's size.
100
- // $arg value: Optional (default is 0). The value with which to fill the new array.
101
- // $return: The new array
102
- 'arrayNewSize': ([size = 0, value = 0]) => new Array(size).fill(value),
103
-
104
- // $function: arrayPop
105
- // $group: Array
106
- // $doc: Remove the last element of the array and return it
107
- // $arg array: The array
108
- // $return: The last element of the array; null if the array is empty.
109
- 'arrayPop': ([array]) => (Array.isArray(array) ? array.pop() ?? null : null),
110
-
111
- // $function: arrayPush
112
- // $group: Array
113
- // $doc: Add one or more values to the end of the array
114
- // $arg array: The array
115
- // $arg values...: The values to add to the end of the array
116
- // $return: The array
117
- 'arrayPush': ([array, ...values]) => {
118
- if (Array.isArray(array)) {
119
- array.push(...values);
120
- }
121
- return array;
122
- },
123
-
124
- // $function: arraySet
125
- // $group: Array
126
- // $doc: Set an array element value
127
- // $arg array: The array
128
- // $arg index: The index of the element to set
129
- // $arg value: The value to set
130
- // $return: The value
131
- 'arraySet': ([array, index, value]) => {
132
- if (Array.isArray(array)) {
133
- array[index] = value;
134
- }
135
- return value;
136
- },
137
-
138
- // $function: arrayShift
139
- // $group: Array
140
- // $doc: Remove the first element of the array and return it
141
- // $arg array: The array
142
- // $return: The first element of the array; null if the array is empty.
143
- 'arrayShift': ([array]) => (Array.isArray(array) ? array.shift() ?? null : null),
144
-
145
- // $function: arraySlice
146
- // $group: Array
147
- // $doc: Copy a portion of an array
148
- // $arg array: The array
149
- // $arg start: Optional (default is 0). The start index of the slice.
150
- // $arg end: Optional (default is the end of the array). The end index of the slice.
151
- // $return: The new array slice
152
- 'arraySlice': ([array, start, end]) => (Array.isArray(array) ? array.slice(start, end) : null),
153
-
154
- // $function: arraySort
155
- // $group: Array
156
- // $doc: Sort an array
157
- // $arg array: The array
158
- // $arg compareFn: Optional (default is null). The comparison function.
159
- // $return: The sorted array
160
- 'arraySort': ([array, compareFn = null], options) => (
161
- Array.isArray(array) ? (compareFn === null ? array.sort() : array.sort((...args) => compareFn(args, options))) : null
162
- ),
163
-
164
-
165
- //
166
- // Data functions
167
- //
168
-
169
- // $function: dataAggregate
170
- // $group: Data
171
- // $doc: Aggregate a data array
172
- // $arg data: The data array
173
- // $arg aggregation: The [aggregation model](https://craigahobbs.github.io/bare-script/library/model.html#var.vName='Aggregation')
174
- // $return: The aggregated data array
175
- 'dataAggregate': ([data, aggregation]) => aggregateData(data, validateAggregation(aggregation)),
176
-
177
- // $function: dataCalculatedField
178
- // $group: Data
179
- // $doc: Add a calculated field to a data array
180
- // $arg data: The data array
181
- // $arg fieldName: The calculated field name
182
- // $arg expr: The calculated field expression
183
- // $arg variables: Optional (default is null). A variables object the expression evaluation.
184
- // $return: The updated data array
185
- 'dataCalculatedField': ([data, fieldName, expr, variables = null], options) => (
186
- addCalculatedField(data, fieldName, expr, variables, options)
187
- ),
188
-
189
- // $function: dataFilter
190
- // $group: Data
191
- // $doc: Filter a data array
192
- // $arg data: The data array
193
- // $arg expr: The filter expression
194
- // $arg variables: Optional (default is null). A variables object the expression evaluation.
195
- // $return: The filtered data array
196
- 'dataFilter': ([data, expr, variables = null], options) => filterData(data, expr, variables, options),
197
-
198
- // $function: dataJoin
199
- // $group: Data
200
- // $doc: Join two data arrays
201
- // $arg leftData: The left data array
202
- // $arg rightData: The right data array
203
- // $arg joinExpr: The [join expression](https://craigahobbs.github.io/bare-script/language/#expressions)
204
- // $arg rightExpr: Optional (default is null).
205
- // $arg rightExpr: The right [join expression](https://craigahobbs.github.io/bare-script/language/#expressions)
206
- // $arg isLeftJoin: Optional (default is false). If true, perform a left join (always include left row).
207
- // $arg variables: Optional (default is null). A variables object for join expression evaluation.
208
- // $return: The joined data array
209
- 'dataJoin': ([leftData, rightData, joinExpr, rightExpr = null, isLeftJoin = false, variables = null], options) => (
210
- joinData(leftData, rightData, joinExpr, rightExpr, isLeftJoin, variables, options)
211
- ),
212
-
213
- // $function: dataParseCSV
214
- // $group: Data
215
- // $doc: Parse CSV text to a data array
216
- // $arg text...: The CSV text
217
- // $return: The data array
218
- 'dataParseCSV': (text) => {
219
- const data = parseCSV(text);
220
- validateData(data, true);
221
- return data;
222
- },
223
-
224
- // $function: dataSort
225
- // $group: Data
226
- // $doc: Sort a data array
227
- // $arg data: The data array
228
- // $arg sorts: The sort field-name/descending-sort tuples
229
- // $return: The sorted data array
230
- 'dataSort': ([data, sorts]) => sortData(data, sorts),
231
-
232
- // $function: dataTop
233
- // $group: Data
234
- // $doc: Keep the top rows for each category
235
- // $arg data: The data array
236
- // $arg count: The number of rows to keep
237
- // $arg categoryFields: Optional (default is null). The category fields.
238
- // $return: The top data array
239
- 'dataTop': ([data, count, categoryFields = null]) => topData(data, count, categoryFields),
240
-
241
- // $function: dataValidate
242
- // $group: Data
243
- // $doc: Validate a data array
244
- // $arg data: The data array
245
- // $return: The validated data array
246
- 'dataValidate': ([data]) => {
247
- validateData(data);
248
- return data;
249
- },
250
-
251
-
252
- //
253
- // Datetime functions
254
- //
255
-
256
- // $function: datetimeDay
257
- // $group: Datetime
258
- // $doc: Get the day of the month of a datetime
259
- // $arg datetime: The datetime
260
- // $arg utc: Optional (default is false). If true, return the UTC day of the month.
261
- // $return: The day of the month
262
- 'datetimeDay': ([datetime, utc = false]) => (datetime instanceof Date ? (utc ? datetime.getUTCDate() : datetime.getDate()) : null),
263
-
264
- // $function: datetimeHour
265
- // $group: Datetime
266
- // $doc: Get the hour of a datetime
267
- // $arg datetime: The datetime
268
- // $arg utc: Optional (default is false). If true, return the UTC hour.
269
- // $return: The hour
270
- 'datetimeHour': ([datetime, utc = false]) => (datetime instanceof Date ? (utc ? datetime.getUTCHours() : datetime.getHours()) : null),
271
-
272
- // $function: datetimeISOFormat
273
- // $group: Datetime
274
- // $doc: Format the datetime as an ISO date/time string
275
- // $arg datetime: The datetime
276
- // $arg isDate: If true, format the datetime as an ISO date
277
- // $return: The formatted datetime string
278
- 'datetimeISOFormat': ([datetime, isDate = false]) => {
279
- let result = null;
280
- if (datetime instanceof Date) {
281
- if (isDate) {
282
- const year = datetime.getFullYear();
283
- const month = datetime.getMonth() + 1;
284
- const day = datetime.getDate();
285
- result = `${year}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
286
- } else {
287
- result = datetime.toISOString();
288
- }
289
- }
290
- return result;
291
- },
292
-
293
- // $function: datetimeISOParse
294
- // $group: Datetime
295
- // $doc: Parse an ISO date/time string
296
- // $arg str: The ISO date/time string
297
- // $return: The datetime, or null if parsing fails
298
- 'datetimeISOParse': ([str]) => parseDatetime(str),
299
-
300
- // $function: datetimeMinute
301
- // $group: Datetime
302
- // $doc: Get the number of minutes of a datetime
303
- // $arg datetime: The datetime
304
- // $arg utc: Optional (default is false). If true, return the UTC minutes.
305
- // $return: The number of minutes
306
- 'datetimeMinute': ([datetime, utc = false]) => (
307
- datetime instanceof Date ? (utc ? datetime.getUTCMinutes() : datetime.getMinutes()) : null
308
- ),
309
-
310
- // $function: datetimeMonth
311
- // $group: Datetime
312
- // $doc: Get the number of the month (1-12) of a datetime
313
- // $arg datetime: The datetime
314
- // $arg utc: Optional (default is false). If true, return the UTC month.
315
- // $return: The number of the month
316
- 'datetimeMonth': ([datetime, utc = false]) => (
317
- datetime instanceof Date ? (utc ? datetime.getUTCMonth() + 1 : datetime.getMonth() + 1) : null
318
- ),
319
-
320
- // $function: datetimeNew
321
- // $group: Datetime
322
- // $doc: Create a new datetime
323
- // $arg year: The full year
324
- // $arg month: The month (1-12)
325
- // $arg day: The day of the month
326
- // $arg hours: Optional (default is 0). The hour (0-23)
327
- // $arg minutes: Optional (default is 0). The number of minutes.
328
- // $arg seconds: Optional (default is 0). The number of seconds.
329
- // $arg milliseconds: Optional (default is 0). The number of milliseconds.
330
- // $return: The new datetime
331
- 'datetimeNew': ([year, month, day, hours = 0, minutes = 0, seconds = 0, milliseconds = 0]) => (
332
- new Date(year, month - 1, day, hours, minutes, seconds, milliseconds)
333
- ),
334
-
335
- // $function: datetimeNewUTC
336
- // $group: Datetime
337
- // $doc: Create a new UTC datetime
338
- // $arg year: The full year
339
- // $arg month: The month (1-12)
340
- // $arg day: The day of the month
341
- // $arg hours: Optional (default is 0). The hour (0-23)
342
- // $arg minutes: Optional (default is 0). The number of minutes.
343
- // $arg seconds: Optional (default is 0). The number of seconds.
344
- // $arg milliseconds: Optional (default is 0). The number of milliseconds.
345
- // $return: The new UTC datetime
346
- 'datetimeNewUTC': ([year, month, day, hours = 0, minutes = 0, seconds = 0, milliseconds = 0]) => (
347
- new Date(Date.UTC(year, month - 1, day, hours, minutes, seconds, milliseconds))
348
- ),
349
-
350
- // $function: datetimeNow
351
- // $group: Datetime
352
- // $doc: Get the current datetime
353
- // $return: The current datetime
354
- 'datetimeNow': () => new Date(),
355
-
356
- // $function: datetimeSecond
357
- // $group: Datetime
358
- // $doc: Get the number of seconds of a datetime
359
- // $arg datetime: The datetime
360
- // $arg utc: Optional (default is false). If true, return the UTC seconds.
361
- // $return: The number of seconds
362
- 'datetimeSecond': ([datetime, utc = false]) => (
363
- datetime instanceof Date ? (utc ? datetime.getUTCSeconds() : datetime.getSeconds()) : null
364
- ),
365
-
366
- // $function: datetimeToday
367
- // $group: Datetime
368
- // $doc: Get today's datetime
369
- // $return: Today's datetime
370
- 'datetimeToday': () => {
371
- const now = new Date();
372
- return new Date(now.getFullYear(), now.getMonth(), now.getDate());
373
- },
374
-
375
- // $function: datetimeYear
376
- // $group: Datetime
377
- // $doc: Get the full year of a datetime
378
- // $arg datetime: The datetime
379
- // $arg utc: Optional (default is false). If true, return the UTC year.
380
- // $return: The full year
381
- 'datetimeYear': ([datetime, utc = false]) => (
382
- datetime instanceof Date ? (utc ? datetime.getUTCFullYear() : datetime.getFullYear()) : null
383
- ),
384
-
385
-
386
- //
387
- // JSON functions
388
- //
389
-
390
- // $function: jsonParse
391
- // $group: JSON
392
- // $doc: Convert a JSON string to an object
393
- // $arg string: The JSON string
394
- // $return: The object
395
- 'jsonParse': ([string]) => JSON.parse(string),
396
-
397
- // $function: jsonStringify
398
- // $group: JSON
399
- // $doc: Convert an object to a JSON string
400
- // $arg value: The object
401
- // $arg space: Optional (default is null). The indentation string or number.
402
- // $return: The JSON string
403
- 'jsonStringify': ([value, space]) => jsonStringifySortKeys(value, space),
404
-
405
-
406
- //
407
- // Math functions
408
- //
409
-
410
- // $function: mathAbs
411
- // $group: Math
412
- // $doc: Compute the absolute value of a number
413
- // $arg x: The number
414
- // $return: The absolute value of the number
415
- 'mathAbs': ([x]) => Math.abs(x),
416
-
417
- // $function: mathAcos
418
- // $group: Math
419
- // $doc: Compute the arccosine, in radians, of a number
420
- // $arg x: The number
421
- // $return: The arccosine, in radians, of the number
422
- 'mathAcos': ([x]) => Math.acos(x),
423
-
424
- // $function: mathAsin
425
- // $group: Math
426
- // $doc: Compute the arcsine, in radians, of a number
427
- // $arg x: The number
428
- // $return: The arcsine, in radians, of the number
429
- 'mathAsin': ([x]) => Math.asin(x),
430
-
431
- // $function: mathAtan
432
- // $group: Math
433
- // $doc: Compute the arctangent, in radians, of a number
434
- // $arg x: The number
435
- // $return: The arctangent, in radians, of the number
436
- 'mathAtan': ([x]) => Math.atan(x),
437
-
438
- // $function: mathAtan2
439
- // $group: Math
440
- // $doc: Compute the angle, in radians, between (0, 0) and a point
441
- // $arg y: The Y-coordinate of the point
442
- // $arg x: The X-coordinate of the point
443
- // $return: The angle, in radians
444
- 'mathAtan2': ([y, x]) => Math.atan2(y, x),
445
-
446
- // $function: mathCeil
447
- // $group: Math
448
- // $doc: Compute the ceiling of a number (round up to the next highest integer)
449
- // $arg x: The number
450
- // $return: The ceiling of the number
451
- 'mathCeil': ([x]) => Math.ceil(x),
452
-
453
- // $function: mathCos
454
- // $group: Math
455
- // $doc: Compute the cosine of an angle, in radians
456
- // $arg x: The angle, in radians
457
- // $return: The cosine of the angle
458
- 'mathCos': ([x]) => Math.cos(x),
459
-
460
- // $function: mathFloor
461
- // $group: Math
462
- // $doc: Compute the floor of a number (round down to the next lowest integer)
463
- // $arg x: The number
464
- // $return: The floor of the number
465
- 'mathFloor': ([x]) => Math.floor(x),
466
-
467
- // $function: mathLn
468
- // $group: Math
469
- // $doc: Compute the natural logarithm (base e) of a number
470
- // $arg x: The number
471
- // $return: The natural logarithm of the number
472
- 'mathLn': ([x]) => Math.log(x),
473
-
474
- // $function: mathLog
475
- // $group: Math
476
- // $doc: Compute the logarithm (base 10) of a number
477
- // $arg x: The number
478
- // $arg base: Optional (default is 10). The logarithm base.
479
- // $return: The logarithm of the number
480
- 'mathLog': ([x, base = 10]) => Math.log(x) / Math.log(base),
481
-
482
- // $function: mathMax
483
- // $group: Math
484
- // $doc: Compute the maximum value
485
- // $arg values...: The values
486
- // $return: The maximum value
487
- 'mathMax': (values) => Math.max(...values),
488
-
489
- // $function: mathMin
490
- // $group: Math
491
- // $doc: Compute the minimum value
492
- // $arg values...: The values
493
- // $return: The minimum value
494
- 'mathMin': (values) => Math.min(...values),
495
-
496
- // $function: mathPi
497
- // $group: Math
498
- // $doc: Return the number pi
499
- // $return: The number pi
500
- 'mathPi': () => Math.PI,
501
-
502
- // $function: mathRandom
503
- // $group: Math
504
- // $doc: Compute a random number between 0 and 1, inclusive
505
- // $return: A random number
506
- 'mathRandom': () => Math.random(),
507
-
508
- // $function: mathRound
509
- // $group: Math
510
- // $doc: Round a number to a certain number of decimal places
511
- // $arg x: The number
512
- // $arg digits: Optional (default is 0). The number of decimal digits to round to.
513
- // $return: The rounded number
514
- 'mathRound': ([x, digits = 0]) => {
515
- const multiplier = 10 ** digits;
516
- return Math.round(x * multiplier) / multiplier;
517
- },
518
-
519
- // $function: mathSign
520
- // $group: Math
521
- // $doc: Compute the sign of a number
522
- // $arg x: The number
523
- // $return: -1 for a negative number, 1 for a positive number, and 0 for zero
524
- 'mathSign': ([x]) => Math.sign(x),
525
-
526
- // $function: mathSin
527
- // $group: Math
528
- // $doc: Compute the sine of an angle, in radians
529
- // $arg x: The angle, in radians
530
- // $return: The sine of the angle
531
- 'mathSin': ([x]) => Math.sin(x),
532
-
533
- // $function: mathSqrt
534
- // $group: Math
535
- // $doc: Compute the square root of a number
536
- // $arg x: The number
537
- // $return: The square root of the number
538
- 'mathSqrt': ([x]) => Math.sqrt(x),
539
-
540
- // $function: mathTan
541
- // $group: Math
542
- // $doc: Compute the tangent of an angle, in radians
543
- // $arg x: The angle, in radians
544
- // $return: The tangent of the angle
545
- 'mathTan': ([x]) => Math.tan(x),
546
-
547
-
548
- //
549
- // Number functions
550
- //
551
-
552
- // $function: numberParseFloat
553
- // $group: Number
554
- // $doc: Parse a string as a floating point number
555
- // $arg string: The string
556
- // $return: The number
557
- 'numberParseFloat': ([string]) => Number.parseFloat(string),
558
-
559
- // $function: numberParseInt
560
- // $group: Number
561
- // $doc: Parse a string as an integer
562
- // $arg string: The string
563
- // $arg radix: Optional (default is 10). The number base.
564
- // $return: The integer
565
- 'numberParseInt': ([string, radix = 10]) => Number.parseInt(string, radix),
566
-
567
- // $function: numberToFixed
568
- // $group: Number
569
- // $doc: Format a number using fixed-point notation
570
- // $arg x: The number
571
- // $arg digits: Optional (default is 2). The number of digits to appear after the decimal point.
572
- // $arg trim: Optional (default is false). If true, trim trailing zeroes and decimal point.
573
- // $return: The fixed-point notation string
574
- 'numberToFixed': ([x, digits = 2, trim = false]) => {
575
- let result = null;
576
- if (typeof x === 'number') {
577
- result = x.toFixed(digits);
578
- if (trim) {
579
- result = result.replace(rNumberCleanup, '');
580
- }
581
- }
582
- return result;
583
- },
584
-
585
-
586
- //
587
- // Object functions
588
- //
589
-
590
- // $function: objectAssign
591
- // $group: Object
592
- // $doc: Assign the keys/values of one object to another
593
- // $arg object: The object to assign to
594
- // $arg object2: The object to assign
595
- // $return: The updated object
596
- 'objectAssign': ([object, object2]) => {
597
- if (object !== null && typeof object === 'object' && !Array.isArray(object) &&
598
- object2 !== null && typeof object2 === 'object' && !Array.isArray(object2)) {
599
- Object.assign(object, object2);
600
- }
601
- return object;
602
- },
603
-
604
- // $function: objectCopy
605
- // $group: Object
606
- // $doc: Create a copy of an object
607
- // $arg object: The object to copy
608
- // $return: The object copy
609
- 'objectCopy': ([object]) => (object !== null && typeof object === 'object' && !Array.isArray(object) ? {...object} : {}),
610
-
611
- // $function: objectDelete
612
- // $group: Object
613
- // $doc: Delete an object key
614
- // $arg object: The object
615
- // $arg key: The key to delete
616
- 'objectDelete': ([object, key]) => {
617
- if (object !== null && typeof object === 'object' && !Array.isArray(object)) {
618
- delete object[key];
619
- }
620
- },
621
-
622
- // $function: objectGet
623
- // $group: Object
624
- // $doc: Get an object key's value
625
- // $arg object: The object
626
- // $arg key: The key
627
- // $arg defaultValue: The default value (optional)
628
- // $return: The value or null if the key does not exist
629
- 'objectGet': ([object, key, defaultValue = null]) => (
630
- object !== null && typeof object === 'object' ? (Object.hasOwn(object, key) ? object[key] : defaultValue) : defaultValue
631
- ),
632
-
633
- // $function: objectHas
634
- // $group: Object
635
- // $doc: Test if an object contains a key
636
- // $arg object: The object
637
- // $arg key: The key
638
- // $return: true if the object contains the key, false otherwise
639
- 'objectHas': ([object, key]) => (object !== null && typeof object === 'object' && Object.hasOwn(object, key)),
640
-
641
- // $function: objectKeys
642
- // $group: Object
643
- // $doc: Get an object's keys
644
- // $arg object: The object
645
- // $return: The array of keys; null if not an object
646
- 'objectKeys': ([object]) => (object !== null && typeof object === 'object' && !Array.isArray(object) ? Object.keys(object) : null),
647
-
648
- // $function: objectNew
649
- // $group: Object
650
- // $doc: Create a new object
651
- // $arg keyValues...: The object's initial key and value pairs
652
- // $return: The new object
653
- 'objectNew': (keyValues) => {
654
- const object = {};
655
- for (let ix = 0; ix < keyValues.length; ix += 2) {
656
- object[keyValues[ix]] = (ix + 1 < keyValues.length ? keyValues[ix + 1] : null);
20
+ //
21
+ // Array functions
22
+ //
23
+
24
+
25
+ // $function: arrayCopy
26
+ // $group: Array
27
+ // $doc: Create a copy of an array
28
+ // $arg array: The array to copy
29
+ // $return: The array copy
30
+ function arrayCopy([array]) {
31
+ return Array.isArray(array) ? [...array] : [];
32
+ }
33
+
34
+
35
+ // $function: arrayExtend
36
+ // $group: Array
37
+ // $doc: Extend one array with another
38
+ // $arg array: The array to extend
39
+ // $arg array2: The array to extend with
40
+ // $return: The extended array
41
+ function arrayExtend([array, array2]) {
42
+ if (Array.isArray(array) && Array.isArray(array2)) {
43
+ array.push(...array2);
44
+ }
45
+ return array;
46
+ }
47
+
48
+
49
+ // $function: arrayGet
50
+ // $group: Array
51
+ // $doc: Get an array element
52
+ // $arg array: The array
53
+ // $arg index: The array element's index
54
+ // $return: The array element
55
+ function arrayGet([array, index]) {
56
+ return Array.isArray(array) ? array[index] ?? null : null;
57
+ }
58
+
59
+
60
+ // $function: arrayIndexOf
61
+ // $group: Array
62
+ // $doc: Find the index of a value in an array
63
+ // $arg array: The array
64
+ // $arg value: The value to find in the array
65
+ // $arg index: Optional (default is 0). The index at which to start the search.
66
+ // $return: The first index of the value in the array; -1 if not found.
67
+ function arrayIndexOf([array, value, index = 0]) {
68
+ return Array.isArray(array) ? array.indexOf(value, index) : -1;
69
+ }
70
+
71
+
72
+ // $function: arrayJoin
73
+ // $group: Array
74
+ // $doc: Join an array with a separator string
75
+ // $arg array: The array
76
+ // $arg separator: The separator string
77
+ // $return: The joined string
78
+ function arrayJoin([array, separator]) {
79
+ return Array.isArray(array) ? array.join(separator) : '';
80
+ }
81
+
82
+
83
+ // $function: arrayLastIndexOf
84
+ // $group: Array
85
+ // $doc: Find the last index of a value in an array
86
+ // $arg array: The array
87
+ // $arg value: The value to find in the array
88
+ // $arg index: Optional (default is the end of the array). The index at which to start the search.
89
+ // $return: The last index of the value in the array; -1 if not found.
90
+ function arrayLastIndexOf([array, value, index = null]) {
91
+ return Array.isArray(array) ? (index === null ? array.lastIndexOf(value) : array.lastIndexOf(value, index)) : -1;
92
+ }
93
+
94
+
95
+ // $function: arrayLength
96
+ // $group: Array
97
+ // $doc: Get the length of an array
98
+ // $arg array: The array
99
+ // $return: The array's length; null if not an array
100
+ function arrayLength([array]) {
101
+ return Array.isArray(array) ? array.length : null;
102
+ }
103
+
104
+
105
+ // $function: arrayNew
106
+ // $group: Array
107
+ // $doc: Create a new array
108
+ // $arg values...: The new array's values
109
+ // $return: The new array
110
+ function arrayNew(values) {
111
+ return values;
112
+ }
113
+
114
+
115
+ // $function: arrayNewSize
116
+ // $group: Array
117
+ // $doc: Create a new array of a specific size
118
+ // $arg size: Optional (default is 0). The new array's size.
119
+ // $arg value: Optional (default is 0). The value with which to fill the new array.
120
+ // $return: The new array
121
+ function arrayNewSize([size = 0, value = 0]) {
122
+ return new Array(size).fill(value);
123
+ }
124
+
125
+
126
+ // $function: arrayPop
127
+ // $group: Array
128
+ // $doc: Remove the last element of the array and return it
129
+ // $arg array: The array
130
+ // $return: The last element of the array; null if the array is empty.
131
+ function arrayPop([array]) {
132
+ return Array.isArray(array) ? array.pop() ?? null : null;
133
+ }
134
+
135
+
136
+ // $function: arrayPush
137
+ // $group: Array
138
+ // $doc: Add one or more values to the end of the array
139
+ // $arg array: The array
140
+ // $arg values...: The values to add to the end of the array
141
+ // $return: The array
142
+ function arrayPush([array, ...values]) {
143
+ if (Array.isArray(array)) {
144
+ array.push(...values);
145
+ }
146
+ return array;
147
+ }
148
+
149
+
150
+ // $function: arraySet
151
+ // $group: Array
152
+ // $doc: Set an array element value
153
+ // $arg array: The array
154
+ // $arg index: The index of the element to set
155
+ // $arg value: The value to set
156
+ // $return: The value
157
+ function arraySet([array, index, value]) {
158
+ if (Array.isArray(array)) {
159
+ array[index] = value;
160
+ }
161
+ return value;
162
+ }
163
+
164
+
165
+ // $function: arrayShift
166
+ // $group: Array
167
+ // $doc: Remove the first element of the array and return it
168
+ // $arg array: The array
169
+ // $return: The first element of the array; null if the array is empty.
170
+ function arrayShift([array]) {
171
+ return Array.isArray(array) ? array.shift() ?? null : null;
172
+ }
173
+
174
+
175
+ // $function: arraySlice
176
+ // $group: Array
177
+ // $doc: Copy a portion of an array
178
+ // $arg array: The array
179
+ // $arg start: Optional (default is 0). The start index of the slice.
180
+ // $arg end: Optional (default is the end of the array). The end index of the slice.
181
+ // $return: The new array slice
182
+ function arraySlice([array, start, end]) {
183
+ return Array.isArray(array) ? array.slice(start, end) : null;
184
+ }
185
+
186
+
187
+ // $function: arraySort
188
+ // $group: Array
189
+ // $doc: Sort an array
190
+ // $arg array: The array
191
+ // $arg compareFn: Optional (default is null). The comparison function.
192
+ // $return: The sorted array
193
+ function arraySort([array, compareFn = null], options) {
194
+ return Array.isArray(array) ? (compareFn === null ? array.sort() : array.sort((...args) => compareFn(args, options))) : null;
195
+ }
196
+
197
+
198
+ //
199
+ // Data functions
200
+ //
201
+
202
+
203
+ // $function: dataAggregate
204
+ // $group: Data
205
+ // $doc: Aggregate a data array
206
+ // $arg data: The data array
207
+ // $arg aggregation: The [aggregation model](https://craigahobbs.github.io/bare-script/library/model.html#var.vName='Aggregation')
208
+ // $return: The aggregated data array
209
+ function dataAggregate([data, aggregation]) {
210
+ return aggregateData(data, validateAggregation(aggregation));
211
+ }
212
+
213
+
214
+ // $function: dataCalculatedField
215
+ // $group: Data
216
+ // $doc: Add a calculated field to a data array
217
+ // $arg data: The data array
218
+ // $arg fieldName: The calculated field name
219
+ // $arg expr: The calculated field expression
220
+ // $arg variables: Optional (default is null). A variables object the expression evaluation.
221
+ // $return: The updated data array
222
+ function dataCalculatedField([data, fieldName, expr, variables = null], options) {
223
+ return addCalculatedField(data, fieldName, expr, variables, options);
224
+ }
225
+
226
+ // $function: dataFilter
227
+ // $group: Data
228
+ // $doc: Filter a data array
229
+ // $arg data: The data array
230
+ // $arg expr: The filter expression
231
+ // $arg variables: Optional (default is null). A variables object the expression evaluation.
232
+ // $return: The filtered data array
233
+ function dataFilter([data, expr, variables = null], options) {
234
+ return filterData(data, expr, variables, options);
235
+ }
236
+
237
+
238
+ // $function: dataJoin
239
+ // $group: Data
240
+ // $doc: Join two data arrays
241
+ // $arg leftData: The left data array
242
+ // $arg rightData: The right data array
243
+ // $arg joinExpr: The [join expression](https://craigahobbs.github.io/bare-script/language/#expressions)
244
+ // $arg rightExpr: Optional (default is null).
245
+ // $arg rightExpr: The right [join expression](https://craigahobbs.github.io/bare-script/language/#expressions)
246
+ // $arg isLeftJoin: Optional (default is false). If true, perform a left join (always include left row).
247
+ // $arg variables: Optional (default is null). A variables object for join expression evaluation.
248
+ // $return: The joined data array
249
+ function dataJoin([leftData, rightData, joinExpr, rightExpr = null, isLeftJoin = false, variables = null], options) {
250
+ return joinData(leftData, rightData, joinExpr, rightExpr, isLeftJoin, variables, options);
251
+ }
252
+
253
+
254
+ // $function: dataParseCSV
255
+ // $group: Data
256
+ // $doc: Parse CSV text to a data array
257
+ // $arg text...: The CSV text
258
+ // $return: The data array
259
+ function dataParseCSV(text) {
260
+ const data = parseCSV(text);
261
+ validateData(data, true);
262
+ return data;
263
+ }
264
+
265
+
266
+ // $function: dataSort
267
+ // $group: Data
268
+ // $doc: Sort a data array
269
+ // $arg data: The data array
270
+ // $arg sorts: The sort field-name/descending-sort tuples
271
+ // $return: The sorted data array
272
+ function dataSort([data, sorts]) {
273
+ return sortData(data, sorts);
274
+ }
275
+
276
+
277
+ // $function: dataTop
278
+ // $group: Data
279
+ // $doc: Keep the top rows for each category
280
+ // $arg data: The data array
281
+ // $arg count: The number of rows to keep
282
+ // $arg categoryFields: Optional (default is null). The category fields.
283
+ // $return: The top data array
284
+ function dataTop([data, count, categoryFields = null]) {
285
+ return topData(data, count, categoryFields);
286
+ }
287
+
288
+
289
+ // $function: dataValidate
290
+ // $group: Data
291
+ // $doc: Validate a data array
292
+ // $arg data: The data array
293
+ // $return: The validated data array
294
+ function dataValidate([data]) {
295
+ validateData(data);
296
+ return data;
297
+ }
298
+
299
+
300
+ //
301
+ // Datetime functions
302
+ //
303
+
304
+
305
+ // $function: datetimeDay
306
+ // $group: Datetime
307
+ // $doc: Get the day of the month of a datetime
308
+ // $arg datetime: The datetime
309
+ // $arg utc: Optional (default is false). If true, return the UTC day of the month.
310
+ // $return: The day of the month
311
+ function datetimeDay([datetime, utc = false]) {
312
+ return datetime instanceof Date ? (utc ? datetime.getUTCDate() : datetime.getDate()) : null;
313
+ }
314
+
315
+
316
+ // $function: datetimeHour
317
+ // $group: Datetime
318
+ // $doc: Get the hour of a datetime
319
+ // $arg datetime: The datetime
320
+ // $arg utc: Optional (default is false). If true, return the UTC hour.
321
+ // $return: The hour
322
+ function datetimeHour([datetime, utc = false]) {
323
+ return datetime instanceof Date ? (utc ? datetime.getUTCHours() : datetime.getHours()) : null;
324
+ }
325
+
326
+
327
+ // $function: datetimeISOFormat
328
+ // $group: Datetime
329
+ // $doc: Format the datetime as an ISO date/time string
330
+ // $arg datetime: The datetime
331
+ // $arg isDate: If true, format the datetime as an ISO date
332
+ // $return: The formatted datetime string
333
+ function datetimeISOFormat([datetime, isDate = false]) {
334
+ let result = null;
335
+ if (datetime instanceof Date) {
336
+ if (isDate) {
337
+ const year = datetime.getFullYear();
338
+ const month = datetime.getMonth() + 1;
339
+ const day = datetime.getDate();
340
+ result = `${year}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
341
+ } else {
342
+ result = datetime.toISOString();
657
343
  }
658
- return object;
659
- },
660
-
661
- // $function: objectSet
662
- // $group: Object
663
- // $doc: Set an object key's value
664
- // $arg object: The object
665
- // $arg key: The key
666
- // $arg value: The value to set
667
- // $return: The value to set
668
- 'objectSet': ([object, key, value]) => {
669
- if (object !== null && typeof object === 'object' && !Array.isArray(object)) {
670
- object[key] = value;
344
+ }
345
+ return result;
346
+ }
347
+
348
+
349
+ // $function: datetimeISOParse
350
+ // $group: Datetime
351
+ // $doc: Parse an ISO date/time string
352
+ // $arg str: The ISO date/time string
353
+ // $return: The datetime, or null if parsing fails
354
+ function datetimeISOParse([str]) {
355
+ return parseDatetime(str);
356
+ }
357
+
358
+
359
+ // $function: datetimeMinute
360
+ // $group: Datetime
361
+ // $doc: Get the number of minutes of a datetime
362
+ // $arg datetime: The datetime
363
+ // $arg utc: Optional (default is false). If true, return the UTC minutes.
364
+ // $return: The number of minutes
365
+ function datetimeMinute([datetime, utc = false]) {
366
+ return datetime instanceof Date ? (utc ? datetime.getUTCMinutes() : datetime.getMinutes()) : null;
367
+ }
368
+
369
+
370
+ // $function: datetimeMonth
371
+ // $group: Datetime
372
+ // $doc: Get the number of the month (1-12) of a datetime
373
+ // $arg datetime: The datetime
374
+ // $arg utc: Optional (default is false). If true, return the UTC month.
375
+ // $return: The number of the month
376
+ function datetimeMonth([datetime, utc = false]) {
377
+ return datetime instanceof Date ? (utc ? datetime.getUTCMonth() + 1 : datetime.getMonth() + 1) : null;
378
+ }
379
+
380
+
381
+ // $function: datetimeNew
382
+ // $group: Datetime
383
+ // $doc: Create a new datetime
384
+ // $arg year: The full year
385
+ // $arg month: The month (1-12)
386
+ // $arg day: The day of the month
387
+ // $arg hours: Optional (default is 0). The hour (0-23)
388
+ // $arg minutes: Optional (default is 0). The number of minutes.
389
+ // $arg seconds: Optional (default is 0). The number of seconds.
390
+ // $arg milliseconds: Optional (default is 0). The number of milliseconds.
391
+ // $return: The new datetime
392
+ function datetimeNew([year, month, day, hours = 0, minutes = 0, seconds = 0, milliseconds = 0]) {
393
+ return new Date(year, month - 1, day, hours, minutes, seconds, milliseconds);
394
+ }
395
+
396
+
397
+ // $function: datetimeNewUTC
398
+ // $group: Datetime
399
+ // $doc: Create a new UTC datetime
400
+ // $arg year: The full year
401
+ // $arg month: The month (1-12)
402
+ // $arg day: The day of the month
403
+ // $arg hours: Optional (default is 0). The hour (0-23)
404
+ // $arg minutes: Optional (default is 0). The number of minutes.
405
+ // $arg seconds: Optional (default is 0). The number of seconds.
406
+ // $arg milliseconds: Optional (default is 0). The number of milliseconds.
407
+ // $return: The new UTC datetime
408
+ function datetimeNewUTC([year, month, day, hours = 0, minutes = 0, seconds = 0, milliseconds = 0]) {
409
+ return new Date(Date.UTC(year, month - 1, day, hours, minutes, seconds, milliseconds));
410
+ }
411
+
412
+
413
+ // $function: datetimeNow
414
+ // $group: Datetime
415
+ // $doc: Get the current datetime
416
+ // $return: The current datetime
417
+ function datetimeNow() {
418
+ return new Date();
419
+ }
420
+
421
+
422
+ // $function: datetimeSecond
423
+ // $group: Datetime
424
+ // $doc: Get the number of seconds of a datetime
425
+ // $arg datetime: The datetime
426
+ // $arg utc: Optional (default is false). If true, return the UTC seconds.
427
+ // $return: The number of seconds
428
+ function datetimeSecond([datetime, utc = false]) {
429
+ return datetime instanceof Date ? (utc ? datetime.getUTCSeconds() : datetime.getSeconds()) : null;
430
+ }
431
+
432
+
433
+ // $function: datetimeToday
434
+ // $group: Datetime
435
+ // $doc: Get today's datetime
436
+ // $return: Today's datetime
437
+ function datetimeToday() {
438
+ const now = new Date();
439
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
440
+ }
441
+
442
+
443
+ // $function: datetimeYear
444
+ // $group: Datetime
445
+ // $doc: Get the full year of a datetime
446
+ // $arg datetime: The datetime
447
+ // $arg utc: Optional (default is false). If true, return the UTC year.
448
+ // $return: The full year
449
+ function datetimeYear([datetime, utc = false]) {
450
+ return datetime instanceof Date ? (utc ? datetime.getUTCFullYear() : datetime.getFullYear()) : null;
451
+ }
452
+
453
+
454
+ //
455
+ // JSON functions
456
+ //
457
+
458
+ // $function: jsonParse
459
+ // $group: JSON
460
+ // $doc: Convert a JSON string to an object
461
+ // $arg string: The JSON string
462
+ // $return: The object
463
+ function jsonParse([string]) {
464
+ return JSON.parse(string);
465
+ }
466
+
467
+
468
+ // $function: jsonStringify
469
+ // $group: JSON
470
+ // $doc: Convert an object to a JSON string
471
+ // $arg value: The object
472
+ // $arg space: Optional (default is null). The indentation string or number.
473
+ // $return: The JSON string
474
+ function jsonStringify([value, space]) {
475
+ return jsonStringifySortKeys(value, space);
476
+ }
477
+
478
+
479
+ //
480
+ // Math functions
481
+ //
482
+
483
+ // $function: mathAbs
484
+ // $group: Math
485
+ // $doc: Compute the absolute value of a number
486
+ // $arg x: The number
487
+ // $return: The absolute value of the number
488
+ function mathAbs([x]) {
489
+ return Math.abs(x);
490
+ }
491
+
492
+
493
+ // $function: mathAcos
494
+ // $group: Math
495
+ // $doc: Compute the arccosine, in radians, of a number
496
+ // $arg x: The number
497
+ // $return: The arccosine, in radians, of the number
498
+ function mathAcos([x]) {
499
+ return Math.acos(x);
500
+ }
501
+
502
+
503
+ // $function: mathAsin
504
+ // $group: Math
505
+ // $doc: Compute the arcsine, in radians, of a number
506
+ // $arg x: The number
507
+ // $return: The arcsine, in radians, of the number
508
+ function mathAsin([x]) {
509
+ return Math.asin(x);
510
+ }
511
+
512
+
513
+ // $function: mathAtan
514
+ // $group: Math
515
+ // $doc: Compute the arctangent, in radians, of a number
516
+ // $arg x: The number
517
+ // $return: The arctangent, in radians, of the number
518
+ function mathAtan([x]) {
519
+ return Math.atan(x);
520
+ }
521
+
522
+
523
+ // $function: mathAtan2
524
+ // $group: Math
525
+ // $doc: Compute the angle, in radians, between (0, 0) and a point
526
+ // $arg y: The Y-coordinate of the point
527
+ // $arg x: The X-coordinate of the point
528
+ // $return: The angle, in radians
529
+ function mathAtan2([y, x]) {
530
+ return Math.atan2(y, x);
531
+ }
532
+
533
+
534
+ // $function: mathCeil
535
+ // $group: Math
536
+ // $doc: Compute the ceiling of a number (round up to the next highest integer)
537
+ // $arg x: The number
538
+ // $return: The ceiling of the number
539
+ function mathCeil([x]) {
540
+ return Math.ceil(x);
541
+ }
542
+
543
+
544
+ // $function: mathCos
545
+ // $group: Math
546
+ // $doc: Compute the cosine of an angle, in radians
547
+ // $arg x: The angle, in radians
548
+ // $return: The cosine of the angle
549
+ function mathCos([x]) {
550
+ return Math.cos(x);
551
+ }
552
+
553
+
554
+ // $function: mathFloor
555
+ // $group: Math
556
+ // $doc: Compute the floor of a number (round down to the next lowest integer)
557
+ // $arg x: The number
558
+ // $return: The floor of the number
559
+ function mathFloor([x]) {
560
+ return Math.floor(x);
561
+ }
562
+
563
+
564
+ // $function: mathLn
565
+ // $group: Math
566
+ // $doc: Compute the natural logarithm (base e) of a number
567
+ // $arg x: The number
568
+ // $return: The natural logarithm of the number
569
+ function mathLn([x]) {
570
+ return Math.log(x);
571
+ }
572
+
573
+
574
+ // $function: mathLog
575
+ // $group: Math
576
+ // $doc: Compute the logarithm (base 10) of a number
577
+ // $arg x: The number
578
+ // $arg base: Optional (default is 10). The logarithm base.
579
+ // $return: The logarithm of the number
580
+ function mathLog([x, base = 10]) {
581
+ return Math.log(x) / Math.log(base);
582
+ }
583
+
584
+
585
+ // $function: mathMax
586
+ // $group: Math
587
+ // $doc: Compute the maximum value
588
+ // $arg values...: The values
589
+ // $return: The maximum value
590
+ function mathMax(values) {
591
+ return Math.max(...values);
592
+ }
593
+
594
+
595
+ // $function: mathMin
596
+ // $group: Math
597
+ // $doc: Compute the minimum value
598
+ // $arg values...: The values
599
+ // $return: The minimum value
600
+ function mathMin(values) {
601
+ return Math.min(...values);
602
+ }
603
+
604
+
605
+ // $function: mathPi
606
+ // $group: Math
607
+ // $doc: Return the number pi
608
+ // $return: The number pi
609
+ function mathPi() {
610
+ return Math.PI;
611
+ }
612
+
613
+
614
+ // $function: mathRandom
615
+ // $group: Math
616
+ // $doc: Compute a random number between 0 and 1, inclusive
617
+ // $return: A random number
618
+ function mathRandom() {
619
+ return Math.random();
620
+ }
621
+
622
+
623
+ // $function: mathRound
624
+ // $group: Math
625
+ // $doc: Round a number to a certain number of decimal places
626
+ // $arg x: The number
627
+ // $arg digits: Optional (default is 0). The number of decimal digits to round to.
628
+ // $return: The rounded number
629
+ function mathRound([x, digits = 0]) {
630
+ const multiplier = 10 ** digits;
631
+ return Math.round(x * multiplier) / multiplier;
632
+ }
633
+
634
+
635
+ // $function: mathSign
636
+ // $group: Math
637
+ // $doc: Compute the sign of a number
638
+ // $arg x: The number
639
+ // $return: -1 for a negative number, 1 for a positive number, and 0 for zero
640
+ function mathSign([x]) {
641
+ return Math.sign(x);
642
+ }
643
+
644
+
645
+ // $function: mathSin
646
+ // $group: Math
647
+ // $doc: Compute the sine of an angle, in radians
648
+ // $arg x: The angle, in radians
649
+ // $return: The sine of the angle
650
+ function mathSin([x]) {
651
+ return Math.sin(x);
652
+ }
653
+
654
+
655
+ // $function: mathSqrt
656
+ // $group: Math
657
+ // $doc: Compute the square root of a number
658
+ // $arg x: The number
659
+ // $return: The square root of the number
660
+ function mathSqrt([x]) {
661
+ return Math.sqrt(x);
662
+ }
663
+
664
+
665
+ // $function: mathTan
666
+ // $group: Math
667
+ // $doc: Compute the tangent of an angle, in radians
668
+ // $arg x: The angle, in radians
669
+ // $return: The tangent of the angle
670
+ function mathTan([x]) {
671
+ return Math.tan(x);
672
+ }
673
+
674
+
675
+ //
676
+ // Number functions
677
+ //
678
+
679
+ // $function: numberParseFloat
680
+ // $group: Number
681
+ // $doc: Parse a string as a floating point number
682
+ // $arg string: The string
683
+ // $return: The number
684
+ function numberParseFloat([string]) {
685
+ return Number.parseFloat(string);
686
+ }
687
+
688
+
689
+ // $function: numberParseInt
690
+ // $group: Number
691
+ // $doc: Parse a string as an integer
692
+ // $arg string: The string
693
+ // $arg radix: Optional (default is 10). The number base.
694
+ // $return: The integer
695
+ function numberParseInt([string, radix = 10]) {
696
+ return Number.parseInt(string, radix);
697
+ }
698
+
699
+
700
+ // $function: numberToFixed
701
+ // $group: Number
702
+ // $doc: Format a number using fixed-point notation
703
+ // $arg x: The number
704
+ // $arg digits: Optional (default is 2). The number of digits to appear after the decimal point.
705
+ // $arg trim: Optional (default is false). If true, trim trailing zeroes and decimal point.
706
+ // $return: The fixed-point notation string
707
+ function numberToFixed([x, digits = 2, trim = false]) {
708
+ let result = null;
709
+ if (typeof x === 'number') {
710
+ result = x.toFixed(digits);
711
+ if (trim) {
712
+ result = result.replace(rNumberCleanup, '');
671
713
  }
672
- return value;
673
- },
674
-
675
-
676
- //
677
- // Regular expression functions
678
- //
679
-
680
- // $function: regexEscape
681
- // $group: Regex
682
- // $doc: Escape a string for use in a regular expression
683
- // $arg string: The string to escape
684
- // $return: The escaped string
685
- 'regexEscape': ([string]) => (typeof string === 'string' ? string.replace(rRegexEscape, '\\$&') : null),
686
-
687
- // $function: regexMatch
688
- // $group: Regex
689
- // $doc: Find the first match of a regular expression in a string
690
- // $arg regex: The regular expression
691
- // $arg string: The string
692
- // $return: The [match object
693
- // $return: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#return_value)
694
- // $return: or null if no matches are found
695
- 'regexMatch': ([regex, string]) => (typeof string === 'string' ? string.match(regex) : null),
696
-
697
- // $function: regexMatchAll
698
- // $group: Regex
699
- // $doc: Find all matches of regular expression in a string
700
- // $arg regex: The regular expression
701
- // $arg string: The string
702
- // $return: The [match object
703
- // $return: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#return_value)
704
- // $return: array or null if no matches are found
705
- 'regexMatchAll': ([regex, string]) => (typeof string === 'string' ? Array.from(string.matchAll(regex)) : null),
706
-
707
- // $function: regexNew
708
- // $group: Regex
709
- // $doc: Create a regular expression
710
- // $arg pattern: The regular expression pattern string
711
- // $arg flags: The [regular expression flags
712
- // $arg flags: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)
713
- // $return: The regular expression or null if the pattern is invalid
714
- 'regexNew': ([pattern, flags]) => new RegExp(pattern, flags),
715
-
716
- // $function: regexTest
717
- // $group: Regex
718
- // $doc: Test if a regular expression matches a string
719
- // $arg regex: The regular expression
720
- // $arg string: The string
721
- // $return: true if the regular expression matches, false otherwise
722
- 'regexTest': ([regex, string]) => (regex instanceof RegExp ? regex.test(string) : null),
723
-
724
-
725
- //
726
- // Schema functions
727
- //
728
-
729
- // $function: schemaParse
730
- // $group: Schema
731
- // $doc: Parse the [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/) text
732
- // $arg lines...: The [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/)
733
- // $arg lines...: text lines (may contain nested arrays of un-split lines)
734
- // $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
735
- 'schemaParse': (lines) => parseSchemaMarkdown(lines),
736
-
737
- // $function: schemaParseEx
738
- // $group: Schema
739
- // $doc: Parse the [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/) text with options
740
- // $arg lines: The array of [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/)
741
- // $arg lines: text lines (may contain nested arrays of un-split lines)
742
- // $arg types: Optional. The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types').
743
- // $arg filename: Optional (default is ""). The file name.
744
- // $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
745
- 'schemaParseEx': ([lines, types = {}, filename = '']) => parseSchemaMarkdown(lines, {types, filename}),
746
-
747
- // $function: schemaTypeModel
748
- // $group: Schema
749
- // $doc: Get the [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
750
- // $return: The [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
751
- 'schemaTypeModel': () => typeModel,
752
-
753
- // $function: schemaValidate
754
- // $group: Schema
755
- // $doc: Validate an object to a schema type
756
- // $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
757
- // $arg typeName: The type name
758
- // $arg value: The object to validate
759
- // $return: The validated object or null if validation fails
760
- 'schemaValidate': ([types, typeName, value]) => validateType(types, typeName, value),
761
-
762
- // $function: schemaValidateTypeModel
763
- // $group: Schema
764
- // $doc: Validate a [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
765
- // $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types') to validate
766
- // $return: The validated [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
767
- 'schemaValidateTypeModel': ([types]) => validateTypeModel(types),
768
-
769
-
770
- //
771
- // String functions
772
- //
773
-
774
- // $function: stringCharCodeAt
775
- // $group: String
776
- // $doc: Get a string index's character code
777
- // $arg string: The string
778
- // $arg index: The character index
779
- // $return: The character code
780
- 'stringCharCodeAt': ([string, index]) => (typeof string === 'string' ? string.charCodeAt(index) : null),
781
-
782
- // $function: stringEndsWith
783
- // $group: String
784
- // $doc: Determine if a string ends with a search string
785
- // $arg string: The string
786
- // $arg searchString: The search string
787
- // $return: true if the string ends with the search string, false otherwise
788
- 'stringEndsWith': ([string, searchString]) => (typeof string === 'string' ? string.endsWith(searchString) : null),
789
-
790
- // $function: stringFromCharCode
791
- // $group: String
792
- // $doc: Create a string of characters from character codes
793
- // $arg charCodes...: The character codes
794
- // $return: The string of characters
795
- 'stringFromCharCode': (charCodes) => String.fromCharCode(...charCodes),
796
-
797
- // $function: stringIndexOf
798
- // $group: String
799
- // $doc: Find the first index of a search string in a string
800
- // $arg string: The string
801
- // $arg searchString: The search string
802
- // $arg index: Optional (default is 0). The index at which to start the search.
803
- // $return: The first index of the search string; -1 if not found.
804
- 'stringIndexOf': ([string, searchString, index]) => (typeof string === 'string' ? string.indexOf(searchString, index) : -1),
805
-
806
- // $function: stringLastIndexOf
807
- // $group: String
808
- // $doc: Find the last index of a search string in a string
809
- // $arg string: The string
810
- // $arg searchString: The search string
811
- // $arg index: Optional (default is the end of the string). The index at which to start the search.
812
- // $return: The last index of the search string; -1 if not found.
813
- 'stringLastIndexOf': ([string, searchString, index]) => (typeof string === 'string' ? string.lastIndexOf(searchString, index) : -1),
814
-
815
- // $function: stringLength
816
- // $group: String
817
- // $doc: Get the length of a string
818
- // $arg string: The string
819
- // $return: The string's length; null if not a string
820
- 'stringLength': ([string]) => (typeof string === 'string' ? string.length : null),
821
-
822
- // $function: stringLower
823
- // $group: String
824
- // $doc: Convert a string to lower-case
825
- // $arg string: The string
826
- // $return: The lower-case string
827
- 'stringLower': ([string]) => (typeof string === 'string' ? string.toLowerCase() : null),
828
-
829
- // $function: stringNew
830
- // $group: String
831
- // $doc: Create a new string from a value
832
- // $arg value: The value
833
- // $return: The new string
834
- 'stringNew': ([value]) => `${value}`,
835
-
836
- // $function: stringRepeat
837
- // $group: String
838
- // $doc: Repeat a string
839
- // $arg string: The string to repeat
840
- // $arg count: The number of times to repeat the string
841
- // $return: The repeated string
842
- 'stringRepeat': ([string, count]) => (typeof string === 'string' ? string.repeat(count) : null),
843
-
844
- // $function: stringReplace
845
- // $group: String
846
- // $doc: Replace all instances of a string with another string
847
- // $arg string: The string to update
848
- // $arg substr: The string to replace
849
- // $arg newSubstr: The replacement string
850
- // $return: The updated string
851
- 'stringReplace': ([string, substr, newSubstr], options) => {
852
- if (typeof string !== 'string') {
714
+ }
715
+ return result;
716
+ }
717
+
718
+ const rNumberCleanup = /\.0*$/;
719
+
720
+
721
+ //
722
+ // Object functions
723
+ //
724
+
725
+ // $function: objectAssign
726
+ // $group: Object
727
+ // $doc: Assign the keys/values of one object to another
728
+ // $arg object: The object to assign to
729
+ // $arg object2: The object to assign
730
+ // $return: The updated object
731
+ function objectAssign([object, object2]) {
732
+ if (object !== null && typeof object === 'object' && !Array.isArray(object) &&
733
+ object2 !== null && typeof object2 === 'object' && !Array.isArray(object2)) {
734
+ Object.assign(object, object2);
735
+ }
736
+ return object;
737
+ }
738
+
739
+
740
+ // $function: objectCopy
741
+ // $group: Object
742
+ // $doc: Create a copy of an object
743
+ // $arg object: The object to copy
744
+ // $return: The object copy
745
+ function objectCopy([object]) {
746
+ return (object !== null && typeof object === 'object' && !Array.isArray(object) ? {...object} : {});
747
+ }
748
+
749
+
750
+ // $function: objectDelete
751
+ // $group: Object
752
+ // $doc: Delete an object key
753
+ // $arg object: The object
754
+ // $arg key: The key to delete
755
+ function objectDelete([object, key]) {
756
+ if (object !== null && typeof object === 'object' && !Array.isArray(object)) {
757
+ delete object[key];
758
+ }
759
+ }
760
+
761
+
762
+ // $function: objectGet
763
+ // $group: Object
764
+ // $doc: Get an object key's value
765
+ // $arg object: The object
766
+ // $arg key: The key
767
+ // $arg defaultValue: The default value (optional)
768
+ // $return: The value or null if the key does not exist
769
+ function objectGet([object, key, defaultValue = null]) {
770
+ return object !== null && typeof object === 'object' ? (Object.hasOwn(object, key) ? object[key] : defaultValue) : defaultValue;
771
+ }
772
+
773
+
774
+ // $function: objectHas
775
+ // $group: Object
776
+ // $doc: Test if an object contains a key
777
+ // $arg object: The object
778
+ // $arg key: The key
779
+ // $return: true if the object contains the key, false otherwise
780
+ function objectHas([object, key]) {
781
+ return object !== null && typeof object === 'object' && Object.hasOwn(object, key);
782
+ }
783
+
784
+
785
+ // $function: objectKeys
786
+ // $group: Object
787
+ // $doc: Get an object's keys
788
+ // $arg object: The object
789
+ // $return: The array of keys; null if not an object
790
+ function objectKeys([object]) {
791
+ return object !== null && typeof object === 'object' && !Array.isArray(object) ? Object.keys(object) : null;
792
+ }
793
+
794
+
795
+ // $function: objectNew
796
+ // $group: Object
797
+ // $doc: Create a new object
798
+ // $arg keyValues...: The object's initial key and value pairs
799
+ // $return: The new object
800
+ function objectNew(keyValues) {
801
+ const object = {};
802
+ for (let ix = 0; ix < keyValues.length; ix += 2) {
803
+ object[keyValues[ix]] = (ix + 1 < keyValues.length ? keyValues[ix + 1] : null);
804
+ }
805
+ return object;
806
+ }
807
+
808
+
809
+ // $function: objectSet
810
+ // $group: Object
811
+ // $doc: Set an object key's value
812
+ // $arg object: The object
813
+ // $arg key: The key
814
+ // $arg value: The value to set
815
+ // $return: The value to set
816
+ function objectSet([object, key, value]) {
817
+ if (object !== null && typeof object === 'object' && !Array.isArray(object)) {
818
+ object[key] = value;
819
+ }
820
+ return value;
821
+ }
822
+
823
+
824
+ //
825
+ // Regex functions
826
+ //
827
+
828
+
829
+ // $function: regexEscape
830
+ // $group: Regex
831
+ // $doc: Escape a string for use in a regular expression
832
+ // $arg string: The string to escape
833
+ // $return: The escaped string
834
+ function regexEscape([string]) {
835
+ return typeof string === 'string' ? string.replace(rRegexEscape, '\\$&') : null;
836
+ }
837
+
838
+ const rRegexEscape = /[.*+?^${}()|[\]\\]/g;
839
+
840
+
841
+ // $function: regexMatch
842
+ // $group: Regex
843
+ // $doc: Find the first match of a regular expression in a string
844
+ // $arg regex: The regular expression
845
+ // $arg string: The string
846
+ // $return: The [match object
847
+ // $return: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#return_value)
848
+ // $return: or null if no matches are found
849
+ function regexMatch([regex, string]) {
850
+ return typeof string === 'string' ? string.match(regex) : null;
851
+ }
852
+
853
+
854
+ // $function: regexMatchAll
855
+ // $group: Regex
856
+ // $doc: Find all matches of regular expression in a string
857
+ // $arg regex: The regular expression
858
+ // $arg string: The string
859
+ // $return: The [match object
860
+ // $return: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#return_value)
861
+ // $return: array or null if no matches are found
862
+ function regexMatchAll([regex, string]) {
863
+ return typeof string === 'string' ? Array.from(string.matchAll(regex)) : null;
864
+ }
865
+
866
+
867
+ // $function: regexNew
868
+ // $group: Regex
869
+ // $doc: Create a regular expression
870
+ // $arg pattern: The regular expression pattern string
871
+ // $arg flags: The [regular expression flags
872
+ // $arg flags: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)
873
+ // $return: The regular expression or null if the pattern is invalid
874
+ function regexNew([pattern, flags]) {
875
+ return new RegExp(pattern, flags);
876
+ }
877
+
878
+
879
+ // $function: regexReplace
880
+ // $group: Regex
881
+ // $doc: Replace regular expression matches with a string
882
+ // $arg regex: The replacement regular expression
883
+ // $arg string: The string
884
+ // $arg substr: The replacement string
885
+ // $return: The updated string
886
+ function regexReplace([regex, string, substr]) {
887
+ if (!(regex instanceof RegExp) || typeof string !== 'string' || typeof substr !== 'string') {
888
+ return null;
889
+ }
890
+
891
+ return string.replace(regex, substr);
892
+ }
893
+
894
+
895
+ // $function: regexSplit
896
+ // $group: Regex
897
+ // $doc: Split a string with a regular expression
898
+ // $arg regex: The regular expression
899
+ // $arg string: The string
900
+ // $return: The array of split parts
901
+ function regexSplit([regex, string]) {
902
+ if (!(regex instanceof RegExp) || typeof string !== 'string') {
903
+ return null;
904
+ }
905
+
906
+ return string.split(regex);
907
+ }
908
+
909
+ // $function: regexTest
910
+ // $group: Regex
911
+ // $doc: Test if a regular expression matches a string
912
+ // $arg regex: The regular expression
913
+ // $arg string: The string
914
+ // $return: true if the regular expression matches, false otherwise
915
+ function regexTest([regex, string]) {
916
+ return regex instanceof RegExp ? regex.test(string) : null;
917
+ }
918
+
919
+
920
+ //
921
+ // Schema functions
922
+ //
923
+
924
+ // $function: schemaParse
925
+ // $group: Schema
926
+ // $doc: Parse the [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/) text
927
+ // $arg lines...: The [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/)
928
+ // $arg lines...: text lines (may contain nested arrays of un-split lines)
929
+ // $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
930
+ function schemaParse(lines) {
931
+ return parseSchemaMarkdown(lines);
932
+ }
933
+
934
+
935
+ // $function: schemaParseEx
936
+ // $group: Schema
937
+ // $doc: Parse the [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/) text with options
938
+ // $arg lines: The array of [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/)
939
+ // $arg lines: text lines (may contain nested arrays of un-split lines)
940
+ // $arg types: Optional. The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types').
941
+ // $arg filename: Optional (default is ""). The file name.
942
+ // $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
943
+ function schemaParseEx([lines, types = {}, filename = '']) {
944
+ return parseSchemaMarkdown(lines, {types, filename});
945
+ }
946
+
947
+
948
+ // $function: schemaTypeModel
949
+ // $group: Schema
950
+ // $doc: Get the [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
951
+ // $return: The [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
952
+ function schemaTypeModel() {
953
+ return typeModel;
954
+ }
955
+
956
+
957
+ // $function: schemaValidate
958
+ // $group: Schema
959
+ // $doc: Validate an object to a schema type
960
+ // $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
961
+ // $arg typeName: The type name
962
+ // $arg value: The object to validate
963
+ // $return: The validated object or null if validation fails
964
+ function schemaValidate([types, typeName, value]) {
965
+ return validateType(types, typeName, value);
966
+ }
967
+
968
+
969
+ // $function: schemaValidateTypeModel
970
+ // $group: Schema
971
+ // $doc: Validate a [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
972
+ // $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types') to validate
973
+ // $return: The validated [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
974
+ function schemaValidateTypeModel([types]) {
975
+ return validateTypeModel(types);
976
+ }
977
+
978
+
979
+ //
980
+ // String functions
981
+ //
982
+
983
+
984
+ // $function: stringCharCodeAt
985
+ // $group: String
986
+ // $doc: Get a string index's character code
987
+ // $arg string: The string
988
+ // $arg index: The character index
989
+ // $return: The character code
990
+ function stringCharCodeAt([string, index]) {
991
+ return typeof string === 'string' ? string.charCodeAt(index) : null;
992
+ }
993
+
994
+
995
+ // $function: stringEndsWith
996
+ // $group: String
997
+ // $doc: Determine if a string ends with a search string
998
+ // $arg string: The string
999
+ // $arg searchString: The search string
1000
+ // $return: true if the string ends with the search string, false otherwise
1001
+ function stringEndsWith([string, searchString]) {
1002
+ return typeof string === 'string' ? string.endsWith(searchString) : null;
1003
+ }
1004
+
1005
+
1006
+ // $function: stringFromCharCode
1007
+ // $group: String
1008
+ // $doc: Create a string of characters from character codes
1009
+ // $arg charCodes...: The character codes
1010
+ // $return: The string of characters
1011
+ function stringFromCharCode(charCodes) {
1012
+ return String.fromCharCode(...charCodes);
1013
+ }
1014
+
1015
+
1016
+ // $function: stringIndexOf
1017
+ // $group: String
1018
+ // $doc: Find the first index of a search string in a string
1019
+ // $arg string: The string
1020
+ // $arg searchString: The search string
1021
+ // $arg index: Optional (default is 0). The index at which to start the search.
1022
+ // $return: The first index of the search string; -1 if not found.
1023
+ function stringIndexOf([string, searchString, index]) {
1024
+ return typeof string === 'string' ? string.indexOf(searchString, index) : -1;
1025
+ }
1026
+
1027
+
1028
+ // $function: stringLastIndexOf
1029
+ // $group: String
1030
+ // $doc: Find the last index of a search string in a string
1031
+ // $arg string: The string
1032
+ // $arg searchString: The search string
1033
+ // $arg index: Optional (default is the end of the string). The index at which to start the search.
1034
+ // $return: The last index of the search string; -1 if not found.
1035
+ function stringLastIndexOf([string, searchString, index]) {
1036
+ return typeof string === 'string' ? string.lastIndexOf(searchString, index) : -1;
1037
+ }
1038
+
1039
+
1040
+ // $function: stringLength
1041
+ // $group: String
1042
+ // $doc: Get the length of a string
1043
+ // $arg string: The string
1044
+ // $return: The string's length; null if not a string
1045
+ function stringLength([string]) {
1046
+ return typeof string === 'string' ? string.length : null;
1047
+ }
1048
+
1049
+
1050
+ // $function: stringLower
1051
+ // $group: String
1052
+ // $doc: Convert a string to lower-case
1053
+ // $arg string: The string
1054
+ // $return: The lower-case string
1055
+ function stringLower([string]) {
1056
+ return typeof string === 'string' ? string.toLowerCase() : null;
1057
+ }
1058
+
1059
+
1060
+ // $function: stringNew
1061
+ // $group: String
1062
+ // $doc: Create a new string from a value
1063
+ // $arg value: The value
1064
+ // $return: The new string
1065
+ function stringNew([value]) {
1066
+ return `${value}`;
1067
+ }
1068
+
1069
+
1070
+ // $function: stringRepeat
1071
+ // $group: String
1072
+ // $doc: Repeat a string
1073
+ // $arg string: The string to repeat
1074
+ // $arg count: The number of times to repeat the string
1075
+ // $return: The repeated string
1076
+ function stringRepeat([string, count]) {
1077
+ return typeof string === 'string' ? string.repeat(count) : null;
1078
+ }
1079
+
1080
+
1081
+ // $function: stringReplace
1082
+ // $group: String
1083
+ // $doc: Replace all instances of a string with another string
1084
+ // $arg string: The string to update
1085
+ // $arg substr: The string to replace
1086
+ // $arg newSubstr: The replacement string
1087
+ // $return: The updated string
1088
+ function stringReplace([string, substr, newSubstr], options) {
1089
+ if (typeof string !== 'string') {
1090
+ return null;
1091
+ }
1092
+ if (typeof newSubstr === 'function') {
1093
+ const replacerFunction = (...args) => newSubstr(args, options);
1094
+ return string.replaceAll(substr, replacerFunction);
1095
+ }
1096
+ return string.replaceAll(substr, newSubstr);
1097
+ }
1098
+
1099
+
1100
+ // $function: stringSlice
1101
+ // $group: String
1102
+ // $doc: Copy a portion of a string
1103
+ // $arg string: The string
1104
+ // $arg start: Optional (default is 0). The start index of the slice.
1105
+ // $arg end: Optional (default is the end of the string). The end index of the slice.
1106
+ // $return: The new string slice
1107
+ function stringSlice([string, beginIndex, endIndex]) {
1108
+ return typeof string === 'string' ? string.slice(beginIndex, endIndex) : null;
1109
+ }
1110
+
1111
+
1112
+ // $function: stringSplit
1113
+ // $group: String
1114
+ // $doc: Split a string
1115
+ // $arg string: The string to split
1116
+ // $arg separator: The separator string or regular expression
1117
+ // $arg limit: The maximum number of strings to split into
1118
+ // $return: The array of split-out strings
1119
+ function stringSplit([string, separator, limit]) {
1120
+ return typeof string === 'string' ? string.split(separator, limit) : null;
1121
+ }
1122
+
1123
+
1124
+ // $function: stringStartsWith
1125
+ // $group: String
1126
+ // $doc: Determine if a string starts with a search string
1127
+ // $arg string: The string
1128
+ // $arg searchString: The search string
1129
+ // $return: true if the string starts with the search string, false otherwise
1130
+ function stringStartsWith([string, searchString]) {
1131
+ return typeof string === 'string' ? string.startsWith(searchString) : null;
1132
+ }
1133
+
1134
+
1135
+ // $function: stringTrim
1136
+ // $group: String
1137
+ // $doc: Trim the whitespace from the beginning and end of a string
1138
+ // $arg string: The string
1139
+ // $return: The trimmed string
1140
+ function stringTrim([string]) {
1141
+ return typeof string === 'string' ? string.trim() : null;
1142
+ }
1143
+
1144
+
1145
+ // $function: stringUpper
1146
+ // $group: String
1147
+ // $doc: Convert a string to upper-case
1148
+ // $arg string: The string
1149
+ // $return: The upper-case string
1150
+ function stringUpper([string]) {
1151
+ return typeof string === 'string' ? string.toUpperCase() : null;
1152
+ }
1153
+
1154
+
1155
+ //
1156
+ // System functions
1157
+ //
1158
+
1159
+
1160
+ // $function: systemFetch
1161
+ // $group: System
1162
+ // $doc: Retrieve a remote JSON or text resource
1163
+ // $arg url: The resource URL or array of URLs
1164
+ // $arg options: Optional (default is null). The [fetch options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters).
1165
+ // $arg isText: Optional (default is false). If true, retrieve the resource as text.
1166
+ // $return: The resource object/string or array of objects/strings; null if an error occurred.
1167
+ async function systemFetch([url, fetchOptions = null, isText = false], options) {
1168
+ const isArray = Array.isArray(url);
1169
+ const urls = (isArray ? url : [url]).map((mURL) => (options !== null && 'urlFn' in options ? options.urlFn(mURL) : mURL));
1170
+ const responses = await Promise.all(urls.map(async (fURL) => {
1171
+ try {
1172
+ return 'fetchFn' in options ? await (fetchOptions ? options.fetchFn(fURL, fetchOptions) : options.fetchFn(fURL)) : null;
1173
+ } catch {
853
1174
  return null;
854
1175
  }
855
- if (typeof newSubstr === 'function') {
856
- const replacerFunction = (...args) => newSubstr(args, options);
857
- return string.replaceAll(substr, replacerFunction);
858
- }
859
- return string.replaceAll(substr, newSubstr);
860
- },
861
-
862
- // $function: stringSlice
863
- // $group: String
864
- // $doc: Copy a portion of a string
865
- // $arg string: The string
866
- // $arg start: Optional (default is 0). The start index of the slice.
867
- // $arg end: Optional (default is the end of the string). The end index of the slice.
868
- // $return: The new string slice
869
- 'stringSlice': ([string, beginIndex, endIndex]) => (typeof string === 'string' ? string.slice(beginIndex, endIndex) : null),
870
-
871
- // $function: stringSplit
872
- // $group: String
873
- // $doc: Split a string
874
- // $arg string: The string to split
875
- // $arg separator: The separator string or regular expression
876
- // $arg limit: The maximum number of strings to split into
877
- // $return: The array of split-out strings
878
- 'stringSplit': ([string, separator, limit]) => (typeof string === 'string' ? string.split(separator, limit) : null),
879
-
880
- // $function: stringStartsWith
881
- // $group: String
882
- // $doc: Determine if a string starts with a search string
883
- // $arg string: The string
884
- // $arg searchString: The search string
885
- // $return: true if the string starts with the search string, false otherwise
886
- 'stringStartsWith': ([string, searchString]) => (typeof string === 'string' ? string.startsWith(searchString) : null),
887
-
888
- // $function: stringTrim
889
- // $group: String
890
- // $doc: Trim the whitespace from the beginning and end of a string
891
- // $arg string: The string
892
- // $return: The trimmed string
893
- 'stringTrim': ([string]) => (typeof string === 'string' ? string.trim() : null),
894
-
895
- // $function: stringUpper
896
- // $group: String
897
- // $doc: Convert a string to upper-case
898
- // $arg string: The string
899
- // $return: The upper-case string
900
- 'stringUpper': ([string]) => (typeof string === 'string' ? string.toUpperCase() : null),
901
-
902
-
903
- //
904
- // System functions
905
- //
906
-
907
- // $function: systemFetch
908
- // $group: System
909
- // $doc: Retrieve a remote JSON or text resource
910
- // $arg url: The resource URL or array of URLs
911
- // $arg options: Optional (default is null). The [fetch options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters).
912
- // $arg isText: Optional (default is false). If true, retrieve the resource as text.
913
- // $return: The resource object/string or array of objects/strings; null if an error occurred.
914
- 'systemFetch': async ([url, fetchOptions = null, isText = false], options) => {
915
- const isArray = Array.isArray(url);
916
- const urls = (isArray ? url : [url]).map((mURL) => (options !== null && 'urlFn' in options ? options.urlFn(mURL) : mURL));
917
- const responses = await Promise.all(urls.map(async (fURL) => {
918
- try {
919
- return 'fetchFn' in options ? await (fetchOptions ? options.fetchFn(fURL, fetchOptions) : options.fetchFn(fURL)) : null;
920
- } catch {
921
- return null;
922
- }
923
- }));
924
- const values = await Promise.all(responses.map(async (response) => {
925
- try {
926
- return response !== null && response.ok ? await (isText ? response.text() : response.json()) : null;
927
- } catch {
928
- return null;
929
- }
930
- }));
931
-
932
- // Log failures
933
- for (const [ixValue, value] of values.entries()) {
934
- if (value === null && options !== null && 'logFn' in options && options.debug) {
935
- const errorURL = urls[ixValue];
936
- options.logFn(`BareScript: Function "systemFetch" failed for ${isText ? 'text' : 'JSON'} resource "${errorURL}"`);
937
- }
1176
+ }));
1177
+ const values = await Promise.all(responses.map(async (response) => {
1178
+ try {
1179
+ return response !== null && response.ok ? await (isText ? response.text() : response.json()) : null;
1180
+ } catch {
1181
+ return null;
938
1182
  }
1183
+ }));
939
1184
 
940
- return isArray ? values : values[0];
941
- },
942
-
943
- // $function: systemGlobalGet
944
- // $group: System
945
- // $doc: Get a global variable value
946
- // $arg name: The global variable name
947
- // $return: The global variable's value or null if it does not exist
948
- 'systemGlobalGet': ([name], options) => {
949
- const globals = (options !== null ? (options.globals ?? null) : null);
950
- return (globals !== null ? (globals[name] ?? null) : null);
951
- },
952
-
953
- // $function: systemGlobalSet
954
- // $group: System
955
- // $doc: Set a global variable value
956
- // $arg name: The global variable name
957
- // $arg value: The global variable's value
958
- // $return: The global variable's value
959
- 'systemGlobalSet': ([name, value], options) => {
960
- if (options !== null) {
961
- const globals = options.globals ?? null;
962
- if (globals !== null) {
963
- globals[name] = value;
964
- }
965
- }
966
- return value;
967
- },
968
-
969
- // $function: systemLog
970
- // $group: System
971
- // $doc: Log a message to the console
972
- // $arg string: The message
973
- 'systemLog': ([string], options) => {
974
- if (options !== null && 'logFn' in options) {
975
- options.logFn(string);
976
- }
977
- },
978
-
979
- // $function: systemLogDebug
980
- // $group: System
981
- // $doc: Log a message to the console, if in debug mode
982
- // $arg string: The message
983
- 'systemLogDebug': ([string], options) => {
984
- if (options !== null && 'logFn' in options && options.debug) {
985
- options.logFn(string);
1185
+ // Log failures
1186
+ for (const [ixValue, value] of values.entries()) {
1187
+ if (value === null && options !== null && 'logFn' in options && options.debug) {
1188
+ const errorURL = urls[ixValue];
1189
+ options.logFn(`BareScript: Function "systemFetch" failed for ${isText ? 'text' : 'JSON'} resource "${errorURL}"`);
986
1190
  }
987
- },
988
-
989
- // $function: systemPartial
990
- // $group: System
991
- // $doc: Return a new function which behaves like "func" called with "args".
992
- // $doc: If additional arguments are passed to the returned function, they are appended to "args".
993
- // $arg func: The function
994
- // $arg args...: The function arguments
995
- // $return: The new function called with "args"
996
- 'systemPartial': ([func, ...args]) => (argsExtra, options) => func([...args, ...argsExtra], options),
997
-
998
- // $function: systemType
999
- // $group: System
1000
- // $doc: Get a value's type string
1001
- // $arg value: The value
1002
- // $return: The type string of the value.
1003
- // $return: Valid values are: 'array', 'boolean', 'datetime', 'function', 'null', 'number', 'object', 'regex', 'string'.
1004
- 'systemType': ([value]) => {
1005
- const type = typeof value;
1006
- if (type === 'object') {
1007
- if (value === null) {
1008
- return 'null';
1009
- } else if (Array.isArray(value)) {
1010
- return 'array';
1011
- } else if (value instanceof Date) {
1012
- return 'datetime';
1013
- } else if (value instanceof RegExp) {
1014
- return 'regex';
1015
- }
1016
- }
1017
- return type;
1018
- },
1019
-
1020
-
1021
- //
1022
- // URL functions
1023
- //
1024
-
1025
- // $function: urlEncode
1026
- // $group: URL
1027
- // $doc: Encode a URL
1028
- // $arg url: The URL string
1029
- // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1030
- // $return: The encoded URL string
1031
- 'urlEncode': ([url, extra = true]) => {
1032
- let urlEncoded = encodeURI(url);
1033
- if (extra) {
1034
- // Replace ')' with '%29' for Markdown links
1035
- urlEncoded = urlEncoded.replaceAll(')', '%29');
1036
- }
1037
- return urlEncoded;
1038
- },
1039
-
1040
- // $function: urlEncodeComponent
1041
- // $group: URL
1042
- // $doc: Encode a URL component
1043
- // $arg url: The URL component string
1044
- // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1045
- // $return: The encoded URL component string
1046
- 'urlEncodeComponent': ([urlComponent, extra = true]) => {
1047
- let urlComponentEncoded = encodeURIComponent(urlComponent);
1048
- if (extra) {
1049
- // Replace ')' with '%29' for Markdown links
1050
- urlComponentEncoded = urlComponentEncoded.replaceAll(')', '%29');
1191
+ }
1192
+
1193
+ return isArray ? values : values[0];
1194
+ }
1195
+
1196
+
1197
+ // $function: systemGlobalGet
1198
+ // $group: System
1199
+ // $doc: Get a global variable value
1200
+ // $arg name: The global variable name
1201
+ // $return: The global variable's value or null if it does not exist
1202
+ function systemGlobalGet([name], options) {
1203
+ const globals = (options !== null ? (options.globals ?? null) : null);
1204
+ return (globals !== null ? (globals[name] ?? null) : null);
1205
+ }
1206
+
1207
+
1208
+ // $function: systemGlobalSet
1209
+ // $group: System
1210
+ // $doc: Set a global variable value
1211
+ // $arg name: The global variable name
1212
+ // $arg value: The global variable's value
1213
+ // $return: The global variable's value
1214
+ function systemGlobalSet([name, value], options) {
1215
+ if (options !== null) {
1216
+ const globals = options.globals ?? null;
1217
+ if (globals !== null) {
1218
+ globals[name] = value;
1051
1219
  }
1052
- return urlComponentEncoded;
1053
1220
  }
1054
- };
1221
+ return value;
1222
+ }
1055
1223
 
1056
1224
 
1057
- // Regex escape regular expression
1058
- const rRegexEscape = /[.*+?^${}()|[\]\\]/g;
1225
+ // $function: systemLog
1226
+ // $group: System
1227
+ // $doc: Log a message to the console
1228
+ // $arg string: The message
1229
+ function systemLog([string], options) {
1230
+ if (options !== null && 'logFn' in options) {
1231
+ options.logFn(string);
1232
+ }
1233
+ }
1059
1234
 
1060
1235
 
1061
- // Fixed-number trim regular expression
1062
- const rNumberCleanup = /\.0*$/;
1236
+ // $function: systemLogDebug
1237
+ // $group: System
1238
+ // $doc: Log a message to the console, if in debug mode
1239
+ // $arg string: The message
1240
+ function systemLogDebug([string], options) {
1241
+ if (options !== null && 'logFn' in options && options.debug) {
1242
+ options.logFn(string);
1243
+ }
1244
+ }
1245
+
1246
+
1247
+ // $function: systemPartial
1248
+ // $group: System
1249
+ // $doc: Return a new function which behaves like "func" called with "args".
1250
+ // $doc: If additional arguments are passed to the returned function, they are appended to "args".
1251
+ // $arg func: The function
1252
+ // $arg args...: The function arguments
1253
+ // $return: The new function called with "args"
1254
+ function systemPartial([func, ...args]) {
1255
+ return (argsExtra, options) => func([...args, ...argsExtra], options);
1256
+ }
1257
+
1258
+
1259
+ // $function: systemType
1260
+ // $group: System
1261
+ // $doc: Get a value's type string
1262
+ // $arg value: The value
1263
+ // $return: The type string of the value.
1264
+ // $return: Valid values are: 'array', 'boolean', 'datetime', 'function', 'null', 'number', 'object', 'regex', 'string'.
1265
+ function systemType([value]) {
1266
+ const type = typeof value;
1267
+ if (type === 'object') {
1268
+ if (value === null) {
1269
+ return 'null';
1270
+ } else if (Array.isArray(value)) {
1271
+ return 'array';
1272
+ } else if (value instanceof Date) {
1273
+ return 'datetime';
1274
+ } else if (value instanceof RegExp) {
1275
+ return 'regex';
1276
+ }
1277
+ }
1278
+ return type;
1279
+ }
1280
+
1281
+
1282
+ //
1283
+ // URL functions
1284
+ //
1285
+
1286
+
1287
+ // $function: urlEncode
1288
+ // $group: URL
1289
+ // $doc: Encode a URL
1290
+ // $arg url: The URL string
1291
+ // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1292
+ // $return: The encoded URL string
1293
+ function urlEncode([url, extra = true]) {
1294
+ let urlEncoded = encodeURI(url);
1295
+ if (extra) {
1296
+ // Replace ')' with '%29' for Markdown links
1297
+ urlEncoded = urlEncoded.replaceAll(')', '%29');
1298
+ }
1299
+ return urlEncoded;
1300
+ }
1301
+
1302
+
1303
+ // $function: urlEncodeComponent
1304
+ // $group: URL
1305
+ // $doc: Encode a URL component
1306
+ // $arg url: The URL component string
1307
+ // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1308
+ // $return: The encoded URL component string
1309
+ function urlEncodeComponent([urlComponent, extra = true]) {
1310
+ let urlComponentEncoded = encodeURIComponent(urlComponent);
1311
+ if (extra) {
1312
+ // Replace ')' with '%29' for Markdown links
1313
+ urlComponentEncoded = urlComponentEncoded.replaceAll(')', '%29');
1314
+ }
1315
+ return urlComponentEncoded;
1316
+ }
1317
+
1318
+
1319
+ // The built-in script functions
1320
+ export const scriptFunctions = {
1321
+ arrayCopy,
1322
+ arrayExtend,
1323
+ arrayGet,
1324
+ arrayIndexOf,
1325
+ arrayJoin,
1326
+ arrayLastIndexOf,
1327
+ arrayLength,
1328
+ arrayNew,
1329
+ arrayNewSize,
1330
+ arrayPop,
1331
+ arrayPush,
1332
+ arraySet,
1333
+ arrayShift,
1334
+ arraySlice,
1335
+ arraySort,
1336
+ dataAggregate,
1337
+ dataCalculatedField,
1338
+ dataFilter,
1339
+ dataJoin,
1340
+ dataParseCSV,
1341
+ dataSort,
1342
+ dataTop,
1343
+ dataValidate,
1344
+ datetimeDay,
1345
+ datetimeHour,
1346
+ datetimeISOFormat,
1347
+ datetimeISOParse,
1348
+ datetimeMinute,
1349
+ datetimeMonth,
1350
+ datetimeNew,
1351
+ datetimeNewUTC,
1352
+ datetimeNow,
1353
+ datetimeSecond,
1354
+ datetimeToday,
1355
+ datetimeYear,
1356
+ jsonParse,
1357
+ jsonStringify,
1358
+ mathAbs,
1359
+ mathAcos,
1360
+ mathAsin,
1361
+ mathAtan,
1362
+ mathAtan2,
1363
+ mathCeil,
1364
+ mathCos,
1365
+ mathFloor,
1366
+ mathLn,
1367
+ mathLog,
1368
+ mathMax,
1369
+ mathMin,
1370
+ mathPi,
1371
+ mathRandom,
1372
+ mathRound,
1373
+ mathSign,
1374
+ mathSin,
1375
+ mathSqrt,
1376
+ mathTan,
1377
+ numberParseFloat,
1378
+ numberParseInt,
1379
+ numberToFixed,
1380
+ objectAssign,
1381
+ objectCopy,
1382
+ objectDelete,
1383
+ objectGet,
1384
+ objectHas,
1385
+ objectKeys,
1386
+ objectNew,
1387
+ objectSet,
1388
+ regexEscape,
1389
+ regexMatch,
1390
+ regexMatchAll,
1391
+ regexNew,
1392
+ regexReplace,
1393
+ regexSplit,
1394
+ regexTest,
1395
+ schemaParse,
1396
+ schemaParseEx,
1397
+ schemaTypeModel,
1398
+ schemaValidate,
1399
+ schemaValidateTypeModel,
1400
+ stringCharCodeAt,
1401
+ stringEndsWith,
1402
+ stringFromCharCode,
1403
+ stringIndexOf,
1404
+ stringLastIndexOf,
1405
+ stringLength,
1406
+ stringLower,
1407
+ stringNew,
1408
+ stringRepeat,
1409
+ stringReplace,
1410
+ stringSlice,
1411
+ stringSplit,
1412
+ stringStartsWith,
1413
+ stringTrim,
1414
+ stringUpper,
1415
+ systemFetch,
1416
+ systemGlobalGet,
1417
+ systemGlobalSet,
1418
+ systemLog,
1419
+ systemLogDebug,
1420
+ systemPartial,
1421
+ systemType,
1422
+ urlEncode,
1423
+ urlEncodeComponent,
1424
+ };
1063
1425
 
1064
1426
 
1065
1427
  // The built-in expression function name script function name map