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 +21 -0
- package/README.md +40 -0
- package/package.json +71 -0
- package/spec/LICENSE +9 -0
- package/spec/spec.md +224 -0
- package/src/index.js +15 -0
- package/src/parse.js +9 -0
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 {#fig:x}
|
|
76
|
+
+implicit_figures  → 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
|
+
| `` | — | `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** — `{#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
|
+
}
|