carve-lang 0.1.7 → 0.1.9

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: e1129dd70ab09658be69bfbbf0158a02d599d9e814fb5e3f015be6d1a6cc5621
4
- data.tar.gz: bc87753903ba603238e2c766b20c8384259dc8f44e87da90af4ef3f6d26ac613
3
+ metadata.gz: 796f44a79683bfd2b7add107652d71a7d38aaf6d4aedf7169acde4fa2401af26
4
+ data.tar.gz: d7f0ba287fc9c13eab535e7e376de3096f1baedfd1df415b5f870ec7bfca395f
5
5
  SHA512:
6
- metadata.gz: f4f5180ccfda64b312e035b7302db7de20b52293466dd1719715d7ab9fcdfa06ba61b23e993d042df746ced604219bdb78cd5931e09a6dd25388ee9fffcac102
7
- data.tar.gz: c1276c1504398c7be637fc9e6699b42217e0a3845f49e7647348650701abdc25148fe2138c49643171154d8834c184ef711b294bd3f3215db29de509299e127c
6
+ metadata.gz: aec21e3a2aafe3fd84c6debf48af398ea241610d606b954c770a84bd6b5e38b90b0b1d056ecb4c47f8ba9ec976d85498a5d3013ab6885e1b9fe8fdae572d516b
7
+ data.tar.gz: 6adc9afe363cd5c135a649b6f336cf41a55b6197a4cb8927273fd57fe4e0c484d47bbe159a8469eea11dc49a41771637ca048712bd596a9bd9515e099e0f611a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.9] - 2026-10-18
11
+
12
+ ### Breaking
13
+
14
+ - `Carve.parse` and AST include results carry `astVersion: "1.2"` on the root.
15
+ Exact-key readers must accept the new field (#210).
16
+ - The carve-lang 0.1.10 pin adds `resolved` to resolved link and image references
17
+ and `form` to comments. `[text]()` and `![alt]()` render as links and images,
18
+ and a bare `[r]:` defines an empty destination. Escape `\[` to keep that
19
+ spelling literal. Checked render reports can also include
20
+ `table-span-flattened`, `table-header-row-demoted`,
21
+ `table-header-row-hoisted`, and `table-header-cell-demoted` (#207).
22
+
23
+ ### Fixes
24
+
25
+ - Source strings in other encodings such as Shift_JIS are transcoded to UTF-8.
26
+ Malformed UTF-8 and binary bytes become replacement characters instead of
27
+ failing at the native boundary (#207).
28
+
29
+ ### Improvements
30
+
31
+ - The carve-lang 0.1.10 pin adds CSV and TSV table fences and keeps plain-text
32
+ list continuation blocks at their item's indentation. It also corrects
33
+ parsing of nested lists with quotes and containers (#207).
34
+
35
+ ## [0.1.8] - 2026-10-14
36
+
37
+ ### Breaking
38
+
39
+ - The engine is the published `carve-lang` 0.1.9 crate, so the AST follows contract 1.1: a cell joined across lines holds a `cell_wrap` node between its text halves, tables carry `separatorWidths`, and `delim` and `bulletChar` appear only for `)` and `*` (#200, markup-carve/carve-rs#2482).
40
+ - New loss codes such as `carrier-attribute-dropped`, `list-style-dropped` and `frontmatter-synthesized`. A check that fails on unknown codes has to list them (#200, markup-carve/carve-rs#2523).
41
+ - Image alt text and quoted include paths decode punctuation escapes, so double a literal backslash. The include rename warning's rule id is now `include-id-rename` instead of `include-heading-id-rename` (#184, markup-carve/carve#2772).
42
+
43
+ ### Fixes
44
+
45
+ - A Markdown writer refusal raises `ArgumentError` instead of panicking in the engine (#184).
46
+ - Adjacent alphabetic and Roman ordered lists stay separate through HTML import and formatting (#185).
47
+ - Under the article profile, denied raw blocks become code blocks that keep their payload (#185).
48
+
49
+ ### Improvements
50
+
51
+ - Markdown import reports assess construct fidelity, including preserved constructs as well as losses (#185).
52
+ - Ordered lists keep a `)` delimiter as `data-delim` in HTML and through Markdown import (#185).
53
+
10
54
  ## [0.1.7] - 2026-10-07
11
55
 
12
56
  ### Changed
@@ -444,7 +488,9 @@ are not listed, because no release ever shipped them.
444
488
  Arrays (every AST node type is covered), enabling custom renderers such as
445
489
  [carve-hexapdf](https://github.com/markup-carve/carve-hexapdf).
446
490
 
447
- [Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.7...HEAD
491
+ [Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.9...HEAD
492
+ [0.1.9]: https://github.com/markup-carve/carve-rb/compare/v0.1.8...v0.1.9
493
+ [0.1.8]: https://github.com/markup-carve/carve-rb/compare/v0.1.7...v0.1.8
448
494
  [0.1.7]: https://github.com/markup-carve/carve-rb/compare/v0.1.6...v0.1.7
449
495
  [0.1.6]: https://github.com/markup-carve/carve-rb/compare/v0.1.5...v0.1.6
450
496
  [0.1.5]: https://github.com/markup-carve/carve-rb/compare/v0.1.4...v0.1.5
data/ext/carve/Cargo.lock CHANGED
@@ -37,9 +37,9 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
37
37
 
38
38
  [[package]]
39
39
  name = "carve-lang"
40
- version = "0.1.8"
40
+ version = "0.1.10"
41
41
  source = "registry+https://github.com/rust-lang/crates.io-index"
42
- checksum = "0889683244be278df2ff8e1cd88e9f932036a424bb8d838546636b7f11e93903"
42
+ checksum = "4a9b7a2a3b758f76ce1888ad1371bd933c5f2ccded01305c03f1550641a45c1d"
43
43
  dependencies = [
44
44
  "html5ever",
45
45
  "markup5ever_rcdom",
@@ -54,7 +54,7 @@ dependencies = [
54
54
 
55
55
  [[package]]
56
56
  name = "carve-rb"
57
- version = "0.1.7"
57
+ version = "0.1.9"
58
58
  dependencies = [
59
59
  "carve-lang",
60
60
  "magnus",
@@ -327,9 +327,16 @@ checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
327
327
  dependencies = [
328
328
  "bitflags",
329
329
  "memchr",
330
+ "pulldown-cmark-escape",
330
331
  "unicase",
331
332
  ]
332
333
 
334
+ [[package]]
335
+ name = "pulldown-cmark-escape"
336
+ version = "0.11.0"
337
+ source = "registry+https://github.com/rust-lang/crates.io-index"
338
+ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
339
+
333
340
  [[package]]
334
341
  name = "quote"
335
342
  version = "1.0.46"
data/ext/carve/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "carve-rb"
3
- version = "0.1.7"
3
+ version = "0.1.9"
4
4
  edition = "2021"
5
5
  rust-version = "1.75"
6
6
  publish = false
@@ -22,4 +22,4 @@ serde_json = "1"
22
22
  # The Carve engine. Imported under the Rust crate alias `carve_rs` so the
23
23
  # binding's own crate name (`carve-rb`) does not collide with the engine's
24
24
  # package name (`carve`) in the Cargo lockfile.
25
- carve_rs = { package = "carve-lang", version = "=0.1.8" }
25
+ carve_rs = { package = "carve-lang", version = "=0.1.10" }
data/ext/carve/src/lib.rs CHANGED
@@ -27,7 +27,9 @@
27
27
  use carve_rs::extensions::registry;
28
28
  use carve_rs::{CarveExtension, Mode, Options, Profile, StaticRenderers};
29
29
  use magnus::value::{InnerValue, Opaque};
30
- use magnus::{function, prelude::*, Error, ExceptionClass, RArray, RHash, Ruby, Value};
30
+ use magnus::{
31
+ function, prelude::*, Error, ExceptionClass, RArray, RHash, RString, Ruby, TryConvert, Value,
32
+ };
31
33
  use std::any::Any;
32
34
  use std::cell::RefCell;
33
35
  use std::panic::{self, AssertUnwindSafe};
@@ -97,6 +99,14 @@ fn extension_names() -> Vec<String> {
97
99
  registry::keys().map(str::to_string).collect()
98
100
  }
99
101
 
102
+ fn ast_json_with_version(json: String) -> String {
103
+ let (body, closing) = json.split_at(json.len() - 1);
104
+ format!(
105
+ "{body},\"astVersion\":\"{}\"{closing}",
106
+ carve_rs::AST_CONTRACT_VERSION
107
+ )
108
+ }
109
+
100
110
  /// Map a Ruby-facing mode string to a carve-rs [`Mode`].
101
111
  ///
102
112
  /// Rejects any unknown string with `ArgumentError`, mirroring the spec's
@@ -378,7 +388,7 @@ fn render_with_includes_json(
378
388
  // parse JSON twice. Positions stay OFF, unlike `to_ast_json`: spec I4
379
389
  // leaves position remapping out of scope in every engine, so a child's
380
390
  // spans would point into a document the caller never passed.
381
- IncludeTarget::Ast => Ok(carve_rs::to_json(&prepared.doc)),
391
+ IncludeTarget::Ast => Ok(ast_json_with_version(carve_rs::to_json(&prepared.doc))),
382
392
  }
383
393
  .map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
384
394
  let value: serde_json::Value = if which == IncludeTarget::Ast {
@@ -471,15 +481,20 @@ fn from_html_json(ruby: &Ruby, source: String, mode: String) -> Result<String, E
471
481
  .to_string())
472
482
  }
473
483
 
474
- fn from_markdown_json(source: String) -> String {
475
- let result = carve_rs::migrate_markdown(&source);
484
+ fn from_markdown_json(ruby: &Ruby, source: String) -> Result<String, Error> {
485
+ let result = carve_rs::try_migrate_markdown(&source).map_err(|error| {
486
+ Error::new(
487
+ ruby.exception_arg_error(),
488
+ format!("Markdown import failed: {error}"),
489
+ )
490
+ })?;
476
491
  let diagnostics = result
477
492
  .report
478
493
  .diagnostics
479
494
  .iter()
480
495
  .map(migration_diagnostic_json)
481
496
  .collect::<Vec<_>>();
482
- serde_json::json!({"value": result.value, "report": {"schemaVersion": result.report.schema_version, "sourceFormat": result.report.source_format.as_str(), "diagnostics": diagnostics}}).to_string()
497
+ Ok(serde_json::json!({"value": result.value, "report": {"schemaVersion": result.report.schema_version, "sourceFormat": result.report.source_format.as_str(), "diagnostics": diagnostics}}).to_string())
483
498
  }
484
499
 
485
500
  fn migration_diagnostic_json(diagnostic: &carve_rs::MigrationDiagnostic) -> serde_json::Value {
@@ -517,7 +532,9 @@ fn migration_diagnostic_json(diagnostic: &carve_rs::MigrationDiagnostic) -> serd
517
532
  fn to_ast_json(source: String) -> String {
518
533
  let mut options = Options::new();
519
534
  options.positions = true;
520
- carve_rs::to_json(&carve_rs::parse_with_options(&source, &options))
535
+ ast_json_with_version(carve_rs::to_json(&carve_rs::parse_with_options(
536
+ &source, &options,
537
+ )))
521
538
  }
522
539
 
523
540
  /// Render Carve source to HTML with the named extensions enabled.
@@ -837,42 +854,70 @@ macro_rules! guarded {
837
854
  ($wrapper:ident ( $($arg:ident : $ty:ty),* ) -> $ret:ty => $inner:ident) => {
838
855
  #[allow(clippy::too_many_arguments)]
839
856
  fn $wrapper($($arg: $ty),*) -> Result<$ret, Error> {
840
- guard(|| Ok($inner($($arg),*)))
857
+ guard(|| Ok($inner($($arg.into()),*)))
841
858
  }
842
859
  };
843
860
  ($wrapper:ident ( $($arg:ident : $ty:ty),* ) -> $ret:ty =>? $inner:ident) => {
844
861
  #[allow(clippy::too_many_arguments)]
845
862
  fn $wrapper($($arg: $ty),*) -> Result<$ret, Error> {
846
- guard(|| $inner($($arg),*))
863
+ guard(|| $inner($($arg.into()),*))
847
864
  }
848
865
  };
849
866
  }
850
867
 
851
- guarded!(g_to_html(source: String) -> String => to_html);
852
- guarded!(g_to_markdown(source: String) -> String => to_markdown);
853
- guarded!(g_to_plain_text(source: String) -> String => to_plain_text);
854
- guarded!(g_to_ansi(source: String) -> String => to_ansi);
855
- guarded!(g_to_carve(source: String) -> String => to_carve);
856
- guarded!(g_from_markdown_json(source: String) -> String => from_markdown_json);
857
- guarded!(g_to_ast_json(source: String) -> String => to_ast_json);
868
+ struct Source(String);
869
+
870
+ impl TryConvert for Source {
871
+ fn try_convert(value: Value) -> Result<Self, Error> {
872
+ let source = RString::try_convert(value)?;
873
+ let encoding: Value = source.funcall("encoding", ())?;
874
+ let name: String = encoding.funcall("name", ())?;
875
+ let text = if name == "ASCII-8BIT" {
876
+ String::from_utf8_lossy(unsafe { source.as_slice() }).into_owned()
877
+ } else {
878
+ let ruby = Ruby::get_with(source);
879
+ let options = ruby.hash_new();
880
+ options.aset(ruby.to_symbol("invalid"), ruby.to_symbol("replace"))?;
881
+ options.aset(ruby.to_symbol("undef"), ruby.to_symbol("replace"))?;
882
+ options.aset(ruby.to_symbol("replace"), "\u{FFFD}")?;
883
+ let encoded: RString = source.funcall("encode", ("UTF-8", magnus::KwArgs(options)))?;
884
+ encoded.to_string()?
885
+ };
886
+ Ok(Self(text))
887
+ }
888
+ }
889
+
890
+ impl From<Source> for String {
891
+ fn from(source: Source) -> Self {
892
+ source.0
893
+ }
894
+ }
895
+
896
+ guarded!(g_to_html(source: Source) -> String => to_html);
897
+ guarded!(g_to_markdown(source: Source) -> String => to_markdown);
898
+ guarded!(g_to_plain_text(source: Source) -> String => to_plain_text);
899
+ guarded!(g_to_ansi(source: Source) -> String => to_ansi);
900
+ guarded!(g_to_carve(source: Source) -> String => to_carve);
901
+ guarded!(g_from_markdown_json(ruby: &Ruby, source: Source) -> String =>? from_markdown_json);
902
+ guarded!(g_to_ast_json(source: Source) -> String => to_ast_json);
858
903
  guarded!(g_extension_names() -> Vec<String> => extension_names);
859
904
  guarded!(g_panic_probe() -> String => panic_probe);
860
905
  guarded!(
861
- g_stamp_needs_review(source: String, current_version: Option<String>) -> bool
906
+ g_stamp_needs_review(source: Source, current_version: Option<String>) -> bool
862
907
  => stamp_needs_review
863
908
  );
864
909
  guarded!(
865
- g_from_html_json(ruby: &Ruby, source: String, mode: String) -> String =>? from_html_json
910
+ g_from_html_json(ruby: &Ruby, source: Source, mode: String) -> String =>? from_html_json
866
911
  );
867
- guarded!(g_read_stamp(ruby: &Ruby, source: String) -> Value =>? read_stamp);
912
+ guarded!(g_read_stamp(ruby: &Ruby, source: Source) -> Value =>? read_stamp);
868
913
  guarded!(
869
- g_to_html_with_extensions(ruby: &Ruby, source: String, names: RArray) -> String
914
+ g_to_html_with_extensions(ruby: &Ruby, source: Source, names: RArray) -> String
870
915
  =>? to_html_with_extensions
871
916
  );
872
917
  guarded!(
873
918
  g_to_html_full(
874
919
  ruby: &Ruby,
875
- source: String,
920
+ source: Source,
876
921
  names: RArray,
877
922
  mode: String,
878
923
  renderers: RHash
@@ -881,7 +926,7 @@ guarded!(
881
926
  guarded!(
882
927
  g_to_html_full_with_symbols(
883
928
  ruby: &Ruby,
884
- source: String,
929
+ source: Source,
885
930
  names: RArray,
886
931
  mode: String,
887
932
  renderers: RHash,
@@ -891,7 +936,7 @@ guarded!(
891
936
  guarded!(
892
937
  g_to_html_safe(
893
938
  ruby: &Ruby,
894
- source: String,
939
+ source: Source,
895
940
  names: RArray,
896
941
  mode: String,
897
942
  renderers: RHash,
@@ -904,7 +949,7 @@ guarded!(
904
949
  guarded!(
905
950
  g_render_with_includes_json(
906
951
  ruby: &Ruby,
907
- source: String,
952
+ source: Source,
908
953
  root: String,
909
954
  source_path: String,
910
955
  target: String,
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.7"
11
+ VERSION = "0.1.9"
12
12
  end
data/lib/carve.rb CHANGED
@@ -319,9 +319,10 @@ module Carve
319
319
  # # => {type: "document",
320
320
  # # children: [{type: "heading", level: 1,
321
321
  # # children: [{type: "text", value: "Hi"}], attrs: nil}],
322
- # # srcByteLength: 4}
322
+ # # srcByteLength: 4, astVersion: "1.2"}
323
323
  #
324
- # The root carries exactly +:type+, +:children+ and +:srcByteLength+ (spec
324
+ # The root carries exactly +:type+, +:children+, +:srcByteLength+ and
325
+ # +:astVersion+ (spec
325
326
  # PART 12 §7). Frontmatter and footnote definitions are block nodes in
326
327
  # +:children+, not root fields: a root field cannot carry the position §4
327
328
  # requires of every node, and both are source an editor navigates to.
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.7
4
+ version: 0.1.9
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-10-07 00:00:00.000000000 Z
11
+ date: 2026-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys