@crombolo/schema 0.0.0-stage → 0.9.1

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.
package/src/write.ts ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Model → canonical TOML.
3
+ *
4
+ * Our own writer, for a fixed vocabulary: deterministic key order, one layout,
5
+ * comments stripped by construction (`description` and `notes` are the fields
6
+ * that survive), and none of the quote or blank-line churn of general-purpose
7
+ * TOML serialisers. `parse(write(x))` must equal `x` — the tests hold it to that.
8
+ */
9
+ import {
10
+ type SchemaContentType,
11
+ type SchemaDataType,
12
+ type SchemaLanguage,
13
+ type SchemaProperty,
14
+ type SchemaTypeKind,
15
+ TYPE_KIND_FILES,
16
+ } from './model.ts'
17
+
18
+ function str(value: string): string {
19
+ return JSON.stringify(value)
20
+ }
21
+
22
+ function scalar(value: unknown): string {
23
+ if (typeof value === 'string') return str(value)
24
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value)
25
+ if (Array.isArray(value)) return `[${value.map(scalar).join(', ')}]`
26
+ if (value === null || value === undefined) return '""'
27
+ return str(JSON.stringify(value))
28
+ }
29
+
30
+ /**
31
+ * A key as TOML will read it back: bare when the grammar allows, quoted and
32
+ * escaped when it does not.
33
+ *
34
+ * Every key in this file is a literal of ours but one — a data type's `config`
35
+ * keys are property-editor value aliases, which arrive from the API and from
36
+ * proposed TOML. An alias holding a newline, written bare, turns one line into
37
+ * several and the file stops parsing: the site then fails to boot on the next
38
+ * read, and with a schema store configured the broken file is published to the
39
+ * other nodes too.
40
+ */
41
+ function keyOf(key: string): string {
42
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : str(key)
43
+ }
44
+
45
+ function line(out: string[], key: string, value: unknown, when = true): void {
46
+ if (!when || value === undefined) return
47
+ out.push(`${keyOf(key)} = ${scalar(value)}`)
48
+ }
49
+
50
+ function writeProperty(out: string[], header: string, p: SchemaProperty): void {
51
+ out.push('', `[[${header}]]`)
52
+ line(out, 'key', p.key)
53
+ line(out, 'alias', p.alias)
54
+ line(out, 'name', p.name)
55
+ line(out, 'description', p.description)
56
+ line(out, 'notes', p.notes)
57
+ line(out, 'type', p.type)
58
+ line(out, 'mandatory', true, p.mandatory)
59
+ line(out, 'mandatory-message', p.mandatoryMessage)
60
+ line(out, 'regex', p.regex)
61
+ line(out, 'regex-message', p.regexMessage)
62
+ line(out, 'varies-by-culture', true, p.variesByCulture)
63
+ line(out, 'varies-by-segment', true, p.variesBySegment)
64
+ line(out, 'label-on-top', true, p.labelOnTop)
65
+ line(out, 'member-can-view', true, p.memberCanView === true)
66
+ line(out, 'member-can-edit', true, p.memberCanEdit === true)
67
+ line(out, 'sensitive', true, p.sensitive === true)
68
+ line(out, 'default', p.default)
69
+ line(out, 'since', p.since)
70
+ }
71
+
72
+ export function writeContentType(t: SchemaContentType, kind: SchemaTypeKind = 'document'): string {
73
+ const out: string[] = [`[${TYPE_KIND_FILES[kind].header}]`]
74
+ line(out, 'key', t.key)
75
+ line(out, 'alias', t.alias)
76
+ line(out, 'name', t.name)
77
+ line(out, 'description', t.description)
78
+ line(out, 'notes', t.notes)
79
+ line(out, 'icon', t.icon, t.icon !== 'icon-document')
80
+ line(out, 'allow-at-root', true, t.allowAtRoot)
81
+ line(out, 'is-element', true, t.isElement)
82
+ line(out, 'allow-in-library', true, t.allowInLibrary)
83
+ line(out, 'varies-by-culture', true, t.variesByCulture)
84
+ line(out, 'varies-by-segment', true, t.variesBySegment)
85
+ line(out, 'compositions', t.compositions, t.compositions.length > 0)
86
+ line(out, 'allow-children', t.allowChildren, t.allowChildren.length > 0)
87
+ line(out, 'components', t.components, t.components.length > 0)
88
+ line(out, 'default-component', t.defaultComponent)
89
+ line(out, 'collection', t.collection)
90
+ line(out, 'folder', t.folder)
91
+ line(out, 'since', t.since)
92
+ if (
93
+ t.cleanup.prevent ||
94
+ t.cleanup.keepAllNewerThanDays !== undefined ||
95
+ t.cleanup.keepLatestPerDayForDays !== undefined
96
+ ) {
97
+ out.push('', `[${TYPE_KIND_FILES.document.header}.cleanup]`)
98
+ line(out, 'prevent', true, t.cleanup.prevent)
99
+ line(out, 'keep-all-newer-than-days', t.cleanup.keepAllNewerThanDays)
100
+ line(out, 'keep-latest-per-day-for-days', t.cleanup.keepLatestPerDayForDays)
101
+ }
102
+ for (const p of t.properties) writeProperty(out, 'property', p)
103
+ for (const g of t.groups ?? []) {
104
+ out.push('', '[[group]]')
105
+ line(out, 'name', g.name)
106
+ line(out, 'alias', g.alias)
107
+ for (const p of g.properties) writeProperty(out, 'group.property', p)
108
+ }
109
+ for (const tab of t.tabs) {
110
+ out.push('', '[[tab]]')
111
+ line(out, 'name', tab.name)
112
+ line(out, 'alias', tab.alias)
113
+ for (const p of tab.properties) writeProperty(out, 'tab.property', p)
114
+ for (const g of tab.groups) {
115
+ out.push('', '[[tab.group]]')
116
+ line(out, 'name', g.name)
117
+ line(out, 'alias', g.alias)
118
+ for (const p of g.properties) writeProperty(out, 'tab.group.property', p)
119
+ }
120
+ }
121
+ return `${out.join('\n')}\n`
122
+ }
123
+
124
+ export function writeMediaType(t: SchemaContentType): string {
125
+ return writeContentType(t, 'media')
126
+ }
127
+
128
+ export function writeMemberType(t: SchemaContentType): string {
129
+ return writeContentType(t, 'member')
130
+ }
131
+
132
+ export function writeDataType(d: SchemaDataType): string {
133
+ const out: string[] = ['[data-type]']
134
+ line(out, 'key', d.key)
135
+ line(out, 'alias', d.alias)
136
+ line(out, 'name', d.name)
137
+ line(out, 'notes', d.notes)
138
+ line(out, 'editor', d.editor)
139
+ line(out, 'editor-ui', d.editorUi)
140
+ line(out, 'folder', d.folder)
141
+ line(out, 'since', d.since)
142
+ const keys = Object.keys(d.config).sort()
143
+ if (keys.length > 0) {
144
+ out.push('', '[data-type.config]')
145
+ for (const key of keys) line(out, key, d.config[key])
146
+ }
147
+ return `${out.join('\n')}\n`
148
+ }
149
+
150
+ export function writeLanguages(languages: readonly SchemaLanguage[]): string {
151
+ const out: string[] = []
152
+ for (const l of languages) {
153
+ if (out.length > 0) out.push('')
154
+ out.push('[[language]]')
155
+ line(out, 'iso', l.iso)
156
+ line(out, 'name', l.name)
157
+ line(out, 'default', true, l.default)
158
+ line(out, 'mandatory', true, l.mandatory)
159
+ line(out, 'fallback', l.fallback)
160
+ }
161
+ return `${out.join('\n')}\n`
162
+ }
163
+
164
+ export function writeSchemaVersion(version: string): string {
165
+ return `[schema]\nversion = ${str(version)}\n`
166
+ }
167
+
168
+ /** The canonical filename for an alias: kebab-case. */
169
+ export function fileNameFor(alias: string): string {
170
+ const kebab = alias
171
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
172
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
173
+ .replace(/[^A-Za-z0-9]+/g, '-')
174
+ .toLowerCase()
175
+ return `${kebab}.toml`
176
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.