bare-script 5.0.0__tar.gz → 5.0.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 (25) hide show
  1. {bare_script-5.0.0/src/bare_script.egg-info → bare_script-5.0.3}/PKG-INFO +1 -1
  2. {bare_script-5.0.0 → bare_script-5.0.3}/setup.cfg +1 -1
  3. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/include_source.py +251 -211
  4. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/value.py +21 -3
  5. {bare_script-5.0.0 → bare_script-5.0.3/src/bare_script.egg-info}/PKG-INFO +1 -1
  6. {bare_script-5.0.0 → bare_script-5.0.3}/LICENSE +0 -0
  7. {bare_script-5.0.0 → bare_script-5.0.3}/README.md +0 -0
  8. {bare_script-5.0.0 → bare_script-5.0.3}/pyproject.toml +0 -0
  9. {bare_script-5.0.0 → bare_script-5.0.3}/setup.py +0 -0
  10. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/__init__.py +0 -0
  11. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/__main__.py +0 -0
  12. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/bare.py +0 -0
  13. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/include.py +0 -0
  14. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/library.py +0 -0
  15. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/lint.py +0 -0
  16. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/model.py +0 -0
  17. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/options.py +0 -0
  18. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/parser.py +0 -0
  19. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/runtime.py +0 -0
  20. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script/runtime_c.c +0 -0
  21. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script.egg-info/SOURCES.txt +0 -0
  22. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script.egg-info/dependency_links.txt +0 -0
  23. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script.egg-info/entry_points.txt +0 -0
  24. {bare_script-5.0.0 → bare_script-5.0.3}/src/bare_script.egg-info/requires.txt +0 -0
  25. {bare_script-5.0.0 → bare_script-5.0.3}/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: 5.0.0
3
+ Version: 5.0.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 = 5.0.0
3
+ version = 5.0.3
4
4
  url = https://github.com/craigahobbs/bare-script
5
5
  author = Craig A. Hobbs
6
6
  author_email = craigahobbs@gmail.com
