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.
Files changed (22) hide show
  1. {bare-script-0.9.2/src/bare_script.egg-info → bare-script-0.9.3}/PKG-INFO +1 -1
  2. {bare-script-0.9.2 → bare-script-0.9.3}/setup.cfg +1 -1
  3. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/__init__.py +9 -0
  4. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/bare.py +30 -27
  5. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/baredoc.py +29 -19
  6. bare-script-0.9.3/src/bare_script/data.py +484 -0
  7. {bare-script-0.9.2 → bare-script-0.9.3/src/bare_script.egg-info}/PKG-INFO +1 -1
  8. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/SOURCES.txt +1 -0
  9. {bare-script-0.9.2 → bare-script-0.9.3}/LICENSE +0 -0
  10. {bare-script-0.9.2 → bare-script-0.9.3}/README.rst +0 -0
  11. {bare-script-0.9.2 → bare-script-0.9.3}/pyproject.toml +0 -0
  12. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/__main__.py +0 -0
  13. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/library.py +0 -0
  14. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/model.py +0 -0
  15. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/options.py +0 -0
  16. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/parser.py +0 -0
  17. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/runtime.py +0 -0
  18. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script/value.py +0 -0
  19. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/dependency_links.txt +0 -0
  20. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/entry_points.txt +0 -0
  21. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/requires.txt +0 -0
  22. {bare-script-0.9.2 → bare-script-0.9.3}/src/bare_script.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bare-script
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: bare-script
5
5
  Home-page: https://github.com/craigahobbs/bare-script
6
6
  Author: Craig A. Hobbs
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = bare-script
3
- version = 0.9.2
3
+ version = 0.9.3
4
4
  url = https://github.com/craigahobbs/bare-script
5
5
  author = Craig A. Hobbs
6
6
  author_email = craigahobbs@gmail.com
@@ -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=_ScriptAction, help='files to process')
27
- parser.add_argument('-c', '--code', nargs=1, action=_ScriptAction, help='execute the BareScript 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
- current_file = None
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 file_, source in args.files:
40
- current_file = file_
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(source if source is not None else fetch_read_write({'url': file_}))
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 "{file_}" ...'
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
- # pylint: disable=broad-exception-raised
57
- raise Exception('Static analysis failed')
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, file_)
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
- if current_file is not None:
87
- print(f'{current_file}:')
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
- # Custom argparse action for script file arguments
96
- class _ScriptAction(argparse.Action):
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
- files = getattr(namespace, 'files', [])
100
- if option_string in ['-c', '--code']:
101
- code_count = getattr(namespace, 'code_count', 0)
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('key')
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
- args = func.get('args')
95
- arg = None
96
- if args is not None:
97
- arg = next((arg_find for arg_find in args if arg_find['name'] == name), None)
98
- if arg is not None or text_trim != '':
99
- if args is None:
100
- args = []
101
- func['args'] = args
102
- if arg is None:
103
- arg = {'name': name, 'doc': []}
104
- args.append(arg)
105
- arg['doc'].append(text)
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['unknown']
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 "{func.name}" missing group')
128
+ errors.append(f'error: Function "{func_name}" missing group')
127
129
  if 'doc' not in func:
128
- errors.append(f'error: Function "{func.name}" missing documentation')
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(len(errors))
135
+ sys.exit(1)
134
136
 
135
- # Output the library JSON
136
- print(json.dumps(library))
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bare-script
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: bare-script
5
5
  Home-page: https://github.com/craigahobbs/bare-script
6
6
  Author: Craig A. Hobbs
@@ -6,6 +6,7 @@ src/bare_script/__init__.py
6
6
  src/bare_script/__main__.py
7
7
  src/bare_script/bare.py
8
8
  src/bare_script/baredoc.py
9
+ src/bare_script/data.py
9
10
  src/bare_script/library.py
10
11
  src/bare_script/model.py
11
12
  src/bare_script/options.py
File without changes
File without changes
File without changes