bare-script 0.9.0__py3-none-any.whl

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.
bare_script/library.py ADDED
@@ -0,0 +1,1912 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/bare-script-py/blob/main/LICENSE
3
+
4
+ """
5
+ The BareScript library
6
+ """
7
+
8
+ import calendar
9
+ import datetime
10
+ import functools
11
+ import json
12
+ import math
13
+ import random
14
+ import re
15
+ import urllib
16
+
17
+ from schema_markdown import TYPE_MODEL, parse_schema_markdown, validate_type, validate_type_model
18
+
19
+ from .value import R_NUMBER_CLEANUP, REGEX_TYPE, round_number, value_boolean, value_compare, value_is, value_json, value_string, value_type
20
+
21
+
22
+ # The default maximum statements for executeScript
23
+ DEFAULT_MAX_STATEMENTS = 1e9
24
+
25
+
26
+ def default_args(args, defaults, last_arg_array=False):
27
+ """
28
+ Helper function to fill-in default arguments
29
+ """
30
+ len_args = len(args)
31
+ yield from ((args[ix] if ix < len_args else default) for ix, default in enumerate(defaults))
32
+ if last_arg_array:
33
+ yield args[len(defaults):]
34
+
35
+
36
+ #
37
+ # Array functions
38
+ #
39
+
40
+
41
+ # $function: arrayCopy
42
+ # $group: Array
43
+ # $doc: Create a copy of an array
44
+ # $arg array: The array to copy
45
+ # $return: The array copy
46
+ def _array_copy(args, unused_options):
47
+ array, = default_args(args, (None,))
48
+ if not isinstance(array, list):
49
+ return None
50
+
51
+ return list(array)
52
+
53
+
54
+ # $function: arrayExtend
55
+ # $group: Array
56
+ # $doc: Extend one array with another
57
+ # $arg array: The array to extend
58
+ # $arg array2: The array to extend with
59
+ # $return: The extended array
60
+ def _array_extend(args, unused_options):
61
+ array, array2 = default_args(args, (None, None))
62
+ if not isinstance(array, list) or not isinstance(array2, list):
63
+ return None
64
+
65
+ array.extend(array2)
66
+ return array
67
+
68
+
69
+ # $function: arrayGet
70
+ # $group: Array
71
+ # $doc: Get an array element
72
+ # $arg array: The array
73
+ # $arg index: The array element's index
74
+ # $return: The array element
75
+ def _array_get(args, unused_options):
76
+ array, index = default_args(args, (None, None))
77
+ if not isinstance(array, list) or not isinstance(index, (int, float)) or int(index) != index or index < 0 or index >= len(array):
78
+ return None
79
+
80
+ return array[int(index)]
81
+
82
+
83
+ # $function: arrayIndexOf
84
+ # $group: Array
85
+ # $doc: Find the index of a value in an array
86
+ # $arg array: The array
87
+ # $arg value: The value to find in the array, or a match function, f(value) -> bool
88
+ # $arg index: Optional (default is 0). The index at which to start the search.
89
+ # $return: The first index of the value in the array; -1 if not found.
90
+ def _array_index_of(args, options):
91
+ array, value, index = default_args(args, (None, None, 0))
92
+ if not isinstance(array, list) or not isinstance(index, (int, float)) or int(index) != index or index < 0 or index >= len(array):
93
+ return -1
94
+
95
+ if callable(value):
96
+ for ix in range(int(index), len(array)):
97
+ if value([array[ix]], options):
98
+ return ix
99
+ else:
100
+ for ix in range(int(index), len(array)):
101
+ if value_compare(array[ix], value) == 0:
102
+ return ix
103
+
104
+ return -1
105
+
106
+
107
+ # $function: arrayJoin
108
+ # $group: Array
109
+ # $doc: Join an array with a separator string
110
+ # $arg array: The array
111
+ # $arg separator: The separator string
112
+ # $return: The joined string
113
+ def _array_join(args, unused_options):
114
+ array, separator = default_args(args, (None, None))
115
+ if not isinstance(array, list) or not isinstance(separator, str):
116
+ return None
117
+
118
+ return separator.join(value_string(value) for value in array)
119
+
120
+
121
+ # $function: arrayLastIndexOf
122
+ # $group: Array
123
+ # $doc: Find the last index of a value in an array
124
+ # $arg array: The array
125
+ # $arg value: The value to find in the array, or a match function, f(value) -> bool
126
+ # $arg index: Optional (default is the end of the array). The index at which to start the search.
127
+ # $return: The last index of the value in the array; -1 if not found.
128
+ def _array_last_index_of(args, options):
129
+ array, value, index = default_args(args, (None, None, None))
130
+ if isinstance(array, list) and index is None:
131
+ index = len(array) - 1
132
+ if not isinstance(array, list) or not isinstance(index, (int, float)) or int(index) != index or index < 0 or index >= len(array):
133
+ return -1
134
+
135
+ if callable(value):
136
+ for ix in range(int(index), -1, -1):
137
+ if value([array[ix]], options):
138
+ return ix
139
+ else:
140
+ for ix in range(int(index), -1, -1):
141
+ if value_compare(array[ix], value) == 0:
142
+ return ix
143
+
144
+ return -1
145
+
146
+
147
+ # $function: arrayLength
148
+ # $group: Array
149
+ # $doc: Get the length of an array
150
+ # $arg array: The array
151
+ # $return: The array's length; zero if not an array
152
+ def _array_length(args, unused_options):
153
+ array, = default_args(args, (None,))
154
+ if not isinstance(array, list):
155
+ return 0
156
+
157
+ return len(array)
158
+
159
+
160
+ # $function: arrayNew
161
+ # $group: Array
162
+ # $doc: Create a new array
163
+ # $arg values...: The new array's values
164
+ # $return: The new array
165
+ def _array_new(args, unused_options):
166
+ return args
167
+
168
+
169
+ # $function: arrayNewSize
170
+ # $group: Array
171
+ # $doc: Create a new array of a specific size
172
+ # $arg size: Optional (default is 0). The new array's size.
173
+ # $arg value: Optional (default is 0). The value with which to fill the new array.
174
+ # $return: The new array
175
+ def _array_new_size(args, unused_options):
176
+ size, value = default_args(args, (0, 0))
177
+ if not isinstance(size, (int, float)) or int(size) != size or size < 0:
178
+ return None
179
+
180
+ return list(value for _ in range(int(size)))
181
+
182
+
183
+ # $function: arrayPop
184
+ # $group: Array
185
+ # $doc: Remove the last element of the array and return it
186
+ # $arg array: The array
187
+ # $return: The last element of the array; null if the array is empty.
188
+ def _array_pop(args, unused_options):
189
+ array, = default_args(args, (None,))
190
+ if not isinstance(array, list) or len(array) == 0:
191
+ return None
192
+
193
+ return array.pop()
194
+
195
+
196
+ # $function: arrayPush
197
+ # $group: Array
198
+ # $doc: Add one or more values to the end of the array
199
+ # $arg array: The array
200
+ # $arg values...: The values to add to the end of the array
201
+ # $return: The array
202
+ def _array_push(args, unused_options):
203
+ array, values = default_args(args, (None,), True)
204
+ if not isinstance(array, list):
205
+ return None
206
+
207
+ array.extend(values)
208
+ return array
209
+
210
+
211
+ # $function: arraySet
212
+ # $group: Array
213
+ # $doc: Set an array element value
214
+ # $arg array: The array
215
+ # $arg index: The index of the element to set
216
+ # $arg value: The value to set
217
+ # $return: The value
218
+ def _array_set(args, unused_options):
219
+ array, index, value = default_args(args, (None, None, None))
220
+ if not isinstance(array, list) or index < 0 or index >= len(array):
221
+ return None
222
+
223
+ array[index] = value
224
+ return value
225
+
226
+
227
+ # $function: arrayShift
228
+ # $group: Array
229
+ # $doc: Remove the first element of the array and return it
230
+ # $arg array: The array
231
+ # $return: The first element of the array; null if the array is empty.
232
+ def _array_shift(args, unused_options):
233
+ array, = default_args(args, (None,))
234
+ if not isinstance(array, list) or len(array) == 0:
235
+ return None
236
+
237
+ result = array[0]
238
+ del array[0]
239
+ return result
240
+
241
+
242
+ # $function: arraySlice
243
+ # $group: Array
244
+ # $doc: Copy a portion of an array
245
+ # $arg array: The array
246
+ # $arg start: Optional (default is 0). The start index of the slice.
247
+ # $arg end: Optional (default is the end of the array). The end index of the slice.
248
+ # $return: The new array slice
249
+ def _array_slice(args, unused_options):
250
+ array, start, end = default_args(args, (None, 0, None))
251
+ if isinstance(array, list) and end is None:
252
+ end = len(array)
253
+ if not isinstance(array, list) or not isinstance(start, (int, float)) or int(start) != start or start < 0 or start >= len(array) or \
254
+ not isinstance(end, (int, float)) or int(end) != end or end < 0 or end > len(array):
255
+ return None
256
+
257
+ return array[int(start):int(end)]
258
+
259
+
260
+ # $function: arraySort
261
+ # $group: Array
262
+ # $doc: Sort an array
263
+ # $arg array: The array
264
+ # $arg compareFn: Optional (default is null). The comparison function.
265
+ # $return: The sorted array
266
+ def _array_sort(args, options):
267
+ array, compare_fn = default_args(args, (None, None))
268
+ if not isinstance(array, list) or (compare_fn is not None and not callable(compare_fn)):
269
+ return None
270
+
271
+ if compare_fn is None:
272
+ array.sort(key=functools.cmp_to_key(value_compare))
273
+ else:
274
+ array.sort(key=functools.cmp_to_key(lambda v1, v2: compare_fn([v1, v2], options)))
275
+ return array
276
+
277
+
278
+ #
279
+ # Data functions
280
+ #
281
+
282
+
283
+ # $function: dataAggregate
284
+ # $group: Data
285
+ # $doc: Aggregate a data array
286
+ # $arg data: The data array
287
+ # $arg aggregation: The [aggregation model](https://craigahobbs.github.io/bare-script-py/library/model.html#var.vName='Aggregation')
288
+ # $return: The aggregated data array
289
+ def _data_aggregate(unused_args, unused_options):
290
+ return None
291
+
292
+
293
+ # $function: dataCalculatedField
294
+ # $group: Data
295
+ # $doc: Add a calculated field to a data array
296
+ # $arg data: The data array
297
+ # $arg fieldName: The calculated field name
298
+ # $arg expr: The calculated field expression
299
+ # $arg variables: Optional (default is null). A variables object the expression evaluation.
300
+ # $return: The updated data array
301
+ def _data_calculated_field(unused_args, unused_options):
302
+ return None
303
+
304
+
305
+ # $function: dataFilter
306
+ # $group: Data
307
+ # $doc: Filter a data array
308
+ # $arg data: The data array
309
+ # $arg expr: The filter expression
310
+ # $arg variables: Optional (default is null). A variables object the expression evaluation.
311
+ # $return: The filtered data array
312
+ def _data_filter(unused_args, unused_options):
313
+ return None
314
+
315
+
316
+ # $function: dataJoin
317
+ # $group: Data
318
+ # $doc: Join two data arrays
319
+ # $arg leftData: The left data array
320
+ # $arg rightData: The right data array
321
+ # $arg joinExpr: The [join expression](https://craigahobbs.github.io/bare-script/language/#expressions)
322
+ # $arg rightExpr: Optional (default is null).
323
+ # $arg rightExpr: The right [join expression](https://craigahobbs.github.io/bare-script/language/#expressions)
324
+ # $arg isLeftJoin: Optional (default is false). If true, perform a left join (always include left row).
325
+ # $arg variables: Optional (default is null). A variables object for join expression evaluation.
326
+ # $return: The joined data array
327
+ def _data_join(unused_args, unused_options):
328
+ return None
329
+
330
+
331
+ # $function: dataParseCSV
332
+ # $group: Data
333
+ # $doc: Parse CSV text to a data array
334
+ # $arg text...: The CSV text
335
+ # $return: The data array
336
+ def _data_parse_csv(unused_args, unused_options):
337
+ return None
338
+
339
+
340
+ # $function: dataSort
341
+ # $group: Data
342
+ # $doc: Sort a data array
343
+ # $arg data: The data array
344
+ # $arg sorts: The sort field-name/descending-sort tuples
345
+ # $return: The sorted data array
346
+ def _data_sort(unused_args, unused_options):
347
+ return None
348
+
349
+
350
+ # $function: dataTop
351
+ # $group: Data
352
+ # $doc: Keep the top rows for each category
353
+ # $arg data: The data array
354
+ # $arg count: The number of rows to keep
355
+ # $arg categoryFields: Optional (default is null). The category fields.
356
+ # $return: The top data array
357
+ def _data_top(unused_args, unused_options):
358
+ return None
359
+
360
+
361
+ # $function: dataValidate
362
+ # $group: Data
363
+ # $doc: Validate a data array
364
+ # $arg data: The data array
365
+ # $return: The validated data array
366
+ def _data_validate(unused_args, unused_options):
367
+ return None
368
+
369
+
370
+ #
371
+ # Datetime functions
372
+ #
373
+
374
+
375
+ # $function: datetimeDay
376
+ # $group: Datetime
377
+ # $doc: Get the day of the month of a datetime
378
+ # $arg datetime: The datetime
379
+ # $return: The day of the month
380
+ def _datetime_day(args, unused_options):
381
+ datetime_, = default_args(args, (None,))
382
+ if not isinstance(datetime_, datetime.datetime):
383
+ return None
384
+
385
+ return datetime_.day
386
+
387
+
388
+ # $function: datetimeHour
389
+ # $group: Datetime
390
+ # $doc: Get the hour of a datetime
391
+ # $arg datetime: The datetime
392
+ # $return: The hour
393
+ def _datetime_hour(args, unused_options):
394
+ datetime_, = default_args(args, (None,))
395
+ if not isinstance(datetime_, datetime.datetime):
396
+ return None
397
+
398
+ return datetime_.hour
399
+
400
+
401
+ # $function: datetimeISOFormat
402
+ # $group: Datetime
403
+ # $doc: Format the datetime as an ISO date/time string
404
+ # $arg datetime: The datetime
405
+ # $arg isDate: If true, format the datetime as an ISO date
406
+ # $return: The formatted datetime string
407
+ def _datetime_iso_format(args, unused_options):
408
+ datetime_, is_date = default_args(args, (None, False))
409
+ if not isinstance(datetime_, datetime.datetime):
410
+ return None
411
+
412
+ if value_boolean(is_date):
413
+ return datetime.date(datetime_.year, datetime_.month, datetime_.day).isoformat()
414
+ return datetime_.astimezone(datetime.timezone.utc).isoformat()
415
+
416
+
417
+ # $function: datetimeISOParse
418
+ # $group: Datetime
419
+ # $doc: Parse an ISO date/time string
420
+ # $arg str: The ISO date/time string
421
+ # $return: The datetime, or null if parsing fails
422
+ def _datetime_iso_parse(args, unused_options):
423
+ string, = default_args(args, (None,))
424
+ if not isinstance(string, str):
425
+ return None
426
+
427
+ try:
428
+ return datetime.datetime.fromisoformat(_R_ZULU.sub('+00:00', string))
429
+ except ValueError:
430
+ return None
431
+
432
+ _R_ZULU = re.compile(r'Z$')
433
+
434
+
435
+ # $function: datetimeMillisecond
436
+ # $group: Datetime
437
+ # $doc: Get the millisecond of a datetime
438
+ # $arg datetime: The datetime
439
+ # $return: The millisecond
440
+ def _datetime_millisecond(args, unused_options):
441
+ datetime_, = default_args(args, (None,))
442
+ if not isinstance(datetime_, datetime.datetime):
443
+ return None
444
+
445
+ return int(round_number(datetime_.microsecond / 1000, 0))
446
+
447
+
448
+ # $function: datetimeMinute
449
+ # $group: Datetime
450
+ # $doc: Get the minute of a datetime
451
+ # $arg datetime: The datetime
452
+ # $return: The minute
453
+ def _datetime_minute(args, unused_options):
454
+ datetime_, = default_args(args, (None,))
455
+ if not isinstance(datetime_, datetime.datetime):
456
+ return None
457
+
458
+ return datetime_.minute
459
+
460
+
461
+ # $function: datetimeMonth
462
+ # $group: Datetime
463
+ # $doc: Get the month (1-12) of a datetime
464
+ # $arg datetime: The datetime
465
+ # $return: The month
466
+ def _datetime_month(args, unused_options):
467
+ datetime_, = default_args(args, (None,))
468
+ if not isinstance(datetime_, datetime.datetime):
469
+ return None
470
+
471
+ return datetime_.month
472
+
473
+
474
+ # $function: datetimeNew
475
+ # $group: Datetime
476
+ # $doc: Create a new datetime
477
+ # $arg year: The full year
478
+ # $arg month: The month (1-12)
479
+ # $arg day: The day of the month
480
+ # $arg hour: Optional (default is 0). The hour (0-23).
481
+ # $arg minute: Optional (default is 0). The minute.
482
+ # $arg second: Optional (default is 0). The second.
483
+ # $arg millisecond: Optional (default is 0). The millisecond.
484
+ # $return: The new datetime
485
+ def _datetime_new(args, unused_options):
486
+ year, month, day, hour, minute, second, millisecond = default_args(args, (None, None, None, 0, 0, 0, 0))
487
+ return _datetime_new_helper(year, month, day, hour, minute, second, millisecond, None)
488
+
489
+
490
+ # $function: datetimeNewUTC
491
+ # $group: Datetime
492
+ # $doc: Create a new UTC datetime
493
+ # $arg year: The full year
494
+ # $arg month: The month (1-12)
495
+ # $arg day: The day of the month
496
+ # $arg hour: Optional (default is 0). The hour (0-23).
497
+ # $arg minute: Optional (default is 0). The minute.
498
+ # $arg second: Optional (default is 0). The second.
499
+ # $arg millisecond: Optional (default is 0). The millisecond.
500
+ # $return: The new UTC datetime
501
+ def _datetime_new_utc(args, unused_options):
502
+ year, month, day, hour, minute, second, millisecond = default_args(args, (None, None, None, 0, 0, 0, 0))
503
+ return _datetime_new_helper(year, month, day, hour, minute, second, millisecond, tzinfo=datetime.timezone.utc)
504
+
505
+
506
+ # datetimeNew helper function
507
+ def _datetime_new_helper(year, month, day, hour, minute, second, millisecond, tzinfo):
508
+ if not isinstance(year, (int, float)) or int(year) != year or \
509
+ not isinstance(month, (int, float)) or int(month) != month or \
510
+ not isinstance(day, (int, float)) or int(day) != day or day < -10000 or day > 10000 or \
511
+ not isinstance(hour, (int, float)) or int(hour) != hour or \
512
+ not isinstance(minute, (int, float)) or int(minute) != minute or \
513
+ not isinstance(second, (int, float)) or int(second) != second or \
514
+ not isinstance(millisecond, (int, float)) or int(millisecond) != millisecond:
515
+ return None
516
+
517
+ # Adjust millisecond
518
+ if millisecond < 0 or millisecond >= 1000:
519
+ extra_seconds = millisecond // 1000
520
+ millisecond -= extra_seconds * 1000
521
+ second += extra_seconds
522
+
523
+ # Adjust seconds
524
+ if second < 0 or second >= 60:
525
+ extra_minutes = second // 60
526
+ second -= extra_minutes * 60
527
+ minute += extra_minutes
528
+
529
+ # Adjust minutes
530
+ if minute < 0 or minute >= 60:
531
+ extra_hours = minute // 60
532
+ minute -= extra_hours * 60
533
+ hour += extra_hours
534
+
535
+ # Adjust hours
536
+ if hour < 0 or hour >= 24:
537
+ extra_days = hour // 24
538
+ hour -= extra_days * 24
539
+ day += extra_days
540
+
541
+ # Adjust month
542
+ if month < 1 or month > 12:
543
+ extra_years = (month - 1) // 12
544
+ month -= extra_years * 12
545
+ year += extra_years
546
+
547
+ # Adjust day
548
+ if day < 1:
549
+ while day < 1:
550
+ year = year if month != 1 else year - 1
551
+ month = month - 1 if month != 1 else 12
552
+ _, month_days = calendar.monthrange(year, month)
553
+ day += month_days
554
+ elif day > 28:
555
+ _, month_days = calendar.monthrange(year, month)
556
+ while day > month_days:
557
+ day -= month_days
558
+ year = year if month != 12 else year + 1
559
+ month = month + 1 if month != 12 else 1
560
+ _, month_days = calendar.monthrange(year, month)
561
+
562
+ # Return the datetime
563
+ result = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second), int(millisecond) * 1000, tzinfo)
564
+ return result if tzinfo is not None else result.astimezone()
565
+
566
+
567
+ # $function: datetimeNow
568
+ # $group: Datetime
569
+ # $doc: Get the current datetime
570
+ # $return: The current datetime
571
+ def _datetime_now(unused_args, unused_options):
572
+ return datetime.datetime.now()
573
+
574
+
575
+ # $function: datetimeSecond
576
+ # $group: Datetime
577
+ # $doc: Get the second of a datetime
578
+ # $arg datetime: The datetime
579
+ # $return: The second
580
+ def _datetime_second(args, unused_options):
581
+ datetime_, = default_args(args, (None,))
582
+ if not isinstance(datetime_, datetime.datetime):
583
+ return None
584
+
585
+ return datetime_.second
586
+
587
+
588
+ # $function: datetimeToday
589
+ # $group: Datetime
590
+ # $doc: Get today's datetime
591
+ # $return: Today's datetime
592
+ def _datetime_today(unused_args, unused_options):
593
+ today = datetime.date.today()
594
+ return datetime.datetime(today.year, today.month, today.day)
595
+
596
+
597
+ # $function: datetimeYear
598
+ # $group: Datetime
599
+ # $doc: Get the full year of a datetime
600
+ # $arg datetime: The datetime
601
+ # $return: The full year
602
+ def _datetime_year(args, unused_options):
603
+ datetime_, = default_args(args, (None,))
604
+ if not isinstance(datetime_, datetime.datetime):
605
+ return None
606
+
607
+ return datetime_.year
608
+
609
+
610
+ #
611
+ # JSON functions
612
+ #
613
+
614
+
615
+ # $function: jsonParse
616
+ # $group: JSON
617
+ # $doc: Convert a JSON string to an object
618
+ # $arg string: The JSON string
619
+ # $return: The object
620
+ def _json_parse(args, unused_options):
621
+ string, = default_args(args, (None,))
622
+ if not isinstance(string, str):
623
+ return None
624
+
625
+ return json.loads(string)
626
+
627
+
628
+ # $function: jsonStringify
629
+ # $group: JSON
630
+ # $doc: Convert an object to a JSON string
631
+ # $arg value: The object
632
+ # $arg indent: Optional (default is null). The indentation number.
633
+ # $return: The JSON string
634
+ def _json_stringify(args, unused_options):
635
+ value, indent = default_args(args, (None, None))
636
+ if indent is not None and (not isinstance(indent, (int, float)) or int(indent) != indent or indent < 1):
637
+ return None
638
+
639
+ return value_json(value, int(indent) if indent is not None else None)
640
+
641
+
642
+ #
643
+ # Math functions
644
+ #
645
+
646
+
647
+ # $function: mathAbs
648
+ # $group: Math
649
+ # $doc: Compute the absolute value of a number
650
+ # $arg x: The number
651
+ # $return: The absolute value of the number
652
+ def _math_abs(args, unused_options):
653
+ x, = default_args(args, (None,))
654
+ if not isinstance(x, (int, float)):
655
+ return None
656
+
657
+ return abs(x)
658
+
659
+
660
+ # $function: mathAcos
661
+ # $group: Math
662
+ # $doc: Compute the arccosine, in radians, of a number
663
+ # $arg x: The number
664
+ # $return: The arccosine, in radians, of the number
665
+ def _math_acos(args, unused_options):
666
+ x, = default_args(args, (None,))
667
+ if not isinstance(x, (int, float)):
668
+ return None
669
+
670
+ return math.acos(x)
671
+
672
+
673
+ # $function: mathAsin
674
+ # $group: Math
675
+ # $doc: Compute the arcsine, in radians, of a number
676
+ # $arg x: The number
677
+ # $return: The arcsine, in radians, of the number
678
+ def _math_asin(args, unused_options):
679
+ x, = default_args(args, (None,))
680
+ if not isinstance(x, (int, float)):
681
+ return None
682
+
683
+ return math.asin(x)
684
+
685
+
686
+ # $function: mathAtan
687
+ # $group: Math
688
+ # $doc: Compute the arctangent, in radians, of a number
689
+ # $arg x: The number
690
+ # $return: The arctangent, in radians, of the number
691
+ def _math_atan(args, unused_options):
692
+ x, = default_args(args, (None,))
693
+ if not isinstance(x, (int, float)):
694
+ return None
695
+
696
+ return math.atan(x)
697
+
698
+
699
+ # $function: mathAtan2
700
+ # $group: Math
701
+ # $doc: Compute the angle, in radians, between (0, 0) and a point
702
+ # $arg y: The Y-coordinate of the point
703
+ # $arg x: The X-coordinate of the point
704
+ # $return: The angle, in radians
705
+ def _math_atan2(args, unused_options):
706
+ y, x = default_args(args, (None, None))
707
+ if not isinstance(y, (int, float)) or not isinstance(x, (int, float)):
708
+ return None
709
+
710
+ return math.atan2(y, x)
711
+
712
+
713
+ # $function: mathCeil
714
+ # $group: Math
715
+ # $doc: Compute the ceiling of a number (round up to the next highest integer)
716
+ # $arg x: The number
717
+ # $return: The ceiling of the number
718
+ def _math_ceil(args, unused_options):
719
+ x, = default_args(args, (None,))
720
+ if not isinstance(x, (int, float)):
721
+ return None
722
+
723
+ return math.ceil(x)
724
+
725
+
726
+ # $function: mathCos
727
+ # $group: Math
728
+ # $doc: Compute the cosine of an angle, in radians
729
+ # $arg x: The angle, in radians
730
+ # $return: The cosine of the angle
731
+ def _math_cos(args, unused_options):
732
+ x, = default_args(args, (None,))
733
+ if not isinstance(x, (int, float)):
734
+ return None
735
+
736
+ return math.cos(x)
737
+
738
+
739
+ # $function: mathFloor
740
+ # $group: Math
741
+ # $doc: Compute the floor of a number (round down to the next lowest integer)
742
+ # $arg x: The number
743
+ # $return: The floor of the number
744
+ def _math_floor(args, unused_options):
745
+ x, = default_args(args, (None,))
746
+ if not isinstance(x, (int, float)):
747
+ return None
748
+
749
+ return math.floor(x)
750
+
751
+
752
+ # $function: mathLn
753
+ # $group: Math
754
+ # $doc: Compute the natural logarithm (base e) of a number
755
+ # $arg x: The number
756
+ # $return: The natural logarithm of the number
757
+ def _math_ln(args, unused_options):
758
+ x, = default_args(args, (None,))
759
+ if not isinstance(x, (int, float)) or x <= 0:
760
+ return None
761
+
762
+ return math.log(x)
763
+
764
+
765
+ # $function: mathLog
766
+ # $group: Math
767
+ # $doc: Compute the logarithm (base 10) of a number
768
+ # $arg x: The number
769
+ # $arg base: Optional (default is 10). The logarithm base.
770
+ # $return: The logarithm of the number
771
+ def _math_log(args, unused_options):
772
+ x, base = default_args(args, (None, 10))
773
+ if not isinstance(x, (int, float)) or x <= 0 or not isinstance(base, (int, float)) or base <= 0 or base == 1:
774
+ return None
775
+
776
+ return math.log(x, base)
777
+
778
+
779
+ # $function: mathMax
780
+ # $group: Math
781
+ # $doc: Compute the maximum value
782
+ # $arg values...: The values
783
+ # $return: The maximum value
784
+ def _math_max(args, unused_options):
785
+ for x in args:
786
+ if not isinstance(x, (int, float)):
787
+ return None
788
+
789
+ return max(*args)
790
+
791
+
792
+ # $function: mathMin
793
+ # $group: Math
794
+ # $doc: Compute the minimum value
795
+ # $arg values...: The values
796
+ # $return: The minimum value
797
+ def _math_min(args, unused_options):
798
+ for x in args:
799
+ if not isinstance(x, (int, float)):
800
+ return None
801
+
802
+ return min(*args)
803
+
804
+
805
+ # $function: mathPi
806
+ # $group: Math
807
+ # $doc: Return the number pi
808
+ # $return: The number pi
809
+ def _math_pi(unused_args, unused_options):
810
+ return math.pi
811
+
812
+
813
+ # $function: mathRandom
814
+ # $group: Math
815
+ # $doc: Compute a random number between 0 and 1, inclusive
816
+ # $return: A random number
817
+ def _math_random(unused_args, unused_options):
818
+ return random.random()
819
+
820
+
821
+ # $function: mathRound
822
+ # $group: Math
823
+ # $doc: Round a number to a certain number of decimal places
824
+ # $arg x: The number
825
+ # $arg digits: Optional (default is 0). The number of decimal digits to round to.
826
+ # $return: The rounded number
827
+ def _math_round(args, unused_options):
828
+ x, digits = default_args(args, (None, 0))
829
+ if not isinstance(x, (int, float)) or not isinstance(digits, (int, float)) or int(digits) != digits or digits < 0:
830
+ return None
831
+
832
+ return round_number(x, digits)
833
+
834
+
835
+ # $function: mathSign
836
+ # $group: Math
837
+ # $doc: Compute the sign of a number
838
+ # $arg x: The number
839
+ # $return: -1 for a negative number, 1 for a positive number, and 0 for zero
840
+ def _math_sign(args, unused_options):
841
+ x, = default_args(args, (None,))
842
+ if not isinstance(x, (int, float)):
843
+ return None
844
+
845
+ return -1 if x < 0 else (0 if x == 0 else 1)
846
+
847
+
848
+ # $function: mathSin
849
+ # $group: Math
850
+ # $doc: Compute the sine of an angle, in radians
851
+ # $arg x: The angle, in radians
852
+ # $return: The sine of the angle
853
+ def _math_sin(args, unused_options):
854
+ x, = default_args(args, (None,))
855
+ if not isinstance(x, (int, float)):
856
+ return None
857
+
858
+ return math.sin(x)
859
+
860
+
861
+ # $function: mathSqrt
862
+ # $group: Math
863
+ # $doc: Compute the square root of a number
864
+ # $arg x: The number
865
+ # $return: The square root of the number
866
+ def _math_sqrt(args, unused_options):
867
+ x, = default_args(args, (None,))
868
+ if not isinstance(x, (int, float)) or x < 0:
869
+ return None
870
+
871
+ return math.sqrt(x)
872
+
873
+
874
+ # $function: mathTan
875
+ # $group: Math
876
+ # $doc: Compute the tangent of an angle, in radians
877
+ # $arg x: The angle, in radians
878
+ # $return: The tangent of the angle
879
+ def _math_tan(args, unused_options):
880
+ x, = default_args(args, (None,))
881
+ if not isinstance(x, (int, float)):
882
+ return None
883
+
884
+ return math.tan(x)
885
+
886
+
887
+ #
888
+ # Number functions
889
+ #
890
+
891
+
892
+ # $function: numberParseFloat
893
+ # $group: Number
894
+ # $doc: Parse a string as a floating point number
895
+ # $arg string: The string
896
+ # $return: The number
897
+ def _number_parse_float(args, unused_options):
898
+ string, = default_args(args, (None,))
899
+ if not isinstance(string, str):
900
+ return None
901
+
902
+ try:
903
+ return float(string)
904
+ except ValueError:
905
+ return None
906
+
907
+
908
+ # $function: numberParseInt
909
+ # $group: Number
910
+ # $doc: Parse a string as an integer
911
+ # $arg string: The string
912
+ # $arg radix: Optional (default is 10). The number base.
913
+ # $return: The integer
914
+ def _number_parse_int(args, unused_options):
915
+ string, radix = default_args(args, (None, 10))
916
+ if not isinstance(string, str) or not isinstance(radix, (int, float)) or int(radix) != radix or radix < 2 or radix > 36:
917
+ return None
918
+
919
+ try:
920
+ return int(string, int(radix))
921
+ except ValueError:
922
+ return None
923
+
924
+
925
+ # $function: numberToFixed
926
+ # $group: Number
927
+ # $doc: Format a number using fixed-point notation
928
+ # $arg x: The number
929
+ # $arg digits: Optional (default is 2). The number of digits to appear after the decimal point.
930
+ # $arg trim: Optional (default is false). If true, trim trailing zeroes and decimal point.
931
+ # $return: The fixed-point notation string
932
+ def _number_to_fixed(args, unused_options):
933
+ x, digits, trim = default_args(args, (None, 2, False))
934
+ if not isinstance(x, (int, float)) or not isinstance(digits, (int, float)) or int(digits) != digits or digits < 0:
935
+ return None
936
+
937
+ result = f'{round_number(x, digits):.{int(digits)}f}'
938
+ if value_boolean(trim):
939
+ return R_NUMBER_CLEANUP.sub('', result)
940
+ return result
941
+
942
+
943
+ #
944
+ # Object functions
945
+ #
946
+
947
+
948
+ # $function: objectAssign
949
+ # $group: Object
950
+ # $doc: Assign the keys/values of one object to another
951
+ # $arg object: The object to assign to
952
+ # $arg object2: The object to assign
953
+ # $return: The updated object
954
+ def _object_assign(args, unused_options):
955
+ object_, object2 = default_args(args, (None, None))
956
+ if not isinstance(object_, dict) or not isinstance(object2, dict):
957
+ return None
958
+
959
+ object_.update(object2)
960
+ return object_
961
+
962
+
963
+ # $function: objectCopy
964
+ # $group: Object
965
+ # $doc: Create a copy of an object
966
+ # $arg object: The object to copy
967
+ # $return: The object copy
968
+ def _object_copy(args, unused_options):
969
+ object_, = default_args(args, (None,))
970
+ if not isinstance(object_, dict):
971
+ return None
972
+
973
+ return dict(object_)
974
+
975
+
976
+ # $function: objectDelete
977
+ # $group: Object
978
+ # $doc: Delete an object key
979
+ # $arg object: The object
980
+ # $arg key: The key to delete
981
+ def _object_delete(args, unused_options):
982
+ object_, key = default_args(args, (None, None))
983
+ if not isinstance(object_, dict) or not isinstance(key, str):
984
+ return None
985
+
986
+ if key in object_:
987
+ del object_[key]
988
+ return None
989
+
990
+
991
+ # $function: objectGet
992
+ # $group: Object
993
+ # $doc: Get an object key's value
994
+ # $arg object: The object
995
+ # $arg key: The key
996
+ # $arg defaultValue: The default value (optional)
997
+ # $return: The value or null if the key does not exist
998
+ def _object_get(args, unused_options):
999
+ object_, key, default_value = default_args(args, (None, None, None))
1000
+ if not isinstance(object_, dict) or not isinstance(key, str):
1001
+ return default_value
1002
+
1003
+ return object_.get(key, default_value)
1004
+
1005
+
1006
+ # $function: objectHas
1007
+ # $group: Object
1008
+ # $doc: Test if an object contains a key
1009
+ # $arg object: The object
1010
+ # $arg key: The key
1011
+ # $return: true if the object contains the key, false otherwise
1012
+ def _object_has(args, unused_options):
1013
+ object_, key = default_args(args, (None, None))
1014
+ if not isinstance(object_, dict) or not isinstance(key, str):
1015
+ return False
1016
+
1017
+ return key in object_
1018
+
1019
+
1020
+ # $function: objectKeys
1021
+ # $group: Object
1022
+ # $doc: Get an object's keys
1023
+ # $arg object: The object
1024
+ # $return: The array of keys
1025
+ def _object_keys(args, unused_options):
1026
+ object_, = default_args(args, (None,))
1027
+ if not isinstance(object_, dict):
1028
+ return None
1029
+
1030
+ return list(object_.keys())
1031
+
1032
+
1033
+ # $function: objectNew
1034
+ # $group: Object
1035
+ # $doc: Create a new object
1036
+ # $arg keyValues...: The object's initial key and value pairs
1037
+ # $return: The new object
1038
+ def _object_new(args, unused_options):
1039
+ args_length = len(args)
1040
+ object_ = {}
1041
+ ix = 0
1042
+ while ix < args_length:
1043
+ object_[args[ix]] = (args[ix + 1] if ix + 1 < len(args) else None)
1044
+ ix += 2
1045
+ return object_
1046
+
1047
+
1048
+ # $function: objectSet
1049
+ # $group: Object
1050
+ # $doc: Set an object key's value
1051
+ # $arg object: The object
1052
+ # $arg key: The key
1053
+ # $arg value: The value to set
1054
+ # $return: The value to set
1055
+ def _object_set(args, unused_options):
1056
+ object_, key, value = default_args(args, (None, None, None))
1057
+ if not isinstance(object_, dict) or not isinstance(key, str):
1058
+ return None
1059
+
1060
+ object_[key] = value
1061
+ return value
1062
+
1063
+
1064
+ #
1065
+ # Regex functions
1066
+ #
1067
+
1068
+
1069
+ # $function: regexEscape
1070
+ # $group: Regex
1071
+ # $doc: Escape a string for use in a regular expression
1072
+ # $arg string: The string to escape
1073
+ # $return: The escaped string
1074
+ def _regex_escape(args, unused_options):
1075
+ string, = default_args(args, (None,))
1076
+ if not isinstance(string, str):
1077
+ return None
1078
+
1079
+ return re.escape(string)
1080
+
1081
+
1082
+ # $function: regexMatch
1083
+ # $group: Regex
1084
+ # $doc: Find the first match of a regular expression in a string
1085
+ # $arg regex: The regular expression
1086
+ # $arg string: The string
1087
+ # $return: The [match object](model.html#var.vName='RegexMatch'), or null if no matches are found
1088
+ def _regex_match(args, unused_options):
1089
+ regex, string = default_args(args, (None, None))
1090
+ if not isinstance(regex, REGEX_TYPE) or not isinstance(string, str):
1091
+ return None
1092
+
1093
+ # Match?
1094
+ match = regex.search(string)
1095
+ if match is None:
1096
+ return None
1097
+
1098
+ return {
1099
+ 'index': match.start(),
1100
+ 'input': match.string,
1101
+ 'match': match.group(0),
1102
+ 'groups': match.groupdict(),
1103
+ 'groupArray': list(match.groups())
1104
+ }
1105
+
1106
+
1107
+ # The regex match model
1108
+ REGEX_MATCH_TYPES = parse_schema_markdown('''\
1109
+ group "RegexMatch"
1110
+
1111
+
1112
+ # A regex match model
1113
+ struct RegexMatch
1114
+
1115
+ # The zero-based index of the match in the input string
1116
+ int(>= 0) index
1117
+
1118
+ # The input string
1119
+ string input
1120
+
1121
+ # The match string
1122
+ string match
1123
+
1124
+ # The named groups
1125
+ string{} groups
1126
+
1127
+ # The ordered groups
1128
+ string[] groupArray
1129
+ ''')
1130
+
1131
+
1132
+ # $function: regexMatchAll
1133
+ # $group: Regex
1134
+ # $doc: Find all matches of regular expression in a string
1135
+ # $arg regex: The regular expression
1136
+ # $arg string: The string
1137
+ # $return: The array of [match objects](model.html#var.vName='RegexMatch')
1138
+ def _regex_match_all(args, unused_options):
1139
+ regex, string = default_args(args, (None, None))
1140
+ if not isinstance(regex, REGEX_TYPE) or not isinstance(string, str):
1141
+ return None
1142
+
1143
+ return [
1144
+ {
1145
+ 'index': match.start(),
1146
+ 'input': match.string,
1147
+ 'match': match.group(0),
1148
+ 'groups': match.groupdict(),
1149
+ 'groupArray': list(match.groups())
1150
+ }
1151
+ for match in regex.finditer(string)
1152
+ ]
1153
+
1154
+ # $function: regexNew
1155
+ # $group: Regex
1156
+ # $doc: Create a regular expression
1157
+ # $arg pattern: The regular expression pattern string
1158
+ # $arg flags: The [regular expression flags ("i", "m", "s", "u")
1159
+ # $return: The regular expression or null if the pattern is invalid
1160
+ def _regex_new(args, unused_options):
1161
+ pattern, flags = default_args(args, (None, None))
1162
+ if not isinstance(pattern, str) or (flags is not None and not isinstance(flags, str)):
1163
+ return None
1164
+
1165
+ # Translate JavaScript named group syntax to Python
1166
+ pattern = R_REGEX_NEW_NAMED.sub(r'(?P<\1>', pattern)
1167
+
1168
+ # Compute the flags mask
1169
+ flags_mask = 0
1170
+ if flags is not None:
1171
+ for flag in flags:
1172
+ if flag == 'i':
1173
+ flags_mask = flags_mask | re.I
1174
+ elif flag == 'm':
1175
+ flags_mask = flags_mask | re.M
1176
+ elif flag == 's':
1177
+ flags_mask = flags_mask | re.S
1178
+ else:
1179
+ return None
1180
+
1181
+ return re.compile(pattern, flags_mask)
1182
+
1183
+
1184
+ R_REGEX_NEW_NAMED = re.compile(r'\(\?<(\w+)>')
1185
+
1186
+
1187
+ # $function: regexReplace
1188
+ # $group: Regex
1189
+ # $doc: Replace regular expression matches with a string
1190
+ # $arg regex: The replacement regular expression
1191
+ # $arg string: The string
1192
+ # $arg substr: The replacement string
1193
+ # $return: The updated string
1194
+ def _regex_replace(args, unused_options):
1195
+ regex, string, substr = default_args(args, (None, None, None))
1196
+ if not isinstance(regex, REGEX_TYPE) or not isinstance(string, str) or not isinstance(substr, str):
1197
+ return None
1198
+
1199
+ # Escape Python escapes
1200
+ substr = substr.replace('\\', '\\\\')
1201
+
1202
+ # Un-escape Javascript escapes
1203
+ substr = substr.replace('$$', '$')
1204
+
1205
+ # Translate JavaScript replacers to Python replacers
1206
+ substr = R_REGEX_REPLACE_INDEX.sub(r'\\\1', substr)
1207
+ substr = R_REGEX_REPLACE_NAMED.sub(r'\\g<\1>', substr)
1208
+
1209
+ return regex.sub(substr, string)
1210
+
1211
+
1212
+ R_REGEX_REPLACE_INDEX = re.compile(r'\$(\d+)')
1213
+ R_REGEX_REPLACE_NAMED = re.compile(r'\$<(?P<name>[^>]+)>')
1214
+
1215
+
1216
+ # $function: regexSplit
1217
+ # $group: Regex
1218
+ # $doc: Split a string with a regular expression
1219
+ # $arg regex: The regular expression
1220
+ # $arg string: The string
1221
+ # $return: The array of split parts
1222
+ def _regex_split(args, unused_options):
1223
+ regex, string = default_args(args, (None, None))
1224
+ if not isinstance(regex, REGEX_TYPE) or not isinstance(string, str):
1225
+ return None
1226
+
1227
+ return regex.split(string)
1228
+
1229
+
1230
+ #
1231
+ # Schema functions
1232
+ #
1233
+
1234
+
1235
+ # $function: schemaParse
1236
+ # $group: Schema
1237
+ # $doc: Parse the [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/) text
1238
+ # $arg lines...: The [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/)
1239
+ # $arg lines...: text lines (may contain nested arrays of un-split lines)
1240
+ # $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1241
+ def _schema_parse(args, unused_options):
1242
+ return parse_schema_markdown(args)
1243
+
1244
+
1245
+ # $function: schemaParseEx
1246
+ # $group: Schema
1247
+ # $doc: Parse the [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/) text with options
1248
+ # $arg lines: The array of [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/)
1249
+ # $arg lines: text lines (may contain nested arrays of un-split lines)
1250
+ # $arg types: Optional. The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types').
1251
+ # $arg filename: Optional (default is ""). The file name.
1252
+ # $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1253
+ def _schema_parse_ex(args, unused_options):
1254
+ lines, types, filename = default_args(args, (None, {}, ''))
1255
+ if not isinstance(lines, (list, str)) or not isinstance(types, dict) or not isinstance(filename, str):
1256
+ return None
1257
+
1258
+ return parse_schema_markdown(lines, types, filename)
1259
+
1260
+
1261
+ # $function: schemaTypeModel
1262
+ # $group: Schema
1263
+ # $doc: Get the [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1264
+ # $return: The [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1265
+ def _schema_type_model(unused_args, unused_options):
1266
+ return TYPE_MODEL
1267
+
1268
+
1269
+ # $function: schemaValidate
1270
+ # $group: Schema
1271
+ # $doc: Validate an object to a schema type
1272
+ # $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1273
+ # $arg typeName: The type name
1274
+ # $arg value: The object to validate
1275
+ # $return: The validated object or null if validation fails
1276
+ def _schema_validate(args, unused_options):
1277
+ types, type_name, value = default_args(args, (None, None, None))
1278
+ if not isinstance(types, dict) or not isinstance(type_name, str):
1279
+ return None
1280
+
1281
+ validate_type_model(types)
1282
+ return validate_type(types, type_name, value)
1283
+
1284
+
1285
+ # $function: schemaValidateTypeModel
1286
+ # $group: Schema
1287
+ # $doc: Validate a [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1288
+ # $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types') to validate
1289
+ # $return: The validated [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
1290
+ def _schema_validate_type_model(args, unused_options):
1291
+ types, = default_args(args, (None,))
1292
+ if not isinstance(types, dict):
1293
+ return None
1294
+
1295
+ return validate_type_model(types)
1296
+
1297
+
1298
+ #
1299
+ # String functions
1300
+ #
1301
+
1302
+
1303
+ # $function: stringCharCodeAt
1304
+ # $group: String
1305
+ # $doc: Get a string index's character code
1306
+ # $arg string: The string
1307
+ # $arg index: The character index
1308
+ # $return: The character code
1309
+ def _string_char_code_at(args, unused_options):
1310
+ string, index = default_args(args, (None, None))
1311
+ if not isinstance(string, str) or not isinstance(index, (int, float)) or int(index) != index or index < 0 or index >= len(string):
1312
+ return None
1313
+
1314
+ return ord(string[int(index)])
1315
+
1316
+
1317
+ # $function: stringEndsWith
1318
+ # $group: String
1319
+ # $doc: Determine if a string ends with a search string
1320
+ # $arg string: The string
1321
+ # $arg search: The search string
1322
+ # $return: true if the string ends with the search string, false otherwise
1323
+ def _string_ends_with(args, unused_options):
1324
+ string, search = default_args(args, (None, None))
1325
+ if not isinstance(string, str) or not isinstance(search, str):
1326
+ return None
1327
+
1328
+ return string.endswith(search)
1329
+
1330
+
1331
+ # $function: stringFromCharCode
1332
+ # $group: String
1333
+ # $doc: Create a string of characters from character codes
1334
+ # $arg charCodes...: The character codes
1335
+ # $return: The string of characters
1336
+ def _string_from_char_code(args, unused_options):
1337
+ if any((not isinstance(code, (int, float)) or int(code) != code or code < 0) for code in args):
1338
+ return None
1339
+
1340
+ return ''.join(chr(int(code)) for code in args)
1341
+
1342
+
1343
+ # $function: stringIndexOf
1344
+ # $group: String
1345
+ # $doc: Find the first index of a search string in a string
1346
+ # $arg string: The string
1347
+ # $arg search: The search string
1348
+ # $arg index: Optional (default is 0). The index at which to start the search.
1349
+ # $return: The first index of the search string; -1 if not found.
1350
+ def _string_index_of(args, unused_options):
1351
+ string, search, index = default_args(args, (None, None, 0))
1352
+ if not isinstance(string, str) or not isinstance(search, str) or \
1353
+ not isinstance(index, (int, float)) or int(index) != index or index < 0 or index >= len(string):
1354
+ return -1
1355
+
1356
+ return string.find(search, int(index))
1357
+
1358
+
1359
+ # $function: stringLastIndexOf
1360
+ # $group: String
1361
+ # $doc: Find the last index of a search string in a string
1362
+ # $arg string: The string
1363
+ # $arg search: The search string
1364
+ # $arg index: Optional (default is the end of the string). The index at which to start the search.
1365
+ # $return: The last index of the search string; -1 if not found.
1366
+ def _string_last_index_of(args, unused_options):
1367
+ string, search, index = default_args(args, (None, None, None))
1368
+ if index is None and isinstance(string, str):
1369
+ index = len(string) - 1
1370
+ if not isinstance(string, str) or not isinstance(search, str) or \
1371
+ not isinstance(index, (int, float)) or int(index) != index or index < 0 or index >= len(string):
1372
+ return -1
1373
+
1374
+ return string.rfind(search, 0, int(index) + len(search))
1375
+
1376
+
1377
+ # $function: stringLength
1378
+ # $group: String
1379
+ # $doc: Get the length of a string
1380
+ # $arg string: The string
1381
+ # $return: The string's length; zero if not a string
1382
+ def _string_length(args, unused_options):
1383
+ string, = default_args(args, (None,))
1384
+ if not isinstance(string, str):
1385
+ return 0
1386
+
1387
+ return len(string)
1388
+
1389
+
1390
+ # $function: stringLower
1391
+ # $group: String
1392
+ # $doc: Convert a string to lower-case
1393
+ # $arg string: The string
1394
+ # $return: The lower-case string
1395
+ def _string_lower(args, unused_options):
1396
+ string, = default_args(args, (None,))
1397
+ if not isinstance(string, str):
1398
+ return None
1399
+
1400
+ return string.lower()
1401
+
1402
+
1403
+ # $function: stringNew
1404
+ # $group: String
1405
+ # $doc: Create a new string from a value
1406
+ # $arg value: The value
1407
+ # $return: The new string
1408
+ def _string_new(args, unused_options):
1409
+ value, = default_args(args, (None,))
1410
+ return value_string(value)
1411
+
1412
+
1413
+ # $function: stringRepeat
1414
+ # $group: String
1415
+ # $doc: Repeat a string
1416
+ # $arg string: The string to repeat
1417
+ # $arg count: The number of times to repeat the string
1418
+ # $return: The repeated string
1419
+ def _string_repeat(args, unused_options):
1420
+ string, count = default_args(args, (None, None))
1421
+ if not isinstance(string, str) or not isinstance(count, (int, float)) or int(count) != count or count < 0:
1422
+ return None
1423
+
1424
+ return string * int(count)
1425
+
1426
+
1427
+ # $function: stringReplace
1428
+ # $group: String
1429
+ # $doc: Replace all instances of a string with another string
1430
+ # $arg string: The string to update
1431
+ # $arg substr: The string to replace
1432
+ # $arg newSubstr: The replacement string
1433
+ # $return: The updated string
1434
+ def _string_replace(args, unused_options):
1435
+ string, substr, new_substr = default_args(args, (None, None, None))
1436
+ if not isinstance(string, str) or not isinstance(substr, str) or not isinstance(new_substr, str):
1437
+ return None
1438
+
1439
+ return string.replace(substr, new_substr)
1440
+
1441
+
1442
+ # $function: stringSlice
1443
+ # $group: String
1444
+ # $doc: Copy a portion of a string
1445
+ # $arg string: The string
1446
+ # $arg start: The start index of the slice
1447
+ # $arg end: Optional (default is the end of the string). The end index of the slice.
1448
+ # $return: The new string slice
1449
+ def _string_slice(args, unused_options):
1450
+ string, begin, end = default_args(args, (None, None, None))
1451
+ if end is None and isinstance(string, str):
1452
+ end = len(string)
1453
+ if not isinstance(string, str) or \
1454
+ not isinstance(begin, (int, float)) or int(begin) != begin or begin < 0 or begin >= len(string) or \
1455
+ not isinstance(end, (int, float)) or int(end) != end or end < 0 or end > len(string):
1456
+ return None
1457
+
1458
+ return string[int(begin):int(end)]
1459
+
1460
+
1461
+ # $function: stringSplit
1462
+ # $group: String
1463
+ # $doc: Split a string
1464
+ # $arg string: The string to split
1465
+ # $arg separator: The separator string
1466
+ # $return: The array of split-out strings
1467
+ def _string_split(args, unused_options):
1468
+ string, separator = default_args(args, (None, None))
1469
+ if not isinstance(string, str) or not isinstance(separator, str):
1470
+ return None
1471
+
1472
+ return string.split(separator)
1473
+
1474
+
1475
+ # $function: stringStartsWith
1476
+ # $group: String
1477
+ # $doc: Determine if a string starts with a search string
1478
+ # $arg string: The string
1479
+ # $arg search: The search string
1480
+ # $return: true if the string starts with the search string, false otherwise
1481
+ def _string_starts_with(args, unused_options):
1482
+ string, search = default_args(args, (None, None))
1483
+ if not isinstance(string, str) or not isinstance(search, str):
1484
+ return None
1485
+
1486
+ return string.startswith(search)
1487
+
1488
+
1489
+ # $function: stringTrim
1490
+ # $group: String
1491
+ # $doc: Trim the whitespace from the beginning and end of a string
1492
+ # $arg string: The string
1493
+ # $return: The trimmed string
1494
+ def _string_trim(args, unused_options):
1495
+ string, = default_args(args, (None,))
1496
+ if not isinstance(string, str):
1497
+ return None
1498
+
1499
+ return string.strip()
1500
+
1501
+
1502
+ # $function: stringUpper
1503
+ # $group: String
1504
+ # $doc: Convert a string to upper-case
1505
+ # $arg string: The string
1506
+ # $return: The upper-case string
1507
+ def _string_upper(args, unused_options):
1508
+ string, = default_args(args, (None,))
1509
+ if not isinstance(string, str):
1510
+ return None
1511
+
1512
+ return string.upper()
1513
+
1514
+
1515
+ #
1516
+ # System functions
1517
+ #
1518
+
1519
+
1520
+ # $function: systemBoolean
1521
+ # $group: System
1522
+ # $doc: Interpret a value as a boolean
1523
+ # $arg value: The value
1524
+ # $return: true or false
1525
+ def _system_boolean(args, unused_options):
1526
+ value, = default_args(args, (None,))
1527
+ return value_boolean(value)
1528
+
1529
+
1530
+ # $function: systemCompare
1531
+ # $group: System
1532
+ # $doc: Compare two values
1533
+ # $arg left: The left value
1534
+ # $arg right: The right value
1535
+ # $return: -1 if the left value is less than the right value, 0 if equal, and 1 if greater than
1536
+ def _system_compare(args, unused_options):
1537
+ left, right = default_args(args, (None, None))
1538
+ return value_compare(left, right)
1539
+
1540
+
1541
+ # $function: systemFetch
1542
+ # $group: System
1543
+ # $doc: Retrieve a URL resource
1544
+ # $arg url: The resource URL, [request model](model.html#var.vName='SystemFetchRequest'), or array of URL and
1545
+ # $arg url: [request model](model.html#var.vName='SystemFetchRequest')
1546
+ # $return: The response string or array of strings; null if an error occurred
1547
+ def _system_fetch(args, options):
1548
+ url_arg, = default_args(args, (None,))
1549
+
1550
+ # Options
1551
+ log_fn = options.get('logFn') if options is not None else None
1552
+ url_fn = options.get('urlFn') if options is not None else None
1553
+ fetch_fn = options.get('fetchFn') if options is not None else None
1554
+
1555
+ # Validate the URL argument
1556
+ requests = []
1557
+ is_response_array = False
1558
+ if isinstance(url_arg, str):
1559
+ requests.append({'url': url_arg})
1560
+ elif isinstance(url_arg, dict):
1561
+ requests.append(validate_type(SYSTEM_FETCH_TYPES, 'SystemFetchRequest', url_arg))
1562
+ elif isinstance(url_arg, list):
1563
+ is_response_array = True
1564
+ for url_item in url_arg:
1565
+ if isinstance(url_item, str):
1566
+ requests.append({'url': url_item})
1567
+ else:
1568
+ requests.append(validate_type(SYSTEM_FETCH_TYPES, 'SystemFetchRequest', url_item))
1569
+ else:
1570
+ return None
1571
+
1572
+ # Get each response
1573
+ responses = []
1574
+ for request in requests:
1575
+ request_fetch = dict(request)
1576
+
1577
+ # Update the URL
1578
+ if url_fn is not None:
1579
+ request_fetch['url'] = url_fn(request_fetch['url'])
1580
+
1581
+ # Fetch the URL
1582
+ response = None
1583
+ if fetch_fn is not None:
1584
+ try:
1585
+ response = fetch_fn(request_fetch)
1586
+ except: # pylint: disable=bare-except
1587
+ pass
1588
+ responses.append(response)
1589
+
1590
+ # Log failure
1591
+ if response is None and log_fn is not None and options.get('debug'):
1592
+ log_fn(f'BareScript: Function "systemFetch" failed for resource "{request_fetch["url"]}"')
1593
+
1594
+ return responses if is_response_array else responses[0]
1595
+
1596
+
1597
+ # The aggregation model
1598
+ SYSTEM_FETCH_TYPES = parse_schema_markdown('''\
1599
+ group "SystemFetch"
1600
+
1601
+
1602
+ # A fetch request model
1603
+ struct SystemFetchRequest
1604
+
1605
+ # The resource URL
1606
+ string url
1607
+
1608
+ # The request body
1609
+ optional string body
1610
+
1611
+ # The request headers
1612
+ optional string{} headers
1613
+ ''')
1614
+
1615
+
1616
+ # $function: systemGlobalGet
1617
+ # $group: System
1618
+ # $doc: Get a global variable value
1619
+ # $arg name: The global variable name
1620
+ # $arg defaultValue: The default value (optional)
1621
+ # $return: The global variable's value or null if it does not exist
1622
+ def _system_global_get(args, options):
1623
+ name, default_value = default_args(args, (None, None))
1624
+ if not isinstance(name, str):
1625
+ return default_value
1626
+
1627
+ globals_ = options.get('globals') if options is not None else None
1628
+ return globals_.get(name, default_value) if globals_ is not None else default_value
1629
+
1630
+
1631
+ # $function: systemGlobalSet
1632
+ # $group: System
1633
+ # $doc: Set a global variable value
1634
+ # $arg name: The global variable name
1635
+ # $arg value: The global variable's value
1636
+ # $return: The global variable's value
1637
+ def _system_global_set(args, options):
1638
+ name, value = default_args(args, (None, None))
1639
+ if not isinstance(name, str):
1640
+ return None
1641
+
1642
+ globals_ = options.get('globals') if options is not None else None
1643
+ if globals_ is not None:
1644
+ globals_[name] = value
1645
+ return value
1646
+
1647
+
1648
+ # $function: systemIs
1649
+ # $group: System
1650
+ # $doc: Test if one value is the same object as another
1651
+ # $arg value1: The first value
1652
+ # $arg value2: The second value
1653
+ # $return: true if values are the same object, false otherwise
1654
+ def _system_is(args, unused_options):
1655
+ value1, value2 = default_args(args, (None, None))
1656
+ return value_is(value1, value2)
1657
+
1658
+
1659
+ # $function: systemLog
1660
+ # $group: System
1661
+ # $doc: Log a message to the console
1662
+ # $arg string: The message
1663
+ def _system_log(args, options):
1664
+ string, = default_args(args, (None,))
1665
+
1666
+ log_fn = options.get('logFn') if options is not None else None
1667
+ if log_fn is not None:
1668
+ log_fn(value_string(string))
1669
+
1670
+
1671
+ # $function: systemLogDebug
1672
+ # $group: System
1673
+ # $doc: Log a message to the console, if in debug mode
1674
+ # $arg string: The message
1675
+ def _system_log_debug(args, options):
1676
+ string, = default_args(args, (None,))
1677
+ if not isinstance(string, str):
1678
+ return None
1679
+
1680
+ log_fn = options.get('logFn') if options is not None else None
1681
+ if log_fn is not None and options.get('debug'):
1682
+ log_fn(string)
1683
+ return None
1684
+
1685
+
1686
+ # $function: systemPartial
1687
+ # $group: System
1688
+ # $doc: Return a new function which behaves like "func" called with "args".
1689
+ # $doc: If additional arguments are passed to the returned function, they are appended to "args".
1690
+ # $arg func: The function
1691
+ # $arg args...: The function arguments
1692
+ # $return: The new function called with "args"
1693
+ def _system_partial(args, unused_options):
1694
+ fn, fn_args = default_args(args, (None,), True)
1695
+ if not callable(fn) or len(fn_args) < 1:
1696
+ return None
1697
+
1698
+ return lambda args_extra, options: fn([*fn_args, *args_extra], options)
1699
+
1700
+
1701
+ # $function: systemType
1702
+ # $group: System
1703
+ # $doc: Get a value's type string
1704
+ # $arg value: The value
1705
+ # $return: The type string of the value.
1706
+ # $return: Valid values are: 'array', 'boolean', 'datetime', 'function', 'null', 'number', 'object', 'regex', 'string'.
1707
+ def _system_type(args, unused_options):
1708
+ value, = default_args(args, (None,))
1709
+ return value_type(value)
1710
+
1711
+
1712
+ #
1713
+ # URL functions
1714
+ #
1715
+
1716
+
1717
+ # $function: urlEncode
1718
+ # $group: URL
1719
+ # $doc: Encode a URL
1720
+ # $arg url: The URL string
1721
+ # $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1722
+ # $return: The encoded URL string
1723
+ def _url_encode(args, unused_options):
1724
+ url, extra = default_args(args, (None, True))
1725
+ if not isinstance(url, str):
1726
+ return None
1727
+
1728
+ safe = "':/&(" if value_boolean(extra) else "':/&()"
1729
+ return urllib.parse.quote(url, safe=safe)
1730
+
1731
+
1732
+ # $function: urlEncodeComponent
1733
+ # $group: URL
1734
+ # $doc: Encode a URL component
1735
+ # $arg url: The URL component string
1736
+ # $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1737
+ # $return: The encoded URL component string
1738
+ def _url_encode_component(args, unused_options):
1739
+ url, extra = default_args(args, (None, True))
1740
+ if not isinstance(url, str):
1741
+ return None
1742
+
1743
+ safe = "'(" if value_boolean(extra) else "'()"
1744
+ return urllib.parse.quote(url, safe=safe)
1745
+
1746
+
1747
+ # The built-in script functions
1748
+ SCRIPT_FUNCTIONS = {
1749
+ 'arrayCopy': _array_copy,
1750
+ 'arrayExtend': _array_extend,
1751
+ 'arrayGet': _array_get,
1752
+ 'arrayIndexOf': _array_index_of,
1753
+ 'arrayJoin': _array_join,
1754
+ 'arrayLastIndexOf': _array_last_index_of,
1755
+ 'arrayLength': _array_length,
1756
+ 'arrayNew': _array_new,
1757
+ 'arrayNewSize': _array_new_size,
1758
+ 'arrayPop': _array_pop,
1759
+ 'arrayPush': _array_push,
1760
+ 'arraySet': _array_set,
1761
+ 'arrayShift': _array_shift,
1762
+ 'arraySlice': _array_slice,
1763
+ 'arraySort': _array_sort,
1764
+ 'dataAggregate': _data_aggregate,
1765
+ 'dataCalculatedField': _data_calculated_field,
1766
+ 'dataFilter': _data_filter,
1767
+ 'dataJoin': _data_join,
1768
+ 'dataParseCSV': _data_parse_csv,
1769
+ 'dataSort': _data_sort,
1770
+ 'dataTop': _data_top,
1771
+ 'dataValidate': _data_validate,
1772
+ 'datetimeDay': _datetime_day,
1773
+ 'datetimeHour': _datetime_hour,
1774
+ 'datetimeISOFormat': _datetime_iso_format,
1775
+ 'datetimeISOParse': _datetime_iso_parse,
1776
+ 'datetimeMillisecond': _datetime_millisecond,
1777
+ 'datetimeMinute': _datetime_minute,
1778
+ 'datetimeMonth': _datetime_month,
1779
+ 'datetimeNew': _datetime_new,
1780
+ 'datetimeNewUTC': _datetime_new_utc,
1781
+ 'datetimeNow': _datetime_now,
1782
+ 'datetimeSecond': _datetime_second,
1783
+ 'datetimeToday': _datetime_today,
1784
+ 'datetimeYear': _datetime_year,
1785
+ 'jsonParse': _json_parse,
1786
+ 'jsonStringify': _json_stringify,
1787
+ 'mathAbs': _math_abs,
1788
+ 'mathAcos': _math_acos,
1789
+ 'mathAsin': _math_asin,
1790
+ 'mathAtan': _math_atan,
1791
+ 'mathAtan2': _math_atan2,
1792
+ 'mathCeil': _math_ceil,
1793
+ 'mathCos': _math_cos,
1794
+ 'mathFloor': _math_floor,
1795
+ 'mathLn': _math_ln,
1796
+ 'mathLog': _math_log,
1797
+ 'mathMax': _math_max,
1798
+ 'mathMin': _math_min,
1799
+ 'mathPi': _math_pi,
1800
+ 'mathRandom': _math_random,
1801
+ 'mathRound': _math_round,
1802
+ 'mathSign': _math_sign,
1803
+ 'mathSin': _math_sin,
1804
+ 'mathSqrt': _math_sqrt,
1805
+ 'mathTan': _math_tan,
1806
+ 'numberParseInt': _number_parse_int,
1807
+ 'numberParseFloat': _number_parse_float,
1808
+ 'numberToFixed': _number_to_fixed,
1809
+ 'objectAssign': _object_assign,
1810
+ 'objectCopy': _object_copy,
1811
+ 'objectDelete': _object_delete,
1812
+ 'objectGet': _object_get,
1813
+ 'objectHas': _object_has,
1814
+ 'objectKeys': _object_keys,
1815
+ 'objectNew': _object_new,
1816
+ 'objectSet': _object_set,
1817
+ 'regexEscape': _regex_escape,
1818
+ 'regexMatch': _regex_match,
1819
+ 'regexMatchAll': _regex_match_all,
1820
+ 'regexNew': _regex_new,
1821
+ 'regexReplace': _regex_replace,
1822
+ 'regexSplit': _regex_split,
1823
+ 'schemaParse': _schema_parse,
1824
+ 'schemaParseEx': _schema_parse_ex,
1825
+ 'schemaTypeModel': _schema_type_model,
1826
+ 'schemaValidate': _schema_validate,
1827
+ 'schemaValidateTypeModel': _schema_validate_type_model,
1828
+ 'stringCharCodeAt': _string_char_code_at,
1829
+ 'stringEndsWith': _string_ends_with,
1830
+ 'stringFromCharCode': _string_from_char_code,
1831
+ 'stringIndexOf': _string_index_of,
1832
+ 'stringLastIndexOf': _string_last_index_of,
1833
+ 'stringLength': _string_length,
1834
+ 'stringLower': _string_lower,
1835
+ 'stringNew': _string_new,
1836
+ 'stringRepeat': _string_repeat,
1837
+ 'stringReplace': _string_replace,
1838
+ 'stringSlice': _string_slice,
1839
+ 'stringSplit': _string_split,
1840
+ 'stringStartsWith': _string_starts_with,
1841
+ 'stringTrim': _string_trim,
1842
+ 'stringUpper': _string_upper,
1843
+ 'systemBoolean': _system_boolean,
1844
+ 'systemCompare': _system_compare,
1845
+ 'systemFetch': _system_fetch,
1846
+ 'systemGlobalGet': _system_global_get,
1847
+ 'systemGlobalSet': _system_global_set,
1848
+ 'systemIs': _system_is,
1849
+ 'systemLog': _system_log,
1850
+ 'systemLogDebug': _system_log_debug,
1851
+ 'systemPartial': _system_partial,
1852
+ 'systemType': _system_type,
1853
+ 'urlEncode': _url_encode,
1854
+ 'urlEncodeComponent': _url_encode_component
1855
+ }
1856
+
1857
+
1858
+ # The built-in expression function name script function name map
1859
+ EXPRESSION_FUNCTION_MAP = {
1860
+ 'abs': 'mathAbs',
1861
+ 'acos': 'mathAcos',
1862
+ 'asin': 'mathAsin',
1863
+ 'atan': 'mathAtan',
1864
+ 'atan2': 'mathAtan2',
1865
+ 'ceil': 'mathCeil',
1866
+ 'charCodeAt': 'stringCharCodeAt',
1867
+ 'cos': 'mathCos',
1868
+ 'date': 'datetimeNew',
1869
+ 'day': 'datetimeDay',
1870
+ 'endsWith': 'stringEndsWith',
1871
+ 'indexOf': 'stringIndexOf',
1872
+ 'fixed': 'numberToFixed',
1873
+ 'floor': 'mathFloor',
1874
+ 'fromCharCode': 'stringFromCharCode',
1875
+ 'hour': 'datetimeHour',
1876
+ 'lastIndexOf': 'stringLastIndexOf',
1877
+ 'len': 'stringLength',
1878
+ 'lower': 'stringLower',
1879
+ 'ln': 'mathLn',
1880
+ 'log': 'mathLog',
1881
+ 'max': 'mathMax',
1882
+ 'min': 'mathMin',
1883
+ 'millisecond': 'datetimeMillisecond',
1884
+ 'minute': 'datetimeMinute',
1885
+ 'month': 'datetimeMonth',
1886
+ 'now': 'datetimeNow',
1887
+ 'parseInt': 'numberParseInt',
1888
+ 'parseFloat': 'numberParseFloat',
1889
+ 'pi': 'mathPi',
1890
+ 'rand': 'mathRandom',
1891
+ 'replace': 'stringReplace',
1892
+ 'rept': 'stringRepeat',
1893
+ 'round': 'mathRound',
1894
+ 'second': 'datetimeSecond',
1895
+ 'sign': 'mathSign',
1896
+ 'sin': 'mathSin',
1897
+ 'slice': 'stringSlice',
1898
+ 'sqrt': 'mathSqrt',
1899
+ 'startsWith': 'stringStartsWith',
1900
+ 'text': 'stringNew',
1901
+ 'tan': 'mathTan',
1902
+ 'today': 'datetimeToday',
1903
+ 'trim': 'stringTrim',
1904
+ 'upper': 'stringUpper',
1905
+ 'year': 'datetimeYear'
1906
+ }
1907
+
1908
+
1909
+ # The built-in expression functions
1910
+ EXPRESSION_FUNCTIONS = dict(
1911
+ (expr_fn_name, SCRIPT_FUNCTIONS[script_fn_name]) for expr_fn_name, script_fn_name in EXPRESSION_FUNCTION_MAP.items()
1912
+ )