aontu 0.63.0 → 0.65.0

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 (101) hide show
  1. package/README.md +8 -8
  2. package/dist/agentsmd.js +3 -3
  3. package/dist/agentsmd.js.map +1 -1
  4. package/dist/aontu.d.ts +3 -3
  5. package/dist/aontu.js +4 -6
  6. package/dist/aontu.js.map +1 -1
  7. package/dist/aontumodel.js +6 -12
  8. package/dist/aontumodel.js.map +1 -1
  9. package/dist/casing.d.ts +5 -0
  10. package/dist/casing.js +90 -0
  11. package/dist/casing.js.map +1 -0
  12. package/dist/cli.d.ts +16 -3
  13. package/dist/cli.js +653 -455
  14. package/dist/cli.js.map +1 -1
  15. package/dist/compat.d.ts +7 -0
  16. package/dist/compat.js +113 -0
  17. package/dist/compat.js.map +1 -0
  18. package/dist/helpdoc.js +5 -5
  19. package/dist/helpdoc.js.map +1 -1
  20. package/dist/hints.js +14 -5
  21. package/dist/hints.js.map +1 -1
  22. package/dist/lang.d.ts +1 -0
  23. package/dist/lang.js +8 -0
  24. package/dist/lang.js.map +1 -1
  25. package/dist/lsp.d.ts +1 -1
  26. package/dist/mcp.js +0 -36
  27. package/dist/mcp.js.map +1 -1
  28. package/dist/mod.d.ts +18 -3
  29. package/dist/mod.js +117 -46
  30. package/dist/mod.js.map +1 -1
  31. package/dist/pkg-net.d.ts +227 -0
  32. package/dist/pkg-net.js +1598 -0
  33. package/dist/pkg-net.js.map +1 -0
  34. package/dist/pkg-zip.d.ts +8 -0
  35. package/dist/pkg-zip.js +196 -0
  36. package/dist/pkg-zip.js.map +1 -0
  37. package/dist/pkg.d.ts +159 -0
  38. package/dist/pkg.js +767 -0
  39. package/dist/pkg.js.map +1 -0
  40. package/dist/profile.d.ts +9 -0
  41. package/dist/profile.js +28 -0
  42. package/dist/profile.js.map +1 -0
  43. package/dist/report-sarif.js +1 -1
  44. package/dist/report-sarif.js.map +1 -1
  45. package/dist/subsume.d.ts +3 -1
  46. package/dist/subsume.js +2 -0
  47. package/dist/subsume.js.map +1 -1
  48. package/dist/trace.d.ts +21 -0
  49. package/dist/trace.js +107 -0
  50. package/dist/trace.js.map +1 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -1
  52. package/dist/unify.js +1 -6
  53. package/dist/unify.js.map +1 -1
  54. package/dist/val/CmpFuncVal.d.ts +2 -0
  55. package/dist/val/CmpFuncVal.js +47 -13
  56. package/dist/val/CmpFuncVal.js.map +1 -1
  57. package/dist/val/NomFuncVal.js +11 -13
  58. package/dist/val/NomFuncVal.js.map +1 -1
  59. package/dist/val/ReferFuncVal.js +3 -0
  60. package/dist/val/ReferFuncVal.js.map +1 -1
  61. package/package.json +1 -1
  62. package/skill/SKILL.md +3 -3
  63. package/skill/error-codes.md +4 -4
  64. package/skill/examples.md +4 -4
  65. package/skill/grammar-card.md +1 -1
  66. package/skill/init/check.sh +2 -2
  67. package/skill/tasks.md +14 -12
  68. package/src/agentsmd.ts +3 -3
  69. package/src/aontu.ts +3 -8
  70. package/src/aontumodel.ts +6 -12
  71. package/src/casing.ts +95 -0
  72. package/src/cli.ts +758 -495
  73. package/src/compat.ts +148 -0
  74. package/src/helpdoc.ts +5 -5
  75. package/src/hints.ts +14 -7
  76. package/src/lang.ts +9 -1
  77. package/src/mcp.ts +0 -39
  78. package/src/mod.ts +142 -51
  79. package/src/pkg-net.ts +1945 -0
  80. package/src/pkg-zip.ts +228 -0
  81. package/src/pkg.ts +1018 -0
  82. package/src/profile.ts +42 -0
  83. package/src/report-sarif.ts +1 -1
  84. package/src/subsume.ts +2 -2
  85. package/src/trace.ts +157 -0
  86. package/src/unify.ts +1 -6
  87. package/src/val/CmpFuncVal.ts +66 -20
  88. package/src/val/NomFuncVal.ts +3 -5
  89. package/src/val/ReferFuncVal.ts +4 -0
  90. package/dist/lower.d.ts +0 -23
  91. package/dist/lower.js +0 -528
  92. package/dist/lower.js.map +0 -1
  93. package/dist/mod-tool.d.ts +0 -58
  94. package/dist/mod-tool.js +0 -403
  95. package/dist/mod-tool.js.map +0 -1
  96. package/dist/render.d.ts +0 -53
  97. package/dist/render.js +0 -432
  98. package/dist/render.js.map +0 -1
  99. package/src/lower.ts +0 -586
  100. package/src/mod-tool.ts +0 -561
  101. package/src/render.ts +0 -580
package/src/aontu.ts CHANGED
@@ -29,13 +29,13 @@ export type {
29
29
  import { graphOf } from './graph'
30
30
  import { relationCheck, relationErrors } from './relation'
31
31
  import { view, viewSet, viewTree } from './view'
32
- import { render, renderValue, renderProfile } from './render'
32
+ import { loadProfile } from './profile'
33
33
  import { desugarTemplate, resugarTemplate, markerFor } from './template'
34
34
  import { format, unifiedDiff } from './format'
35
35
  export type { LintFinding, FormatReport, FormatOptions } from './format'
36
36
 
37
37
 
38
- const VERSION = '0.63.0'
38
+ const VERSION = '0.65.0'
39
39
 
40
40
 
41
41
  function genQuiet(val: any, aontu: Aontu): any {
@@ -387,12 +387,7 @@ export {
387
387
  viewSet,
388
388
  viewTree,
389
389
 
390
- // G9 -- the renderer (docs/design/RENDER.0.md): evaluate, vet and
391
- // fold an aontu:code instance into bytes, or the fold alone over
392
- // generate() output.
393
- render,
394
- renderValue,
395
- renderProfile,
390
+ loadProfile,
396
391
 
397
392
  desugarTemplate,
398
393
  resugarTemplate,
package/src/aontumodel.ts CHANGED
@@ -8,23 +8,17 @@
8
8
  const AONTU_SCHEME = 'aontu:'
9
9
 
10
10
  const AONTU_SOURCES: Record<string, string> = {
11
- "aontu:code": "# aontu:code --- THE OUTPUT VOCABULARY. A transform evaluates to an\n# instance of this schema and 'aontu render' turns it into bytes. The\n# root is not type()-marked because render reads the instance through\n# generate(). EXPERIMENTAL until the distribution layer can version it\n# by canon-hash.\n\n%name = string & re(\"^[A-Za-z_][A-Za-z0-9_]*$\") & length(min(1) & max(255))\n\n%text = close({ k:\"text\" lang:string & length(min(1)) text:string })\n\n%doc = close({\n text: string\n deprecated?: close({ msg?:string use?:string since?:string })\n})\n\n%check = close({ c:\"min\" n:number exclusive: *false|boolean })\n | close({ c:\"max\" n:number exclusive: *false|boolean })\n | close({ c:\"re\" p:string })\n | close({ c:\"len\" min?:integer & min(0) max?:integer & min(0) })\n | close({ c:\"unique\" key?:%name })\n | close({ c:\"ne\" n: [&: string|number|boolean] })\n | close({ c:\"must\" note:string })\n\n%prim = close({\n k: \"prim\"\n prim: \"string\"|\"int\"|\"bigint\"|\"float\"|\"decimal\"|\"bool\"|\"null\"|\"any\"\n})\n\n%ref = close({ k:\"ref\" name:%name unit?:string })\n%leaf = %prim|%ref|%text\n\n%type = %leaf\n | close({ k:\"list\" n:%leaf })\n | close({ k:\"map\" key:%leaf n:%leaf })\n | close({ k:\"opt\" n:%leaf })\n | close({ k:\"union\" n: [&: %leaf] })\n | close({ k:\"lit\" n: [&: string|number|boolean|null] })\n\n%field = close({\n name: %name\n type: %type\n optional: *false|boolean\n doc?: %doc\n default?: string|number|boolean|null\n check?: [&: %check]\n rel?: close({ to:string name?:string })\n x?: {}\n})\n\n%member = close({ name:%name value?:string|number doc?:%doc x?: {} })\n\n%param = close({\n name: %name\n type: %type\n default?: string|number|boolean|null\n x?: {}\n})\n\n# THE FRAGMENT ALGEBRA. A line's inline pieces hold no terminator; a\n# blank is its terminators alone; a raw is the one piece that may carry\n# terminators, re-indented to its depth unless it says not to.\n%inline = string & re(\"^[^\\n\\r]*$\")|%ref\n%line = close({ k:\"line\" at: *0|integer & min(0) & max(64) n: [&: %inline] })\n%blank = close({ k:\"blank\" n: *1|integer & min(1) & max(16) })\n\n%raw = close({\n k: \"raw\"\n at: *0|integer & min(0) & max(64)\n text: string\n reindent: *true|boolean\n})\n\n%piece = %line|%blank|%raw|string & re(\"^[^\\n\\r]*$\")\n%frag = close({ k:\"frag\" n: [&: %piece] })\n%body = %frag|close({ k:\"abstract\" })\n\n%record = close({\n k: \"record\"\n name: %name\n doc?: %doc\n open: *false|boolean\n fields: [&: %field]\n entity?: string\n check?: [&: %check]\n x?: {}\n})\n\n%enum = close({ k:\"enum\" name:%name doc?:%doc members: [&: %member] x?: {} })\n\n%alias = close({\n k: \"alias\"\n name: %name\n doc?: %doc\n type: %type\n check?: [&: %check]\n x?: {}\n})\n\n%const = close({\n k: \"const\"\n name: %name\n doc?: %doc\n type?: %type\n value: string|number|boolean|null\n x?: {}\n})\n\n%func = close({\n k: \"func\"\n name: %name\n doc?: %doc\n params: [&: %param]\n returns?: %type\n body: %body\n x?: {}\n})\n\n%decl = %record|%enum|%alias|%const|%func|%text|%frag\n\n%import = close({\n from: string & length(min(1))\n names?: [&: %name]\n alias?: %name\n x?: {}\n})\n\n%unit = close({\n path: string & length(min(1))\n lang: string & length(min(1))\n pkg?: string\n doc?: %doc\n imports?: [&: %import]\n decls: [&: %decl]\n profile?: {}\n x?: {}\n})\n\n%source = close({ path?:string hash?:string & re(\"^aon1-[A-Za-z0-9_-]+$\") })\n\naontu: Code: close({ source?:%source units: [&: %unit] })\n",
12
- "aontu:render": "# aontu:render --- THE RENDER VOCABULARY. A profile is the data\n# 'aontu render' applies to a unit of one language, and only data: a\n# field belongs here if the renderer applies it without looking at the\n# shape of any node. The root is not type()-marked because a profile is\n# read through generate(). EXPERIMENTAL, versioned by canon-hash later.\n\n%name = string & re(\"^[A-Za-z_][A-Za-z0-9_]*$\")\n\n%case = \"as-is\"|\"camel\"|\"pascal\"|\"snake\"|\"screaming\"|\"kebab\"\n\n%comment = close({ open?:string prefix?:string close?:string })\n\n%form = close({\n open: *\"\"|string\n close: *\"\"|string\n prec: *9|integer & min(0) & max(9)\n childPrec: *0|integer & min(0) & max(9)\n})\n\n%profile = close({\n lang: string & length(min(1))\n lowering?: \"typescript\"|\"go\"\n indent: close({\n unit: *\" \"|string & length(min(1))\n width: *2|integer & min(0) & max(16)\n })\n comment?: close({ line?:%comment block?:%comment doc?:%comment })\n str?: close({\n quote: *\"\\\"\"|string & length(min(1) & max(1))\n escape: { &: string }\n })\n ident?: close({\n chars: *\"ascii-word\"|\"ascii-word\"\n reserved?: [&: string]\n case?: close({\n record?: %case\n field?: %case\n enum?: %case\n member?: %case\n const?: %case\n func?: %case\n param?: %case\n alias?: %case\n })\n acronyms?: [&: %name]\n })\n types?: close({\n prim?: { &: string }\n list?: %form\n map?: %form\n opt?: %form\n union?: %form\n lit?: %form\n })\n template?: close({\n marker: string & length(min(1))\n close?: string & length(min(1))\n ext?: [&: string & re(\"^[A-Za-z0-9_+-]+$\")]\n })\n banner?: string\n})\n\n# Profile names the schema; Lang is where one lands. type() so naming\n# the schema neither generates it nor asks a document to fill it.\naontu: render: { Profile:type(%profile) Lang:%profile }\n",
13
- "aontu:render/lang/go": "# aontu:render/lang/go --- THE GO PROFILE. Indentation, comment forms, string\n# escapes, identifier case and type forms. Lowering a declaration to a\n# struct is code; what the renderer applies without looking at a node's\n# shape is here. EXPERIMENTAL until the distribution layer can version\n# it by canon-hash.\n\n@\"aontu:render\"\n\naontu: render: Lang: {\n lang: \"go\"\n lowering: \"go\"\n indent: { unit:\"\\t\" width:1 }\n comment: line: prefix: \"// \"\n comment: block: { open:\"/*\" close:\"*/\" }\n comment: doc: prefix: \"// \"\n str: quote: \"\\\"\"\n str: escape: {\n \"7\": \"\\\\a\"\n \"8\": \"\\\\b\"\n \"9\": \"\\\\t\"\n \"10\": \"\\\\n\"\n \"11\": \"\\\\v\"\n \"12\": \"\\\\f\"\n \"13\": \"\\\\r\"\n \"34\": \"\\\\\\\"\"\n \"92\": \"\\\\\\\\\"\n }\n ident: {\n chars: \"ascii-word\"\n reserved: [\n \"break\"\n \"case\"\n \"chan\"\n \"const\"\n \"continue\"\n \"default\"\n \"defer\"\n \"else\"\n \"fallthrough\"\n \"for\"\n \"func\"\n \"go\"\n \"goto\"\n \"if\"\n \"import\"\n \"interface\"\n \"map\"\n \"package\"\n \"range\"\n \"return\"\n \"select\"\n \"struct\"\n \"switch\"\n \"type\"\n \"var\"\n ]\n case: record: \"pascal\"\n case: field: \"pascal\"\n case: enum: \"pascal\"\n case: member: \"pascal\"\n case: const: \"pascal\"\n case: func: \"pascal\"\n case: param: \"camel\"\n case: alias: \"pascal\"\n acronyms: [\n \"ACL\"\n \"API\"\n \"ASCII\"\n \"CPU\"\n \"CSS\"\n \"DNS\"\n \"EOF\"\n \"GUID\"\n \"HTML\"\n \"HTTP\"\n \"HTTPS\"\n \"ID\"\n \"IP\"\n \"JSON\"\n \"LHS\"\n \"QPS\"\n \"RAM\"\n \"RHS\"\n \"RPC\"\n \"SLA\"\n \"SMTP\"\n \"SQL\"\n \"SSH\"\n \"TCP\"\n \"TLS\"\n \"TTL\"\n \"UDP\"\n \"UI\"\n \"UID\"\n \"UUID\"\n \"URI\"\n \"URL\"\n \"UTF8\"\n \"VM\"\n \"XML\"\n \"XMPP\"\n \"XSRF\"\n \"XSS\"\n ]\n }\n types: prim: {\n string: \"string\"\n int: \"int64\"\n bigint: \"string\"\n float: \"float64\"\n decimal: \"string\"\n bool: \"bool\"\n null: \"any\"\n any: \"any\"\n }\n types: list: open: \"[]\"\n types: map: open: \"map[\"\n types: opt: open: \"*\"\n banner: \"Code generated by aontu from {path}. DO NOT EDIT.\"\n}\n",
14
- "aontu:render/lang/markdown": "# aontu:render/lang/markdown --- THE MARKDOWN PROFILE. No declarations to\n# lower, so this is the text profile plus markdown's own two: the HTML\n# comment form and the template marker its files carry. EXPERIMENTAL\n# until the distribution layer can version it by canon-hash.\n\n@\"aontu:render\"\n\naontu: render: Lang: lang: \"markdown\"\naontu: render: Lang: indent: { unit:\" \" width:2 }\naontu: render: Lang: comment: block: { open:\"<!--\" close:\"-->\" }\naontu: render: Lang: template: { marker:\"<!---\" ext: [\"md\" \"markdown\"] }\n",
15
- "aontu:render/lang/text": "# aontu:render/lang/text --- THE TEXT PROFILE. Indentation and nothing else:\n# a fold over fragments applies nothing else. Every language without a\n# lowering renders under it. EXPERIMENTAL until the distribution layer\n# can version it by canon-hash.\n\n@\"aontu:render\"\n\naontu: render: Lang: { lang:\"text\" indent: { unit:\" \" width:2 } }\n",
16
- "aontu:render/lang/typescript": "# aontu:render/lang/typescript --- THE TYPESCRIPT PROFILE. Indentation,\n# comment forms, string escapes, identifier case and type forms.\n# Lowering a declaration to an interface is code; what the renderer\n# applies without looking at a node's shape is here. EXPERIMENTAL until\n# the distribution layer can version it by canon-hash.\n\n@\"aontu:render\"\n\naontu: render: Lang: {\n lang: \"typescript\"\n lowering: \"typescript\"\n indent: { unit:\" \" width:2 }\n comment: line: prefix: \"// \"\n comment: block: { open:\"/*\" prefix:\" * \" close:\" */\" }\n comment: doc: { open:\"/**\" prefix:\" * \" close:\" */\" }\n str: quote: \"\\\"\"\n str: escape: {\n \"8\": \"\\\\b\"\n \"9\": \"\\\\t\"\n \"10\": \"\\\\n\"\n \"12\": \"\\\\f\"\n \"13\": \"\\\\r\"\n \"34\": \"\\\\\\\"\"\n \"92\": \"\\\\\\\\\"\n }\n ident: {\n chars: \"ascii-word\"\n reserved: [\n \"await\"\n \"break\"\n \"case\"\n \"catch\"\n \"class\"\n \"const\"\n \"continue\"\n \"debugger\"\n \"default\"\n \"delete\"\n \"do\"\n \"else\"\n \"enum\"\n \"export\"\n \"extends\"\n \"false\"\n \"finally\"\n \"for\"\n \"function\"\n \"if\"\n \"implements\"\n \"import\"\n \"in\"\n \"instanceof\"\n \"interface\"\n \"let\"\n \"new\"\n \"null\"\n \"package\"\n \"private\"\n \"protected\"\n \"public\"\n \"return\"\n \"static\"\n \"super\"\n \"switch\"\n \"this\"\n \"throw\"\n \"true\"\n \"try\"\n \"typeof\"\n \"var\"\n \"void\"\n \"while\"\n \"with\"\n \"yield\"\n ]\n case: record: \"pascal\"\n case: field: \"camel\"\n case: enum: \"pascal\"\n case: member: \"pascal\"\n case: const: \"camel\"\n case: func: \"camel\"\n case: param: \"camel\"\n case: alias: \"pascal\"\n acronyms: []\n }\n types: prim: {\n string: \"string\"\n int: \"number\"\n bigint: \"bigint\"\n float: \"number\"\n decimal: \"string\"\n bool: \"boolean\"\n null: \"null\"\n any: \"unknown\"\n }\n types: list: { close:\"[]\" childPrec:2 }\n types: map: { open:\"Record<\" close:\">\" }\n types: opt: { close:\" | null\" prec:1 childPrec:2 }\n types: union: { prec:1 childPrec:2 }\n types: lit: prec: 1\n banner: \"Code generated by aontu from {path}. DO NOT EDIT.\"\n}\n",
11
+ "aontu:lang/markdown": "# aontu:lang/markdown --- THE MARKDOWN PROFILE. The text profile plus\n# markdown's own two: the HTML comment form, and the template marker\n# its files carry.\n\n@\"aontu:profile\"\n\naontu: Lang: lang: \"markdown\"\naontu: Lang: indent: { unit:\" \" width:2 }\naontu: Lang: comment: block: { open:\"<!--\" close:\"-->\" }\naontu: Lang: template: { marker:\"<!---\" ext: [\"md\" \"markdown\"] }\n",
12
+ "aontu:lang/text": "# aontu:lang/text --- THE TEXT PROFILE. Indentation and nothing else.\n# Every language without one of its own is read under it.\n\n@\"aontu:profile\"\n\naontu: Lang: { lang:\"text\" indent: { unit:\" \" width:2 } }\n",
13
+ "aontu:profile": "# aontu:profile --- THE PROFILE VOCABULARY. A language declared as\n# data: its name, its indentation, its comment forms and the marker its\n# generators carry. `aontu template` and `aontu fmt` read one through\n# --profile. EXPERIMENTAL, versioned by canon-hash later.\n\n%comment = close({ open?:string prefix?:string close?:string })\n\n%profile = close({\n lang: string & length(min(1))\n indent: close({\n unit: *\" \"|string & length(min(1))\n width: *2|integer & min(0) & max(16)\n })\n comment?: close({ line?:%comment block?:%comment doc?:%comment })\n template?: close({\n marker: string & length(min(1))\n close?: string & length(min(1))\n ext?: [&: string & re(\"^[A-Za-z0-9_+-]+$\")]\n })\n})\n\n# Profile names the schema; Lang is where one lands. type() so naming\n# the schema neither generates it nor asks a document to fill it.\naontu: { Profile:type(%profile) Lang:%profile }\n",
17
14
  "aontu:system": "# aontu:system --- the SYSTEM VOCABULARY. EXPERIMENTAL until the\n# distribution layer can version it by canon-hash.\n\naontu: System: {\n Port: type({ direction: *in|out|inout protocol?:string })\n\n Component: type({ ports?: { &: $.aontu.System.Port } })\n\n # Written out rather than Component & { kind: service }: a reference\n # to a type()-marked member does not survive the include.\n Service: type({ kind:service ports?: { &: $.aontu.System.Port } })\n\n # A grammar, not re(): the identifier shape is a quantified group\n # holding a quantifier, which re() refuses. hide()n so a schema's\n # grammar does not generate into the document it checks.\n semverPreRelease: hide(\n abnf(\n `pre-release = pre-release-id *( \".\" pre-release-id )\npre-release-id = \"0\" [ *digit alnum-tail ]\n / positive-digit *digit [ alnum-tail ] / alnum-tail\nalnum-tail = non-digit *id-char\nid-char = digit / non-digit\nnon-digit = letter / \"-\"\ndigit = \"0\" / positive-digit\npositive-digit = %x31-39\nletter = %x41-5A / %x61-7A\n`\n )\n )\n semverBuild: hide(\n abnf(\n `build = build-id *( \".\" build-id )\nbuild-id = 1*id-char\nid-char = digit / non-digit\nnon-digit = letter / \"-\"\ndigit = \"0\" / positive-digit\npositive-digit = %x31-39\nletter = %x41-5A / %x61-7A\n`\n )\n )\n\n # major minor patch pre-release build, tail defaulted.\n Semver: type(\n [\n integer & min(0)\n *0|(integer & min(0))\n *0|(integer & min(0))\n *\"\"|parse($.aontu.System.semverPreRelease)\n *\"\"|parse($.aontu.System.semverBuild)\n ] & length(5)\n )\n}\n",
18
15
  "aontu:view": "# aontu:view --- the FIGURE VOCABULARY. EXPERIMENTAL until the\n# distribution layer can version it by canon-hash.\n\naontu: View: Figure: type({\n kind: doc|lattice|tree|matrix|graph|layer|sets|layers\n | ladder\n out: string\n\n # Every kind.\n as?: text|mermaid|dot|er|svg\n at?: string\n maxRows?: integer & min(0)\n\n # doc.\n depth?: integer & min(0)\n\n # tree, matrix, layer, graph.\n relation?: string\n relations?: [&: string]\n roots?: [&: string]\n\n # matrix.\n order?: canon|partition\n closure?: boolean\n\n # graph, layer.\n groupBy?: string\n label?: string\n layers?: [&: string]\n edges?: upward|all|none\n\n # sets, layers.\n sets?: string\n member?: string\n universe?: string\n minDegree?: integer & min(0)\n maxCols?: integer & min(0)\n minSize?: integer & min(0)\n})\n"
19
16
  }
20
17
 
21
18
  const AONTU_MODELS: string[] = [
22
- "aontu:code",
23
- "aontu:render",
24
- "aontu:render/lang/go",
25
- "aontu:render/lang/markdown",
26
- "aontu:render/lang/text",
27
- "aontu:render/lang/typescript",
19
+ "aontu:lang/markdown",
20
+ "aontu:lang/text",
21
+ "aontu:profile",
28
22
  "aontu:system",
29
23
  "aontu:view"
30
24
  ]
package/src/casing.ts ADDED
@@ -0,0 +1,95 @@
1
+ /* Copyright (c) 2026 Richard Rodger, MIT License */
2
+
3
+
4
+ function isUpper(c: string): boolean {
5
+ return 'A' <= c && c <= 'Z'
6
+ }
7
+
8
+ function isLower(c: string): boolean {
9
+ return 'a' <= c && c <= 'z'
10
+ }
11
+
12
+ function isDigit(c: string): boolean {
13
+ return '0' <= c && c <= '9'
14
+ }
15
+
16
+ function isASCII(c: string): boolean {
17
+ return (c.codePointAt(0) as number) < 0x80
18
+ }
19
+
20
+ export function splitWords(name: string): string[] {
21
+ const words: string[] = []
22
+ let cur = ''
23
+ const chars = Array.from(name)
24
+ for (let i = 0; i < chars.length; i++) {
25
+ const c = chars[i]
26
+ if ('_' === c || '-' === c || ' ' === c) {
27
+ if ('' !== cur) {
28
+ words.push(cur)
29
+ }
30
+ cur = ''
31
+ continue
32
+ }
33
+ if ('' !== cur) {
34
+ const prev = chars[i - 1]
35
+ const next = chars[i + 1]
36
+ const boundary = isASCII(c) && isASCII(prev) && (
37
+ (isUpper(c) && (isLower(prev) || isDigit(prev))) ||
38
+ (isDigit(c) !== isDigit(prev)) ||
39
+ (isUpper(c) && isUpper(prev) && undefined !== next && isLower(next)))
40
+ if (boundary) {
41
+ words.push(cur)
42
+ cur = ''
43
+ }
44
+ }
45
+ cur += c
46
+ }
47
+ if ('' !== cur) {
48
+ words.push(cur)
49
+ }
50
+ return words
51
+ }
52
+
53
+ export function lowerASCII(s: string): string {
54
+ return Array.from(s).map((c) =>
55
+ isUpper(c) ? String.fromCharCode(c.charCodeAt(0) + 32) : c).join('')
56
+ }
57
+
58
+ export function upperASCII(s: string): string {
59
+ return Array.from(s).map((c) =>
60
+ isLower(c) ? String.fromCharCode(c.charCodeAt(0) - 32) : c).join('')
61
+ }
62
+
63
+ // A word capitalised: the acronym set wins, so `id` is `ID` under a
64
+ // profile that lists it and `Id` under one that does not.
65
+ export function capitalise(word: string, acronyms: string[]): string {
66
+ const low = lowerASCII(word)
67
+ for (const a of acronyms) {
68
+ if (lowerASCII(a) === low) {
69
+ return a
70
+ }
71
+ }
72
+ const chars = Array.from(low)
73
+ return upperASCII(chars[0]) + chars.slice(1).join('')
74
+ }
75
+
76
+ // THE CASE STYLES, over the words. `nom` maps its own style names
77
+ // onto these.
78
+ export function caseName(name: string, style: string, acronyms: string[]): string {
79
+ const words = splitWords(name)
80
+ if ('snake' === style) {
81
+ return words.map(lowerASCII).join('_')
82
+ }
83
+ if ('screaming' === style) {
84
+ return words.map(upperASCII).join('_')
85
+ }
86
+ if ('kebab' === style) {
87
+ return words.map(lowerASCII).join('-')
88
+ }
89
+ const caps = words.map((w) => capitalise(w, acronyms))
90
+ if ('pascal' === style) {
91
+ return caps.join('')
92
+ }
93
+ // camel: the first word lower, and never an acronym.
94
+ return lowerASCII(words[0]) + caps.slice(1).join('')
95
+ }