choirmark 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christophe Le Bars
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # ChoirMark
2
+
3
+ An open format for documents — content carries the meaning, themes carry the look. Like
4
+ [CommonMark](https://commonmark.org), but for whole documents.
5
+
6
+ ChoirMark is CommonMark plus a small extension profile (fenced divs `::: role`, bracketed spans
7
+ `[text]{.role}`, attributes) and a closed role vocabulary. A `.cmk` file describes *what* each part
8
+ of a document is; a theme decides *how* it looks.
9
+
10
+ See [`spec/spec.md`](spec/spec.md) for the format. The spec is also the conformance suite: every
11
+ `example` block in it becomes a test (see `tools/extract-examples.js`).
12
+
13
+ ## Status
14
+
15
+ Early. The specification skeleton and the reference-implementation surface are in place; the parser
16
+ is **not implemented yet** — `parse()` and `toHtml()` currently throw. Track progress in
17
+ [`CHANGELOG.md`](CHANGELOG.md).
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install choirmark
23
+ ```
24
+
25
+ Requires Node ≥ 20.
26
+
27
+ ## Usage
28
+
29
+ ```js
30
+ import { parse, toHtml } from 'choirmark'
31
+
32
+ const doc = parse(source) // .cmk source → document model
33
+ const html = toHtml(source) // .cmk source → role-tagged HTML pivot
34
+ ```
35
+
36
+ ## License
37
+
38
+ MIT. See [`LICENSE`](LICENSE).
39
+
40
+ [choirmark.org](https://choirmark.org)
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "choirmark",
3
+ "version": "0.1.0",
4
+ "description": "An open format for documents — content carries the meaning, themes carry the look. Like CommonMark, but for whole documents.",
5
+ "type": "module",
6
+ "exports": "./src/index.js",
7
+ "files": [
8
+ "src",
9
+ "spec/spec.md",
10
+ "spec/LICENSE"
11
+ ],
12
+ "scripts": {
13
+ "lint": "standard",
14
+ "test": "node --test",
15
+ "examples": "node tools/extract-examples.js spec/spec.md",
16
+ "build:spec": "node tools/build-spec.js spec/spec.md",
17
+ "prerelease": "npm run lint && npm run test",
18
+ "release": "release-it"
19
+ },
20
+ "keywords": [
21
+ "choirmark",
22
+ "cmk",
23
+ "document",
24
+ "markup",
25
+ "commonmark",
26
+ "format",
27
+ "specification"
28
+ ],
29
+ "author": "Christophe Le Bars",
30
+ "license": "MIT",
31
+ "homepage": "https://choirmark.org",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/clbrge/choirmark.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/clbrge/choirmark/issues"
38
+ },
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "publishConfig": {
43
+ "registry": "https://registry.npmjs.org",
44
+ "access": "public",
45
+ "provenance": true
46
+ },
47
+ "release-it": {
48
+ "hooks": {
49
+ "before:bump": "grep -q '^## \\[${version}\\]' CHANGELOG.md || (echo 'CHANGELOG.md is missing a ## [${version}] section — add it before releasing.' >&2 && exit 1)",
50
+ "after:bump": "npm install --package-lock-only --ignore-scripts"
51
+ },
52
+ "git": {
53
+ "commitMessage": "release: v${version}",
54
+ "requireUpstream": false,
55
+ "tagName": "v${version}",
56
+ "push": true
57
+ },
58
+ "npm": {
59
+ "publish": false
60
+ },
61
+ "github": {
62
+ "release": true,
63
+ "releaseName": "choirmark v${version}",
64
+ "releaseNotes": "awk '/^## \\[${version}\\]/{flag=1;next}/^## \\[/{flag=0}flag' CHANGELOG.md"
65
+ }
66
+ },
67
+ "devDependencies": {
68
+ "release-it": "^20.2.0",
69
+ "standard": "^17.1.2"
70
+ }
71
+ }
package/spec/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ The ChoirMark specification — everything in this directory (spec/) — is licensed under the
2
+ Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0).
3
+
4
+ https://creativecommons.org/licenses/by-sa/4.0/
5
+
6
+ Copyright (c) 2026 Christophe Le Bars
7
+
8
+ The reference implementation (all code outside spec/) is licensed separately under the MIT
9
+ License — see the LICENSE file at the repository root.
package/spec/spec.md ADDED
@@ -0,0 +1,224 @@
1
+ # ChoirMark Specification
2
+
3
+ **Version:** 0.1.0-draft · **Status:** early draft — the syntax is not yet frozen.
4
+
5
+ > Licensed CC BY-SA 4.0 (see `spec/LICENSE`). The reference implementation is MIT.
6
+
7
+ ## 1. Introduction
8
+
9
+ ChoirMark is an open format for **documents** — letters, reports, decks — as opposed to web pages. It
10
+ keeps a document's **content and meaning** separate from its **presentation**: the source carries what
11
+ each part *is* (a date, a sender, a slide title), and a theme decides how it *looks*.
12
+
13
+ A ChoirMark document is a [CommonMark](https://spec.commonmark.org/) document extended with a fixed,
14
+ named profile of constructs for assigning **roles** to content (§4). The profile is adopted
15
+ explicitly — nothing outside it is ChoirMark. This specification defines the extensions and the
16
+ canonical rendering to the HTML pivot; it includes CommonMark by normative reference rather than
17
+ restating it. It does **not** define how roles are *inferred* from unmarked content (loose →
18
+ resolved), nor how a document is themed, paginated, or judged — those are engine concerns, downstream
19
+ of this specification (§2).
20
+
21
+ This is a draft. Sections marked _TODO_ are not yet complete.
22
+
23
+ ## 2. Scope — the HTML pivot
24
+
25
+ ChoirMark owns exactly one transformation: a **resolved** `.cmk` document → a **canonical, role-tagged
26
+ HTML pivot**. That transformation is deterministic, theme-free, and judgment-free — the same way
27
+ CommonMark stops at HTML and says nothing about CSS or whether a page looks good.
28
+
29
+ ```
30
+ resolved .cmk ──[ChoirMark]──▶ role-tagged HTML ──[engine]──▶ themed forme ──▶ gate ──▶ PDF
31
+ ▲ the pivot = the border ▲
32
+ ```
33
+
34
+ The role-tagged HTML is **normative**: `::: date` → `<div data-role="date">…</div>` is fixed by this
35
+ spec, so every implementation and every theme can rely on it. A consuming engine (such as Aldina)
36
+ reads the pivot; nothing reaches back across the border.
37
+
38
+ **In scope (ChoirMark):** the syntax, the role vocabulary and classes (the frozen interface), the
39
+ structural auto-roles, and the resolved-`.cmk` → HTML mapping with its conformance examples.
40
+
41
+ **Out of scope (the engine):** inferring roles for *unmarked* content, applying a theme, the quality
42
+ gate, repair, and projection to PDF. The litmus: if a step needs a **theme** or a **judgment**, it is
43
+ downstream of the border.
44
+
45
+ A theme may only *style existing roles* — it can never invent one. A new role or class is a **spec**
46
+ change, never a theme feature; that is what keeps the interface frozen.
47
+
48
+ ## 3. How to read this spec
49
+
50
+ The specification is also the conformance suite. Throughout, examples are given as an input and the
51
+ HTML it must produce, separated by a single `.`:
52
+
53
+ ````example
54
+ # Notice of Termination
55
+ .
56
+ <h1>Notice of Termination</h1>
57
+ ````
58
+
59
+ The block above is one such example: ChoirMark, inheriting CommonMark, renders an ATX heading as an
60
+ `<h1>`. `tools/extract-examples.js` lifts every example into `test/spec.test.js`, so the spec and any
61
+ implementation cannot drift; an implementation in any language can run the same examples by parsing
62
+ this file.
63
+
64
+ ## 4. The extension profile
65
+
66
+ ChoirMark is CommonMark with exactly the following extensions ON; everything else — notably raw
67
+ HTML/TeX — is OFF. The names follow Pandoc's Markdown extension vocabulary:
68
+
69
+ `````
70
+ +yaml_metadata_block front matter (document type, page/title fields)
71
+ +fenced_divs ::: block role / structure
72
+ +bracketed_spans [text]{.role} inline role
73
+ +header_attributes # Heading {#id .class}
74
+ +fenced_code_attributes ```lang {#id}
75
+ +link_attributes [x](u){…} and image attributes ![cap](src){#fig:x}
76
+ +implicit_figures ![caption](src) → a figure
77
+ +pipe_tables tables
78
+ +table_captions : caption {#tbl:x}
79
+ +footnotes text[^id] / [^id]: …
80
+ +citations [@key]
81
+ -raw_html NO escape hatch — content cannot bypass the role system
82
+ -raw_tex
83
+ -markdown_in_html_blocks
84
+ `````
85
+
86
+ Cross-references (`[@fig:x]`, `[@sec:x]`, `[@ch:x]`, `[@tbl:x]`) use a `@`-prefix whose target is a
87
+ `#fig:` / `#sec:` / `#tbl:` label, distinct from a bibliographic citation `[@key]` (a key with no
88
+ reserved prefix). Whether an implementation parses with Pandoc or its own reader is an implementation
89
+ detail — **the syntax above is the contract.**
90
+
91
+ ## 5. Documents and classes
92
+
93
+ A ChoirMark document belongs to a **class** — `letter`, `folio` (reports), or `deck` — which fixes the
94
+ available roles. (How those roles *look* is a theme's job, downstream.) _TODO: class declaration in
95
+ front matter; the per-class role vocabulary._
96
+
97
+ ## 6. Roles
98
+
99
+ A **role** is a class name drawn from the document class's closed vocabulary. There are two forms:
100
+
101
+ | scope | syntax | → HTML pivot |
102
+ |-------|--------|--------------|
103
+ | block (multi-line) | `::: <role>` … `:::` | `<div data-role="<role>">…</div>` |
104
+ | span / single line | `[text]{.<role>}` | `<span data-role="<role>">text</span>` |
105
+
106
+ ````example
107
+ ::: date
108
+ March 3, 2026
109
+ :::
110
+ .
111
+ <div data-role="date">March 3, 2026</div>
112
+ ````
113
+
114
+ Rules:
115
+
116
+ - A role on a div/span **must** be a known role for the class. An unknown role is an **error**, not a
117
+ silently-dropped annotation (no silent fallback).
118
+ - Roles are single-valued: one role per block/span.
119
+ - The shorthand `::: sender-block` ≡ `::: {.sender-block}`.
120
+
121
+ _TODO: nested divs; the full attribute syntax `{.role #id key=val}`; the enumerated role vocabulary per
122
+ class; cardinality and interaction constraints._
123
+
124
+ ## 7. Structural auto-roles
125
+
126
+ Markdown structure that already *implies* a role is never tagged — the construct **is** the role. The
127
+ mapping is per-class; representative:
128
+
129
+ | construct | letter | deck | folio |
130
+ |-----------|--------|------|-------|
131
+ | `-` / `1.` list | `list` | `bullet-group` / `numbered-steps` | `list` |
132
+ | fenced code block | — | `code-block` | `code-block` |
133
+ | `>` blockquote | — | `pull-quote` | `pull-quote` |
134
+ | pipe table | — | `metrics-table` | `table` |
135
+ | `![cap](src)` | — | `visual` | `figure` |
136
+ | `[^id]` footnote | — | — | `footnote` |
137
+ | `#`…`######` heading | — (rare) | `slide-title` | `section` / `chapter` / `part` (by level × variant) |
138
+
139
+ Only blocks *not* covered by structure take an explicit `:::` / `{.}` tag — for letters that's the
140
+ envelope/closing roles (`sender-block`, `recipient-block`, `date`, `subject`, `signature-block`, …).
141
+
142
+ ## 8. Relational markup (folio)
143
+
144
+ The author writes labels, references, and citations; the implementation derives the numbers, table of
145
+ contents, index, and bibliography:
146
+
147
+ - **labelled float** — `![caption](src){#fig:arch}` · table `: caption {#tbl:x}`
148
+ - **cross-reference** — `[@fig:arch]`, `[@sec:method]`, `[@ch:background]` → "Figure 2", "§1.1",
149
+ "Chapter 3"
150
+ - **citation** — `[@smith2020]` → a generated bibliography placeholder `::: refs` (≡ `::: {#refs}`)
151
+ - **footnote** — `text[^pivot]` + `[^pivot]: …`
152
+
153
+ ## 9. Two densities
154
+
155
+ The same syntax supports two densities:
156
+
157
+ - **Minimal** (authoring) — tag only the ambiguous blocks; let structure and patterns carry the rest.
158
+ - **Resolved** — every block carries an explicit role; the inference is *frozen* and
159
+ author-correctable (a mis-tag is a one-line fix, `::: recipient-block` → `::: sender-block`).
160
+
161
+ Both are valid ChoirMark, but the **normative** transformation — the part this spec defines and
162
+ conformance-tests — is **resolved `.cmk` → HTML pivot**: explicit roles plus the deterministic
163
+ structural auto-roles of §7. Turning *minimal* into *resolved* by guessing roles for unmarked prose is
164
+ **inference**, an engine concern (§2), not part of the standard.
165
+
166
+ ## 10. Worked example — a complaint letter (resolved)
167
+
168
+ ```text
169
+ ---
170
+ type: complaint
171
+ format: us-letter
172
+ window: us-10-left
173
+ ---
174
+
175
+ ::: sender-block
176
+ Marcus Feld
177
+ 14 Rosewood Court
178
+ Denver, CO 80205
179
+ :::
180
+
181
+ [June 25, 2026]{.date}
182
+
183
+ ::: recipient-block
184
+ Customer Relations Department
185
+ Crestline Appliances
186
+ 2200 Industrial Parkway
187
+ Columbus, OH 43215
188
+ :::
189
+
190
+ [RE: Defective dishwasher — Order #CA-558129]{.subject}
191
+
192
+ ::: salutation
193
+ Dear Customer Relations,
194
+ :::
195
+
196
+ I am writing to formally complain about a Crestline Model 700 dishwasher...
197
+
198
+ 1. It fails to drain completely, leaving standing water;
199
+ 2. The upper rack derailed within the first week;
200
+ 3. The control panel intermittently loses power.
201
+
202
+ ::: closing
203
+ Sincerely,
204
+ :::
205
+
206
+ ::: signature-block
207
+ Marcus Feld
208
+ :::
209
+
210
+ ::: enclosures
211
+ Enc.: Copy of receipt; Warranty card
212
+ :::
213
+ ```
214
+
215
+ Every semantic block is tagged; the body paragraphs and the numbered list stay plain (their role is
216
+ structural, §7). ChoirMark renders this deterministically to the pivot — `::: role` → `<div
217
+ data-role>` — and there it stops.
218
+
219
+ ## 11. Conformance
220
+
221
+ A conforming implementation produces, for every example in this specification, output that matches the
222
+ expected HTML after normalization. _TODO: normalization rules (whitespace, attribute order); the
223
+ canonical document-model → HTML mapping (escaping, block vs inline); required vs optional constructs;
224
+ versioning._
package/src/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // ChoirMark — reference implementation (public API).
2
+ //
3
+ // ChoirMark is an open format for documents: content carries the meaning, themes carry the
4
+ // look. See spec/spec.md for the format. This is a scaffold — the parser is not implemented
5
+ // yet; the public surface below is the contract the conformance suite asserts against.
6
+
7
+ export { parse } from './parse.js'
8
+
9
+ // toHtml(source) → HTML string.
10
+ // Renders a ChoirMark source string to its HTML pivot (role-tagged elements). This is the
11
+ // function test/spec.test.js runs every spec example through.
12
+ export function toHtml (source) {
13
+ // TODO: parse(source) → document model → render to the HTML pivot. See spec/spec.md.
14
+ throw new Error('choirmark.toHtml: not yet implemented — see spec/spec.md')
15
+ }
package/src/parse.js ADDED
@@ -0,0 +1,9 @@
1
+ // Parse a ChoirMark (.cmk) source string into a document model.
2
+ //
3
+ // ChoirMark is CommonMark plus a named extension profile (fenced divs `::: role`, bracketed
4
+ // spans `[text]{.role}`, attributes) and a closed role vocabulary. See spec/spec.md.
5
+
6
+ export function parse (source) {
7
+ // TODO: implement per spec/spec.md — CommonMark core, the extension profile, role assignment.
8
+ throw new Error('choirmark.parse: not yet implemented — see spec/spec.md')
9
+ }