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