carve-lang 0.1.1 → 0.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f75d9a093c48417037884f79d3372f6d8ba90ef5b60781789ec293e68f8a170c
4
- data.tar.gz: 39418a3a8ab54c8b9886066a90d5aac6071bea72a6b70467fbf3e955032c6979
3
+ metadata.gz: '092afb8611f9491f14b1bdc5e74730dc30fd5efd269a3cf153246d8181e0e94c'
4
+ data.tar.gz: 57b9ce435040eb96ff5148a2ce15e0369bcb6f696bbf22422cb76ce42c60d1a9
5
5
  SHA512:
6
- metadata.gz: e9e3c6dddd06a979acc9c6bd100f711b5e32b741f885a82a873547dc72abba3b6b7c64a5861b1bc87cedd62ff85a45ed4a439b2084ab5f4d85deec7678d58112
7
- data.tar.gz: e05bd66036ca360b3516161990b02e8e5f38ea140a52dc955fc55b082fc4f7da3d57d7bcdf46711aa4e765aa16682c6371f573ed241eb619434a2fbec0dcfc98
6
+ metadata.gz: e8bfb7fea75aa1819fd2bde47740d428ba1c3846d004f5ca4e7fb11e89041750ecd7992ed3913fad42c4a627bbc5842e9e0adf66369f45c71499e34d623a3158
7
+ data.tar.gz: 18f13425d6701f44da146ee93c312acd0e8dec16a76fb55ab7995deadcf7931fbdd9fa8c666d19e7f153b7cd8b19d490cbe42e4389a0bfa9fe3be5183c65c64a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,162 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.3] - 2026-09-08
11
+
12
+ ### Changed
13
+
14
+ - The engine moves from carve-rs `da45f9d2` to `42df4092`, 46 commits, clearing
15
+ the binding-parity drift the nightly run found on 34 corpus documents (#110).
16
+ A comment leaf span begins at its markup, an emptied definition description
17
+ narrows to its marker line, a footnote nested past a footnote body becomes its
18
+ own note, a nested item's lead fence or colon container takes its flush-left
19
+ body, and an all-blank or lone-pipe row is no longer read as a table (the lone
20
+ pipe no longer panics).
21
+ - The engine moves from carve-rs `2e9c43f2` to `da45f9d2`, 20 commits. A colon
22
+ followed by a space and a tab opens no description, an authored task state
23
+ reaches the tree and the HTML, a titled medium emits one title attribute, an
24
+ empty external link target is omitted, and a table continuation survives Djot
25
+ migration. The stale pin is what failed PART 12 binding parity in
26
+ markup-carve/carve#1877 - a binding has no vote of its own.
27
+
28
+ ## [0.1.2] - 2026-08-27
29
+
30
+ ### Security
31
+
32
+ - The gem is published through RubyGems trusted publishing rather than a
33
+ long-lived API key, and the gemspec sets `rubygems_mfa_required`, so RubyGems
34
+ refuses a push from an account without MFA (#81).
35
+
36
+ ### Changed
37
+
38
+ - The engine moves from carve-rs `0.1.3` (`a33c42ad`), which `0.1.1` shipped, to
39
+ `bd19414d` - 106 commits across four pin bumps (#80, #82, #84, #87). Rendering
40
+ changes an existing document can see: a column's alignment defaults come from
41
+ the header section, an explicit alignment run is validated and inherited, and
42
+ a table cell's marker run must end at a space; tables gained semantic row
43
+ partitions and a list table can carry local headers; a braced hyphen pair is
44
+ an en dash, an empty brace pair is text, and the doubled run is the canonical
45
+ arrow; a hyphen run opening a word after whitespace is a flag, not a dash; a
46
+ continuation marker attaches only a flush-left block; the footnote backlink
47
+ has an accessible name; a caption no longer disables a diagram fence's preset;
48
+ the first code group is numbered like every other one; a heading at a content
49
+ column leaves no paragraph open, and a definition at a list item's content
50
+ column closes its paragraph; an all-blank raw payload stays distinct from an
51
+ absent one; and depth limits guard every recursive parse, render and AST-JSON
52
+ path. `<thead>` and `<tfoot>` also write one row per line, which changes the
53
+ emitted HTML without changing what it renders. The last bump adds: three blank
54
+ lines are a hard list boundary rather than a wide gap between two items, so a
55
+ document that used the run as spacing now parses as two lists; a heading id
56
+ can opt in to ASCII folding; a tab control renders as a button and a code
57
+ group takes the mode option; and a `%%` line a verbatim run has already
58
+ swallowed no longer produces a comment node after that run, which changes the
59
+ tree without changing the HTML (#84).
60
+ The fourth bump is 18 further commits, and over the spec corpus exactly one
61
+ document's HTML moves with it: a list marker at an item's content column
62
+ opens a sublist whether or not it is the item's first, so a marker that
63
+ follows a blank line and a paragraph inside an item no longer joins that
64
+ paragraph (markup-carve/carve-rs#1251). A further 104 documents change only
65
+ their reported positions, because a container's span now starts at its
66
+ opening markup and ends at its last placed child, and a definition list's
67
+ likewise (markup-carve/carve-rs#1238, markup-carve/carve-rs#1248,
68
+ markup-carve/carve-rs#1252). Nothing else an existing document can see
69
+ changes (#87).
70
+ - The engine then moves from `bd19414d` to `f2fbb24c` - 39 further commits
71
+ (#91). A container with no closer ends at its last placed child rather than
72
+ one codepoint past it, which is the PART 12 §4 extent defect this gem was
73
+ still the one implementation reporting (markup-carve/carve-rs#1308, #89); a
74
+ footnote continuation survives a blank run (markup-carve/carve-rs#1295); and
75
+ a list takes PART 9 §17 L7's consumed `loose` attribute, which loosens the
76
+ list instead of being emitted on it and which `Carve.parse` publishes on a
77
+ definition list as a `loose` field (markup-carve/carve-rs#1304). Seven corpus
78
+ documents move with the bump and all seven are documents the corpus gained,
79
+ so nothing that already rendered correctly changed.
80
+ - The embedded engine moves to carve-rs `42a952cb`. An indented lone image now
81
+ parses as a paragraph holding an inline image rather than a block image
82
+ (markup-carve/carve#1660, markup-carve/carve-rs#1347). AST only - the
83
+ rendered HTML is unchanged on every corpus document (#93).
84
+ - The embedded engine moves from `42a952cb` to `468e71af` - 70 further commits
85
+ across three pin bumps (#96, #97, #98), and each of the three changes what an
86
+ existing document renders as. The first two settle one rule between them: a
87
+ list item's content column is measured from the bare marker, so neither the
88
+ task checkbox nor a marker-attached attribute block widens it
89
+ (markup-carve/carve-rs#1364, markup-carve/carve-rs#1374,
90
+ markup-carve/carve-rs#1379). `-{#k} [x] a` has content column 2, so a `# h`
91
+ written under it at column 2 opens a heading inside the item where the wider
92
+ column made it lazy paragraph text. Also in those two: a lone image at an
93
+ item's content column keeps the item's looseness, restoring the `<p>` wrapper
94
+ the item used to drop (markup-carve/carve-rs#1359); the ANSI quote bar
95
+ reports containment, so a quoted heading, code block, table, thematic break
96
+ and lone image keep the bar and a quoted list's bar sits outside its marker
97
+ (markup-carve/carve-rs#1363); and on the import side an imported heading
98
+ records its id, an imported task item comes back a task item, an input's type
99
+ matches the checkbox keyword case-insensitively, an HTML comment imports as a
100
+ Carve comment, and an imported code block no longer gains a trailing blank
101
+ line on every pass (markup-carve/carve-rs#1361, markup-carve/carve-rs#1366,
102
+ markup-carve/carve-rs#1378, markup-carve/carve-rs#1386,
103
+ markup-carve/carve-rs#1384).
104
+ The third bump is 44 commits on its own, and rendered HTML moves on 50 corpus
105
+ documents with it. Measured against the corpus's own expected output at the
106
+ spec the new pin pins, the old pin differed on all 50 and the new pin on
107
+ none, so those 50 were being rendered wrongly and this is a correctness fix
108
+ rather than a preference. What an author notices is that a recognized block
109
+ opener written past a container's content column now opens that block inside
110
+ the container instead of folding into text: an indented `> q` under a list
111
+ item wrote `<p>&gt; q</p>` and now writes a block quote, an indented `# h`
112
+ wrote `<p># h</p>` and now writes a heading, an indented code fence folded
113
+ into inline code inside a paragraph and now writes a `<pre>` block, and a
114
+ tab-indented `> quote` wrote text on the item's own line and now writes a
115
+ block quote; where a blank line preceded the block, the item is no longer
116
+ reported loose either. The same authored base applies inside a footnote body
117
+ and a definition description, so a table or a link reference definition
118
+ written past the body's column registers there (markup-carve/carve-rs#1387,
119
+ markup-carve/carve-rs#1420, markup-carve/carve-rs#1422,
120
+ markup-carve/carve-rs#1425, markup-carve/carve-rs#1431,
121
+ markup-carve/carve-rs#1432, markup-carve/carve-rs#1433, measured over the
122
+ corpus that markup-carve/carve-rs#1417 pins). Four further changes an
123
+ existing document can see ride along: an explicit id or class may begin with
124
+ an ASCII digit, so `::: 123` opens a div with that class instead of staying a
125
+ paragraph (markup-carve/carve-rs#1393); `::: >` spells a block quote with no
126
+ marker column, and its closing fence takes a caption
127
+ (markup-carve/carve-rs#1399, markup-carve/carve-rs#1411); reference and
128
+ footnote labels differing only in ASCII whitespace resolve to one definition
129
+ (markup-carve/carve-rs#1397); and `{align=left|right|center}` on an element
130
+ whose `align` means text alignment renders a CSS declaration rather than the
131
+ deprecated presentational attribute (markup-carve/carve-rs#1412).
132
+ `Carve.to_carve` also writes one space after a definition separator
133
+ (markup-carve/carve-rs#1426) (#98).
134
+ - The final release pin moves from `0c54d2ea` to the released carve-rs 0.1.4
135
+ (`2e9c43f2`). This closes every declared spec-drift window: link and
136
+ abbreviation definitions below a definition body's column follow the same
137
+ folding rule, a comment in a footnote body leaves no blank line behind, the
138
+ continuation marker's column gate reaches every container, an empty
139
+ description body writes the `{empty}` sentinel, citation items are typed and
140
+ positioned, and paragraph block-image promotion is published on the AST.
141
+ - The stale pin is what made this gem the only implementation failing PART 12
142
+ conformance in markup-carve/carve#1451 - a binding has no vote of its own, and
143
+ this one was voting with an engine 28 commits behind (#82). It recurred within
144
+ two days, on three documents and a pin one day old, because nothing in this
145
+ repository compares the tree this gem produces against the engine's own (#84).
146
+ It recurred again the next day, on one document, with that comparison still
147
+ unmerged (#86) and the pin 18 commits behind (#87). It recurred twice more
148
+ inside this window: at 39 commits behind, still carrying the PART 12 §4
149
+ extent defect (#89, #91), and again on a ruling only the tree can see,
150
+ which a byte-for-byte HTML comparison cannot detect (#93).
151
+
152
+ ### Added
153
+
154
+ - `Carve.from_html(source, mode: :safe)` and `Carve.from_markdown(source)`
155
+ import an existing document into canonical Carve (#92). Both return the
156
+ shared migration shape `{value:, report:}`: `value` is the Carve source, and
157
+ `report` carries `diagnostics`, each with `code`, `message`, `severity` and,
158
+ where the engine placed one, `path`. `from_html` takes `:safe`, `:semantic`
159
+ or `:roundtrip` and reports back the `mode` and `adapter` it ran with; any
160
+ other mode raises `ArgumentError`. `from_markdown` reports
161
+ `source_format: "markdown"` and diagnoses nothing.
162
+ - `Carve.to_markdown`, `to_plain_text`, `to_ansi` and `to_carve`, so every core
163
+ render target the embedded engine already understood is reachable from Ruby
164
+ (#88). `Carve.to_html` and the AST entry points are unchanged.
165
+
10
166
  ## [0.1.1] - 2026-08-18
11
167
 
12
168
  Everything below is the delta from `v0.1.0`, which is the only version a
@@ -168,6 +324,8 @@ are not listed, because no release ever shipped them.
168
324
  Arrays (every AST node type is covered), enabling custom renderers such as
169
325
  [carve-hexapdf](https://github.com/markup-carve/carve-hexapdf).
170
326
 
171
- [Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.1...HEAD
327
+ [Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.3...HEAD
328
+ [0.1.3]: https://github.com/markup-carve/carve-rb/compare/v0.1.2...v0.1.3
329
+ [0.1.2]: https://github.com/markup-carve/carve-rb/compare/v0.1.1...v0.1.2
172
330
  [0.1.1]: https://github.com/markup-carve/carve-rb/compare/v0.1.0...v0.1.1
173
331
  [0.1.0]: https://github.com/markup-carve/carve-rb/releases/tag/v0.1.0
data/README.md CHANGED
@@ -37,6 +37,14 @@ time via `rb_sys`.
37
37
  require "carve"
38
38
 
39
39
  Carve.to_html("# Hello *world*")
40
+
41
+ # Every core engine target is available from the binding.
42
+ Carve.to_markdown(source)
43
+ Carve.to_plain_text(source)
44
+ Carve.to_ansi(source)
45
+ Carve.to_carve(source)
46
+ Carve.from_html('<p>Hello <strong>world</strong></p>')
47
+ Carve.from_markdown('*em* and **strong**')
40
48
  # => "<section id=\"Hello-world\">\n <h1>Hello <strong>world</strong></h1>\n</section>"
41
49
 
42
50
  # Carve syntax note: *...* is STRONG (bold), /.../ is EMPHASIS (italic).
@@ -320,7 +328,3 @@ that has fallen behind fails a build. Locally:
320
328
  ```sh
321
329
  CARVE_SPEC_CORPUS=/path/to/carve/tests/corpus bundle exec rake test
322
330
  ```
323
-
324
- ## License
325
-
326
- MIT, markup-carve.
data/ext/carve/Cargo.lock CHANGED
@@ -37,21 +37,26 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
37
37
 
38
38
  [[package]]
39
39
  name = "carve-lang"
40
- version = "0.1.3"
41
- source = "git+https://github.com/markup-carve/carve-rs?rev=a33c42ade077467733435322a66cce7957cd491c#a33c42ade077467733435322a66cce7957cd491c"
40
+ version = "0.1.4"
41
+ source = "git+https://github.com/markup-carve/carve-rs?rev=42df409227dc260cec336a6569f4f41c712cdc22#42df409227dc260cec336a6569f4f41c712cdc22"
42
42
  dependencies = [
43
43
  "html5ever",
44
44
  "markup5ever_rcdom",
45
45
  "pulldown-cmark",
46
+ "regex",
47
+ "serde",
48
+ "serde_json",
49
+ "unicode-normalization",
46
50
  ]
47
51
 
48
52
  [[package]]
49
53
  name = "carve-rb"
50
- version = "0.1.1"
54
+ version = "0.1.3"
51
55
  dependencies = [
52
56
  "carve-lang",
53
57
  "magnus",
54
58
  "rb-sys",
59
+ "serde_json",
55
60
  ]
56
61
 
57
62
  [[package]]
@@ -87,14 +92,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
87
92
  checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
88
93
 
89
94
  [[package]]
90
- name = "futf"
91
- version = "0.1.5"
95
+ name = "fastrand"
96
+ version = "2.5.0"
92
97
  source = "registry+https://github.com/rust-lang/crates.io-index"
93
- checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
94
- dependencies = [
95
- "mac",
96
- "new_debug_unreachable",
97
- ]
98
+ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
98
99
 
99
100
  [[package]]
100
101
  name = "glob"
@@ -104,16 +105,12 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
104
105
 
105
106
  [[package]]
106
107
  name = "html5ever"
107
- version = "0.27.0"
108
+ version = "0.39.0"
108
109
  source = "registry+https://github.com/rust-lang/crates.io-index"
109
- checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
110
+ checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
110
111
  dependencies = [
111
112
  "log",
112
- "mac",
113
113
  "markup5ever",
114
- "proc-macro2",
115
- "quote",
116
- "syn 2.0.118",
117
114
  ]
118
115
 
119
116
  [[package]]
@@ -125,6 +122,12 @@ dependencies = [
125
122
  "either",
126
123
  ]
127
124
 
125
+ [[package]]
126
+ name = "itoa"
127
+ version = "1.0.18"
128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
129
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
130
+
128
131
  [[package]]
129
132
  name = "lazy_static"
130
133
  version = "1.5.0"
@@ -162,12 +165,6 @@ version = "0.4.33"
162
165
  source = "registry+https://github.com/rust-lang/crates.io-index"
163
166
  checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
164
167
 
165
- [[package]]
166
- name = "mac"
167
- version = "0.1.1"
168
- source = "registry+https://github.com/rust-lang/crates.io-index"
169
- checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
170
-
171
168
  [[package]]
172
169
  name = "magnus"
173
170
  version = "0.8.2"
@@ -193,23 +190,20 @@ dependencies = [
193
190
 
194
191
  [[package]]
195
192
  name = "markup5ever"
196
- version = "0.12.1"
193
+ version = "0.39.0"
197
194
  source = "registry+https://github.com/rust-lang/crates.io-index"
198
- checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
195
+ checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
199
196
  dependencies = [
200
197
  "log",
201
- "phf",
202
- "phf_codegen",
203
- "string_cache",
204
- "string_cache_codegen",
205
198
  "tendril",
199
+ "web_atoms",
206
200
  ]
207
201
 
208
202
  [[package]]
209
203
  name = "markup5ever_rcdom"
210
- version = "0.3.0"
204
+ version = "0.39.0+unofficial"
211
205
  source = "registry+https://github.com/rust-lang/crates.io-index"
212
- checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18"
206
+ checksum = "3ac010f19d6c4af81eeb4018a39d7a115de9d285af45c126a4ac02e6fc5716b7"
213
207
  dependencies = [
214
208
  "html5ever",
215
209
  "markup5ever",
@@ -270,18 +264,19 @@ dependencies = [
270
264
 
271
265
  [[package]]
272
266
  name = "phf"
273
- version = "0.11.3"
267
+ version = "0.13.1"
274
268
  source = "registry+https://github.com/rust-lang/crates.io-index"
275
- checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
269
+ checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
276
270
  dependencies = [
277
271
  "phf_shared",
272
+ "serde",
278
273
  ]
279
274
 
280
275
  [[package]]
281
276
  name = "phf_codegen"
282
- version = "0.11.3"
277
+ version = "0.13.1"
283
278
  source = "registry+https://github.com/rust-lang/crates.io-index"
284
- checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
279
+ checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
285
280
  dependencies = [
286
281
  "phf_generator",
287
282
  "phf_shared",
@@ -289,19 +284,19 @@ dependencies = [
289
284
 
290
285
  [[package]]
291
286
  name = "phf_generator"
292
- version = "0.11.3"
287
+ version = "0.13.1"
293
288
  source = "registry+https://github.com/rust-lang/crates.io-index"
294
- checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
289
+ checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
295
290
  dependencies = [
291
+ "fastrand",
296
292
  "phf_shared",
297
- "rand",
298
293
  ]
299
294
 
300
295
  [[package]]
301
296
  name = "phf_shared"
302
- version = "0.11.3"
297
+ version = "0.13.1"
303
298
  source = "registry+https://github.com/rust-lang/crates.io-index"
304
- checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
299
+ checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
305
300
  dependencies = [
306
301
  "siphasher",
307
302
  ]
@@ -341,21 +336,6 @@ dependencies = [
341
336
  "proc-macro2",
342
337
  ]
343
338
 
344
- [[package]]
345
- name = "rand"
346
- version = "0.8.7"
347
- source = "registry+https://github.com/rust-lang/crates.io-index"
348
- checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
349
- dependencies = [
350
- "rand_core",
351
- ]
352
-
353
- [[package]]
354
- name = "rand_core"
355
- version = "0.6.4"
356
- source = "registry+https://github.com/rust-lang/crates.io-index"
357
- checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
358
-
359
339
  [[package]]
360
340
  name = "rb-sys"
361
341
  version = "0.9.128"
@@ -449,6 +429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
449
429
  checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
450
430
  dependencies = [
451
431
  "serde_core",
432
+ "serde_derive",
452
433
  ]
453
434
 
454
435
  [[package]]
@@ -471,6 +452,19 @@ dependencies = [
471
452
  "syn 3.0.3",
472
453
  ]
473
454
 
455
+ [[package]]
456
+ name = "serde_json"
457
+ version = "1.0.151"
458
+ source = "registry+https://github.com/rust-lang/crates.io-index"
459
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
460
+ dependencies = [
461
+ "itoa",
462
+ "memchr",
463
+ "serde",
464
+ "serde_core",
465
+ "zmij",
466
+ ]
467
+
474
468
  [[package]]
475
469
  name = "shell-words"
476
470
  version = "1.1.1"
@@ -497,9 +491,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
497
491
 
498
492
  [[package]]
499
493
  name = "string_cache"
500
- version = "0.8.9"
494
+ version = "0.9.0"
501
495
  source = "registry+https://github.com/rust-lang/crates.io-index"
502
- checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
496
+ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
503
497
  dependencies = [
504
498
  "new_debug_unreachable",
505
499
  "parking_lot",
@@ -510,9 +504,9 @@ dependencies = [
510
504
 
511
505
  [[package]]
512
506
  name = "string_cache_codegen"
513
- version = "0.5.4"
507
+ version = "0.6.1"
514
508
  source = "registry+https://github.com/rust-lang/crates.io-index"
515
- checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
509
+ checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
516
510
  dependencies = [
517
511
  "phf_generator",
518
512
  "phf_shared",
@@ -544,15 +538,28 @@ dependencies = [
544
538
 
545
539
  [[package]]
546
540
  name = "tendril"
547
- version = "0.4.3"
541
+ version = "0.5.1"
548
542
  source = "registry+https://github.com/rust-lang/crates.io-index"
549
- checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
543
+ checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08"
550
544
  dependencies = [
551
- "futf",
552
- "mac",
553
- "utf-8",
545
+ "new_debug_unreachable",
546
+ ]
547
+
548
+ [[package]]
549
+ name = "tinyvec"
550
+ version = "1.12.0"
551
+ source = "registry+https://github.com/rust-lang/crates.io-index"
552
+ checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
553
+ dependencies = [
554
+ "tinyvec_macros",
554
555
  ]
555
556
 
557
+ [[package]]
558
+ name = "tinyvec_macros"
559
+ version = "0.1.1"
560
+ source = "registry+https://github.com/rust-lang/crates.io-index"
561
+ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
562
+
556
563
  [[package]]
557
564
  name = "unicase"
558
565
  version = "2.9.0"
@@ -566,10 +573,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
566
573
  checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
567
574
 
568
575
  [[package]]
569
- name = "utf-8"
570
- version = "0.7.6"
576
+ name = "unicode-normalization"
577
+ version = "0.1.25"
578
+ source = "registry+https://github.com/rust-lang/crates.io-index"
579
+ checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
580
+ dependencies = [
581
+ "tinyvec",
582
+ ]
583
+
584
+ [[package]]
585
+ name = "web_atoms"
586
+ version = "0.2.6"
571
587
  source = "registry+https://github.com/rust-lang/crates.io-index"
572
- checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
588
+ checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3"
589
+ dependencies = [
590
+ "phf",
591
+ "phf_codegen",
592
+ "string_cache",
593
+ "string_cache_codegen",
594
+ ]
573
595
 
574
596
  [[package]]
575
597
  name = "windows-link"
@@ -579,11 +601,16 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
579
601
 
580
602
  [[package]]
581
603
  name = "xml5ever"
582
- version = "0.18.1"
604
+ version = "0.39.0"
583
605
  source = "registry+https://github.com/rust-lang/crates.io-index"
584
- checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69"
606
+ checksum = "5ab627f34ff61b80d756180d556f9c68801d836d271b3b8c094504ceca69d221"
585
607
  dependencies = [
586
608
  "log",
587
- "mac",
588
609
  "markup5ever",
589
610
  ]
611
+
612
+ [[package]]
613
+ name = "zmij"
614
+ version = "1.0.23"
615
+ source = "registry+https://github.com/rust-lang/crates.io-index"
616
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
data/ext/carve/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "carve-rb"
3
- version = "0.1.1"
3
+ version = "0.1.3"
4
4
  edition = "2021"
5
5
  rust-version = "1.75"
6
6
  publish = false
@@ -17,8 +17,9 @@ crate-type = ["cdylib"]
17
17
  # interop handles so this builds cleanly under rb_sys/mkmf for Ruby 3.2.
18
18
  magnus = { version = "0.8", features = ["rb-sys"] }
19
19
  rb-sys = "0.9"
20
+ serde_json = "1"
20
21
 
21
22
  # The Carve engine. Imported under the Rust crate alias `carve_rs` so the
22
23
  # binding's own crate name (`carve-rb`) does not collide with the engine's
23
24
  # package name (`carve`) in the Cargo lockfile.
24
- carve_rs = { package = "carve-lang", git = "https://github.com/markup-carve/carve-rs", rev = "a33c42ade077467733435322a66cce7957cd491c" }
25
+ carve_rs = { package = "carve-lang", git = "https://github.com/markup-carve/carve-rs", rev = "42df409227dc260cec336a6569f4f41c712cdc22" }
data/ext/carve/src/lib.rs CHANGED
@@ -24,7 +24,6 @@
24
24
  //! symbols: {...}, safe: ..., profile: ..., sections: ...)` on top of these
25
25
  //! primitives.
26
26
 
27
-
28
27
  use carve_rs::extensions::registry;
29
28
  use carve_rs::{CarveExtension, Mode, Options, Profile, StaticRenderers};
30
29
  use magnus::value::{InnerValue, Opaque};
@@ -200,6 +199,73 @@ fn to_html(source: String) -> String {
200
199
  carve_rs::to_html(&source)
201
200
  }
202
201
 
202
+ fn to_markdown(source: String) -> String {
203
+ carve_rs::to_markdown(&source)
204
+ }
205
+
206
+ fn to_plain_text(source: String) -> String {
207
+ carve_rs::to_plain_text(&source)
208
+ }
209
+
210
+ fn to_ansi(source: String) -> String {
211
+ carve_rs::to_ansi(&source)
212
+ }
213
+
214
+ fn to_carve(source: String) -> String {
215
+ carve_rs::to_carve(&source)
216
+ }
217
+
218
+ fn from_html_json(ruby: &Ruby, source: String, mode: String) -> Result<String, Error> {
219
+ let mode = carve_rs::HtmlImportMode::from_name(&mode).ok_or_else(|| {
220
+ Error::new(
221
+ ruby.exception_arg_error(),
222
+ "mode must be safe, semantic, or roundtrip".to_string(),
223
+ )
224
+ })?;
225
+ let result = carve_rs::html_to_carve(
226
+ &source,
227
+ &carve_rs::HtmlImportOptions {
228
+ mode,
229
+ ..Default::default()
230
+ },
231
+ )
232
+ .map_err(|error| {
233
+ Error::new(
234
+ ruby.exception_runtime_error(),
235
+ format!("HTML import failed: {error:?}"),
236
+ )
237
+ })?;
238
+ let diagnostics = result
239
+ .report
240
+ .diagnostics
241
+ .iter()
242
+ .map(|diagnostic| {
243
+ let mut value = serde_json::json!({
244
+ "code": diagnostic.code.as_str(),
245
+ "message": diagnostic.message,
246
+ "severity": diagnostic.severity.as_str(),
247
+ });
248
+ if let Some(path) = &diagnostic.path {
249
+ value["path"] = serde_json::json!(path);
250
+ }
251
+ value
252
+ })
253
+ .collect::<Vec<_>>();
254
+ Ok(serde_json::json!({
255
+ "value": result.value,
256
+ "report": {
257
+ "mode": result.report.mode.as_str(),
258
+ "adapter": result.report.adapter.as_str(),
259
+ "diagnostics": diagnostics,
260
+ }
261
+ })
262
+ .to_string())
263
+ }
264
+
265
+ fn from_markdown(source: String) -> String {
266
+ carve_rs::markdown_to_carve(&source)
267
+ }
268
+
203
269
  /// Parse Carve source and return its AST as a JSON string.
204
270
  ///
205
271
  /// The pure-Ruby wrapper (`Carve.parse`) turns this into a tree of Ruby
@@ -319,7 +385,9 @@ fn to_html_full_with_symbols(
319
385
  // The safe-render arguments default off and section wrapping stays on, so
320
386
  // this signature stays as published; `to_html_safe` is the one that takes
321
387
  // them.
322
- to_html_safe(ruby, source, names, mode, renderers, symbols, false, None, true)
388
+ to_html_safe(
389
+ ruby, source, names, mode, renderers, symbols, false, None, true,
390
+ )
323
391
  }
324
392
 
325
393
  /// Map a profile name to a [`Profile`], or raise Ruby ArgumentError.
@@ -407,7 +475,6 @@ fn to_html_safe(
407
475
  .map_err(|e| Error::new(ruby.exception_arg_error(), e.to_string()))
408
476
  }
409
477
 
410
-
411
478
  /// Read a document's provenance marker.
412
479
  ///
413
480
  /// Returns a Hash `{version:, generated_by:}` or nil when the document carries
@@ -452,6 +519,12 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
452
519
  // these. `_to_html` is the no-extension fast path; the wrapper owns the
453
520
  // bare `to_html` name.
454
521
  module.define_singleton_method("_to_html", function!(to_html, 1))?;
522
+ module.define_singleton_method("to_markdown", function!(to_markdown, 1))?;
523
+ module.define_singleton_method("to_plain_text", function!(to_plain_text, 1))?;
524
+ module.define_singleton_method("to_ansi", function!(to_ansi, 1))?;
525
+ module.define_singleton_method("to_carve", function!(to_carve, 1))?;
526
+ module.define_singleton_method("_from_html_json", function!(from_html_json, 2))?;
527
+ module.define_singleton_method("_from_markdown", function!(from_markdown, 1))?;
455
528
  module.define_singleton_method("_to_ast_json", function!(to_ast_json, 1))?;
456
529
  module.define_singleton_method(
457
530
  "to_html_with_extensions",
data/lib/carve/version.rb CHANGED
@@ -8,5 +8,5 @@ module Carve
8
8
  # compares it against ext/carve/Cargo.toml and against the newest cut
9
9
  # CHANGELOG section on every run, and .github/workflows/release.yml refuses to
10
10
  # publish a gem whose version is not the tag being released.
11
- VERSION = "0.1.1"
11
+ VERSION = "0.1.3"
12
12
  end
data/lib/carve.rb CHANGED
@@ -42,6 +42,19 @@ module Carve
42
42
  RENDERER_KEYS = %i[mermaid chart graphviz math].freeze
43
43
 
44
44
  class << self
45
+ # Import HTML or Markdown into canonical Carve. Both methods return the
46
+ # shared migration shape `{ value:, report: }`.
47
+ def from_html(source, mode: :safe)
48
+ JSON.parse(_from_html_json(source, mode.to_s), symbolize_names: true)
49
+ end
50
+
51
+ def from_markdown(source)
52
+ {
53
+ value: _from_markdown(source),
54
+ report: { source_format: "markdown", diagnostics: [] }
55
+ }
56
+ end
57
+
45
58
  # Render Carve +source+ to an HTML string.
46
59
  #
47
60
  # With no extensions:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carve-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - markup-carve
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-18 00:00:00.000000000 Z
11
+ date: 2026-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys
@@ -88,7 +88,8 @@ files:
88
88
  homepage: https://github.com/markup-carve/carve-rb
89
89
  licenses:
90
90
  - MIT
91
- metadata: {}
91
+ metadata:
92
+ rubygems_mfa_required: 'true'
92
93
  post_install_message:
93
94
  rdoc_options: []
94
95
  require_paths: