gitoriaLog in with ident

antcolony

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commita6af7883a6af7883tracker: worker box sees calendar.worldapi.org (login to copy)mrea6af7883/lib/util.hl

4.1 KB

  1. // util.hl — small helpers the language does not ship (statics only; importing runs nothing).
  2. // Workarounds noted where a Hybriel ticket is the reason (see the worker guide on Byrodin:
  3. // /CONTAINERS/projects/antcolony/docs/hybriel-for-app-workers.md).
  4. import { readFile, exists } from 'hl:fs'
  5. import { cwd } from 'hl:proc'
  6. import { jsonErrorAt } from './jsoncheck.hl'
  7. static NL = "
  8. "
  9. // hlTypeName says 'Hybrid' for objects AND lists (hybriel#14): a list has a length, an object has not
  10. static isList = (v) => { return v != null && hlTypeName(v) == 'Hybrid' && JSON.stringify(v).startsWith('[') }
  11. static isObject = (v) => { return v != null && hlTypeName(v) == 'Hybrid' && !isList(v) }
  12. static isString = (v) => { return v != null && hlTypeName(v) == 'String' }
  13. static nonEmpty = (v) => { return isString(v) && v.trim() != '' }
  14. // a path the USER typed (argument or env) is relative to where they ran us, not to this
  15. // script (hl:fs resolves relative paths against the entry script's directory)
  16. static fromCwd = (p) => {
  17. if (p == null || p == '') { return p }
  18. if (p.startsWith('/')) { return p }
  19. return cwd() + '/' + p
  20. }
  21. // a file as JSON: { value } or { error } — JSON.parse cannot fail softly (hybriel#6), so the
  22. // syntax is checked first (jsoncheck.hl, a copy of tickets')
  23. static readJson = (path) => {
  24. if (!exists(path)) { return { error = 'no such file: ' + path } }
  25. let text = readFile(path)
  26. let at = jsonErrorAt(text)
  27. if (at >= 0) { return { error = path + ': invalid JSON at character ' + at } }
  28. return { value = JSON.parse(text) text = text }
  29. }
  30. // sort a list IN PLACE by a numeric key, ascending, stable (list sort() is NotCallable, hybriel#1)
  31. static sortByNumber = (list, keyOf) => {
  32. let i = 1
  33. while (i < list.length) {
  34. let cur = list[i]
  35. let k = keyOf(cur)
  36. let j = i - 1
  37. while (j >= 0 && keyOf(list[j]) > k) {
  38. list[j + 1] = list[j]
  39. j = j - 1
  40. }
  41. list[j + 1] = cur
  42. i = i + 1
  43. }
  44. return list
  45. }
  46. // a > b for strings by char code (no string ordering operators, hybriel#2)
  47. static after = (a, b) => {
  48. let i = 0
  49. while (i < a.length && i < b.length) {
  50. let x = a.charCodeAt(i)
  51. let y = b.charCodeAt(i)
  52. if (x != y) { return x > y }
  53. i = i + 1
  54. }
  55. return a.length > b.length
  56. }
  57. static sortStrings = (list) => {
  58. let i = 1
  59. while (i < list.length) {
  60. let cur = list[i]
  61. let j = i - 1
  62. while (j >= 0 && after(list[j], cur)) {
  63. list[j + 1] = list[j]
  64. j = j - 1
  65. }
  66. list[j + 1] = cur
  67. i = i + 1
  68. }
  69. return list
  70. }
  71. // a Markdown code fence longer than any backtick run inside `text`
  72. static fenceFor = (text) => {
  73. let longest = 0
  74. let run = 0
  75. let i = 0
  76. while (i < text.length) {
  77. if (text.charCodeAt(i) == 96) { run = run + 1 } else { run = 0 }
  78. if (run > longest) { longest = run }
  79. i = i + 1
  80. }
  81. let f = '```'
  82. while (f.length <= longest) { f = f + '`' }
  83. return f
  84. }
  85. // `--name value` from an argument list, or null
  86. static option = (argv, name) => {
  87. let i = 0
  88. while (i < argv.length) {
  89. if (argv[i] == name && i + 1 < argv.length) { return argv[i + 1] }
  90. i = i + 1
  91. }
  92. return null
  93. }
  94. // the positional arguments (no --options and their values; --post, --dry-run, --again, --once, --live, --expire and the
  95. // librarian's --post-files, --render, --retry-refused are flags)
  96. static positionals = (argv) => {
  97. let out = []
  98. let i = 0
  99. while (i < argv.length) {
  100. let a = argv[i]
  101. if (a == '--post' || a == '--dry-run' || a == '--again' || a == '--once' || a == '--live' || a == '--expire' || a == '--deploy' || a == '--post-files' || a == '--render' || a == '--retry-refused') {
  102. i = i + 1
  103. } else if (a.startsWith('--')) {
  104. i = i + 2
  105. } else {
  106. out.push(a)
  107. i = i + 1
  108. }
  109. }
  110. return out
  111. }
  112. // capabilities (antcolony#15): "gpu, chrome ,deploy" → ['gpu','chrome','deploy'] (trimmed, lower case, no empties, no repeats)
  113. static parseCapabilities = (text) => {
  114. let out = []
  115. if (text == null) { return out }
  116. for (p of text.split(',')) {
  117. let c = p.trim().toLowerCase()
  118. if (c != '' && !out.includes(c)) { out.push(c) }
  119. }
  120. return out
  121. }
  122. // what of `needs` a host with `offers` lacks (both lists of lower-case names)
  123. static missingCapabilities = (needs, offers) => {
  124. let out = []
  125. for (n of needs) { if (!offers.includes(n)) { out.push(n) } }
  126. return out
  127. }

Branches

Latest commits

  • a6af7883tracker: worker box sees calendar.worldapi.org (login to copy)mre
  • c613d26btemplates: bridges to external components (login.js for ident's selector) are allowed (creator 2026-09-27)mre
  • 9062978ctracker: worker box sees /media/STORAGE/projects/old-tracker read-only (tracker#2 source data)mre
  • 7f9660eeState of 2026-09-27, before the move to gitoriamre