bare-script 4.1.3__tar.gz → 4.1.5__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 (44) hide show
  1. {bare_script-4.1.3/src/bare_script.egg-info → bare_script-4.1.5}/PKG-INFO +27 -1
  2. {bare_script-4.1.3 → bare_script-4.1.5}/README.md +26 -0
  3. {bare_script-4.1.3 → bare_script-4.1.5}/setup.cfg +1 -1
  4. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/data.bare +56 -35
  5. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/dataLineChart.bare +42 -22
  6. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/dataTable.bare +79 -61
  7. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/markdownElements.bare +72 -64
  8. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/markdownHighlight.bare +37 -53
  9. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/markdownParser.bare +121 -98
  10. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/markdownUp.bare +16 -0
  11. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/qrcode.bare +75 -43
  12. {bare_script-4.1.3 → bare_script-4.1.5/src/bare_script.egg-info}/PKG-INFO +27 -1
  13. {bare_script-4.1.3 → bare_script-4.1.5}/LICENSE +0 -0
  14. {bare_script-4.1.3 → bare_script-4.1.5}/pyproject.toml +0 -0
  15. {bare_script-4.1.3 → bare_script-4.1.5}/setup.py +0 -0
  16. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/__init__.py +0 -0
  17. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/__main__.py +0 -0
  18. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/bare.py +0 -0
  19. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/__init__.py +0 -0
  20. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/args.bare +0 -0
  21. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/baredoc.bare +0 -0
  22. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/baredocCLI.bare +0 -0
  23. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/dataUtil.bare +0 -0
  24. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/diff.bare +0 -0
  25. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/draw.bare +0 -0
  26. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/elementModel.bare +0 -0
  27. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/forms.bare +0 -0
  28. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/markdown.bare +0 -0
  29. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/pager.bare +0 -0
  30. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/schemaDoc.bare +0 -0
  31. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/unittest.bare +0 -0
  32. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/include/unittestMock.bare +0 -0
  33. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/library.py +0 -0
  34. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/model.py +0 -0
  35. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/options.py +0 -0
  36. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/parser.py +0 -0
  37. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/runtime.py +0 -0
  38. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/runtime_c.c +0 -0
  39. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script/value.py +0 -0
  40. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script.egg-info/SOURCES.txt +0 -0
  41. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script.egg-info/dependency_links.txt +0 -0
  42. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script.egg-info/entry_points.txt +0 -0
  43. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script.egg-info/requires.txt +0 -0
  44. {bare_script-4.1.3 → bare_script-4.1.5}/src/bare_script.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bare-script
3
- Version: 4.1.3
3
+ Version: 4.1.5
4
4
  Summary: bare-script
5
5
  Home-page: https://github.com/craigahobbs/bare-script
6
6
  Author: Craig A. Hobbs
@@ -199,6 +199,32 @@ automatically; otherwise the pure-Python runtime is used as a fallback. Set the
199
199
  variable `BARESCRIPT_RUNTIME_PY=1` to force the pure-Python runtime.
200
200
 
201
201
 
202
+ ## Using BareScript with an AI Assistant
203
+
204
+ This repository ships a
205
+ [`SKILL.md`](https://github.com/craigahobbs/bare-script-py/blob/main/SKILL.md)
206
+ file that teaches an AI coding assistant how to write idiomatic BareScript — language syntax, the
207
+ built-in and include libraries, the MarkdownUp application pattern, and the unit-test conventions.
208
+ It is plain Markdown and applies to either BareScript implementation.
209
+
210
+ For [Claude Code](https://claude.com/claude-code) and other tools that follow the
211
+ [Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)
212
+ convention, install it as a project or user skill:
213
+
214
+ ```
215
+ mkdir -p .claude/skills/bare-script
216
+ cp SKILL.md .claude/skills/bare-script/SKILL.md
217
+ ```
218
+
219
+ Use `~/.claude/skills/bare-script/SKILL.md` instead to make it available across all projects. For
220
+ other assistants, include the file's contents in your system prompt or rules file.
221
+
222
+ Once installed, you can prompt the assistant with tasks like:
223
+
224
+ > Build a MarkdownUp application that plays tic-tac-toe against the user, with a reset button
225
+ > and a running win/loss/draw tally rendered as a bar chart.
226
+
227
+
202
228
  ## Development
203
229
 
204
230
  This package is developed using [python-build](https://github.com/craigahobbs/python-build#readme).
@@ -174,6 +174,32 @@ automatically; otherwise the pure-Python runtime is used as a fallback. Set the
174
174
  variable `BARESCRIPT_RUNTIME_PY=1` to force the pure-Python runtime.
175
175
 
176
176
 
177
+ ## Using BareScript with an AI Assistant
178
+
179
+ This repository ships a
180
+ [`SKILL.md`](https://github.com/craigahobbs/bare-script-py/blob/main/SKILL.md)
181
+ file that teaches an AI coding assistant how to write idiomatic BareScript — language syntax, the
182
+ built-in and include libraries, the MarkdownUp application pattern, and the unit-test conventions.
183
+ It is plain Markdown and applies to either BareScript implementation.
184
+
185
+ For [Claude Code](https://claude.com/claude-code) and other tools that follow the
186
+ [Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)
187
+ convention, install it as a project or user skill:
188
+
189
+ ```
190
+ mkdir -p .claude/skills/bare-script
191
+ cp SKILL.md .claude/skills/bare-script/SKILL.md
192
+ ```
193
+
194
+ Use `~/.claude/skills/bare-script/SKILL.md` instead to make it available across all projects. For
195
+ other assistants, include the file's contents in your system prompt or rules file.
196
+
197
+ Once installed, you can prompt the assistant with tasks like:
198
+
199
+ > Build a MarkdownUp application that plays tic-tac-toe against the user, with a reset button
200
+ > and a running win/loss/draw tally rendered as a bar chart.
201
+
202
+
177
203
  ## Development
178
204
 
179
205
  This package is developed using [python-build](https://github.com/craigahobbs/python-build#readme).
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = bare-script
3
- version = 4.1.3
3
+ version = 4.1.5
4
4
  url = https://github.com/craigahobbs/bare-script
5
5
  author = Craig A. Hobbs
6
6
  author_email = craigahobbs@gmail.com
@@ -33,8 +33,8 @@ function dataParseCSV(text):
33
33
  linePart = line
34
34
  while linePart != null:
35
35
  # Quoted field?
36
- mQuoted = regexMatch(dataParseCSVQuotedField, linePart)
37
- if mQuoted != null:
36
+ mQuoted = stringStartsWith(linePart, '"') && regexMatch(dataParseCSVQuotedField, linePart)
37
+ if mQuoted:
38
38
  groups = objectGet(mQuoted, 'groups')
39
39
  arrayPush(row, stringReplace(objectGet(groups, '1'), '""', '"'))
40
40
  linePart = stringSlice(linePart, stringLength(objectGet(groups, '0')))
@@ -96,8 +96,8 @@ function dataValidate(data, csv):
96
96
  types = {}
97
97
  for row in data:
98
98
  for field in objectKeys(row):
99
- value = objectGet(row, field)
100
99
  if !objectGet(types, field):
100
+ value = objectGet(row, field)
101
101
  valueType = systemType(value)
102
102
  if valueType == 'boolean':
103
103
  objectSet(types, field, 'boolean')
@@ -137,14 +137,12 @@ function dataValidate(data, csv):
137
137
  endif
138
138
  endfor
139
139
 
140
- # Validate field values
140
+ # Validate field values (typeKeys ensures fieldType is always set by the fix-up pass above)
141
+ typeKeys = objectKeys(types)
141
142
  for row in data:
142
- for field in objectKeys(row):
143
+ for field in typeKeys:
143
144
  value = objectGet(row, field)
144
145
  fieldType = objectGet(types, field)
145
- if fieldType == null:
146
- continue
147
- endif
148
146
  valueType = systemType(value)
149
147
 
150
148
  # Null string?
@@ -324,10 +322,15 @@ function dataCalculatedField(data, fieldName, expr, variables):
324
322
  calcExpr = barescriptParseExpression(expr, false)
325
323
 
326
324
  # Compute the calculated field for each row
327
- for row in data:
328
- rowVars = if(variables != null, objectAssign(objectCopy(row), variables), row)
329
- objectSet(row, fieldName, barescriptEvaluateExpression(calcExpr, rowVars))
330
- endfor
325
+ if variables != null:
326
+ for row in data:
327
+ objectSet(row, fieldName, barescriptEvaluateExpression(calcExpr, objectAssign(objectCopy(row), variables)))
328
+ endfor
329
+ else:
330
+ for row in data:
331
+ objectSet(row, fieldName, barescriptEvaluateExpression(calcExpr, row))
332
+ endfor
333
+ endif
331
334
 
332
335
  return data
333
336
  endfunction
@@ -347,12 +350,19 @@ function dataFilter(data, expr, variables):
347
350
  filterExpr = barescriptParseExpression(expr, false)
348
351
 
349
352
  # Filter the data
350
- for row in data:
351
- rowVars = if(variables != null, objectAssign(objectCopy(row), variables), row)
352
- if systemBoolean(barescriptEvaluateExpression(filterExpr, rowVars)):
353
- arrayPush(result, row)
354
- endif
355
- endfor
353
+ if variables != null:
354
+ for row in data:
355
+ if barescriptEvaluateExpression(filterExpr, objectAssign(objectCopy(row), variables)):
356
+ arrayPush(result, row)
357
+ endif
358
+ endfor
359
+ else:
360
+ for row in data:
361
+ if barescriptEvaluateExpression(filterExpr, row):
362
+ arrayPush(result, row)
363
+ endif
364
+ endfor
365
+ endif
356
366
 
357
367
  return result
358
368
  endfunction
@@ -374,6 +384,17 @@ function dataAggregate(data, aggregation):
374
384
  categories = objectGet(aggregation, 'categories')
375
385
  measures = objectGet(aggregation, 'measures')
376
386
 
387
+ # Precompute per-measure metadata: [outputName, inputFieldKey, function]
388
+ measureMeta = []
389
+ for measure in measures:
390
+ measureFieldKey = objectGet(measure, 'field')
391
+ arrayPush(measureMeta, [ \
392
+ objectGet(measure, 'name', measureFieldKey), \
393
+ measureFieldKey, \
394
+ objectGet(measure, 'function') \
395
+ ])
396
+ endfor
397
+
377
398
  # Create the aggregate rows
378
399
  categoryRows = {}
379
400
  for row in data:
@@ -391,7 +412,7 @@ function dataAggregate(data, aggregation):
391
412
 
392
413
  # Get or create the aggregate row
393
414
  aggregateRow = objectGet(categoryRows, rowKey)
394
- if aggregateRow == null:
415
+ if !aggregateRow:
395
416
  aggregateRow = {}
396
417
  objectSet(categoryRows, rowKey, aggregateRow)
397
418
  if categories != null:
@@ -402,13 +423,13 @@ function dataAggregate(data, aggregation):
402
423
  endif
403
424
 
404
425
  # Accumulate measure values
405
- for measure in measures:
406
- measureField = objectGet(measure, 'name', objectGet(measure, 'field'))
407
- measureValue = objectGet(row, objectGet(measure, 'field'))
408
- measureValues = objectGet(aggregateRow, measureField)
426
+ for meta in measureMeta:
427
+ measureName = arrayGet(meta, 0)
428
+ measureValue = objectGet(row, arrayGet(meta, 1))
429
+ measureValues = objectGet(aggregateRow, measureName)
409
430
  if measureValues == null:
410
431
  measureValues = []
411
- objectSet(aggregateRow, measureField, measureValues)
432
+ objectSet(aggregateRow, measureName, measureValues)
412
433
  endif
413
434
  if measureValue != null:
414
435
  arrayPush(measureValues, measureValue)
@@ -421,32 +442,32 @@ function dataAggregate(data, aggregation):
421
442
  for categoryRowsKey in objectKeys(categoryRows):
422
443
  aggregateRow = objectGet(categoryRows, categoryRowsKey)
423
444
  arrayPush(aggregateRows, aggregateRow)
424
- for measure in measures:
425
- measureField = objectGet(measure, 'name', objectGet(measure, 'field'))
426
- measureFunc = objectGet(measure, 'function')
427
- measureValues = objectGet(aggregateRow, measureField)
445
+ for meta in measureMeta:
446
+ measureName = arrayGet(meta, 0)
447
+ measureFunc = arrayGet(meta, 2)
448
+ measureValues = objectGet(aggregateRow, measureName)
428
449
  if !arrayLength(measureValues):
429
- objectSet(aggregateRow, measureField, null)
450
+ objectSet(aggregateRow, measureName, null)
430
451
  elif measureFunc == 'count':
431
- objectSet(aggregateRow, measureField, arrayLength(measureValues))
452
+ objectSet(aggregateRow, measureName, arrayLength(measureValues))
432
453
  elif measureFunc == 'max':
433
454
  maxVal = arrayGet(measureValues, 0)
434
455
  for val in measureValues:
435
456
  maxVal = if(val > maxVal, val, maxVal)
436
457
  endfor
437
- objectSet(aggregateRow, measureField, maxVal)
458
+ objectSet(aggregateRow, measureName, maxVal)
438
459
  elif measureFunc == 'min':
439
460
  minVal = arrayGet(measureValues, 0)
440
461
  for val in measureValues:
441
462
  minVal = if(val < minVal, val, minVal)
442
463
  endfor
443
- objectSet(aggregateRow, measureField, minVal)
464
+ objectSet(aggregateRow, measureName, minVal)
444
465
  elif measureFunc == 'sum':
445
466
  sumVal = 0
446
467
  for val in measureValues:
447
468
  sumVal = sumVal + val
448
469
  endfor
449
- objectSet(aggregateRow, measureField, sumVal)
470
+ objectSet(aggregateRow, measureName, sumVal)
450
471
  elif measureFunc == 'stddev':
451
472
  sumVal = 0
452
473
  for val in measureValues:
@@ -457,14 +478,14 @@ function dataAggregate(data, aggregation):
457
478
  for val in measureValues:
458
479
  sumSqDiff = sumSqDiff + (val - avgVal) ** 2
459
480
  endfor
460
- objectSet(aggregateRow, measureField, mathSqrt(sumSqDiff / arrayLength(measureValues)))
481
+ objectSet(aggregateRow, measureName, mathSqrt(sumSqDiff / arrayLength(measureValues)))
461
482
  else:
462
483
  # measureFunc == 'average'
463
484
  sumVal = 0
464
485
  for val in measureValues:
465
486
  sumVal = sumVal + val
466
487
  endfor
467
- objectSet(aggregateRow, measureField, sumVal / arrayLength(measureValues))
488
+ objectSet(aggregateRow, measureName, sumVal / arrayLength(measureValues))
468
489
  endif
469
490
  endfor
470
491
  endfor
@@ -195,22 +195,27 @@ function dataLineChartElements(data, lineChart, options):
195
195
  yMin = null
196
196
  xMax = null
197
197
  yMax = null
198
+ chartPrecision = objectGet(lineChart, 'precision')
199
+ chartDatetime = objectGet(lineChart, 'datetime')
198
200
  if colorField != null:
199
201
  # Determine the set of color encoding values
202
+ isSingleY = (arrayLength(yFields) == 1)
200
203
  colorValueSet = {}
201
204
  pointsMap = {}
202
205
  for row in data:
206
+ xRow = objectGet(row, xField)
207
+ colorValue = dataUtilFormatValue(objectGet(row, colorField), chartPrecision, chartDatetime)
203
208
  for yField in yFields:
204
- colorValue = dataUtilFormatValue(objectGet(row, colorField), objectGet(lineChart, 'precision'), objectGet(lineChart, 'datetime'))
205
- rowKey = if(arrayLength(yFields) == 1, colorValue, yField + ', ' + colorValue)
209
+ rowKey = if(isSingleY, colorValue, yField + ', ' + colorValue)
206
210
  objectSet(colorValueSet, rowKey, true)
207
- xRow = objectGet(row, xField)
208
211
  yRow = objectGet(row, yField)
209
212
  if xRow != null && yRow != null:
210
- if !objectHas(pointsMap, rowKey):
211
- objectSet(pointsMap, rowKey, [])
213
+ rowPoints = objectGet(pointsMap, rowKey)
214
+ if rowPoints == null:
215
+ rowPoints = []
216
+ objectSet(pointsMap, rowKey, rowPoints)
212
217
  endif
213
- arrayPush(objectGet(pointsMap, rowKey), [xRow, yRow])
218
+ arrayPush(rowPoints, [xRow, yRow])
214
219
  xMin = if(xMin == null, xRow, xMin)
215
220
  yMin = if(yMin == null, yRow, if(yRow < yMin, yRow, yMin))
216
221
  xMax = xRow
@@ -265,8 +270,6 @@ function dataLineChartElements(data, lineChart, options):
265
270
  chartTitle = objectGet(lineChart, 'title')
266
271
  chartWidth = objectGet(lineChart, 'width', dataLineChartDefaultWidth)
267
272
  chartHeight = objectGet(lineChart, 'height', dataLineChartDefaultHeight)
268
- chartPrecision = objectGet(lineChart, 'precision')
269
- chartDatetime = objectGet(lineChart, 'datetime')
270
273
 
271
274
  # Compute Y-axis tick values
272
275
  yAxisTicks = []
@@ -515,29 +518,46 @@ function dataLineChartElements(data, lineChart, options):
515
518
 
516
519
  # Render lines
517
520
  lineElements = []
521
+ xRange = xMax - xMin
522
+ yRange = yMax - yMin
523
+ xScale = if(xRange == 0, 0, (chartRight - chartLeft) / xRange)
524
+ yScale = if(yRange == 0, 0, (chartTop - chartBottom) / yRange)
525
+ svgPrecision = dataLineChartSvgPrecision
526
+ chartLineWidthValue = numberToFixed(dataLineChartChartLineWidth, svgPrecision)
518
527
  for linePoint in linePointsReversed:
519
528
  linePointColor = objectGet(linePoint, 'color')
520
529
  linePointPoints = objectGet(linePoint, 'points')
521
530
  linePointParts = []
522
- for point, ixPoint in linePointPoints:
523
- xCoord = arrayGet(point, 0)
524
- yCoord = arrayGet(point, 1)
525
- xPoint = dataLineChartChartPoint(xCoord, xMin, xMax, chartLeft, chartRight)
526
- yPoint = dataLineChartChartPoint(yCoord, yMin, yMax, chartBottom, chartTop)
527
- if arrayLength(linePointPoints) == 1:
528
- arrayPush(linePointParts, \
529
- 'M ' + dataLineChartSvgValue(xPoint - 0.5 * dataLineChartChartLineWidth) + ' ' + dataLineChartSvgValue(yPoint) + \
530
- ' L ' + dataLineChartSvgValue(xPoint + 0.5 * dataLineChartChartLineWidth) + ' ' + dataLineChartSvgValue(yPoint))
531
- else:
531
+ nPoints = arrayLength(linePointPoints)
532
+ if nPoints == 1:
533
+ point = arrayGet(linePointPoints, 0)
534
+ xPoint = chartLeft + (arrayGet(point, 0) - xMin) * xScale
535
+ yPoint = chartBottom + (arrayGet(point, 1) - yMin) * yScale
536
+ yPointStr = numberToFixed(yPoint, svgPrecision)
537
+ arrayPush(linePointParts, \
538
+ 'M ' + numberToFixed(xPoint - 0.5 * dataLineChartChartLineWidth, svgPrecision) + ' ' + yPointStr + \
539
+ ' L ' + numberToFixed(xPoint + 0.5 * dataLineChartChartLineWidth, svgPrecision) + ' ' + yPointStr)
540
+ else:
541
+ firstPoint = arrayGet(linePointPoints, 0)
542
+ xPoint = chartLeft + (arrayGet(firstPoint, 0) - xMin) * xScale
543
+ yPoint = chartBottom + (arrayGet(firstPoint, 1) - yMin) * yScale
544
+ arrayPush(linePointParts, \
545
+ 'M ' + numberToFixed(xPoint, svgPrecision) + ' ' + numberToFixed(yPoint, svgPrecision))
546
+ ixPoint = 1
547
+ while ixPoint < nPoints:
548
+ point = arrayGet(linePointPoints, ixPoint)
549
+ xPoint = chartLeft + (arrayGet(point, 0) - xMin) * xScale
550
+ yPoint = chartBottom + (arrayGet(point, 1) - yMin) * yScale
532
551
  arrayPush(linePointParts, \
533
- if(ixPoint == 0, 'M ', 'L ') + dataLineChartSvgValue(xPoint) + ' ' + dataLineChartSvgValue(yPoint))
534
- endif
535
- endfor
552
+ 'L ' + numberToFixed(xPoint, svgPrecision) + ' ' + numberToFixed(yPoint, svgPrecision))
553
+ ixPoint = ixPoint + 1
554
+ endwhile
555
+ endif
536
556
  arrayPush(lineElements, { \
537
557
  'svg': 'path', \
538
558
  'attr': { \
539
559
  'stroke': linePointColor, \
540
- 'stroke-width': dataLineChartSvgValue(dataLineChartChartLineWidth), \
560
+ 'stroke-width': chartLineWidthValue, \
541
561
  'fill': 'none', \
542
562
  'd': arrayJoin(linePointParts, ' ') \
543
563
  } \
@@ -111,55 +111,60 @@ function dataTableMarkdown(data, model):
111
111
  precisionTrim = if(model != null, objectGet(model, 'trim', true), true)
112
112
  formats = if(model != null, objectGet(model, 'formats'))
113
113
 
114
- # Compute the field header widths
114
+ # Initialize widths to the field header width
115
115
  widths = {}
116
116
  for field in fields:
117
- fieldWidth = stringLength(field)
118
- if !objectHas(widths, field) || fieldWidth > objectGet(widths, field):
119
- objectSet(widths, field, fieldWidth)
120
- endif
117
+ objectSet(widths, field, stringLength(field))
121
118
  endfor
122
119
 
123
120
  # Compute the formatted field value strings and widths
121
+ hasDatetimeFormat = precisionDatetime != null
124
122
  dataFormat = []
125
123
  for row in data:
126
124
  rowFormat = {}
127
125
  arrayPush(dataFormat, rowFormat)
128
126
  for field in fields:
129
- # Format the value
127
+ # Format the value (newline regex only on string/other types - numbers and datetimes can't contain newlines)
130
128
  value = objectGet(row, field)
131
129
  valueType = systemType(value)
132
- if valueType == 'string':
133
- valueFormat = stringTrim(value)
134
- elif valueType == 'number':
130
+ if valueType == 'number':
135
131
  valueFormat = numberToFixed(value, precisionNumber, precisionTrim)
136
132
  elif valueType == 'datetime':
137
- valueFormat = datetimeISOFormat(value, precisionDatetime != null)
133
+ valueFormat = datetimeISOFormat(value, hasDatetimeFormat)
138
134
  else:
139
- valueFormat = stringNew(value)
135
+ valueFormat = if(valueType == 'string', stringTrim(value), stringNew(value))
136
+ valueFormat = regexReplace(dataTableMarkdownRegexMultipleNewline, valueFormat, '<br><br>')
137
+ valueFormat = regexReplace(dataTableMarkdownRegexSingleNewline, valueFormat, ' ')
140
138
  endif
141
139
 
142
- # Eliminate value string newlines
143
- valueFormat = regexReplace(dataTableMarkdownRegexMultipleNewline, valueFormat, '<br><br>')
144
- valueFormat = regexReplace(dataTableMarkdownRegexSingleNewline, valueFormat, ' ')
145
-
146
140
  # Set the field value string
147
141
  objectSet(rowFormat, field, valueFormat)
148
142
 
149
- # Update the field width
143
+ # Update the field width (widths is initialized above for every field)
150
144
  valueWidth = stringLength(valueFormat)
151
- if !objectHas(widths, field) || valueWidth > objectGet(widths, field):
145
+ if valueWidth > objectGet(widths, field):
152
146
  objectSet(widths, field, valueWidth)
153
147
  endif
154
148
  endfor
155
149
  endfor
156
150
 
157
- # Compute the field header separator
158
- headerSeparator = ''
151
+ # Precompute per-field rendering metadata: [field, width, align, header]
152
+ fieldMeta = []
159
153
  for field in fields:
160
- width = objectGet(widths, field)
161
154
  format = if(formats != null, objectGet(formats, field))
162
- align = if(format != null, objectGet(format, 'align'))
155
+ arrayPush(fieldMeta, [ \
156
+ field, \
157
+ objectGet(widths, field), \
158
+ if(format != null, objectGet(format, 'align')), \
159
+ if(format != null, objectGet(format, 'header', field), field) \
160
+ ])
161
+ endfor
162
+
163
+ # Compute the field header separator
164
+ headerSeparator = ''
165
+ for meta in fieldMeta:
166
+ width = arrayGet(meta, 1)
167
+ align = arrayGet(meta, 2)
163
168
  alignLeft = if(align == 'center', ':', '-')
164
169
  alignRight = if(align == 'center' || align == 'right', ':', '-')
165
170
  headerSeparator = headerSeparator + '|' + alignLeft + dataTableMarkdownField('', width, align, '-') + alignRight
@@ -168,11 +173,10 @@ function dataTableMarkdown(data, model):
168
173
 
169
174
  # Compute the table header fields
170
175
  headerFields = ''
171
- for field in fields:
172
- width = objectGet(widths, field)
173
- format = if(formats != null, objectGet(formats, field))
174
- align = if(format != null, objectGet(format, 'align'))
175
- header = if(format != null, objectGet(format, 'header', field), field)
176
+ for meta in fieldMeta:
177
+ width = arrayGet(meta, 1)
178
+ align = arrayGet(meta, 2)
179
+ header = arrayGet(meta, 3)
176
180
  headerFields = headerFields + '| ' + dataTableMarkdownField(header, width, align, ' ') + ' '
177
181
  endfor
178
182
  headerFields = headerFields + '|'
@@ -184,15 +188,14 @@ function dataTableMarkdown(data, model):
184
188
 
185
189
  # Output each row
186
190
  for row in dataFormat:
187
- line = ''
188
- for field in fields:
189
- width = objectGet(widths, field)
190
- format = if(formats != null, objectGet(formats, field))
191
- align = if(format != null, objectGet(format, 'align'))
192
- line = line + '| ' + dataTableMarkdownField(objectGet(row, field), width, align, ' ') + ' '
191
+ parts = []
192
+ for meta in fieldMeta:
193
+ field = arrayGet(meta, 0)
194
+ width = arrayGet(meta, 1)
195
+ align = arrayGet(meta, 2)
196
+ arrayPush(parts, dataTableMarkdownField(objectGet(row, field), width, align, ' '))
193
197
  endfor
194
- line = line + '|'
195
- arrayPush(lines, line)
198
+ arrayPush(lines, '| ' + arrayJoin(parts, ' | ') + ' |')
196
199
  endfor
197
200
 
198
201
  return lines
@@ -271,20 +274,34 @@ function dataTableElements(data, dataTable):
271
274
  resultRows = []
272
275
  resultElements = {'html': 'table', 'elem': resultRows}
273
276
 
274
- # Header row
275
- headerRowElements = []
277
+ # Precompute per-field metadata: [field, isCategory, attr, isMarkdown, headerText]
276
278
  fieldArrays = [categories, tableFields]
277
- for fieldArray in fieldArrays:
279
+ fieldMeta = []
280
+ for fieldArray, ixFieldArray in fieldArrays:
281
+ isCategory = ixFieldArray == 0
278
282
  for field in fieldArray:
279
283
  fieldFormat = if(fieldFormats != null, objectGet(fieldFormats, field))
280
- formatHeader = if(fieldFormat != null, objectGet(fieldFormat, 'header'))
281
- arrayPush(headerRowElements, { \
282
- 'html': 'th', \
283
- 'attr': dataTableElementsFieldAttr(fieldFormat), \
284
- 'elem': {'text': formatHeader || field} \
285
- })
284
+ arrayPush(fieldMeta, [ \
285
+ field, \
286
+ isCategory, \
287
+ dataTableElementsFieldAttr(fieldFormat), \
288
+ fieldFormat != null && objectGet(fieldFormat, 'markdown'), \
289
+ if(fieldFormat != null, objectGet(fieldFormat, 'header')) || field \
290
+ ])
286
291
  endfor
287
292
  endfor
293
+
294
+ # Header row
295
+ headerRowElements = []
296
+ for meta in fieldMeta:
297
+ fieldAttr = arrayGet(meta, 2)
298
+ headerText = arrayGet(meta, 4)
299
+ arrayPush(headerRowElements, { \
300
+ 'html': 'th', \
301
+ 'attr': fieldAttr, \
302
+ 'elem': {'text': headerText} \
303
+ })
304
+ endfor
288
305
  arrayPush(resultRows, {'html': 'tr', 'elem': headerRowElements})
289
306
 
290
307
  # Generate the data table's element model
@@ -292,26 +309,27 @@ function dataTableElements(data, dataTable):
292
309
  rowPrev = if(ixRow > 0, arrayGet(data, ixRow - 1))
293
310
  skip = rowPrev != null
294
311
  fieldRowElements = []
295
- for fieldArray, ixFieldArray in fieldArrays:
296
- for field in fieldArray:
297
- value = objectGet(row, field)
312
+ for meta in fieldMeta:
313
+ field = arrayGet(meta, 0)
314
+ fieldAttr = arrayGet(meta, 2)
315
+ isMarkdown = arrayGet(meta, 3)
316
+ value = objectGet(row, field)
298
317
 
299
- # Skip this value?
300
- if skip:
301
- skip = ixFieldArray == 0 && systemCompare(value, objectGet(rowPrev, field)) == 0
302
- endif
318
+ # Skip this value?
319
+ if skip:
320
+ isCategory = arrayGet(meta, 1)
321
+ skip = isCategory && systemCompare(value, objectGet(rowPrev, field)) == 0
322
+ endif
303
323
 
304
- fieldFormat = if(fieldFormats != null, objectGet(fieldFormats, field))
305
- fieldElements = if(fieldFormat != null && objectGet(fieldFormat, 'markdown'), \
306
- markdownElements(markdownParse(stringNew(value))), \
307
- {'text': dataUtilFormatValue(value, formatPrecision, formatDatetime, formatTrim)} \
308
- )
309
- arrayPush(fieldRowElements, { \
310
- 'html': 'td', \
311
- 'attr': dataTableElementsFieldAttr(fieldFormat), \
312
- 'elem': if(skip, null, fieldElements) \
313
- })
314
- endfor
324
+ fieldElements = if(isMarkdown, \
325
+ markdownElements(markdownParse(stringNew(value))), \
326
+ {'text': dataUtilFormatValue(value, formatPrecision, formatDatetime, formatTrim)} \
327
+ )
328
+ arrayPush(fieldRowElements, { \
329
+ 'html': 'td', \
330
+ 'attr': fieldAttr, \
331
+ 'elem': if(skip, null, fieldElements) \
332
+ })
315
333
  endfor
316
334
  arrayPush(resultRows, {'html': 'tr', 'elem': fieldRowElements})
317
335
  endfor