@markup-carve/carve-grammars 0.1.2

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) 2024 php-collective
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,240 @@
1
+ # Carve Grammars
2
+
3
+ Grammars for the [Carve](https://github.com/markup-carve/carve) markup language:
4
+
5
+ - a **Tiptap** integration (editor kit + serializer) that turns a Tiptap/ProseMirror document into Carve markup;
6
+ - **Prism** and **highlight.js** syntax-highlighting grammars for rendering Carve source on the web;
7
+ - a **TextMate** grammar (`textmate/carve.tmLanguage.json`) for TextMate-based highlighters such as Shiki (used by VitePress).
8
+
9
+ Modeled on [djot-grammars](https://github.com/php-collective/djot-grammars), adapted to Carve's syntax. The Tiptap mark mapping mirrors `carve-php`'s `HtmlToCarve` converter; the highlighting grammars mirror the canonical token set in [`carve/resources/grammar.ebnf`](https://github.com/markup-carve/carve) and the TextMate grammar in [vscode-carve](https://github.com/markup-carve/vscode-carve).
10
+
11
+ > **Status:** Tiptap integration, plus Prism, highlight.js and TextMate grammars.
12
+ > Sibling editor grammars live in their own repos: editor-bundled **TextMate** copies in
13
+ > [vscode-carve](https://github.com/markup-carve/vscode-carve) and
14
+ > [intellij-carve](https://github.com/markup-carve/intellij-carve);
15
+ > **Tree-sitter** in [tree-sitter-carve](https://github.com/markup-carve/tree-sitter-carve)
16
+ > and [zed-carve](https://github.com/markup-carve/zed-carve).
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install @markup-carve/carve-grammars
22
+ ```
23
+
24
+ All peer dependencies are optional - install only what you use:
25
+ `@tiptap/core` + `@tiptap/starter-kit` (v2) for the editor, `prismjs` (v1) for
26
+ Prism, `highlight.js` (v11) for highlight.js.
27
+
28
+ `CarveKit` also pulls in several standalone Tiptap marks/extensions (highlight,
29
+ subscript, superscript, underline, link, image, table, task-list); install the
30
+ `@tiptap/extension-*` packages you use, or disable them via
31
+ `CarveKit.configure({ underline: false, ... })`.
32
+
33
+ ## Usage
34
+
35
+ ```js
36
+ import { Editor } from '@tiptap/core'
37
+ import { CarveKit, serializeToCarve } from '@markup-carve/carve-grammars/tiptap'
38
+
39
+ const editor = new Editor({
40
+ element: document.getElementById('editor'),
41
+ extensions: [CarveKit],
42
+ onUpdate: ({ editor }) => {
43
+ const carve = serializeToCarve(editor.getJSON())
44
+ console.log(carve)
45
+ },
46
+ })
47
+ ```
48
+
49
+ ### Individual extensions
50
+
51
+ ```js
52
+ import StarterKit from '@tiptap/starter-kit'
53
+ import { CarveInsert, CarveDelete, CarveDiv, serializeToCarve } from '@markup-carve/carve-grammars/tiptap'
54
+
55
+ const editor = new Editor({
56
+ extensions: [StarterKit, CarveInsert, CarveDelete, CarveDiv],
57
+ })
58
+ ```
59
+
60
+ ## Mark mapping
61
+
62
+ | Tiptap mark | Carve token | Renders as |
63
+ |-------------|-------------|------------|
64
+ | bold | `*text*` / `{*text*}` | `<strong>` |
65
+ | italic | `/text/` / `{/text/}` | `<em>` |
66
+ | underline | `_text_` / `{_text_}` | `<u>` |
67
+ | code | `` `text` `` | `<code>` |
68
+ | highlight | `=text=` / `{=text=}` | `<mark>` |
69
+ | strike | `~text~` / `{~text~}` | `<s>` |
70
+ | subscript | `{,text,}` (braced only) | `<sub>` |
71
+ | superscript | `{^text^}` (braced only) | `<sup>` |
72
+ | insert | `{+text+}` | `<ins>` |
73
+ | delete | `{-text-}` | `<del>` |
74
+ | link | `[text](url)` / `[text](url "title")` | `<a>` |
75
+ | image | `![alt](src)` / `![alt](src "title")` | `<img>` |
76
+ | span | `[text]{.class}` | `<span class>` |
77
+ | abbreviation | `[text]{abbr="..."}` | `<abbr title>` \*\*\* |
78
+
79
+ \*\*\* `[text]{abbr="..."}` renders a real `<abbr title>` only when carve's
80
+ `SemanticSpanExtension` is enabled (the same opt-in extension also maps `{kbd}`
81
+ -> `<kbd>`, `{dfn}` -> `<dfn>`, `{samp}` -> `<samp>`, `{var}` -> `<var>`).
82
+ Without it, the attribute stays literal: `<span abbr="...">`. The mark's
83
+ `parseHTML` reads back the `<abbr title>` form.
84
+
85
+ The tokens target carve-php's **parser** (the contract: serialized Carve must parse
86
+ back to the same elements). Carve's inline syntax differs notably from Djot's:
87
+ emphasis is `/text/` (Djot uses `_`), `_text_` is underline, `~text~` is
88
+ strikethrough, highlight is `=text=`, and subscript/superscript are the
89
+ braced `{,text,}` / `{^text^}` only (a bare `,` or `^` is literal text since
90
+ carve #259).
91
+
92
+ Each single-char delimiter has two equivalent forms: a **bare** form
93
+ (`=text=`) and a **forced brace** form (`{=text=}`) that also works intraword;
94
+ both parse to the same element. The two columns above list bare / forced.
95
+ `serializeToCarve` emits the bare form for `* / _ ~` and the forced `{…}` form
96
+ for `= , ^` (round-trip-safe — those delimiters are likelier to be inert bare);
97
+ `{+…+}` / `{-…-}` (insert / delete) have only the brace form, since `+` / `-`
98
+ are not emphasis delimiters.
99
+
100
+ ### Escaping
101
+
102
+ To honor that round-trip contract, `serializeToCarve` escapes literal Carve
103
+ syntax in plain text so it parses back as text rather than markup - inline code,
104
+ links, footnotes, CriticMarkup, mentions/tags/emoji, and an emphasis delimiter
105
+ appearing inside its own span. Escaping is **contextual**: Carve's flanking rules
106
+ already make most lone delimiters inert (`price * 2`, intraword `x_1`,
107
+ `comma,, two`, `C:\path`, `a@b.com`), so those stay clean. The same logic is
108
+ exposed as `escapeCarve(text)`.
109
+
110
+ ## Block elements
111
+
112
+ Headings (`#`), bullet / ordered / task lists, blockquotes (`>`), fenced code
113
+ blocks (`` ``` lang ``), horizontal rules (`---`), tables (with `|=` header
114
+ cells and `^` / `<` row / column spans), container divs (`::: class`), and
115
+ definition lists.
116
+
117
+ ## Syntax highlighting
118
+
119
+ Render Carve source as highlighted HTML on the web. Both grammars cover the full
120
+ Carve token set: headings, lists, tables, blockquotes, fenced/raw blocks,
121
+ container divs, front matter and comments, plus inline emphasis
122
+ (`*bold*` `/italic/` `_underline_` `~strike~` `=highlight=`, braced
123
+ `{^sup^}` `{,sub,}`),
124
+ code, links, images, spans, attributes, footnotes, math (`` $`x`$ ``),
125
+ CriticMarkup (`{+ins+}` `{-del-}`), mentions, tags and emoji.
126
+
127
+ ### Prism
128
+
129
+ The grammar registers itself against the global `Prism`, so `Prism` must be
130
+ global before the grammar module runs. Because static `import` statements are
131
+ hoisted (they all evaluate before any top-level assignment), load the grammar
132
+ with a dynamic `import` after assigning `globalThis.Prism`:
133
+
134
+ ```js
135
+ import Prism from 'prismjs'
136
+
137
+ globalThis.Prism = Prism // grammar reads the global Prism
138
+ await import('@markup-carve/carve-grammars/prism/carve.js') // registers Prism.languages.carve
139
+
140
+ const html = Prism.highlight(source, Prism.languages.carve, 'carve')
141
+ ```
142
+
143
+ In the browser, load `prismjs` first (it sets the global `Prism`), then load
144
+ `@markup-carve/carve-grammars/prism/carve.js`.
145
+
146
+ ### highlight.js
147
+
148
+ ```js
149
+ import hljs from 'highlight.js'
150
+ import carve from '@markup-carve/carve-grammars/highlightjs/carve.js'
151
+
152
+ hljs.registerLanguage('carve', carve)
153
+ const { value } = hljs.highlight(source, { language: 'carve' })
154
+ ```
155
+
156
+ Loaded as a classic `<script>` after highlight.js, it self-registers against
157
+ the global `hljs`:
158
+
159
+ ```html
160
+ <script src="highlight.min.js"></script>
161
+ <script src="node_modules/@markup-carve/carve-grammars/highlightjs/carve.js"></script>
162
+ <script>hljs.highlightAll();</script>
163
+ ```
164
+
165
+ ### Shiki / VitePress
166
+
167
+ `@markup-carve/carve-grammars/shiki` is the shared kit every Carve docs site uses, so
168
+ highlighting stays identical across them: the TextMate grammar, GitHub
169
+ light/dark themes extended with Carve scope colors, and a transformer + CSS
170
+ pair that bridges what Shiki's HTML emitter cannot express (strikethrough,
171
+ sub/superscript positioning, highlight background).
172
+
173
+ ```ts
174
+ // .vitepress/config.ts
175
+ import { defineConfig } from 'vitepress'
176
+ import { carveMarkdown } from '@markup-carve/carve-grammars/shiki'
177
+
178
+ export default defineConfig({
179
+ markdown: {
180
+ ...carveMarkdown(),
181
+ // carveMarkdown({ light, dark, languages }) to override base themes
182
+ // or register extra grammars
183
+ },
184
+ })
185
+ ```
186
+
187
+ ```ts
188
+ // .vitepress/theme/index.ts
189
+ import '@markup-carve/carve-grammars/shiki/carve.css'
190
+ ```
191
+
192
+ Named exports for other setups: `carveGrammar`, `carveLightExtras` /
193
+ `carveDarkExtras`, `carveLightTheme` / `carveDarkTheme`, `extendTheme`,
194
+ `carveStylingTransformer`.
195
+
196
+ ## API
197
+
198
+ - `serializeToCarve(doc)` - serialize an `editor.getJSON()` document to Carve markup.
199
+ - `escapeCarve(text)` - contextually escape literal Carve syntax in a plain-text run so it round-trips as text (used internally by `serializeToCarve`).
200
+ - `CarveKit` - the bundled Tiptap extension set.
201
+ - Individual extensions: `CarveInsert`, `CarveDelete`, `CarveDiv`, `CarveSpan`, `CarveFootnote`, `CarveFootnoteDefinition`, `CarveMath`, `CarveEmbed`, `CarveAbbreviation`, `CarveDefinitionList`.
202
+
203
+ ## Attributes, math and footnotes
204
+
205
+ - **Attributes** - spans, headings and images serialize an `id` and `class`
206
+ (and any extra non-structural attrs) as a `{#id .class key="val"}` block, e.g.
207
+ `[text]{#me .note}`, `![alt](src){.wide}`. Inline attrs trail their target;
208
+ block attrs (headings) sit on the **preceding** line (strict djot), e.g.
209
+ `{#slug}` then `# Title`.
210
+ - **Math** - `CarveMath` (inline atom) serializes to `` $`x`$ `` and, with
211
+ `display: true`, `` $$`x`$$ ``.
212
+ - **Footnotes** - `CarveFootnote` is the inline `[^label]` reference;
213
+ `CarveFootnoteDefinition` is the matching body block, serialized as
214
+ `[^label]: body`.
215
+
216
+ ## Tests
217
+
218
+ ```bash
219
+ npm test
220
+ ```
221
+
222
+ The suite holds all three grammars to one source of truth: the shared corpus
223
+ from the [`markup-carve/carve`](https://github.com/markup-carve/carve) spec,
224
+ vendored as the `spec/` git submodule (`git submodule update --init`).
225
+
226
+ - `npm run test:coverage` - the coverage matrix. Each grammar (prism,
227
+ highlightjs, tiptap) declares a covered-category set and a skip set (with a
228
+ reason per skip); the test fails if the two do not partition every corpus
229
+ category, so a new spec category forces a deliberate decision.
230
+ - `npm run test:snapshot` - golden token snapshots. Each covered `.crv` is
231
+ tokenized with Prism's and highlight.js's own tokenizers and the token stream
232
+ (type + text) is compared against a committed golden in `tests/snapshots/`.
233
+ Refresh intended changes with `npm run snapshots:update`.
234
+ - `npm run test:roundtrip` - the Tiptap serializer round-trip. Each covered
235
+ `.crv` runs `parse -> ProseMirror JSON -> serializeToCarve -> parse` and the
236
+ two parsed ASTs must be identical, catching serializer drift. Categories the
237
+ serializer cannot represent are skipped with a reason.
238
+
239
+ `npm test` runs all of the above plus the structural grammar and serializer
240
+ unit tests. CI runs the same on Node 18, 20 and 22.