@@ -138,7 +138,7 @@ function argsURL(arguments, args, explicit, headerText, url):
138
138
  default = objectGet(argument, \'default\')
139
139
 
140
140
  # Add the argument name (for unknown argument check below)
141
- objectSet(argNames, name, null)
141
+ objectSet(argNames, name, true)
142
142
 
143
143
  # Add the URL variable, if any
144
144
  value = null
@@ -168,7 +168,7 @@ function argsURL(arguments, args, explicit, headerText, url):
168
168
 
169
169
  # Create the URL
170
170
  return \'#\' + if(url != null, \'url=\' + urlEncodeComponent(url) + \'&\', \'\') + \\
171
- if(arrayLength(urlVars), arrayJoin(urlVars, \'&\'), \'var=\') + \\
171
+ if(urlVars, arrayJoin(urlVars, \'&\'), \'var=\') + \\
172
172
  if(headerText != null, \'&\' + if(headerText == argsTopHeaderId, argsTopHeaderId, markdownHeaderId(headerText)), \'\')
173
173
  endfunction
174
174
 
@@ -265,14 +265,12 @@ function argsFormatValue(value, type):
265
265
  return "\'" + datetimeISOFormat(value, true) + "\'"
266
266
  elif type == \'datetime\':
267
267
  return "\'" + datetimeISOFormat(value) + "\'"
268
- elif type == \'float\':
269
- return stringNew(value)
270
- elif type == \'int\':
268
+ elif type == \'float\' || type == \'int\':
271
269
  return stringNew(value)
272
270
  endif
273
271
 
274
- # type == \'string\'
275
- return "\'" + value + "\'"
272
+ # type == \'string\' - escape backslashes and single-quotes for the URL argument expression
273
+ return "\'" + stringReplace(stringReplace(value, \'\\\\\', \'\\\\\\\\\'), "\'", "\\\\\'") + "\'"
276
274
  endfunction
277
275
 
278
276
 
@@ -300,7 +298,8 @@ function argsValidateValue(value, type, global, warn):
300
298
  value = datetimeISOParse(value)
301
299
  valueType = systemType(value)
302
300
  endif
303
- if valueType != \'datetime\' || datetimeHour(value) != 0 || datetimeMinute(value) != 0 || datetimeSecond(value) != 0:
301
+ if valueType != \'datetime\' || datetimeHour(value) != 0 || datetimeMinute(value) != 0 || datetimeSecond(value) != 0 || \\
302
+ datetimeMillisecond(value) != 0:
304
303
  if warn != false:
305
304
  systemLogDebug(\'args.bare: Invalid value \' + jsonStringify(valueOrig) + \' for URL argument "\' + global + \'"\')
306
305
  endif
@@ -558,7 +557,7 @@ async function baredocSinglePage(config, args, sectionGroups, groups):
558
557
  arrayPush(urls, url)
559
558
  endif
560
559
  endfor
561
- texts = systemFetch(urls)
560
+ texts = if(urls, systemFetch(urls), [])
562
561
  groupContent = {}
563
562
  for groupName, ixName in contentNames:
564
563
  text = arrayGet(texts, ixName)
@@ -837,11 +836,14 @@ baredocCLIMainArguments = argsValidate([ \\
837
836
  function baredocCLIParse(functions, source, filename):
838
837
  errors = []
839
838
 
839
+ # Note the previously-parsed functions - only functions added by this call are validated below
840
+ functionsPrev = objectCopy(functions)
841
+
840
842
  # Process documentation comments line-by-line
841
843
  func = null
842
844
  for line, ixLine in stringSplitLines(source):
843
845
  # function/group/doc/return documentation keywords?
844
- matchKey = regexMatch(baredocCLIMainRegexKey, line)
846
+ matchKey = regexMatch(baredocCLIParseRegexKey, line)
845
847
  if matchKey != null:
846
848
  groupsKey = objectGet(matchKey, \'groups\')
847
849
  key = objectGet(groupsKey, \'key\')
@@ -885,7 +887,7 @@ function baredocCLIParse(functions, source, filename):
885
887
  arrayPush(errors, filename + \':\' + (ixLine + 1) + \': Invalid ignore value "\' + textTrim + \'"\')
886
888
  continue
887
889
  endif
888
- objectSet(func, key, text == \'true\')
890
+ objectSet(func, key, textTrim == \'true\')
889
891
 
890
892
  else:
891
893
  # key == \'function\'
@@ -907,7 +909,7 @@ function baredocCLIParse(functions, source, filename):
907
909
  endif
908
910
 
909
911
  # arg keyword?
910
- matchArg = regexMatch(baredocCLIMainRegexArg, line)
912
+ matchArg = regexMatch(baredocCLIParseRegexArg, line)
911
913
  if matchArg != null:
912
914
  groupsArg = objectGet(matchArg, \'groups\')
913
915
  name = objectGet(groupsArg, \'name\')
@@ -955,7 +957,7 @@ function baredocCLIParse(functions, source, filename):
955
957
  endif
956
958
 
957
959
  # Unknown documentation comment?
958
- matchUnknown = regexMatch(baredocCLIMainRegexUnknown, line)
960
+ matchUnknown = regexMatch(baredocCLIParseRegexUnknown, line)
959
961
  if matchUnknown != null:
960
962
  groupsUnknown = objectGet(matchUnknown, \'groups\')
961
963
  unknown = objectGet(groupsUnknown, \'unknown\')
@@ -964,15 +966,12 @@ function baredocCLIParse(functions, source, filename):
964
966
  endif
965
967
  endfor
966
968
 
967
- # Create the sorted function model array
968
- libraryFunctions = []
969
+ # Validate the function documentation models added by this call
969
970
  for funcName in arraySort(objectKeys(functions)):
970
- arrayPush(libraryFunctions, objectGet(functions, funcName))
971
- endfor
972
-
973
- # Validate the library documentation model
974
- for funcObj in libraryFunctions:
975
- funcName = objectGet(funcObj, \'name\')
971
+ if objectHas(functionsPrev, funcName):
972
+ continue
973
+ endif
974
+ funcObj = objectGet(functions, funcName)
976
975
  if !objectHas(funcObj, \'group\'):
977
976
  arrayPush(errors, \'error: Function "\' + funcName + \'" missing group\')
978
977
  endif
@@ -989,9 +988,9 @@ endfunction
989
988
 
990
989
 
991
990
  # baredocCLIParse regular expressions
992
- baredocCLIMainRegexKey = regexNew(\'^\\\\s*(?:\\\\/\\\\/|#)\\\\s*\\\\$(?<key>function|group|doc|return|ignore):\\\\s?(?<text>.*)$\')
993
- baredocCLIMainRegexArg = regexNew(\'^\\\\s*(?:\\\\/\\\\/|#)\\\\s*\\\\$arg\\\\s+(?<name>[A-Za-z_][A-Za-z0-9_]*(?:\\\\.\\\\.\\\\.)?):\\\\s?(?<text>.*)$\')
994
- baredocCLIMainRegexUnknown = regexNew(\'^\\\\s*(?:\\\\/\\\\/|#)\\\\s*\\\\$(?<unknown>[^:]+):\')
991
+ baredocCLIParseRegexKey = regexNew(\'^\\\\s*(?:\\\\/\\\\/|#)\\\\s*\\\\$(?<key>function|group|doc|return|ignore):\\\\s?(?<text>.*)$\')
992
+ baredocCLIParseRegexArg = regexNew(\'^\\\\s*(?:\\\\/\\\\/|#)\\\\s*\\\\$arg\\\\s+(?<name>[A-Za-z_][A-Za-z0-9_]*(?:\\\\.\\\\.\\\\.)?):\\\\s?(?<text>.*)$\')
993
+ baredocCLIParseRegexUnknown = regexNew(\'^\\\\s*(?:\\\\/\\\\/|#)\\\\s*\\\\$(?<unknown>[^:]+):\')
995
994
  '''
996
995
 
997
996
 
@@ -1007,8 +1006,9 @@ include <schemaParser.bare>
1007
1006
  # $function: dataParseCSV
1008
1007
  # $group: data.bare
1009
1008
  # $doc: Parse CSV text to a data array
1010
- # $arg text: The CSV text
1011
- # $return: The data array
1009
+ # $arg text: The CSV text or array of CSV text
1010
+ # $return: The data array. String values are parsed into typed values; if a value fails to parse as its
1011
+ # $return: column\'s type, a debug message is logged and the remaining column values are left as strings.
1012
1012
  function dataParseCSV(text):
1013
1013
  # Collect all lines from variadic text arguments
1014
1014
  if systemType(text) == \'array\':
@@ -1039,7 +1039,9 @@ function dataParseCSV(text):
1039
1039
  if mQuoted:
1040
1040
  groups = objectGet(mQuoted, \'groups\')
1041
1041
  arrayPush(row, stringReplace(objectGet(groups, \'1\'), \'""\', \'"\'))
1042
- linePart = stringSlice(linePart, stringLength(objectGet(groups, \'0\')))
1042
+
1043
+ # Continue parsing fields only on a comma delimiter - at end-of-line the field is the last
1044
+ linePart = if(objectGet(groups, \'2\') == \',\', stringSlice(linePart, stringLength(objectGet(groups, \'0\'))))
1043
1045
  continue
1044
1046
  endif
1045
1047
 
@@ -1244,7 +1246,8 @@ endfunction
1244
1246
  # $arg joinExpr: The join expression
1245
1247
  # $arg rightExpr: Optional (default is null).
1246
1248
  # $arg rightExpr: The right join expression
1247
- # $arg isLeftJoin: Optional (default is false). If true, perform a left join (always include left row).
1249
+ # $arg isLeftJoin: Optional (default is false). By default, all left rows are included in the result.
1250
+ # $arg isLeftJoin: If true, left rows with no matching right row are excluded.
1248
1251
  # $arg variables: Optional (default is null). A variables object for join expression evaluation.
1249
1252
  # $return: The joined data array
1250
1253
  function dataJoin(leftData, rightData, joinExpr, rightExpr, isLeftJoin, variables):
@@ -1252,32 +1255,30 @@ function dataJoin(leftData, rightData, joinExpr, rightExpr, isLeftJoin, variable
1252
1255
  leftNames = {}
1253
1256
  for leftRow in leftData:
1254
1257
  for fieldName in objectKeys(leftRow):
1255
- if !objectHas(leftNames, fieldName):
1256
- objectSet(leftNames, fieldName, fieldName)
1257
- endif
1258
+ objectSet(leftNames, fieldName, fieldName)
1258
1259
  endfor
1259
1260
  endfor
1260
1261
 
1261
1262
  rightNamesRaw = {}
1262
1263
  for rightRow in rightData:
1263
1264
  for fieldName in objectKeys(rightRow):
1264
- if !objectHas(rightNamesRaw, fieldName):
1265
- objectSet(rightNamesRaw, fieldName, fieldName)
1266
- endif
1265
+ objectSet(rightNamesRaw, fieldName, fieldName)
1267
1266
  endfor
1268
1267
  endfor
1269
1268
 
1270
1269
  rightNames = {}
1270
+ rightNamesUsed = {}
1271
1271
  for fieldName in objectKeys(rightNamesRaw):
1272
1272
  if !objectHas(leftNames, fieldName):
1273
1273
  objectSet(rightNames, fieldName, fieldName)
1274
1274
  else:
1275
1275
  ixUnique = 2
1276
1276
  uniqueName = fieldName + ixUnique
1277
- while objectHas(leftNames, uniqueName) || objectHas(rightNames, uniqueName) || objectHas(rightNamesRaw, uniqueName):
1277
+ while objectHas(leftNames, uniqueName) || objectHas(rightNamesUsed, uniqueName) || objectHas(rightNamesRaw, uniqueName):
1278
1278
  ixUnique = ixUnique + 1
1279
1279
  uniqueName = fieldName + ixUnique
1280
1280
  endwhile
1281
+ objectSet(rightNamesUsed, uniqueName, uniqueName)
1281
1282
  objectSet(rightNames, fieldName, uniqueName)
1282
1283
  endif
1283
1284
  endfor
@@ -1291,12 +1292,12 @@ function dataJoin(leftData, rightData, joinExpr, rightExpr, isLeftJoin, variable
1291
1292
  for rightRow in rightData:
1292
1293
  rightRowVars = if(variables != null, objectAssign(objectCopy(rightRow), variables), rightRow)
1293
1294
  categoryKey = jsonStringify(barescriptEvaluateExpression(rightExprParsed, rightRowVars))
1294
- rightCategoryKeys = objectGet(rightCategoryRows, categoryKey)
1295
- if rightCategoryKeys == null:
1296
- rightCategoryKeys = []
1297
- objectSet(rightCategoryRows, categoryKey, rightCategoryKeys)
1295
+ rightRows = objectGet(rightCategoryRows, categoryKey)
1296
+ if rightRows == null:
1297
+ rightRows = []
1298
+ objectSet(rightCategoryRows, categoryKey, rightRows)
1298
1299
  endif
1299
- arrayPush(rightCategoryKeys, rightRow)
1300
+ arrayPush(rightRows, rightRow)
1300
1301
  endfor
1301
1302
 
1302
1303
  # Join the left with the right
@@ -1327,7 +1328,7 @@ endfunction
1327
1328
  # $arg data: The data array
1328
1329
  # $arg fieldName: The calculated field name
1329
1330
  # $arg expr: The calculated field expression
1330
- # $arg variables: Optional (default is null). A variables object the expression evaluation.
1331
+ # $arg variables: Optional (default is null). A variables object for the expression evaluation.
1331
1332
  # $return: The updated data array
1332
1333
  function dataCalculatedField(data, fieldName, expr, variables):
1333
1334
  # Parse the calculation expression
@@ -1353,7 +1354,7 @@ endfunction
1353
1354
  # $doc: Filter a data array
1354
1355
  # $arg data: The data array
1355
1356
  # $arg expr: The filter expression
1356
- # $arg variables: Optional (default is null). A variables object the expression evaluation.
1357
+ # $arg variables: Optional (default is null). A variables object for the expression evaluation.
1357
1358
  # $return: The filtered data array
1358
1359
  function dataFilter(data, expr, variables):
1359
1360
  result = []
@@ -1604,7 +1605,7 @@ function dataTop(data, count, categoryFields):
1604
1605
  if categoryFields != null:
1605
1606
  categoryValues = []
1606
1607
  for field in categoryFields:
1607
- arrayPush(categoryValues, if(objectHas(row, field), objectGet(row, field), null))
1608
+ arrayPush(categoryValues, objectGet(row, field))
1608
1609
  endfor
1609
1610
  categoryKey = jsonStringify(categoryValues)
1610
1611
  else:
@@ -1621,18 +1622,18 @@ function dataTop(data, count, categoryFields):
1621
1622
  endfor
1622
1623
 
1623
1624
  # Take only the top rows per category
1624
- dataTop = []
1625
+ result = []
1625
1626
  for categoryKey in categoryOrder:
1626
1627
  categoryKeyRows = objectGet(categoryRows, categoryKey)
1627
1628
  categoryKeyLength = arrayLength(categoryKeyRows)
1628
1629
  ixRow = 0
1629
1630
  while ixRow < count && ixRow < categoryKeyLength:
1630
- arrayPush(dataTop, arrayGet(categoryKeyRows, ixRow))
1631
+ arrayPush(result, arrayGet(categoryKeyRows, ixRow))
1631
1632
  ixRow = ixRow + 1
1632
1633
  endwhile
1633
1634
  endfor
1634
1635
 
1635
- return dataTop
1636
+ return result
1636
1637
  endfunction
1637
1638
  '''
1638
1639
 
@@ -1862,15 +1863,17 @@ function dataLineChartElements(data, lineChart, options):
1862
1863
  colorValue = dataUtilFormatValue(objectGet(row, colorField), chartPrecision, chartDatetime)
1863
1864
  for yField in yFields:
1864
1865
  rowKey = if(isSingleY, colorValue, yField + \', \' + colorValue)
1865
- objectSet(colorValueSet, rowKey, true)
1866
1866
  yRow = objectGet(row, yField)
1867
1867
  if xRow != null && yRow != null:
1868
+ objectSet(colorValueSet, rowKey, true)
1868
1869
  rowPoints = objectGet(pointsMap, rowKey)
1869
1870
  if rowPoints == null:
1870
1871
  rowPoints = []
1871
1872
  objectSet(pointsMap, rowKey, rowPoints)
1872
1873
  endif
1873
1874
  arrayPush(rowPoints, [xRow, yRow])
1875
+
1876
+ # The rows are sorted by the X field, so the first X value is the min and the last is the max
1874
1877
  xMin = if(xMin == null, xRow, xMin)
1875
1878
  yMin = if(yMin == null, yRow, if(yRow < yMin, yRow, yMin))
1876
1879
  xMax = xRow
@@ -1896,10 +1899,9 @@ function dataLineChartElements(data, lineChart, options):
1896
1899
  # Create a line for each y-field
1897
1900
  for yField, ixField in yFields:
1898
1901
  color = arrayGet(dataLineChartCategoricalColors, ixField % arrayLength(dataLineChartCategoricalColors))
1899
- points = []
1900
- arrayPush(linePoints, {\'label\': yField, \'color\': color, \'points\': points})
1901
1902
 
1902
- # Add the points
1903
+ # Add the points - each y-field iterates the sorted rows separately, so compare in full for min/max
1904
+ points = []
1903
1905
  for row in data:
1904
1906
  xRow = objectGet(row, xField)
1905
1907
  yRow = objectGet(row, yField)
@@ -1907,10 +1909,15 @@ function dataLineChartElements(data, lineChart, options):
1907
1909
  arrayPush(points, [xRow, yRow])
1908
1910
  xMin = if(xMin == null, xRow, if(xRow < xMin, xRow, xMin))
1909
1911
  yMin = if(yMin == null, yRow, if(yRow < yMin, yRow, yMin))
1910
- xMax = xRow
1912
+ xMax = if(xMax == null, xRow, if(xRow > xMax, xRow, xMax))
1911
1913
  yMax = if(yMax == null, yRow, if(yRow > yMax, yRow, yMax))
1912
1914
  endif
1913
1915
  endfor
1916
+
1917
+ # Skip y-fields with no points
1918
+ if points:
1919
+ arrayPush(linePoints, {\'label\': yField, \'color\': color, \'points\': points})
1920
+ endif
1914
1921
  endfor
1915
1922
  endif
1916
1923
  linePointsReversed = arrayReverse(arrayCopy(linePoints))
@@ -2614,20 +2621,30 @@ function dataTableMarkdown(data, model):
2614
2621
  endfor
2615
2622
  arrayExtend(fields, objectKeys(fieldNames))
2616
2623
  endif
2617
- if !arrayLength(fields):
2624
+ if !fields:
2618
2625
  return null
2619
2626
  endif
2620
2627
 
2621
2628
  # Get precision and formatting
2622
- precisionDatetime = if(model != null, objectGet(model, \'datetime\'))
2623
- precisionNumber = if(model != null, objectGet(model, \'precision\', 2), 2)
2624
- precisionTrim = if(model != null, objectGet(model, \'trim\', true), true)
2629
+ formatDatetime = if(model != null, objectGet(model, \'datetime\'))
2630
+ formatPrecision = if(model != null, objectGet(model, \'precision\'))
2631
+ formatTrim = if(model != null, objectGet(model, \'trim\'))
2625
2632
  formats = if(model != null, objectGet(model, \'formats\'))
2626
2633
 
2627
- # Initialize widths to the field header width
2634
+ # Precompute per-field rendering metadata, [field, align, header, isCategory], and
2635
+ # initialize widths to the field header width
2636
+ fieldMeta = []
2628
2637
  widths = {}
2629
2638
  for field in fields:
2630
- objectSet(widths, field, stringLength(field))
2639
+ format = if(formats != null, objectGet(formats, field))
2640
+ header = if(format != null, objectGet(format, \'header\', field), field)
2641
+ arrayPush(fieldMeta, [ \\
2642
+ field, \\
2643
+ if(format != null, objectGet(format, \'align\')), \\
2644
+ header, \\
2645
+ modelCategories != null && arrayIndexOf(modelCategories, field) != -1 \\
2646
+ ])
2647
+ objectSet(widths, field, stringLength(header))
2631
2648
  endfor
2632
2649
 
2633
2650
  # Compute the formatted field value strings and widths
@@ -2640,7 +2657,7 @@ function dataTableMarkdown(data, model):
2640
2657
  value = objectGet(row, field)
2641
2658
  valueType = systemType(value)
2642
2659
  if valueType == \'number\' || valueType == \'datetime\':
2643
- valueFormat = dataUtilFormatValue(value, precisionNumber, precisionDatetime, precisionTrim)
2660
+ valueFormat = dataUtilFormatValue(value, formatPrecision, formatDatetime, formatTrim)
2644
2661
  else:
2645
2662
  valueFormat = if(valueType == \'string\', stringTrim(value), stringNew(value))
2646
2663
  valueFormat = regexReplace(dataTableMarkdownRegexMultipleNewline, valueFormat, \'<br><br>\')
@@ -2658,24 +2675,16 @@ function dataTableMarkdown(data, model):
2658
2675
  endfor
2659
2676
  endfor
2660
2677
 
2661
- # Precompute per-field rendering metadata: [field, width, align, header, isCategory]
2662
- fieldMeta = []
2663
- for field in fields:
2664
- format = if(formats != null, objectGet(formats, field))
2665
- arrayPush(fieldMeta, [ \\
2666
- field, \\
2667
- objectGet(widths, field), \\
2668
- if(format != null, objectGet(format, \'align\')), \\
2669
- if(format != null, objectGet(format, \'header\', field), field), \\
2670
- modelCategories != null && arrayIndexOf(modelCategories, field) != -1 \\
2671
- ])
2678
+ # Add each field\'s computed width to its metadata, [field, align, header, isCategory, width]
2679
+ for meta in fieldMeta:
2680
+ arrayPush(meta, objectGet(widths, arrayGet(meta, 0)))
2672
2681
  endfor
2673
2682
 
2674
2683
  # Compute the field header separator
2675
2684
  headerSeparator = \'\'
2676
2685
  for meta in fieldMeta:
2677
- width = arrayGet(meta, 1)
2678
- align = arrayGet(meta, 2)
2686
+ align = arrayGet(meta, 1)
2687
+ width = arrayGet(meta, 4)
2679
2688
  alignLeft = if(align == \'center\', \':\', \'-\')
2680
2689
  alignRight = if(align == \'center\' || align == \'right\', \':\', \'-\')
2681
2690
  headerSeparator = headerSeparator + \'|\' + alignLeft + dataTableMarkdownField(\'\', width, align, \'-\') + alignRight
@@ -2685,9 +2694,9 @@ function dataTableMarkdown(data, model):
2685
2694
  # Compute the table header fields
2686
2695
  headerFields = \'\'
2687
2696
  for meta in fieldMeta:
2688
- width = arrayGet(meta, 1)
2689
- align = arrayGet(meta, 2)
2690
- header = arrayGet(meta, 3)
2697
+ align = arrayGet(meta, 1)
2698
+ header = arrayGet(meta, 2)
2699
+ width = arrayGet(meta, 4)
2691
2700
  headerFields = headerFields + \'| \' + dataTableMarkdownField(header, width, align, \' \') + \' \'
2692
2701
  endfor
2693
2702
  headerFields = headerFields + \'|\'
@@ -2705,12 +2714,12 @@ function dataTableMarkdown(data, model):
2705
2714
  parts = []
2706
2715
  for meta in fieldMeta:
2707
2716
  field = arrayGet(meta, 0)
2708
- width = arrayGet(meta, 1)
2709
- align = arrayGet(meta, 2)
2717
+ align = arrayGet(meta, 1)
2718
+ width = arrayGet(meta, 4)
2710
2719
 
2711
2720
  # Skip this value? (blank a category cell matching the previous row)
2712
2721
  if skip:
2713
- isCategory = arrayGet(meta, 4)
2722
+ isCategory = arrayGet(meta, 3)
2714
2723
  skip = isCategory && systemCompare(objectGet(rowRaw, field), objectGet(rowPrevRaw, field)) == 0
2715
2724
  endif
2716
2725
 
@@ -2807,7 +2816,7 @@ function dataTableElements(data, dataTable):
2807
2816
  isCategory, \\
2808
2817
  dataTableElementsFieldAttr(fieldFormat), \\
2809
2818
  fieldFormat != null && objectGet(fieldFormat, \'markdown\'), \\
2810
- if(fieldFormat != null, objectGet(fieldFormat, \'header\')) || field \\
2819
+ if(fieldFormat != null, objectGet(fieldFormat, \'header\', field), field) \\
2811
2820
  ])
2812
2821
  endfor
2813
2822
  endfor
@@ -2842,14 +2851,12 @@ function dataTableElements(data, dataTable):
2842
2851
  skip = isCategory && systemCompare(value, objectGet(rowPrev, field)) == 0
2843
2852
  endif
2844
2853
 
2845
- fieldElements = if(isMarkdown, \\
2846
- markdownElements(markdownParse(stringNew(value))), \\
2847
- {\'text\': dataUtilFormatValue(value, formatPrecision, formatDatetime, formatTrim)} \\
2848
- )
2849
2854
  arrayPush(fieldRowElements, { \\
2850
2855
  \'html\': \'td\', \\
2851
2856
  \'attr\': fieldAttr, \\
2852
- \'elem\': if(skip, null, fieldElements) \\
2857
+ \'elem\': if(skip, null, if(isMarkdown, \\
2858
+ markdownElements(markdownParse(stringNew(value))), \\
2859
+ {\'text\': dataUtilFormatValue(value, formatPrecision, formatDatetime, formatTrim)})) \\
2853
2860
  })
2854
2861
  endfor
2855
2862
  arrayPush(resultRows, {\'html\': \'tr\', \'elem\': fieldRowElements})
@@ -2994,17 +3001,13 @@ function diffLines(left, right):
2994
3001
  leftLength = arrayLength(leftLines)
2995
3002
  rightLength = arrayLength(rightLines)
2996
3003
  while ixLeft < leftLength || ixRight < rightLength:
2997
- # If we\'ve run out of lines on either side
3004
+ # If we\'ve run out of lines on either side (the loop condition ensures lines remain on the other)
2998
3005
  if ixLeft >= leftLength:
2999
- if ixRight < rightLength:
3000
- arrayPush(diffs, {\'type\': \'Add\', \'lines\': arraySlice(rightLines, ixRight)})
3001
- endif
3006
+ arrayPush(diffs, {\'type\': \'Add\', \'lines\': arraySlice(rightLines, ixRight)})
3002
3007
  break
3003
3008
  endif
3004
3009
  if ixRight >= rightLength:
3005
- if ixLeft < leftLength:
3006
- arrayPush(diffs, {\'type\': \'Remove\', \'lines\': arraySlice(leftLines, ixLeft)})
3007
- endif
3010
+ arrayPush(diffs, {\'type\': \'Remove\', \'lines\': arraySlice(leftLines, ixLeft)})
3008
3011
  break
3009
3012
  endif
3010
3013
 
@@ -3036,16 +3039,12 @@ function diffLines(left, right):
3036
3039
  ixLeftTmp = ixLeftTmp + 1
3037
3040
  endwhile
3038
3041
 
3039
- # If no match found, use remaining lines
3042
+ # If no match found, use remaining lines (lines remain on both sides or we\'d have broken above)
3040
3043
  if matchIxLeft == null:
3041
- if ixLeft < leftLength:
3042
- arrayPush(diffs, {\'type\': \'Remove\', \'lines\': arraySlice(leftLines, ixLeft)})
3043
- ixLeft = leftLength
3044
- endif
3045
- if ixRight < rightLength:
3046
- arrayPush(diffs, {\'type\': \'Add\', \'lines\': arraySlice(rightLines, ixRight)})
3047
- ixRight = rightLength
3048
- endif
3044
+ arrayPush(diffs, {\'type\': \'Remove\', \'lines\': arraySlice(leftLines, ixLeft)})
3045
+ arrayPush(diffs, {\'type\': \'Add\', \'lines\': arraySlice(rightLines, ixRight)})
3046
+ ixLeft = leftLength
3047
+ ixRight = rightLength
3049
3048
  continue
3050
3049
  endif
3051
3050
 
@@ -3359,7 +3358,7 @@ endfunction
3359
3358
  # $function: drawOnClick
3360
3359
  # $group: draw.bare
3361
3360
  # $doc: Set the most recent drawing object\'s on-click event handler
3362
- # $arg callback: The on-click event callback function (x, y)
3361
+ # $arg callback: The on-click event callback function (x, y, width, height)
3363
3362
  function drawOnClick(callback):
3364
3363
  drawing = drawGetCurrentDrawing()
3365
3364
  svg = objectGet(drawing, \'svg\')
@@ -3429,13 +3428,19 @@ endfunction
3429
3428
  # $arg strokeDashArray: [dash array](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray#usage_notes).
3430
3429
  function drawStyle(stroke, strokeWidth, fill, strokeDashArray):
3431
3430
  drawing = drawGetCurrentDrawing()
3431
+
3432
+ # Apply the style defaults and compare with the current styles - an unchanged style continues the current path
3433
+ stroke = if(stroke != null, stroke, \'black\')
3434
+ strokeWidth = if(strokeWidth != null, strokeWidth, 1)
3435
+ fill = if(fill != null, fill, \'none\')
3436
+ strokeDashArray = if(strokeDashArray != null, strokeDashArray, \'none\')
3432
3437
  if stroke != objectGet(drawing, \'stroke\') || strokeWidth != objectGet(drawing, \'strokeWidth\') || \\
3433
3438
  strokeDashArray != objectGet(drawing, \'strokeDashArray\') || fill != objectGet(drawing, \'fill\'):
3434
3439
  drawFinishDrawingPath(drawing)
3435
- objectSet(drawing, \'stroke\', if(stroke != null, stroke, \'black\'))
3436
- objectSet(drawing, \'strokeWidth\', if(strokeWidth != null, strokeWidth, 1))
3437
- objectSet(drawing, \'strokeDashArray\', if(strokeDashArray != null, strokeDashArray, \'none\'))
3438
- objectSet(drawing, \'fill\', if(fill != null, fill, \'none\'))
3440
+ objectSet(drawing, \'stroke\', stroke)
3441
+ objectSet(drawing, \'strokeWidth\', strokeWidth)
3442
+ objectSet(drawing, \'strokeDashArray\', strokeDashArray)
3443
+ objectSet(drawing, \'fill\', fill)
3439
3444
  endif
3440
3445
  endfunction
3441
3446
 
@@ -3639,10 +3644,22 @@ function elementModelValidateEx(elements):
3639
3644
  endif
3640
3645
  endif
3641
3646
 
3642
- # Validate creation callback
3647
+ # Validate the element callback - a map of event name to event callback function
3643
3648
  callback = objectGet(elements, \'callback\')
3644
- if callback != null && systemType(callback) != \'function\':
3645
- return {\'error\': \'Invalid element callback function \' + elementModelTruncate(jsonStringify(callback)) + \' (type "\' + systemType(callback) + \'")\'}
3649
+ if callback != null:
3650
+ callbackType = systemType(callback)
3651
+ if callbackType != \'object\':
3652
+ return {\'error\': \'Invalid element callback function \' + elementModelTruncate(jsonStringify(callback)) + \' (type "\' + callbackType + \'")\'}
3653
+ endif
3654
+
3655
+ # Validate the map\'s event callback functions
3656
+ for callbackEvent in objectKeys(callback):
3657
+ eventCallback = objectGet(callback, callbackEvent)
3658
+ if systemType(eventCallback) != \'function\':
3659
+ return {\'error\': \'Invalid element event callback function \' + elementModelTruncate(jsonStringify(eventCallback)) + \\
3660
+ \' (type "\' + systemType(eventCallback) + \'")\'}
3661
+ endif
3662
+ endfor
3646
3663
  endif
3647
3664
 
3648
3665
  return {\'result\': elements}
@@ -3714,9 +3731,8 @@ function elementModelToStringHelper(elements, indentStr, level):
3714
3731
  attrStr = \'\'
3715
3732
  attr = objectGet(elements, \'attr\')
3716
3733
  if tag == \'svg\':
3717
- if attr == null:
3718
- attr = {}
3719
- endif
3734
+ # Add the SVG namespace attribute (copy the attributes - the caller\'s model is not modified)
3735
+ attr = if(attr != null, objectCopy(attr), {})
3720
3736
  objectSet(attr, \'xmlns\', \'http://www.w3.org/2000/svg\')
3721
3737
  endif
3722
3738
  if attr != null:
@@ -3914,7 +3930,7 @@ function markdownParserGetSpanText(span):
3914
3930
  endfor
3915
3931
  return arrayJoin(parts, \'\')
3916
3932
  endif
3917
- return objectGet(span, \'text\')
3933
+ return objectGet(span, \'text\', \'\')
3918
3934
  endfunction
3919
3935
 
3920
3936
 
@@ -5664,7 +5680,6 @@ INCLUDE_SOURCE_MARKDOWN_PARSER = '''\
5664
5680
 
5665
5681
 
5666
5682
  # Markdown regex
5667
- markdownParserRegexLineSplit = regexNew(\'\\r?\\n\')
5668
5683
  markdownParserRegexParagraphEmpty = regexNew(\'^\\\\s*$\')
5669
5684
  markdownParserRegexIndent = regexNew(\'^(?<indent> *)(?<notIndent>.*)$\')
5670
5685
  markdownParserRegexHeading = regexNew(\'^ {0,3}(?<heading>#{1,6})\\\\s+(?<text>.*?)(?:\\\\s+#+)?\\\\s*$\')
@@ -5828,9 +5843,6 @@ function markdownParseInternal(markdown, startLineNumber, linkRefsRaw):
5828
5843
  markdownParserCloseParagraph( \\
5829
5844
  markdownParts, paragraphLines, paragraphPart, paragraphLineNumber, fencedMark, linkRefs, null \\
5830
5845
  )
5831
- paragraphPart = null
5832
- paragraphLineNumber = null
5833
- tablePart = null
5834
5846
  matchHeadingGroups = objectGet(matchHeading, \'groups\')
5835
5847
  paragraphLines = [objectGet(matchHeadingGroups, \'text\')]
5836
5848
  headingStyle = \'h\' + stringLength(objectGet(matchHeadingGroups, \'heading\'))
@@ -6085,7 +6097,7 @@ function markdownParserCloseParagraph(markdownParts, paragraphLines, paragraphPa
6085
6097
  endif
6086
6098
 
6087
6099
  # Check for more link reference definitions
6088
- text = stringSlice(text, stringLength(objectGet(objectGet(matchLinkDef, \'groups\'), \'0\')))
6100
+ text = stringSlice(text, stringLength(objectGet(matchLinkDefGroups, \'0\')))
6089
6101
  matchLinkDef = regexMatch(markdownParserRegexLinkDef, text)
6090
6102
  endwhile
6091
6103
 
@@ -6381,7 +6393,8 @@ function markdownParserCreateLinkRefSpan(refText, optText, fullText, linkRefs, t
6381
6393
  arrayPush(objectGet(linkRefs, \'links\'), { \\
6382
6394
  \'refKey\': markdownParserGetLinkRefKey(refText), \\
6383
6395
  \'linkSpan\': {\'link\': { \\
6384
- \'spans\': if(systemType(optText) == \'array\', optText, markdownParserParagraphSpans(optText || refText, linkRefs)) \\
6396
+ \'spans\': if(systemType(optText) == \'array\', optText, \\
6397
+ markdownParserParagraphSpans(if(optText != null, optText, refText), linkRefs)) \\
6385
6398
  }}, \\
6386
6399
  \'linkRefSpan\': linkRefSpan \\
6387
6400
  })
@@ -6394,7 +6407,7 @@ function markdownParserCreateImageRefSpan(refText, optText, fullText, linkRefs,
6394
6407
  linkRefSpan = {\'linkRef\': {\'spans\': markdownParserCreateFallbackSpan(fullText, linkRefs, textFallback)}}
6395
6408
  arrayPush(objectGet(linkRefs, \'links\'), { \\
6396
6409
  \'refKey\': markdownParserGetLinkRefKey(refText), \\
6397
- \'imageSpan\': {\'image\': {\'alt\': markdownParserGetImageAltText(optText || refText)}}, \\
6410
+ \'imageSpan\': {\'image\': {\'alt\': markdownParserGetImageAltText(if(optText != null, optText, refText))}}, \\
6398
6411
  \'linkRefSpan\': linkRefSpan \\
6399
6412
  })
6400
6413
  return linkRefSpan
@@ -6844,7 +6857,7 @@ endfunction
6844
6857
  # $arg text: The text to write
6845
6858
  # $arg type: The clipboard content type (default is "text/plain")
6846
6859
  function windowClipboardWrite(text):
6847
- return objectSet(markdownUpState, \'windowClipboard\', text)
6860
+ objectSet(markdownUpState, \'windowClipboard\', text)
6848
6861
  endfunction
6849
6862
 
6850
6863
 
@@ -6931,6 +6944,7 @@ endfunction
6931
6944
  # $arg contentType: Optional (default is "text/plain"). The object content type.
6932
6945
  # $return: The object URL string
6933
6946
  function windowURLObject(data, contentType):
6947
+ contentType = if(contentType != null, contentType, \'text/plain\')
6934
6948
  return \'blob:\' + urlEncode(contentType) + \'-\' + urlEncode(if(stringLength(data) < 20, data, stringSlice(data, 0, 20)))
6935
6949
  endfunction
6936
6950
 
@@ -7067,17 +7081,16 @@ async function pagerMain(pagerModel, options):
7067
7081
  for page, ixPage in pages:
7068
7082
  pageName = objectGet(page, \'name\')
7069
7083
  pageHidden = objectGet(page, \'hidden\')
7070
- pageTypeKey = arrayGet(objectKeys(objectGet(page, \'type\')), 0)
7071
- pageNavigable = pageTypeKey != \'link\'
7084
+ pageNavigable = !objectHas(objectGet(page, \'type\'), \'link\')
7072
7085
 
7073
7086
  # Start page?
7074
- if pageNavigable && !pageHidden && startPageName == null:
7087
+ if !startPageExplicit && pageName == optionStart:
7075
7088
  startPageName = pageName
7076
7089
  startPageIndex = ixPage
7077
- elif !startPageExplicit && pageName == optionStart:
7090
+ startPageExplicit = true
7091
+ elif pageNavigable && !pageHidden && startPageName == null:
7078
7092
  startPageName = pageName
7079
7093
  startPageIndex = ixPage
7080
- startPageExplicit = true
7081
7094
  endif
7082
7095
 
7083
7096
  # Update the end page
@@ -7112,8 +7125,7 @@ async function pagerMain(pagerModel, options):
7112
7125
  curPageIndex = startPageIndex
7113
7126
  for page, ixPage in pages:
7114
7127
  pageName = objectGet(page, \'name\')
7115
- pageTypeKey = arrayGet(objectKeys(objectGet(page, \'type\')), 0)
7116
- pageNavigable = pageTypeKey != \'link\'
7128
+ pageNavigable = !objectHas(objectGet(page, \'type\'), \'link\')
7117
7129
 
7118
7130
  # Current page?
7119
7131
  if pageNavigable && pageName == argPage:
@@ -7131,8 +7143,7 @@ async function pagerMain(pagerModel, options):
7131
7143
  page = arrayGet(pages, ixPage)
7132
7144
  pageName = objectGet(page, \'name\')
7133
7145
  pageHidden = objectGet(page, \'hidden\')
7134
- pageTypeKey = arrayGet(objectKeys(objectGet(page, \'type\')), 0)
7135
- pageNavigable = pageTypeKey != \'link\'
7146
+ pageNavigable = !objectHas(objectGet(page, \'type\'), \'link\')
7136
7147
  if !pageHidden && pageNavigable:
7137
7148
  nextPageName = pageName
7138
7149
  break
@@ -7147,8 +7158,7 @@ async function pagerMain(pagerModel, options):
7147
7158
  page = arrayGet(pages, ixPage)
7148
7159
  pageName = objectGet(page, \'name\')
7149
7160
  pageHidden = objectGet(page, \'hidden\')
7150
- pageTypeKey = arrayGet(objectKeys(objectGet(page, \'type\')), 0)
7151
- pageNavigable = pageTypeKey != \'link\'
7161
+ pageNavigable = !objectHas(objectGet(page, \'type\'), \'link\')
7152
7162
  if !pageHidden && pageNavigable:
7153
7163
  prevPageName = pageName
7154
7164
  break
@@ -7163,8 +7173,7 @@ async function pagerMain(pagerModel, options):
7163
7173
  pageName = objectGet(page, \'name\')
7164
7174
  pageHidden = objectGet(page, \'hidden\')
7165
7175
  pageType = objectGet(page, \'type\')
7166
- pageTypeKey = arrayGet(objectKeys(pageType), 0)
7167
- pageNavigable = pageTypeKey != \'link\'
7176
+ pageLink = objectGet(pageType, \'link\')
7168
7177
 
7169
7178
  # Hidden page?
7170
7179
  if pageHidden:
@@ -7172,10 +7181,10 @@ async function pagerMain(pagerModel, options):
7172
7181
  endif
7173
7182
 
7174
7183
  # Render the menu link
7175
- if pageTypeKey == \'link\':
7176
- pageLinkURL = objectGet(objectGet(pageType, \'link\'), \'url\')
7184
+ if pageLink != null:
7185
+ pageLinkURL = objectGet(pageLink, \'url\')
7177
7186
  arrayPush(menuItems, \'[\' + markdownEscape(pageName) + \'](\' + urlEncode(pageLinkURL) + \')\')
7178
- elif pageName == objectGet(curPage, \'name\'):
7187
+ elif pageName == curPageName:
7179
7188
  arrayPush(menuItems, markdownEscape(pageName))
7180
7189
  else:
7181
7190
  arrayPush(menuItems, argsLink(arguments, pageName, {\'page\': pageName}))
@@ -7198,8 +7207,7 @@ async function pagerMain(pagerModel, options):
7198
7207
 
7199
7208
  # Function page?
7200
7209
  curPageType = objectGet(curPage, \'type\')
7201
- curPageTypeKey = arrayGet(objectKeys(curPageType), 0)
7202
- if curPageTypeKey == \'function\':
7210
+ if objectHas(curPageType, \'function\'):
7203
7211
  # Set the title
7204
7212
  title = objectGet(objectGet(curPageType, \'function\'), \'title\')
7205
7213
  if title != null:
@@ -7210,7 +7218,7 @@ async function pagerMain(pagerModel, options):
7210
7218
  # Call the page function
7211
7219
  pageFn = objectGet(objectGet(curPageType, \'function\'), \'function\')
7212
7220
  pageFn(args)
7213
- elif curPageTypeKey == \'markdown\':
7221
+ elif objectHas(curPageType, \'markdown\'):
7214
7222
  # Fetch the Markdown text
7215
7223
  url = objectGet(objectGet(curPageType, \'markdown\'), \'url\')
7216
7224
  markdownText = systemFetch(url)
@@ -7275,6 +7283,9 @@ include <draw.bare>
7275
7283
  function qrcodeDraw(message, x, y, size, level):
7276
7284
  # Get the QR code pixel matrix
7277
7285
  matrix = if(systemType(message) == \'string\', qrcodeMatrix(message, level), message)
7286
+ if matrix == null:
7287
+ return
7288
+ endif
7278
7289
  qrcodeSize = arrayLength(matrix)
7279
7290
 
7280
7291
  # Draw the white background
@@ -7315,6 +7326,9 @@ endfunction
7315
7326
  function qrcodeElements(message, size, level):
7316
7327
  # Get the QR code pixel matrix
7317
7328
  matrix = if(systemType(message) == \'string\', qrcodeMatrix(message, level), message)
7329
+ if matrix == null:
7330
+ return null
7331
+ endif
7318
7332
  qrcodeSize = arrayLength(matrix)
7319
7333
 
7320
7334
  # Compute the black pixel paths
@@ -7722,15 +7736,15 @@ function qrcodeDataBits(qrcode, level, message):
7722
7736
  # Compute the message bits
7723
7737
  messageBits = qrcodeMessageBits(qrcode, level, message)
7724
7738
 
7725
- # Padding bits
7739
+ # Padding bits - the pad bytes 236 and 17 alternate
7726
7740
  paddingBits = []
7727
7741
  remainingBitCount = totalMessageCodewords * 8 - arrayLength(messageBits)
7728
7742
  byteCount = mathFloor(remainingBitCount / 8)
7743
+ padBits236 = qrcodeBytesToBits([236])
7744
+ padBits17 = qrcodeBytesToBits([17])
7729
7745
  ix = 0
7730
7746
  while ix < byteCount:
7731
- padValue = if(ix % 2, 17, 236)
7732
- padBits = qrcodeBytesToBits([padValue])
7733
- arrayExtend(paddingBits, padBits)
7747
+ arrayExtend(paddingBits, if(ix % 2, padBits17, padBits236))
7734
7748
  ix = ix + 1
7735
7749
  endwhile
7736
7750
 
@@ -7844,7 +7858,7 @@ endfunction
7844
7858
 
7845
7859
  # Mode determination regex
7846
7860
  qrcodeModeNumericRegex = regexNew(\'^[0-9]+$\')
7847
- qrcodeModeAlphanumericRegex = regexNew(\'^[0-9A-Z $%*+-./:]+$\')
7861
+ qrcodeModeAlphanumericRegex = regexNew(\'^[0-9A-Z $%*+\\\\-./:]+$\')
7848
7862
  qrcodeModeAlphanumericStr = \'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:\'
7849
7863
 
7850
7864
 
@@ -8915,27 +8929,27 @@ async function schemaDocMain(url, title, hideNoGroup):
8915
8929
  markdownPrint(\'\', argsLink(schemaDocArguments, if(single, \'Multi Page\', \'Single Page\'), {\'single\': !single}))
8916
8930
  endif
8917
8931
 
8918
- # Group the types
8932
+ # Group the type names
8919
8933
  groups = {}
8920
8934
  typeNames = arraySort(objectKeys(types))
8921
- typeGroups = {\'action\': \'Actions\', \'enum\': \'Enums\', \'struct\': \'Structs\', \'typedef\': \'Typedefs\'}
8922
8935
  for typeName in typeNames:
8923
8936
  type = objectGet(types, typeName)
8924
- group = objectGet(objectGet(type, arrayGet(objectKeys(type), 0)), \'docGroup\')
8937
+ typeKey = arrayGet(objectKeys(type), 0)
8938
+ group = objectGet(objectGet(type, typeKey), \'docGroup\')
8925
8939
 
8926
8940
  # No group? Use the type\'s default group.
8927
8941
  if group == null:
8928
8942
  if hideNoGroup:
8929
8943
  continue
8930
8944
  endif
8931
- group = objectGet(typeGroups, arrayGet(objectKeys(type), 0))
8945
+ group = objectGet(schemaDocTypeGroups, typeKey)
8932
8946
  endif
8933
8947
 
8934
- # Add the type to the group
8948
+ # Add the type name to the group
8935
8949
  if !objectHas(groups, group):
8936
8950
  objectSet(groups, group, [])
8937
8951
  endif
8938
- arrayPush(objectGet(groups, group), type)
8952
+ arrayPush(objectGet(groups, group), typeName)
8939
8953
  endfor
8940
8954
  groupNames = arraySort(objectKeys(groups))
8941
8955
 
@@ -8958,9 +8972,7 @@ async function schemaDocMain(url, title, hideNoGroup):
8958
8972
  endif
8959
8973
 
8960
8974
  # Render the group type links
8961
- groupTypes = objectGet(groups, groupName)
8962
- for groupType in groupTypes:
8963
- groupTypeName = objectGet(objectGet(groupType, arrayGet(objectKeys(groupType), 0)), \'name\')
8975
+ for groupTypeName in objectGet(groups, groupName):
8964
8976
  if single:
8965
8977
  markdownPrint(\'\', schemaDocMarkdown(types, groupTypeName, {\'headerPrefix\': \'###\', \'hideReferenced\': true}))
8966
8978
  else:
@@ -8971,6 +8983,10 @@ async function schemaDocMain(url, title, hideNoGroup):
8971
8983
  endfunction
8972
8984
 
8973
8985
 
8986
+ # The map of type key to default documentation group name
8987
+ schemaDocTypeGroups = {\'action\': \'Actions\', \'enum\': \'Enums\', \'struct\': \'Structs\', \'typedef\': \'Typedefs\'}
8988
+
8989
+
8974
8990
  # The Schema Markdown documentation viewer arguments
8975
8991
  schemaDocArguments = argsValidate([ \\
8976
8992
  {\'name\': \'name\'}, \\
@@ -9003,31 +9019,31 @@ function schemaDocMarkdown(types, typeName, options):
9003
9019
  userType = objectGet(types, typeName)
9004
9020
  action = objectGet(userType, \'action\')
9005
9021
 
9006
- # Compute the referenced types
9007
- referencedTypes = schemaGetReferencedTypes(types, typeName)
9008
- if action != null:
9009
- typesFilter = [ \\
9010
- typeName, objectGet(action, \'path\'), objectGet(action, \'query\'), objectGet(action, \'input\'), \\
9011
- objectGet(action, \'output\'), objectGet(action, \'errors\') \\
9012
- ]
9013
- else:
9014
- typesFilter = [typeName]
9015
- endif
9016
-
9017
- # Filter and sort referenced types
9022
+ # Compute, filter, and sort the referenced types (not needed when referenced types are hidden)
9018
9023
  filteredTypeNames = []
9019
- for refTypeName in arraySort(objectKeys(referencedTypes)):
9020
- if arrayIndexOf(typesFilter, refTypeName) == -1:
9021
- arrayPush(filteredTypeNames, refTypeName)
9024
+ if !hideReferenced:
9025
+ referencedTypes = schemaGetReferencedTypes(types, typeName)
9026
+ if action != null:
9027
+ typesFilter = [ \\
9028
+ typeName, objectGet(action, \'path\'), objectGet(action, \'query\'), objectGet(action, \'input\'), \\
9029
+ objectGet(action, \'output\'), objectGet(action, \'errors\') \\
9030
+ ]
9031
+ else:
9032
+ typesFilter = [typeName]
9022
9033
  endif
9023
- endfor
9034
+ for refTypeName in arraySort(objectKeys(referencedTypes)):
9035
+ if arrayIndexOf(typesFilter, refTypeName) == -1:
9036
+ arrayPush(filteredTypeNames, refTypeName)
9037
+ endif
9038
+ endfor
9039
+ endif
9024
9040
 
9025
9041
  # The user type documentation
9026
9042
  lines = []
9027
9043
  arrayExtend(lines, schemaDocUserTypeMarkdown(types, typeName, options, headerPrefix))
9028
9044
 
9029
9045
  # Referenced type documentation
9030
- if filteredTypeNames && !hideReferenced:
9046
+ if filteredTypeNames:
9031
9047
  arrayPush(lines, \'\')
9032
9048
  arrayPush(lines, \'---\')
9033
9049
  arrayPush(lines, \'\')
@@ -9188,7 +9204,7 @@ function schemaDocUserTypeMarkdown(types, typeName, options, headerPrefix, title
9188
9204
  endif
9189
9205
 
9190
9206
  # Type table data
9191
- attrText = if(objectHas(typedef, \'attr\'), schemaDocAttrText(typedef))
9207
+ attrText = schemaDocAttrText(typedef)
9192
9208
  tableData = [{\'Type\': schemaDocTypeText(types, objectGet(typedef, \'type\'))}]
9193
9209
  if attrText != null:
9194
9210
  objectSet(arrayGet(tableData, 0), \'Attributes\', attrText)
@@ -9413,7 +9429,7 @@ function schemaDocAttrParts(parts, noun, attr, optional):
9413
9429
  arrayPush(parts, noun + \' >= \' + objectGet(attr, \'gte\'))
9414
9430
  endif
9415
9431
  if objectHas(attr, \'lt\'):
9416
- arrayPush(parts, noun + \' < \'+ objectGet(attr, \'lt\'))
9432
+ arrayPush(parts, noun + \' < \' + objectGet(attr, \'lt\'))
9417
9433
  endif
9418
9434
  if objectHas(attr, \'lte\'):
9419
9435
  arrayPush(parts, noun + \' <= \' + objectGet(attr, \'lte\'))
@@ -11053,7 +11069,7 @@ function unittestReport(options):
11053
11069
  testFailCount = 0
11054
11070
  for testName in testNames:
11055
11071
  testFailures = objectGet(unittestTests, testName)
11056
- if arrayLength(testFailures):
11072
+ if testFailures:
11057
11073
  testFailCount = testFailCount + 1
11058
11074
  endif
11059
11075
  endfor
@@ -11066,10 +11082,11 @@ function unittestReport(options):
11066
11082
 
11067
11083
  # Render the page title
11068
11084
  documentSetTitle(title)
11085
+ testSummary = \'Ran \' + testCount + \' tests - \' + testPassCount + \' passed, \' + testFailCount + \' failed\'
11069
11086
  markdownPrint( \\
11070
11087
  \'# \' + markdownEscape(title), \\
11071
11088
  \'\', \\
11072
- \'Ran \' + testCount + \' tests - \' + testPassCount + \' passed, \' + testFailCount + \' failed\' \\
11089
+ testSummary \\
11073
11090
  )
11074
11091
  if !isReport:
11075
11092
  if testNameArg:
@@ -11097,7 +11114,7 @@ function unittestReport(options):
11097
11114
  if !hideTests || testNameArg:
11098
11115
  for testName in testNames:
11099
11116
  testFailures = objectGet(unittestTests, testName)
11100
- if arrayLength(testFailures):
11117
+ if testFailures:
11101
11118
  failureLink = if(isReport, testName, \\
11102
11119
  argsLink(unittestReportArguments, testName, {\'test\': testName}, false, \'_top\'))
11103
11120
  failureLines = [\'\', failureLink + \' - FAIL\']
@@ -11126,7 +11143,7 @@ function unittestReport(options):
11126
11143
  if !hideTests || testNameArg:
11127
11144
  for testName in testNames:
11128
11145
  testFailures = objectGet(unittestTests, testName)
11129
- if !arrayLength(testFailures):
11146
+ if !testFailures:
11130
11147
  testLink = if(isReport, testName, \\
11131
11148
  argsLink(unittestReportArguments, testName, {\'test\': testName}, false, \'_top\'))
11132
11149
  markdownPrint(\'\', testLink + \' - OK\')
@@ -11135,23 +11152,26 @@ function unittestReport(options):
11135
11152
  endif
11136
11153
  endif
11137
11154
 
11138
- # Coverage report
11155
+ # Compute the coverage data, the total coverage percentage, and the coverage error, if any
11139
11156
  coverage = unittestCoverageGlobal()
11157
+ coverageData = if(!testNameArg && coverage, unittestCoverageData(coverageExclude))
11158
+ coveragePercent = if(coverageData, objectGet(arrayGet(coverageData, arrayLength(coverageData) - 1), \'Coverage\'))
11159
+ coverageSummary = if(coveragePercent != null, \'Coverage: \' + numberToFixed(coveragePercent, 2) + \'%\' + \\
11160
+ if(coverageMin, \' (minimum \' + numberToFixed(coverageMin, 2) + \'%)\', \'\'))
11161
+ coverageError = if(coveragePercent != null && coveragePercent < coverageMin, \\
11162
+ \'**Error**: Coverage percentage, \' + numberToFixed(coveragePercent, 2) + \\
11163
+ \'%, is below the minimum coverage percentage of \' + numberToFixed(coverageMin, 2) + \'%.\')
11164
+
11165
+ # Coverage report
11140
11166
  coverageFailCount = 0
11141
11167
  if !testNameArg && coverage:
11142
11168
  markdownPrint(\'\', \'## Coverage Report\')
11143
-
11144
- # Compute the coverage data table
11145
- coverageData = unittestCoverageData(coverageExclude)
11146
11169
  if coverageData:
11170
+ markdownPrint(\'\', coverageSummary)
11171
+
11147
11172
  # Verify the coverage percent
11148
- coveragePercent = objectGet(arrayGet(coverageData, arrayLength(coverageData) - 1), \'Coverage\')
11149
- if coveragePercent < coverageMin:
11150
- markdownPrint( \\
11151
- \'\', \\
11152
- \'**Error**: Coverage percentage, \' + numberToFixed(coveragePercent, 1, true) + \\
11153
- \'%, is below the minimum coverage percentage of \' + numberToFixed(coverageMin, 1, true) + \'%.\' \\
11154
- )
11173
+ if coverageError != null:
11174
+ markdownPrint(\'\', coverageError)
11155
11175
  coverageFailCount = coverageFailCount + 1
11156
11176
  endif
11157
11177
 
@@ -11161,7 +11181,7 @@ function unittestReport(options):
11161
11181
  if !isReport && !stringStartsWith(scriptName, \'**\'):
11162
11182
  objectSet(row, \'Script\', argsLink(unittestReportArguments, scriptName, {"script": scriptName}, false, "_top"))
11163
11183
  endif
11164
- objectSet(row, \'Coverage\', numberToFixed(objectGet(row, \'Coverage\'), 1) + \'%\')
11184
+ objectSet(row, \'Coverage\', numberToFixed(objectGet(row, \'Coverage\'), 2) + \'%\')
11165
11185
  endfor
11166
11186
  markdownPrint(\'\', dataTableMarkdown(coverageData, { \\
11167
11187
  \'fields\': [\'Script\', \'Statements\', \'Missing\', \'Coverage\'], \\
@@ -11176,6 +11196,15 @@ function unittestReport(options):
11176
11196
  endif
11177
11197
  endif
11178
11198
 
11199
+ # Render the summary section
11200
+ markdownPrint(\'\', \'## Summary\', \'\', testSummary)
11201
+ if coverageSummary != null:
11202
+ markdownPrint(\'\', coverageSummary)
11203
+ endif
11204
+ if coverageError != null:
11205
+ markdownPrint(\'\', coverageError)
11206
+ endif
11207
+
11179
11208
  # Return status code
11180
11209
  return testWarningCount + testFailCount + coverageFailCount
11181
11210
  endfunction
@@ -11273,7 +11302,7 @@ function unittestReportScriptLines(codeLineElements, lines, color, noNewline):
11273
11302
  endfunction
11274
11303
 
11275
11304
 
11276
- # Get the coverage data table - columns are "Script", "Statement", "Covered", and "Percent"
11305
+ # Get the coverage data table - columns are "Script", "Statements", "Missing", and "Coverage"
11277
11306
  function unittestCoverageData(coverageExclude):
11278
11307
  data = []
11279
11308
 
@@ -11328,8 +11357,7 @@ function unittestCoverageData(coverageExclude):
11328
11357
  \'Script\': scriptName, \\
11329
11358
  \'Statements\': statementCount, \\
11330
11359
  \'Missing\': missingCount, \\
11331
- \'Coverage\': coveragePercent, \\
11332
- \'CoverageStr\': numberToFixed(coveragePercent, 1) + \'%\' \\
11360
+ \'Coverage\': coveragePercent \\
11333
11361
  })
11334
11362
  endfor
11335
11363
 
@@ -11340,8 +11368,7 @@ function unittestCoverageData(coverageExclude):
11340
11368
  \'Script\': \'**Total**\', \\
11341
11369
  \'Statements\': totalStatements, \\
11342
11370
  \'Missing\': totalMissing, \\
11343
- \'Coverage\': coveragePercent, \\
11344
- \'CoverageStr\': numberToFixed(coveragePercent, 1) + \'%\' \\
11371
+ \'Coverage\': coveragePercent \\
11345
11372
  })
11346
11373
  endif
11347
11374
  endif
@@ -11359,9 +11386,9 @@ function unittestScriptStatements(script):
11359
11386
  arrayPush(statements, statement)
11360
11387
 
11361
11388
  # Add function statements
11362
- statementKey = arrayGet(objectKeys(statement), 0)
11363
- if statementKey == \'function\':
11364
- for funcStatement in objectGet(objectGet(statement, statementKey), \'statements\'):
11389
+ statementFunction = objectGet(statement, \'function\')
11390
+ if statementFunction != null:
11391
+ for funcStatement in objectGet(statementFunction, \'statements\'):
11365
11392
  arrayPush(statements, funcStatement)
11366
11393
  endfor
11367
11394
  endif
@@ -11388,15 +11415,20 @@ unittestMockWindowHeight = 768
11388
11415
  unittestMockWindowWidth = 1024
11389
11416
 
11390
11417
 
11418
+ # Create a new mocked MarkdownUp state
11419
+ function unittestMockStateNew():
11420
+ return { \\
11421
+ \'localStorage\': {}, \\
11422
+ \'sessionStorage\': {}, \\
11423
+ \'windowClipboard\': \'\' \\
11424
+ }
11425
+ endfunction
11426
+
11427
+
11391
11428
  # The mocked MarkdownUp state
11392
11429
  unittestMockCalls = []
11393
11430
  unittestMockFunctions = {}
11394
- unittestMockStateDefault = { \\
11395
- \'localStorage\': {}, \\
11396
- \'sessionStorage\': {}, \\
11397
- \'windowClipboard\': \'\' \\
11398
- }
11399
- unittestMockState = objectCopy(unittestMockStateDefault)
11431
+ unittestMockState = unittestMockStateNew()
11400
11432
 
11401
11433
 
11402
11434
  # $function: unittestMockAll
@@ -11459,7 +11491,7 @@ endfunction
11459
11491
  # $arg funcName: The name of the function to mock
11460
11492
  # $arg mockFunc: The mock function
11461
11493
  function unittestMockOne(funcName, mockFunc):
11462
- # Replace the function withi the mocked function
11494
+ # Replace the function with the mocked function
11463
11495
  objectSet(unittestMockFunctions, funcName, systemGlobalGet(funcName))
11464
11496
  systemGlobalSet(funcName, mockFunc)
11465
11497
  endfunction
@@ -11494,7 +11526,7 @@ function unittestMockEnd():
11494
11526
  mockCalls = unittestMockCalls
11495
11527
  systemGlobalSet(\'unittestMockCalls\', [])
11496
11528
  systemGlobalSet(\'unittestMockFunctions\', {})
11497
- systemGlobalSet(\'unittestMockState\', objectCopy(unittestMockStateDefault))
11529
+ systemGlobalSet(\'unittestMockState\', unittestMockStateNew())
11498
11530
 
11499
11531
  return mockCalls
11500
11532
  endfunction
@@ -11644,6 +11676,7 @@ endfunction
11644
11676
 
11645
11677
 
11646
11678
  function unittestMock_windowURLObject(data, contentType):
11679
+ contentType = if(contentType != null, contentType, \'text/plain\')
11647
11680
  return \'blob:\' + urlEncode(contentType) + \'-\' + urlEncode(if(stringLength(data) < 20, data, stringSlice(data, 0, 20)))
11648
11681
  endfunction
11649
11682
 
@@ -11701,7 +11734,7 @@ endfunction
11701
11734
  function urlEncodeQueryStringHelper(obj, memberFqn, keyValues):
11702
11735
  objType = systemType(obj)
11703
11736
  if objType == \'array\':
11704
- if arrayLength(obj) == 0:
11737
+ if !obj:
11705
11738
  arrayPush(keyValues, if(memberFqn != null, memberFqn + \'=\', \'\'))
11706
11739
  else:
11707
11740
  for value, ixValue in obj:
@@ -11713,7 +11746,7 @@ function urlEncodeQueryStringHelper(obj, memberFqn, keyValues):
11713
11746
  arrayPush(keyValues, if(memberFqn != null, memberFqn + \'=\' + objEncoded, objEncoded))
11714
11747
  elif objType == \'object\':
11715
11748
  keys = arraySort(objectKeys(obj))
11716
- if arrayLength(keys) == 0:
11749
+ if !keys:
11717
11750
  arrayPush(keyValues, if(memberFqn != null, memberFqn + \'=\', \'\'))
11718
11751
  else:
11719
11752
  for key in keys:
@@ -11881,6 +11914,7 @@ function urlEncodeHelper(url, regexSafe, regexUnsafe):
11881
11914
  # implementation, in which case null is returned below
11882
11915
  parts = []
11883
11916
  ixSafe = 0
11917
+ encodeFailed = false
11884
11918
  for match in regexMatchAll(regexUnsafe, url):
11885
11919
  ixUnsafe = objectGet(match, \'index\')
11886
11920
  unsafe = objectGet(objectGet(match, \'groups\'), \'0\')
@@ -11893,6 +11927,7 @@ function urlEncodeHelper(url, regexSafe, regexUnsafe):
11893
11927
 
11894
11928
  # Percent-encode the run\'s unsafe UTF-8 bytes
11895
11929
  bytes = stringEncode(unsafe)
11930
+ encodeFailed = encodeFailed || bytes == null
11896
11931
  for byte in if(bytes != null, bytes, []):
11897
11932
  arrayPush(parts, arrayGet(urlEncodeBytes, byte))
11898
11933
  endfor
@@ -11903,7 +11938,7 @@ function urlEncodeHelper(url, regexSafe, regexUnsafe):
11903
11938
  arrayPush(parts, stringSlice(url, ixSafe))
11904
11939
  endif
11905
11940
 
11906
- return if(bytes != null, arrayJoin(parts, \'\'))
11941
+ return if(!encodeFailed, arrayJoin(parts, \'\'))
11907
11942
  endfunction
11908
11943
 
11909
11944
 
@@ -11934,9 +11969,12 @@ function urlDecodeComponent(string):
11934
11969
  return string
11935
11970
  endif
11936
11971
 
11937
- # Compute the decoded UTF-8 byte array
11972
+ # Compute the decoded UTF-8 byte array - stringEncode fails on surrogate code points under
11973
+ # the Python implementation, in which case null is returned below
11938
11974
  parts = stringSplit(string, \'%\')
11939
- bytes = stringEncode(arrayGet(parts, 0))
11975
+ partBytes = stringEncode(arrayGet(parts, 0))
11976
+ encodeFailed = partBytes == null
11977
+ bytes = if(partBytes != null, partBytes, [])
11940
11978
  for part, ixPart in parts:
11941
11979
  if ixPart == 0:
11942
11980
  continue
@@ -11949,12 +11987,14 @@ function urlDecodeComponent(string):
11949
11987
  endif
11950
11988
  arrayPush(bytes, numberParseInt(stringSlice(part, 0, 2), 16))
11951
11989
  if stringLength(part) > 2:
11952
- arrayExtend(bytes, stringEncode(stringSlice(part, 2)))
11990
+ partBytes = stringEncode(stringSlice(part, 2))
11991
+ encodeFailed = encodeFailed || partBytes == null
11992
+ arrayExtend(bytes, if(partBytes != null, partBytes, []))
11953
11993
  endif
11954
11994
  endfor
11955
11995
 
11956
- # Invalid UTF-8?
11957
- decoded = stringDecode(bytes)
11996
+ # Invalid UTF-8 or unencodable input?
11997
+ decoded = if(!encodeFailed, stringDecode(bytes))
11958
11998
  if decoded == null:
11959
11999
  systemLogDebug("url.bare: Invalid percent-encoding \'" + urlTruncate(string) + "\'")
11960
12000
  endif
@@ -507,8 +507,23 @@ def value_parse_datetime(text):
507
507
  month = int(m_date.group('month'))
508
508
  day = int(m_date.group('day'))
509
509
  return datetime.datetime(year, month, day)
510
- if _R_DATETIME.match(text):
511
- result = datetime.datetime.fromisoformat(_R_DATETIME_ZULU.sub('+00:00', text)).astimezone().replace(tzinfo=None)
510
+ m_datetime = _R_DATETIME.match(text)
511
+ if m_datetime is not None:
512
+ text_zulu = _R_DATETIME_ZULU.sub('+00:00', text)
513
+
514
+ # fromisoformat's support for ISO 8601's hour-24 end-of-day special case varies across Python
515
+ # versions, so handle it explicitly rather than relying on stdlib behavior
516
+ is_hour24 = m_datetime.group('hour') == '24'
517
+ if is_hour24:
518
+ if m_datetime.group('minute') != '00' or m_datetime.group('second') != '00' or \
519
+ (m_datetime.group('fraction') is not None and int(m_datetime.group('fraction')) != 0):
520
+ return None
521
+ text_zulu = text_zulu[:11] + '00' + text_zulu[13:]
522
+
523
+ result = datetime.datetime.fromisoformat(text_zulu)
524
+ if is_hour24:
525
+ result += datetime.timedelta(days=1)
526
+ result = result.astimezone().replace(tzinfo=None)
512
527
  return result.replace(microsecond=(result.microsecond // 1000) * 1000)
513
528
  except ValueError:
514
529
  return None
@@ -516,7 +531,10 @@ def value_parse_datetime(text):
516
531
  return None
517
532
 
518
533
  _R_DATE = re.compile(r'^(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})$')
519
- _R_DATETIME = re.compile(r'^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,6})?(?:Z|[+-][0-9]{2}:[0-9]{2})$')
534
+ _R_DATETIME = re.compile(
535
+ r'^[0-9]{4}-[0-9]{2}-[0-9]{2}T(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})'
536
+ r'(?:\.(?P<fraction>[0-9]{1,6}))?(?:Z|[+-][0-9]{2}:[0-9]{2})$'
537
+ )
520
538
  _R_DATETIME_ZULU = re.compile(r'Z$')
521
539
 
522
540
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bare-script
3
- Version: 5.0.0
3
+ Version: 5.0.3
4
4
  Summary: bare-script
5
5
  Home-page: https://github.com/craigahobbs/bare-script
6
6
  Author: Craig A. Hobbs
File without changes
File without changes
File without changes
File without changes