carve-lang 0.1.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f75d9a093c48417037884f79d3372f6d8ba90ef5b60781789ec293e68f8a170c
4
- data.tar.gz: 39418a3a8ab54c8b9886066a90d5aac6071bea72a6b70467fbf3e955032c6979
3
+ metadata.gz: 9dc82ae49fa620a500b09b60ad705caca3488b99c18ec3dda8ff22f1aabc5b55
4
+ data.tar.gz: 192a18307bfb8cc9bd9364a333592b92bf0cd9075f2403fa602b09307705912b
5
5
  SHA512:
6
- metadata.gz: e9e3c6dddd06a979acc9c6bd100f711b5e32b741f885a82a873547dc72abba3b6b7c64a5861b1bc87cedd62ff85a45ed4a439b2084ab5f4d85deec7678d58112
7
- data.tar.gz: e05bd66036ca360b3516161990b02e8e5f38ea140a52dc955fc55b082fc4f7da3d57d7bcdf46711aa4e765aa16682c6371f573ed241eb619434a2fbec0dcfc98
6
+ metadata.gz: a0eb63cc0fb830f1d6a69136d7a934931069718a700aa280017313e05a704614ef805bd9a136ce17620fd88188bb39cd4e335e059ecb002683a7a7bf4a646c1c
7
+ data.tar.gz: 33039444b491c79db93ef2041556d4ec40533ec98ed1dec8cc154c79b4b147ffde9a8533bf8df0839ac72c74da352d5087251c1eccb5521d769d0ff3a202a035
data/CHANGELOG.md CHANGED
@@ -7,6 +7,144 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.2] - 2026-08-27
11
+
12
+ ### Security
13
+
14
+ - The gem is published through RubyGems trusted publishing rather than a
15
+ long-lived API key, and the gemspec sets `rubygems_mfa_required`, so RubyGems
16
+ refuses a push from an account without MFA (#81).
17
+
18
+ ### Changed
19
+
20
+ - The engine moves from carve-rs `0.1.3` (`a33c42ad`), which `0.1.1` shipped, to
21
+ `bd19414d` - 106 commits across four pin bumps (#80, #82, #84, #87). Rendering
22
+ changes an existing document can see: a column's alignment defaults come from
23
+ the header section, an explicit alignment run is validated and inherited, and
24
+ a table cell's marker run must end at a space; tables gained semantic row
25
+ partitions and a list table can carry local headers; a braced hyphen pair is
26
+ an en dash, an empty brace pair is text, and the doubled run is the canonical
27
+ arrow; a hyphen run opening a word after whitespace is a flag, not a dash; a
28
+ continuation marker attaches only a flush-left block; the footnote backlink
29
+ has an accessible name; a caption no longer disables a diagram fence's preset;
30
+ the first code group is numbered like every other one; a heading at a content
31
+ column leaves no paragraph open, and a definition at a list item's content
32
+ column closes its paragraph; an all-blank raw payload stays distinct from an
33
+ absent one; and depth limits guard every recursive parse, render and AST-JSON
34
+ path. `<thead>` and `<tfoot>` also write one row per line, which changes the
35
+ emitted HTML without changing what it renders. The last bump adds: three blank
36
+ lines are a hard list boundary rather than a wide gap between two items, so a
37
+ document that used the run as spacing now parses as two lists; a heading id
38
+ can opt in to ASCII folding; a tab control renders as a button and a code
39
+ group takes the mode option; and a `%%` line a verbatim run has already
40
+ swallowed no longer produces a comment node after that run, which changes the
41
+ tree without changing the HTML (#84).
42
+ The fourth bump is 18 further commits, and over the spec corpus exactly one
43
+ document's HTML moves with it: a list marker at an item's content column
44
+ opens a sublist whether or not it is the item's first, so a marker that
45
+ follows a blank line and a paragraph inside an item no longer joins that
46
+ paragraph (markup-carve/carve-rs#1251). A further 104 documents change only
47
+ their reported positions, because a container's span now starts at its
48
+ opening markup and ends at its last placed child, and a definition list's
49
+ likewise (markup-carve/carve-rs#1238, markup-carve/carve-rs#1248,
50
+ markup-carve/carve-rs#1252). Nothing else an existing document can see
51
+ changes (#87).
52
+ - The engine then moves from `bd19414d` to `f2fbb24c` - 39 further commits
53
+ (#91). A container with no closer ends at its last placed child rather than
54
+ one codepoint past it, which is the PART 12 §4 extent defect this gem was
55
+ still the one implementation reporting (markup-carve/carve-rs#1308, #89); a
56
+ footnote continuation survives a blank run (markup-carve/carve-rs#1295); and
57
+ a list takes PART 9 §17 L7's consumed `loose` attribute, which loosens the
58
+ list instead of being emitted on it and which `Carve.parse` publishes on a
59
+ definition list as a `loose` field (markup-carve/carve-rs#1304). Seven corpus
60
+ documents move with the bump and all seven are documents the corpus gained,
61
+ so nothing that already rendered correctly changed.
62
+ - The embedded engine moves to carve-rs `42a952cb`. An indented lone image now
63
+ parses as a paragraph holding an inline image rather than a block image
64
+ (markup-carve/carve#1660, markup-carve/carve-rs#1347). AST only - the
65
+ rendered HTML is unchanged on every corpus document (#93).
66
+ - The embedded engine moves from `42a952cb` to `468e71af` - 70 further commits
67
+ across three pin bumps (#96, #97, #98), and each of the three changes what an
68
+ existing document renders as. The first two settle one rule between them: a
69
+ list item's content column is measured from the bare marker, so neither the
70
+ task checkbox nor a marker-attached attribute block widens it
71
+ (markup-carve/carve-rs#1364, markup-carve/carve-rs#1374,
72
+ markup-carve/carve-rs#1379). `-{#k} [x] a` has content column 2, so a `# h`
73
+ written under it at column 2 opens a heading inside the item where the wider
74
+ column made it lazy paragraph text. Also in those two: a lone image at an
75
+ item's content column keeps the item's looseness, restoring the `<p>` wrapper
76
+ the item used to drop (markup-carve/carve-rs#1359); the ANSI quote bar
77
+ reports containment, so a quoted heading, code block, table, thematic break
78
+ and lone image keep the bar and a quoted list's bar sits outside its marker
79
+ (markup-carve/carve-rs#1363); and on the import side an imported heading
80
+ records its id, an imported task item comes back a task item, an input's type
81
+ matches the checkbox keyword case-insensitively, an HTML comment imports as a
82
+ Carve comment, and an imported code block no longer gains a trailing blank
83
+ line on every pass (markup-carve/carve-rs#1361, markup-carve/carve-rs#1366,
84
+ markup-carve/carve-rs#1378, markup-carve/carve-rs#1386,
85
+ markup-carve/carve-rs#1384).
86
+ The third bump is 44 commits on its own, and rendered HTML moves on 50 corpus
87
+ documents with it. Measured against the corpus's own expected output at the
88
+ spec the new pin pins, the old pin differed on all 50 and the new pin on
89
+ none, so those 50 were being rendered wrongly and this is a correctness fix
90
+ rather than a preference. What an author notices is that a recognized block
91
+ opener written past a container's content column now opens that block inside
92
+ the container instead of folding into text: an indented `> q` under a list
93
+ item wrote `<p>&gt; q</p>` and now writes a block quote, an indented `# h`
94
+ wrote `<p># h</p>` and now writes a heading, an indented code fence folded
95
+ into inline code inside a paragraph and now writes a `<pre>` block, and a
96
+ tab-indented `> quote` wrote text on the item's own line and now writes a
97
+ block quote; where a blank line preceded the block, the item is no longer
98
+ reported loose either. The same authored base applies inside a footnote body
99
+ and a definition description, so a table or a link reference definition
100
+ written past the body's column registers there (markup-carve/carve-rs#1387,
101
+ markup-carve/carve-rs#1420, markup-carve/carve-rs#1422,
102
+ markup-carve/carve-rs#1425, markup-carve/carve-rs#1431,
103
+ markup-carve/carve-rs#1432, markup-carve/carve-rs#1433, measured over the
104
+ corpus that markup-carve/carve-rs#1417 pins). Four further changes an
105
+ existing document can see ride along: an explicit id or class may begin with
106
+ an ASCII digit, so `::: 123` opens a div with that class instead of staying a
107
+ paragraph (markup-carve/carve-rs#1393); `::: >` spells a block quote with no
108
+ marker column, and its closing fence takes a caption
109
+ (markup-carve/carve-rs#1399, markup-carve/carve-rs#1411); reference and
110
+ footnote labels differing only in ASCII whitespace resolve to one definition
111
+ (markup-carve/carve-rs#1397); and `{align=left|right|center}` on an element
112
+ whose `align` means text alignment renders a CSS declaration rather than the
113
+ deprecated presentational attribute (markup-carve/carve-rs#1412).
114
+ `Carve.to_carve` also writes one space after a definition separator
115
+ (markup-carve/carve-rs#1426) (#98).
116
+ - The final release pin moves from `0c54d2ea` to the released carve-rs 0.1.4
117
+ (`2e9c43f2`). This closes every declared spec-drift window: link and
118
+ abbreviation definitions below a definition body's column follow the same
119
+ folding rule, a comment in a footnote body leaves no blank line behind, the
120
+ continuation marker's column gate reaches every container, an empty
121
+ description body writes the `{empty}` sentinel, citation items are typed and
122
+ positioned, and paragraph block-image promotion is published on the AST.
123
+ - The stale pin is what made this gem the only implementation failing PART 12
124
+ conformance in markup-carve/carve#1451 - a binding has no vote of its own, and
125
+ this one was voting with an engine 28 commits behind (#82). It recurred within
126
+ two days, on three documents and a pin one day old, because nothing in this
127
+ repository compares the tree this gem produces against the engine's own (#84).
128
+ It recurred again the next day, on one document, with that comparison still
129
+ unmerged (#86) and the pin 18 commits behind (#87). It recurred twice more
130
+ inside this window: at 39 commits behind, still carrying the PART 12 §4
131
+ extent defect (#89, #91), and again on a ruling only the tree can see,
132
+ which a byte-for-byte HTML comparison cannot detect (#93).
133
+
134
+ ### Added
135
+
136
+ - `Carve.from_html(source, mode: :safe)` and `Carve.from_markdown(source)`
137
+ import an existing document into canonical Carve (#92). Both return the
138
+ shared migration shape `{value:, report:}`: `value` is the Carve source, and
139
+ `report` carries `diagnostics`, each with `code`, `message`, `severity` and,
140
+ where the engine placed one, `path`. `from_html` takes `:safe`, `:semantic`
141
+ or `:roundtrip` and reports back the `mode` and `adapter` it ran with; any
142
+ other mode raises `ArgumentError`. `from_markdown` reports
143
+ `source_format: "markdown"` and diagnoses nothing.
144
+ - `Carve.to_markdown`, `to_plain_text`, `to_ansi` and `to_carve`, so every core
145
+ render target the embedded engine already understood is reachable from Ruby
146
+ (#88). `Carve.to_html` and the AST entry points are unchanged.
147
+
10
148
  ## [0.1.1] - 2026-08-18
11
149
 
12
150
  Everything below is the delta from `v0.1.0`, which is the only version a
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,23 @@ 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=2e9c43f22c5ed05dbd93323ae6b9c94bb867b285#2e9c43f22c5ed05dbd93323ae6b9c94bb867b285"
42
42
  dependencies = [
43
43
  "html5ever",
44
44
  "markup5ever_rcdom",
45
45
  "pulldown-cmark",
46
+ "regex",
46
47
  ]
47
48
 
48
49
  [[package]]
49
50
  name = "carve-rb"
50
- version = "0.1.1"
51
+ version = "0.1.2"
51
52
  dependencies = [
52
53
  "carve-lang",
53
54
  "magnus",
54
55
  "rb-sys",
56
+ "serde_json",
55
57
  ]
56
58
 
57
59
  [[package]]
@@ -125,6 +127,12 @@ dependencies = [
125
127
  "either",
126
128
  ]
127
129
 
130
+ [[package]]
131
+ name = "itoa"
132
+ version = "1.0.18"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
135
+
128
136
  [[package]]
129
137
  name = "lazy_static"
130
138
  version = "1.5.0"
@@ -471,6 +479,19 @@ dependencies = [
471
479
  "syn 3.0.3",
472
480
  ]
473
481
 
482
+ [[package]]
483
+ name = "serde_json"
484
+ version = "1.0.151"
485
+ source = "registry+https://github.com/rust-lang/crates.io-index"
486
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
487
+ dependencies = [
488
+ "itoa",
489
+ "memchr",
490
+ "serde",
491
+ "serde_core",
492
+ "zmij",
493
+ ]
494
+
474
495
  [[package]]
475
496
  name = "shell-words"
476
497
  version = "1.1.1"
@@ -587,3 +608,9 @@ dependencies = [
587
608
  "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.2"
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 = "2e9c43f22c5ed05dbd93323ae6b9c94bb867b285" }
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.2"
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.2
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-08-27 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: