bare-script 0.9.2__tar.gz → 0.9.3__tar.gz
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-0.9.2/src/bare_script.egg-info → bare-script-0.9.3}/PKG-INFO +1 -1
- {bare-script-0.9.2 → bare-script-0.9.3}/setup.cfg +1 -1
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/__init__.py +9 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/bare.py +30 -27
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/baredoc.py +29 -19
- bare-script-0.9.3/src/bare_script/data.py +484 -0
- {bare-script-0.9.2 → bare-script-0.9.3/src/bare_script.egg-info}/PKG-INFO +1 -1
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/SOURCES.txt +1 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/LICENSE +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/README.rst +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/pyproject.toml +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/__main__.py +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/library.py +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/model.py +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/options.py +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/parser.py +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/runtime.py +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/value.py +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/dependency_links.txt +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/entry_points.txt +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/requires.txt +0 -0
- {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
bare-script package
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from .data import \
|
|
9
|
+
add_calculated_field, \
|
|
10
|
+
aggregate_data, \
|
|
11
|
+
filter_data, \
|
|
12
|
+
join_data, \
|
|
13
|
+
sort_data, \
|
|
14
|
+
top_data, \
|
|
15
|
+
validate_data
|
|
16
|
+
|
|
8
17
|
from .library import \
|
|
9
18
|
EXPRESSION_FUNCTIONS, \
|
|
10
19
|
SCRIPT_FUNCTIONS
|
|
@@ -23,8 +23,8 @@ def main(argv=None):
|
|
|
23
23
|
|
|
24
24
|
# Command line arguments
|
|
25
25
|
parser = argparse.ArgumentParser(prog='bare', description='The BareScript command-line interface')
|
|
26
|
-
parser.add_argument('file', nargs='*', action=
|
|
27
|
-
parser.add_argument('-c', '--code',
|
|
26
|
+
parser.add_argument('file', nargs='*', action=_FileScriptAction, help='files to process')
|
|
27
|
+
parser.add_argument('-c', '--code', action=_InlineScriptAction, help='execute the BareScript code')
|
|
28
28
|
parser.add_argument('-d', '--debug', action='store_true', help='enable debug mode')
|
|
29
29
|
parser.add_argument('-s', '--static', action='store_true', help='perform static analysis')
|
|
30
30
|
parser.add_argument('-v', '--var', nargs=2, action='append', metavar=('VAR', 'EXPR'),
|
|
@@ -32,20 +32,27 @@ def main(argv=None):
|
|
|
32
32
|
args = parser.parse_args(args=argv)
|
|
33
33
|
|
|
34
34
|
status_code = 0
|
|
35
|
-
|
|
35
|
+
inline_count = 0
|
|
36
36
|
try:
|
|
37
37
|
# Parse and execute all source files in order
|
|
38
38
|
globals_ = dict(args.var) if args.var is not None else {}
|
|
39
|
-
for
|
|
40
|
-
|
|
39
|
+
for script_type, script_value in args.scripts:
|
|
40
|
+
# Get the script source
|
|
41
|
+
if script_type == 'file':
|
|
42
|
+
script_name = script_value
|
|
43
|
+
script_source = fetch_read_write({'url': script_value})
|
|
44
|
+
else:
|
|
45
|
+
inline_count += 1
|
|
46
|
+
script_name = f'-c {inline_count}'
|
|
47
|
+
script_source = script_value
|
|
41
48
|
|
|
42
|
-
# Parse the source
|
|
43
|
-
script = parse_script(
|
|
49
|
+
# Parse the script source
|
|
50
|
+
script = parse_script(script_source)
|
|
44
51
|
|
|
45
52
|
# Run the bare-script linter?
|
|
46
53
|
if args.static or args.debug:
|
|
47
54
|
warnings = lint_script(script)
|
|
48
|
-
warning_prefix = f'BareScript: Static analysis "{
|
|
55
|
+
warning_prefix = f'BareScript: Static analysis "{script_name}" ...'
|
|
49
56
|
if not warnings:
|
|
50
57
|
print(f'{warning_prefix} OK')
|
|
51
58
|
else:
|
|
@@ -53,8 +60,8 @@ def main(argv=None):
|
|
|
53
60
|
for warning in warnings:
|
|
54
61
|
print(f'BareScript: {warning}')
|
|
55
62
|
if args.static:
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
status_code = 1
|
|
64
|
+
break
|
|
58
65
|
if args.static:
|
|
59
66
|
continue
|
|
60
67
|
|
|
@@ -66,7 +73,7 @@ def main(argv=None):
|
|
|
66
73
|
'globals': globals_,
|
|
67
74
|
'logFn': log_print,
|
|
68
75
|
'systemPrefix': 'https://craigahobbs.github.io/markdown-up/include/',
|
|
69
|
-
'urlFn': partial(url_file_relative,
|
|
76
|
+
'urlFn': partial(url_file_relative, script_value) if script_type == 'file' else None
|
|
70
77
|
})
|
|
71
78
|
if isinstance(result, (int, float)) and int(result) == result and 0 <= result <= 255:
|
|
72
79
|
status_code = int(result)
|
|
@@ -83,27 +90,23 @@ def main(argv=None):
|
|
|
83
90
|
break
|
|
84
91
|
|
|
85
92
|
except Exception as e: # pylint: disable=broad-exception-caught
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
print(str(e))
|
|
93
|
+
print(f'{script_name}:')
|
|
94
|
+
print(str(e).strip())
|
|
89
95
|
status_code = 1
|
|
90
96
|
|
|
91
97
|
# Return the status code
|
|
92
98
|
sys.exit(status_code)
|
|
93
99
|
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
class _InlineScriptAction(argparse.Action):
|
|
102
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
103
|
+
if 'scripts' not in namespace:
|
|
104
|
+
setattr(namespace, 'scripts', [])
|
|
105
|
+
namespace.scripts.append(('code', values))
|
|
106
|
+
|
|
97
107
|
|
|
108
|
+
class _FileScriptAction(argparse.Action):
|
|
98
109
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
code_count += 1
|
|
103
|
-
setattr(namespace, 'code_count', code_count)
|
|
104
|
-
for value in values:
|
|
105
|
-
files.append((f'-c {code_count}', value))
|
|
106
|
-
elif option_string is None:
|
|
107
|
-
for value in values:
|
|
108
|
-
files.append((value, None))
|
|
109
|
-
setattr(namespace, 'files', files)
|
|
110
|
+
if 'scripts' not in namespace:
|
|
111
|
+
setattr(namespace, 'scripts', [])
|
|
112
|
+
namespace.scripts.extend(('file', value) for value in values)
|
|
@@ -19,6 +19,7 @@ def main(argv=None):
|
|
|
19
19
|
# Command line arguments
|
|
20
20
|
parser = argparse.ArgumentParser(prog='bare', description='The BareScript library documentation tool')
|
|
21
21
|
parser.add_argument('files', metavar='file', nargs='+', help='files to process')
|
|
22
|
+
parser.add_argument('-o', dest='output', metavar='file', help='write output to file')
|
|
22
23
|
args = parser.parse_args(args=argv)
|
|
23
24
|
|
|
24
25
|
# Parse each source file line-by-line
|
|
@@ -56,7 +57,7 @@ def main(argv=None):
|
|
|
56
57
|
|
|
57
58
|
elif key in ('doc', 'return'):
|
|
58
59
|
# Add the documentation line - don't add leading blank lines
|
|
59
|
-
func_doc = func.get(
|
|
60
|
+
func_doc = func.get(key)
|
|
60
61
|
if func_doc is not None or text_trim != '':
|
|
61
62
|
if func_doc is None:
|
|
62
63
|
func_doc = []
|
|
@@ -91,25 +92,25 @@ def main(argv=None):
|
|
|
91
92
|
continue
|
|
92
93
|
|
|
93
94
|
# Add the function arg documentation line - don't add leading blank lines
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if
|
|
97
|
-
|
|
98
|
-
if
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
func['args'] =
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
func_args = func.get('args')
|
|
96
|
+
func_arg = None
|
|
97
|
+
if func_args is not None:
|
|
98
|
+
func_arg = next((find_arg for find_arg in func_args if find_arg['name'] == name), None)
|
|
99
|
+
if func_arg is not None or text_trim != '':
|
|
100
|
+
if func_args is None:
|
|
101
|
+
func_args = []
|
|
102
|
+
func['args'] = func_args
|
|
103
|
+
if func_arg is None:
|
|
104
|
+
func_arg = {'name': name, 'doc': []}
|
|
105
|
+
func_args.append(func_arg)
|
|
106
|
+
func_arg['doc'].append(text)
|
|
106
107
|
|
|
107
108
|
continue
|
|
108
109
|
|
|
109
110
|
# Unknown documentation comment?
|
|
110
111
|
match_unknown = R_UNKNOWN.match(line)
|
|
111
112
|
if match_unknown is not None:
|
|
112
|
-
unknown = match_unknown.groups
|
|
113
|
+
unknown = match_unknown.groups('unknown')
|
|
113
114
|
errors.append(f'{file_}:{ix_line + 1}: Invalid documentation comment "{unknown}"')
|
|
114
115
|
continue
|
|
115
116
|
|
|
@@ -122,18 +123,27 @@ def main(argv=None):
|
|
|
122
123
|
if len(library['functions']) == 0:
|
|
123
124
|
errors.append('error: No library functions')
|
|
124
125
|
for func in library['functions']:
|
|
126
|
+
func_name = func['name']
|
|
125
127
|
if 'group' not in func:
|
|
126
|
-
errors.append(f'error: Function "{
|
|
128
|
+
errors.append(f'error: Function "{func_name}" missing group')
|
|
127
129
|
if 'doc' not in func:
|
|
128
|
-
errors.append(f'error: Function "{
|
|
130
|
+
errors.append(f'error: Function "{func_name}" missing documentation')
|
|
129
131
|
|
|
130
132
|
# Errors?
|
|
131
133
|
if len(errors) != 0:
|
|
132
134
|
print('\n'.join(errors))
|
|
133
|
-
sys.exit(
|
|
135
|
+
sys.exit(1)
|
|
134
136
|
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
+
# JSON-serialize the library documentation model
|
|
138
|
+
library_json = json.dumps(library, separators=(',', ':'), sort_keys=True)
|
|
139
|
+
|
|
140
|
+
# Output to stdout?
|
|
141
|
+
if args.output is None or args.output == '-':
|
|
142
|
+
print(library_json)
|
|
143
|
+
|
|
144
|
+
# Output to file
|
|
145
|
+
with open(args.output, 'w', encoding='utf-8') as fh:
|
|
146
|
+
fh.write(library_json)
|
|
137
147
|
|
|
138
148
|
|
|
139
149
|
# Library documentation regular expressions
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Licensed under the MIT License
|
|
2
|
+
# https://github.com/craigahobbs/bare-script-py/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
The BareScript data manipulation library
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
from schema_markdown import parse_schema_markdown
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_data(unused_data, unused_csv=False):
|
|
14
|
+
"""
|
|
15
|
+
Determine data field types and parse/validate field values
|
|
16
|
+
|
|
17
|
+
:param data: The data array. Row objects are updated with parsed/validated values.
|
|
18
|
+
:type data: list[dict]
|
|
19
|
+
:param csv: If true, parse number and null strings
|
|
20
|
+
:type csv: bool
|
|
21
|
+
:return: The map of field name to field type ("datetime", "number", "string")
|
|
22
|
+
:rtype: dict
|
|
23
|
+
:raises TypeError: Data is invalid
|
|
24
|
+
"""
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
# # Determine field types
|
|
28
|
+
# types = {}
|
|
29
|
+
# for (row of data) {
|
|
30
|
+
# for ([field, value] of Object.entries(row)) {
|
|
31
|
+
# if !(field in types)) {
|
|
32
|
+
# if typeof value == 'number') {
|
|
33
|
+
# types[field] = 'number'
|
|
34
|
+
# elif value instanceof Date:
|
|
35
|
+
# types[field] = 'datetime'
|
|
36
|
+
# elif typeof value == 'string' && (!csv || value != 'null'):
|
|
37
|
+
# if parseDatetime(value) != null:
|
|
38
|
+
# types[field] = 'datetime'
|
|
39
|
+
# elif csv && parseNumber(value) != null:
|
|
40
|
+
# types[field] = 'number'
|
|
41
|
+
# else:
|
|
42
|
+
# types[field] = 'string'
|
|
43
|
+
# }
|
|
44
|
+
# }
|
|
45
|
+
# }
|
|
46
|
+
# }
|
|
47
|
+
# }
|
|
48
|
+
|
|
49
|
+
# # Validate field values
|
|
50
|
+
# throwFieldError = (field, fieldType, fieldValue) => {
|
|
51
|
+
# throw new Error(`Invalid "${field}" field value ${JSON.stringify(fieldValue)}, expected type ${fieldType}`)
|
|
52
|
+
# }
|
|
53
|
+
# for (row of data) {
|
|
54
|
+
# for ([field, value] of Object.entries(row)) {
|
|
55
|
+
# fieldType = types[field]
|
|
56
|
+
|
|
57
|
+
# # Null string?
|
|
58
|
+
# if csv && value == 'null':
|
|
59
|
+
# row[field] = null
|
|
60
|
+
|
|
61
|
+
# # Number field
|
|
62
|
+
# elif fieldType == 'number':
|
|
63
|
+
# if csv && typeof value == 'string':
|
|
64
|
+
# numberValue = parseNumber(value)
|
|
65
|
+
# if numberValue == null:
|
|
66
|
+
# throwFieldError(field, fieldType, value)
|
|
67
|
+
# }
|
|
68
|
+
# row[field] = numberValue
|
|
69
|
+
# elif value != null && typeof value != 'number':
|
|
70
|
+
# throwFieldError(field, fieldType, value)
|
|
71
|
+
# }
|
|
72
|
+
|
|
73
|
+
# # Datetime field
|
|
74
|
+
# elif fieldType == 'datetime':
|
|
75
|
+
# if typeof value == 'string':
|
|
76
|
+
# datetimeValue = parseDatetime(value)
|
|
77
|
+
# if datetimeValue == null:
|
|
78
|
+
# throwFieldError(field, fieldType, value)
|
|
79
|
+
# }
|
|
80
|
+
# row[field] = datetimeValue
|
|
81
|
+
# elif value != null && !(value instanceof Date):
|
|
82
|
+
# throwFieldError(field, fieldType, value)
|
|
83
|
+
# }
|
|
84
|
+
|
|
85
|
+
# # String field
|
|
86
|
+
# else:
|
|
87
|
+
# if value != null && typeof value != 'string':
|
|
88
|
+
# throwFieldError(field, fieldType, value)
|
|
89
|
+
# }
|
|
90
|
+
# }
|
|
91
|
+
# }
|
|
92
|
+
# }
|
|
93
|
+
|
|
94
|
+
# return types
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _parse_number(unused_text):
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
# value = Number.parseFloat(text)
|
|
101
|
+
# if Number.isNaN(value) || !Number.isFinite(value):
|
|
102
|
+
# return null
|
|
103
|
+
# }
|
|
104
|
+
# return value
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _parse_datetime(unused_text):
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
# mDate = text.match(rDate)
|
|
111
|
+
# if mDate != null:
|
|
112
|
+
# year = Number.parseInt(mDate.groups.year, 10)
|
|
113
|
+
# month = Number.parseInt(mDate.groups.month, 10)
|
|
114
|
+
# day = Number.parseInt(mDate.groups.day, 10)
|
|
115
|
+
# return new Date(year, month - 1, day)
|
|
116
|
+
# elif rDatetime.test(text):
|
|
117
|
+
# return new Date(text)
|
|
118
|
+
# }
|
|
119
|
+
# return null
|
|
120
|
+
|
|
121
|
+
R_DATE = re.compile(r'^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$')
|
|
122
|
+
R_DATETIME = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$')
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def join_data(unused_left_data, unused_right_data, unused_join_expr, unused_right_expr=None, unused_is_left_join=False,
|
|
126
|
+
unused_variables=None, unused_options=None):
|
|
127
|
+
"""
|
|
128
|
+
Join two data arrays
|
|
129
|
+
|
|
130
|
+
:param leftData: The left data array
|
|
131
|
+
:type leftData: list[dict]
|
|
132
|
+
:param rightData: The left data array
|
|
133
|
+
:type rightData: list[dict]
|
|
134
|
+
:param joinExpr: The join `expression <https://craigahobbs.github.io/bare-script/language/#expressions>`__
|
|
135
|
+
:type joinExpr: str
|
|
136
|
+
:param rightExpr: The right join `expression <https://craigahobbs.github.io/bare-script/language/#expressions>`__
|
|
137
|
+
:type rightExpr: str
|
|
138
|
+
:param isLeftJoin: If true, perform a left join (always include left row)
|
|
139
|
+
:type isLeftJoin: bool
|
|
140
|
+
:param variables: Additional variables for expression evaluation
|
|
141
|
+
:type variables: dict
|
|
142
|
+
:param options: The :class:`script execution options <ExecuteScriptOptions>`
|
|
143
|
+
:type options: dict
|
|
144
|
+
:return: The joined data array
|
|
145
|
+
:rtype: list[dict]
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
# # Compute the map of row field name to joined row field name
|
|
151
|
+
# leftNames = {}
|
|
152
|
+
# for (row of leftData) {
|
|
153
|
+
# for (fieldName of Object.keys(row)) {
|
|
154
|
+
# if !(fieldName in leftNames):
|
|
155
|
+
# leftNames[fieldName] = fieldName
|
|
156
|
+
# }
|
|
157
|
+
# }
|
|
158
|
+
# }
|
|
159
|
+
# rightNames = {}
|
|
160
|
+
# for (row of rightData) {
|
|
161
|
+
# for (fieldName of Object.keys(row)) {
|
|
162
|
+
# if !(fieldName in rightNames):
|
|
163
|
+
# if !(fieldName in leftNames):
|
|
164
|
+
# rightNames[fieldName] = fieldName
|
|
165
|
+
# else:
|
|
166
|
+
# uniqueName = fieldName
|
|
167
|
+
# ixUnique = 2
|
|
168
|
+
# do {
|
|
169
|
+
# uniqueName = `${fieldName}${ixUnique}`
|
|
170
|
+
# ixUnique += 1
|
|
171
|
+
# } while (uniqueName in leftNames || uniqueName in rightNames)
|
|
172
|
+
# rightNames[fieldName] = uniqueName
|
|
173
|
+
# }
|
|
174
|
+
# }
|
|
175
|
+
# }
|
|
176
|
+
# }
|
|
177
|
+
|
|
178
|
+
# # Create the evaluation options object
|
|
179
|
+
# evalOptions = options
|
|
180
|
+
# if variables != null:
|
|
181
|
+
# evalOptions = (options != null ? {...options} : {})
|
|
182
|
+
# if 'globals' in evalOptions:
|
|
183
|
+
# evalOptions.globals = {...evalOptions.globals, ...variables}
|
|
184
|
+
# else:
|
|
185
|
+
# evalOptions.globals = variables
|
|
186
|
+
# }
|
|
187
|
+
# }
|
|
188
|
+
|
|
189
|
+
# # Parse the left and right expressions
|
|
190
|
+
# leftExpression = parseExpression(joinExpr)
|
|
191
|
+
# rightExpression = (rightExpr != null ? parseExpression(rightExpr) : leftExpression)
|
|
192
|
+
|
|
193
|
+
# # Bucket the right rows by the right expression value
|
|
194
|
+
# rightCategoryRows = {}
|
|
195
|
+
# for (rightRow of rightData) {
|
|
196
|
+
# categoryKey = jsonStringifySortKeys(evaluateExpression(rightExpression, evalOptions, rightRow))
|
|
197
|
+
# if !(categoryKey in rightCategoryRows):
|
|
198
|
+
# rightCategoryRows[categoryKey] = []
|
|
199
|
+
# }
|
|
200
|
+
# rightCategoryRows[categoryKey].push(rightRow)
|
|
201
|
+
# }
|
|
202
|
+
|
|
203
|
+
# # Join the left with the right
|
|
204
|
+
# data = []
|
|
205
|
+
# for (leftRow of leftData) {
|
|
206
|
+
# categoryKey = jsonStringifySortKeys(evaluateExpression(leftExpression, evalOptions, leftRow))
|
|
207
|
+
# if categoryKey in rightCategoryRows:
|
|
208
|
+
# for (rightRow of rightCategoryRows[categoryKey]) {
|
|
209
|
+
# joinRow = {...leftRow}
|
|
210
|
+
# for ([rightName, rightValue] of Object.entries(rightRow)) {
|
|
211
|
+
# joinRow[rightNames[rightName]] = rightValue
|
|
212
|
+
# }
|
|
213
|
+
# data.push(joinRow)
|
|
214
|
+
# }
|
|
215
|
+
# elif !isLeftJoin:
|
|
216
|
+
# data.push({...leftRow})
|
|
217
|
+
# }
|
|
218
|
+
# }
|
|
219
|
+
|
|
220
|
+
# return data
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def add_calculated_field(unused_unused_data, unused_field_name, unused_expr, unused_variables=None, unused_options=None):
|
|
224
|
+
"""
|
|
225
|
+
Add a calculated field to each row of a data array
|
|
226
|
+
|
|
227
|
+
@param {Object[]} data - The data array. Row objects are updated with the calculated field values.
|
|
228
|
+
@param {string} fieldName - The calculated field name
|
|
229
|
+
@param {string} expr - The calculated field expression
|
|
230
|
+
@param {?Object} [variables = null] - Additional variables for expression evaluation
|
|
231
|
+
@param {?Object} [options = null] - The [script execution options]{@link module:lib/runtime~ExecuteScriptOptions}
|
|
232
|
+
@returns {Object[]} - The updated data array
|
|
233
|
+
"""
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
# # Parse the calculation expression
|
|
237
|
+
# calcExpr = parseExpression(expr)
|
|
238
|
+
|
|
239
|
+
# # Create the evaluation options object
|
|
240
|
+
# evalOptions = options
|
|
241
|
+
# if variables != null:
|
|
242
|
+
# evalOptions = (options != null ? {...options} : {})
|
|
243
|
+
# if 'globals' in evalOptions:
|
|
244
|
+
# evalOptions.globals = {...evalOptions.globals, ...variables}
|
|
245
|
+
# else:
|
|
246
|
+
# evalOptions.globals = variables
|
|
247
|
+
# }
|
|
248
|
+
# }
|
|
249
|
+
|
|
250
|
+
# # Compute the calculated field for each row
|
|
251
|
+
# for (row of data) {
|
|
252
|
+
# row[fieldName] = evaluateExpression(calcExpr, evalOptions, row)
|
|
253
|
+
# }
|
|
254
|
+
# return data
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def filter_data(unused_data, unused_expr, unused_variables=None, unused_options=None):
|
|
258
|
+
"""
|
|
259
|
+
Filter data rows
|
|
260
|
+
|
|
261
|
+
@param {Object[]} data - The data array
|
|
262
|
+
@param {string} expr - The boolean filter [expression]{@link https://craigahobbs.github.io/bare-script/language/#expressions}
|
|
263
|
+
@param {?Object} [variables = null] - Additional variables for expression evaluation
|
|
264
|
+
@param {?Object} [options = null] - The [script execution options]{@link module:lib/runtime~ExecuteScriptOptions}
|
|
265
|
+
@returns {Object[]} - The filtered data array
|
|
266
|
+
"""
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
# result = []
|
|
270
|
+
|
|
271
|
+
# # Parse the filter expression
|
|
272
|
+
# filterExpr = parseExpression(expr)
|
|
273
|
+
|
|
274
|
+
# # Create the evaluation options object
|
|
275
|
+
# evalOptions = options
|
|
276
|
+
# if variables != null:
|
|
277
|
+
# evalOptions = (options != null ? {...options} : {})
|
|
278
|
+
# if 'globals' in evalOptions:
|
|
279
|
+
# evalOptions.globals = {...evalOptions.globals, ...variables}
|
|
280
|
+
# else:
|
|
281
|
+
# evalOptions.globals = variables
|
|
282
|
+
# }
|
|
283
|
+
# }
|
|
284
|
+
|
|
285
|
+
# # Filter the data
|
|
286
|
+
# for (row of data) {
|
|
287
|
+
# if evaluateExpression(filterExpr, evalOptions, row):
|
|
288
|
+
# result.push(row)
|
|
289
|
+
# }
|
|
290
|
+
# }
|
|
291
|
+
|
|
292
|
+
# return result
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def aggregate_data(unused_data, unused_aggregation):
|
|
296
|
+
"""
|
|
297
|
+
Aggregate data rows
|
|
298
|
+
|
|
299
|
+
@param {Object[]} data - The data array
|
|
300
|
+
@param {Object} aggregation - The
|
|
301
|
+
[aggregation model]{@link https://craigahobbs.github.io/bare-script/library/model.html#var.vName='Aggregation'}
|
|
302
|
+
@returns {Object[]} - The aggregated data array
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
# Validate the aggregation model
|
|
308
|
+
# validate_type(AGGREGATION_TYPES, 'Aggregation', aggregation)
|
|
309
|
+
|
|
310
|
+
# categories = aggregation.categories ?? null
|
|
311
|
+
|
|
312
|
+
# # Create the aggregate rows
|
|
313
|
+
# categoryRows = {}
|
|
314
|
+
# for (row of data) {
|
|
315
|
+
# # Compute the category values
|
|
316
|
+
# categoryValues = (categories != null ? categories.map((categoryField) => row[categoryField]) : null)
|
|
317
|
+
|
|
318
|
+
# # Get or create the aggregate row
|
|
319
|
+
# aggregateRow
|
|
320
|
+
# rowKey = (categoryValues != null ? jsonStringifySortKeys(categoryValues) : '')
|
|
321
|
+
# if rowKey in categoryRows:
|
|
322
|
+
# aggregateRow = categoryRows[rowKey]
|
|
323
|
+
# else:
|
|
324
|
+
# aggregateRow = {}
|
|
325
|
+
# categoryRows[rowKey] = aggregateRow
|
|
326
|
+
# if categories != null:
|
|
327
|
+
# for (ixCategoryField = 0; ixCategoryField < categories.length; ixCategoryField++) {
|
|
328
|
+
# aggregateRow[categories[ixCategoryField]] = categoryValues[ixCategoryField]
|
|
329
|
+
# }
|
|
330
|
+
# }
|
|
331
|
+
# }
|
|
332
|
+
|
|
333
|
+
# # Add to the aggregate measure values
|
|
334
|
+
# for (measure of aggregation.measures) {
|
|
335
|
+
# field = measure.name ?? measure.field
|
|
336
|
+
# value = row[measure.field] ?? null
|
|
337
|
+
# if !(field in aggregateRow):
|
|
338
|
+
# aggregateRow[field] = []
|
|
339
|
+
# }
|
|
340
|
+
# if value != null:
|
|
341
|
+
# aggregateRow[field].push(value)
|
|
342
|
+
# }
|
|
343
|
+
# }
|
|
344
|
+
# }
|
|
345
|
+
|
|
346
|
+
# # Compute the measure values aggregate function value
|
|
347
|
+
# aggregateRows = Object.values(categoryRows)
|
|
348
|
+
# for (aggregateRow of aggregateRows) {
|
|
349
|
+
# for (measure of aggregation.measures) {
|
|
350
|
+
# field = measure.name ?? measure.field
|
|
351
|
+
# func = measure.function
|
|
352
|
+
# measureValues = aggregateRow[field]
|
|
353
|
+
# if !measureValues.length:
|
|
354
|
+
# aggregateRow[field] = null
|
|
355
|
+
# elif func == 'count':
|
|
356
|
+
# aggregateRow[field] = measureValues.length
|
|
357
|
+
# elif func == 'max':
|
|
358
|
+
# aggregateRow[field] = measureValues.reduce((max, val) => (val > max ? val : max))
|
|
359
|
+
# elif func == 'min':
|
|
360
|
+
# aggregateRow[field] = measureValues.reduce((min, val) => (val < min ? val : min))
|
|
361
|
+
# elif func == 'sum':
|
|
362
|
+
# aggregateRow[field] = measureValues.reduce((sum, val) => sum + val, 0)
|
|
363
|
+
# elif func == 'stddev':
|
|
364
|
+
# average = measureValues.reduce((sum, val) => sum + val, 0) / measureValues.length
|
|
365
|
+
# aggregateRow[field] = Math.sqrt(measureValues.reduce((sum, val) => sum + (val - average) ** 2, 0) / measureValues.length)
|
|
366
|
+
# else:
|
|
367
|
+
# # func == 'average'
|
|
368
|
+
# aggregateRow[field] = measureValues.reduce((sum, val) => sum + val, 0) / measureValues.length
|
|
369
|
+
# }
|
|
370
|
+
# }
|
|
371
|
+
# }
|
|
372
|
+
|
|
373
|
+
# return aggregateRows
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# The aggregation model
|
|
377
|
+
AGGREGATION_TYPES = parse_schema_markdown('''\
|
|
378
|
+
group "Aggregation"
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# A data aggregation specification
|
|
382
|
+
struct Aggregation
|
|
383
|
+
|
|
384
|
+
# The aggregation category fields
|
|
385
|
+
optional string[len > 0] categories
|
|
386
|
+
|
|
387
|
+
# The aggregation measures
|
|
388
|
+
AggregationMeasure[len > 0] measures
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# An aggregation measure specification
|
|
392
|
+
struct AggregationMeasure
|
|
393
|
+
|
|
394
|
+
# The aggregation measure field
|
|
395
|
+
string field
|
|
396
|
+
|
|
397
|
+
# The aggregation function
|
|
398
|
+
AggregationFunction function
|
|
399
|
+
|
|
400
|
+
# The aggregated-measure field name
|
|
401
|
+
optional string name
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
# An aggregation function
|
|
405
|
+
enum AggregationFunction
|
|
406
|
+
|
|
407
|
+
# The average of the measure's values
|
|
408
|
+
average
|
|
409
|
+
|
|
410
|
+
# The count of the measure's values
|
|
411
|
+
count
|
|
412
|
+
|
|
413
|
+
# The greatest of the measure's values
|
|
414
|
+
max
|
|
415
|
+
|
|
416
|
+
# The least of the measure's values
|
|
417
|
+
min
|
|
418
|
+
|
|
419
|
+
# The standard deviation of the measure's values
|
|
420
|
+
stddev
|
|
421
|
+
|
|
422
|
+
# The sum of the measure's values
|
|
423
|
+
sum
|
|
424
|
+
''')
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def sort_data(unused_data, unused_sorts):
|
|
428
|
+
"""
|
|
429
|
+
Sort data rows
|
|
430
|
+
|
|
431
|
+
@param {Object[]} data - The data array
|
|
432
|
+
@param {Object[]} sorts - The sort field-name/descending-sort tuples
|
|
433
|
+
@returns {Object[]} - The sorted data array
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
return None
|
|
437
|
+
|
|
438
|
+
# return data.sort((row1, row2) => sorts.reduce((result, sort) => {
|
|
439
|
+
# if result != 0:
|
|
440
|
+
# return result
|
|
441
|
+
# }
|
|
442
|
+
# [field, desc = false] = sort
|
|
443
|
+
# value1 = row1[field] ?? null
|
|
444
|
+
# value2 = row2[field] ?? null
|
|
445
|
+
# compare = compareValues(value1, value2)
|
|
446
|
+
# return desc ? -compare : compare
|
|
447
|
+
# }, 0))
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def top_data(unused_data, unused_count, unused_category_fields=None):
|
|
451
|
+
"""
|
|
452
|
+
Top data rows
|
|
453
|
+
|
|
454
|
+
@param {Object[]} data - The data array
|
|
455
|
+
@param {number} count - The number of rows to keep
|
|
456
|
+
@param {?string[]} [categoryFields = null] - The category fields
|
|
457
|
+
@returns {Object[]} - The top data array
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
# # Bucket rows by category
|
|
463
|
+
# categoryRows = {}
|
|
464
|
+
# categoryOrder = []
|
|
465
|
+
# for (row of data) {
|
|
466
|
+
# categoryKey = categoryFields == null ? ''
|
|
467
|
+
# : jsonStringifySortKeys(categoryFields.map((field) => (field in row ? row[field] : null)))
|
|
468
|
+
# if !(categoryKey in categoryRows):
|
|
469
|
+
# categoryRows[categoryKey] = []
|
|
470
|
+
# categoryOrder.push(categoryKey)
|
|
471
|
+
# }
|
|
472
|
+
# categoryRows[categoryKey].push(row)
|
|
473
|
+
# }
|
|
474
|
+
# # Take only the top rows
|
|
475
|
+
# dataTop = []
|
|
476
|
+
# topCount = count
|
|
477
|
+
# for (categoryKey of categoryOrder) {
|
|
478
|
+
# categoryKeyRows = categoryRows[categoryKey]
|
|
479
|
+
# categoryKeyLength = categoryKeyRows.length
|
|
480
|
+
# for (ixRow = 0; ixRow < topCount && ixRow < categoryKeyLength; ixRow++) {
|
|
481
|
+
# dataTop.push(categoryKeyRows[ixRow])
|
|
482
|
+
# }
|
|
483
|
+
# }
|
|
484
|
+
# return dataTop
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|