carve-lang 0.1.8 → 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: 701231311b5228a33a5ca37b7855209d3521f8ece697824f0b3e3322cb69a30a
4
- data.tar.gz: 869fc6cbce2b49755f7c9e734afd1ec1afc860f508cec75f4e8f3b13b7ed91db
3
+ metadata.gz: 796f44a79683bfd2b7add107652d71a7d38aaf6d4aedf7169acde4fa2401af26
4
+ data.tar.gz: d7f0ba287fc9c13eab535e7e376de3096f1baedfd1df415b5f870ec7bfca395f
5
5
  SHA512:
6
- metadata.gz: c46d50c7fe1dde855c0d4959067b8d99c45a905610c156a68d40ebf1f5b241d8fc036754755309c8a58d54d41e85c0bd3418031eed1fd393f3dfb6e9b5226ee1
7
- data.tar.gz: 23f7732176af5392bd32839ddffc77e565ed0a9d3a68677f9206d19c71db796922301fcc6231f028dc7bf3ca250ccab54912d879ed8b3c2305eff18123cd7e17
6
+ metadata.gz: aec21e3a2aafe3fd84c6debf48af398ea241610d606b954c770a84bd6b5e38b90b0b1d056ecb4c47f8ba9ec976d85498a5d3013ab6885e1b9fe8fdae572d516b
7
+ data.tar.gz: 6adc9afe363cd5c135a649b6f336cf41a55b6197a4cb8927273fd57fe4e0c484d47bbe159a8469eea11dc49a41771637ca048712bd596a9bd9515e099e0f611a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ 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
+
10
35
  ## [0.1.8] - 2026-10-14
11
36
 
12
37
  ### Breaking
@@ -463,7 +488,8 @@ are not listed, because no release ever shipped them.
463
488
  Arrays (every AST node type is covered), enabling custom renderers such as
464
489
  [carve-hexapdf](https://github.com/markup-carve/carve-hexapdf).
465
490
 
466
- [Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.8...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
467
493
  [0.1.8]: https://github.com/markup-carve/carve-rb/compare/v0.1.7...v0.1.8
468
494
  [0.1.7]: https://github.com/markup-carve/carve-rb/compare/v0.1.6...v0.1.7
469
495
  [0.1.6]: https://github.com/markup-carve/carve-rb/compare/v0.1.5...v0.1.6
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.9"
40
+ version = "0.1.10"
41
41
  source = "registry+https://github.com/rust-lang/crates.io-index"
42
- checksum = "8747e096cacdd7097d884ffb9dd15dc70dd7e636081a31335bb786313924f341"
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.8"
57
+ version = "0.1.9"
58
58
  dependencies = [
59
59
  "carve-lang",
60
60
  "magnus",
data/ext/carve/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "carve-rb"
3
- version = "0.1.8"
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.9" }
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 {
@@ -522,7 +532,9 @@ fn migration_diagnostic_json(diagnostic: &carve_rs::MigrationDiagnostic) -> serd
522
532
  fn to_ast_json(source: String) -> String {
523
533
  let mut options = Options::new();
524
534
  options.positions = true;
525
- 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
+ )))
526
538
  }
527
539
 
528
540
  /// Render Carve source to HTML with the named extensions enabled.
@@ -842,42 +854,70 @@ macro_rules! guarded {
842
854
  ($wrapper:ident ( $($arg:ident : $ty:ty),* ) -> $ret:ty => $inner:ident) => {
843
855
  #[allow(clippy::too_many_arguments)]
844
856
  fn $wrapper($($arg: $ty),*) -> Result<$ret, Error> {
845
- guard(|| Ok($inner($($arg),*)))
857
+ guard(|| Ok($inner($($arg.into()),*)))
846
858
  }
847
859
  };
848
860
  ($wrapper:ident ( $($arg:ident : $ty:ty),* ) -> $ret:ty =>? $inner:ident) => {
849
861
  #[allow(clippy::too_many_arguments)]
850
862
  fn $wrapper($($arg: $ty),*) -> Result<$ret, Error> {
851
- guard(|| $inner($($arg),*))
863
+ guard(|| $inner($($arg.into()),*))
852
864
  }
853
865
  };
854
866
  }
855
867
 
856
- guarded!(g_to_html(source: String) -> String => to_html);
857
- guarded!(g_to_markdown(source: String) -> String => to_markdown);
858
- guarded!(g_to_plain_text(source: String) -> String => to_plain_text);
859
- guarded!(g_to_ansi(source: String) -> String => to_ansi);
860
- guarded!(g_to_carve(source: String) -> String => to_carve);
861
- guarded!(g_from_markdown_json(ruby: &Ruby, source: String) -> String =>? from_markdown_json);
862
- 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);
863
903
  guarded!(g_extension_names() -> Vec<String> => extension_names);
864
904
  guarded!(g_panic_probe() -> String => panic_probe);
865
905
  guarded!(
866
- g_stamp_needs_review(source: String, current_version: Option<String>) -> bool
906
+ g_stamp_needs_review(source: Source, current_version: Option<String>) -> bool
867
907
  => stamp_needs_review
868
908
  );
869
909
  guarded!(
870
- 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
871
911
  );
872
- guarded!(g_read_stamp(ruby: &Ruby, source: String) -> Value =>? read_stamp);
912
+ guarded!(g_read_stamp(ruby: &Ruby, source: Source) -> Value =>? read_stamp);
873
913
  guarded!(
874
- 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
875
915
  =>? to_html_with_extensions
876
916
  );
877
917
  guarded!(
878
918
  g_to_html_full(
879
919
  ruby: &Ruby,
880
- source: String,
920
+ source: Source,
881
921
  names: RArray,
882
922
  mode: String,
883
923
  renderers: RHash
@@ -886,7 +926,7 @@ guarded!(
886
926
  guarded!(
887
927
  g_to_html_full_with_symbols(
888
928
  ruby: &Ruby,
889
- source: String,
929
+ source: Source,
890
930
  names: RArray,
891
931
  mode: String,
892
932
  renderers: RHash,
@@ -896,7 +936,7 @@ guarded!(
896
936
  guarded!(
897
937
  g_to_html_safe(
898
938
  ruby: &Ruby,
899
- source: String,
939
+ source: Source,
900
940
  names: RArray,
901
941
  mode: String,
902
942
  renderers: RHash,
@@ -909,7 +949,7 @@ guarded!(
909
949
  guarded!(
910
950
  g_render_with_includes_json(
911
951
  ruby: &Ruby,
912
- source: String,
952
+ source: Source,
913
953
  root: String,
914
954
  source_path: String,
915
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.8"
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.8
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-14 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