carve-lang 0.1.9 → 0.1.10

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: 796f44a79683bfd2b7add107652d71a7d38aaf6d4aedf7169acde4fa2401af26
4
- data.tar.gz: d7f0ba287fc9c13eab535e7e376de3096f1baedfd1df415b5f870ec7bfca395f
3
+ metadata.gz: 2d4e85e4ab93621b11f1502ca546d64cc0d46e513e4074e6e4014cec8f5aee04
4
+ data.tar.gz: 3100558ccb654d5c2441358ce8a13ba363db7937e0e979be3944780322b62561
5
5
  SHA512:
6
- metadata.gz: aec21e3a2aafe3fd84c6debf48af398ea241610d606b954c770a84bd6b5e38b90b0b1d056ecb4c47f8ba9ec976d85498a5d3013ab6885e1b9fe8fdae572d516b
7
- data.tar.gz: 6adc9afe363cd5c135a649b6f336cf41a55b6197a4cb8927273fd57fe4e0c484d47bbe159a8469eea11dc49a41771637ca048712bd596a9bd9515e099e0f611a
6
+ metadata.gz: db44ca3900ce90483edd037944efa79adf6c55a921afe8b00b00a31ba687d6f0f5fce97efba6ec3fb67e69f3c931544bd6b0e09169d62e9a8549b99e0836c693
7
+ data.tar.gz: 28dbc892917d7fb9074005e71cff53fe603bc25ca4d3a54add3042be7813aa4bed7e2db0eea5b950b1a5e2c416de9d9588c38ac7f8985cb90aa126f482d17c86
data/CHANGELOG.md CHANGED
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.10] - 2026-10-23
11
+
12
+ ### Breaking
13
+
14
+ - The engine is the published `carve-lang` 0.1.11 crate. A quote-prefixed `+`
15
+ needs one space after each `>`, so `>+` and `>>+` now render as text, and a
16
+ continuation marker reads only at column 0 or right after a block opener.
17
+ Re-check documents that place `+` elsewhere (#215, markup-carve/carve#3421).
18
+ - Includes of a file that is not valid UTF-8 are refused with an
19
+ `include-non-text` warning instead of being spliced (#215,
20
+ markup-carve/carve-rs#2885).
21
+
22
+ ### Fixes
23
+
24
+ - The carve-lang 0.1.11 pin corrects container ownership for quotes, lazy lines,
25
+ colon divs and footnotes, fences inside nested lists and quotes, and list
26
+ tightness around captions (#215).
27
+ - Includes see through a byte order mark and keep link definitions and region
28
+ markers in place. Markdown import keeps link destinations, autolink brackets
29
+ and pipe escapes as written (#215).
30
+ - `Carve.render_with_includes` with the `markdown`, `plain` and `ansi` targets
31
+ keeps a link definition where it was written, as `Carve.to_markdown` does,
32
+ instead of moving it to the end (#221).
33
+
34
+ ### Improvements
35
+
36
+ - Precompiled gems for `x86_64-linux`, `x86_64-linux-musl`, `aarch64-linux`,
37
+ `x86_64-darwin`, `arm64-darwin` and `x64-mingw-ucrt` install without a Rust
38
+ toolchain on Ruby 3.0 to 4.0 (#214).
39
+ - `Carve.to_carve(source, align_tables: true)` pads table columns to display
40
+ width (#215).
41
+ - `Carve.select_include_region(source, name, mode:)` resolves a named
42
+ `@region` to a Range of content lines and raises `Carve::IncludeRegionError`
43
+ for a missing or malformed region (#215).
44
+ - An include with `@code` or `@code:lang` splices a file or region as one code
45
+ block (#215).
46
+ - Include warnings carry `includedBy`, the chain of directives that reached the
47
+ file the warning arose in, with root-relative paths, lines and columns (#221).
48
+
10
49
  ## [0.1.9] - 2026-10-18
11
50
 
12
51
  ### Breaking
@@ -488,7 +527,8 @@ are not listed, because no release ever shipped them.
488
527
  Arrays (every AST node type is covered), enabling custom renderers such as
489
528
  [carve-hexapdf](https://github.com/markup-carve/carve-hexapdf).
490
529
 
491
- [Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.9...HEAD
530
+ [Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.10...HEAD
531
+ [0.1.10]: https://github.com/markup-carve/carve-rb/compare/v0.1.9...v0.1.10
492
532
  [0.1.9]: https://github.com/markup-carve/carve-rb/compare/v0.1.8...v0.1.9
493
533
  [0.1.8]: https://github.com/markup-carve/carve-rb/compare/v0.1.7...v0.1.8
494
534
  [0.1.7]: https://github.com/markup-carve/carve-rb/compare/v0.1.6...v0.1.7
data/README.md CHANGED
@@ -16,7 +16,48 @@ bundle install
16
16
  ```
17
17
 
18
18
  The distribution name is `carve-lang`, while the require path is `carve`.
19
- Building from source requires Rust 1.75 or newer and Ruby development headers.
19
+
20
+ Releases after 0.1.9 ship precompiled gems for `x86_64-linux`,
21
+ `x86_64-linux-musl`, `aarch64-linux`, `x86_64-darwin`, `arm64-darwin` and
22
+ `x64-mingw-ucrt`, with a binary for each Ruby from 3.0 to 4.0 (from 3.1 on
23
+ Windows, where Ruby 3.0 has no UCRT build). On those platforms `gem install`
24
+ needs no Rust toolchain or libclang. Everywhere else, including newer Rubies,
25
+ RubyGems installs the source gem and builds it as described below.
26
+
27
+ ## Installing from source
28
+
29
+ Version 0.1.9 and earlier ship source only, and so does every release on a
30
+ platform or Ruby without a precompiled gem. There `gem install carve-lang`
31
+ compiles a Rust extension on your machine, so the install needs:
32
+
33
+ - A Rust toolchain with `cargo`, Rust 1.77 or newer. [rustup](https://rustup.rs)
34
+ is the usual way to get one.
35
+ - Ruby development headers (`ruby-dev` on Debian and Ubuntu).
36
+ - libclang and clang. The build runs `bindgen` through `rb-sys` to read Ruby's
37
+ C headers. On Debian and Ubuntu:
38
+
39
+ ```bash
40
+ sudo apt install libclang-dev clang
41
+ ```
42
+
43
+ If only libclang is installed, without the `clang` package, the build stops
44
+ with this error:
45
+
46
+ ```text
47
+ /usr/include/ruby-3.2.0/ruby/ruby.h:23:10: fatal error: 'stdarg.h' file not found
48
+ thread 'main' panicked at .../rb-sys-0.9.128/build/main.rs:52:6:
49
+ generate bindings: ClangDiagnostic("/usr/include/ruby-3.2.0/ruby/ruby.h:23:10: fatal error: 'stdarg.h' file not found\n")
50
+ error: failed to run custom build command for `rb-sys v0.9.128`
51
+ ```
52
+
53
+ libclang cannot find its own builtin headers. Install `clang`, or point
54
+ bindgen at GCC's copy of those headers:
55
+
56
+ ```bash
57
+ BINDGEN_EXTRA_CLANG_ARGS="-I$(gcc -print-file-name=include)" gem install carve-lang
58
+ ```
59
+
60
+ With Bundler, export the variable before running `bundle install`.
20
61
 
21
62
  ## Render Carve
22
63
 
@@ -28,6 +69,7 @@ Carve.to_markdown(source)
28
69
  Carve.to_plain_text(source)
29
70
  Carve.to_ansi(source)
30
71
  Carve.to_carve(source)
72
+ Carve.to_carve(source, align_tables: true) # pad table columns
31
73
  ```
32
74
 
33
75
  Options configure extensions, safe rendering, profiles, symbols, section
@@ -91,7 +133,9 @@ result = Carve.to_html_with_includes(
91
133
  )
92
134
  ```
93
135
 
94
- The result contains the value, warnings, and root-relative dependencies.
136
+ The result contains the value, warnings, and root-relative dependencies. A
137
+ warning from an included file names the chain of directives that reached it in
138
+ `:includedBy`.
95
139
  Other render methods leave directives literal. See the
96
140
  [include reference](docs/reference.md#file-includes) for budgets, AST expansion,
97
141
  targets, and containment behavior.
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.10"
40
+ version = "0.1.11"
41
41
  source = "registry+https://github.com/rust-lang/crates.io-index"
42
- checksum = "4a9b7a2a3b758f76ce1888ad1371bd933c5f2ccded01305c03f1550641a45c1d"
42
+ checksum = "753b90fbe80fab43887d5d559996c153347d9631452bbcb8d0283aad6dfa7ef8"
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.9"
57
+ version = "0.1.10"
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.9"
3
+ version = "0.1.10"
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.10" }
25
+ carve_rs = { package = "carve-lang", version = "=0.1.11" }
data/ext/carve/src/lib.rs CHANGED
@@ -226,8 +226,44 @@ fn to_ansi(source: String) -> String {
226
226
  carve_rs::to_ansi(&source)
227
227
  }
228
228
 
229
- fn to_carve(source: String) -> String {
230
- carve_rs::to_carve(&source)
229
+ /// Canonical Carve source; a writer refusal returns the source as authored.
230
+ fn to_carve(source: String, align_tables: bool) -> String {
231
+ let options = Options::default().with_align_tables(align_tables);
232
+ carve_rs::try_to_carve_with_options(&source, &options).unwrap_or(source)
233
+ }
234
+
235
+ /// Resolve `@region:name` with the engine's marker rules (spec I1b).
236
+ ///
237
+ /// Returns `[start, end]` (1-based, inclusive content lines) or, when the
238
+ /// region is invalid, the warning rule id as a String; `lib/carve.rb` turns
239
+ /// that into `Carve::IncludeRegionError`.
240
+ fn select_include_region(
241
+ ruby: &Ruby,
242
+ source: String,
243
+ name: String,
244
+ mode: String,
245
+ ) -> Result<Value, Error> {
246
+ let mode = match mode.as_str() {
247
+ "carve" => carve_rs::IncludeRegionMode::Carve,
248
+ "code" => carve_rs::IncludeRegionMode::Code,
249
+ other => {
250
+ return Err(Error::new(
251
+ ruby.exception_arg_error(),
252
+ format!("mode must be :carve or :code, got {other:?}"),
253
+ ))
254
+ }
255
+ };
256
+ Ok(
257
+ match carve_rs::select_include_region(&source, &name, mode) {
258
+ Ok(region) => ruby
259
+ .ary_new_from_values(&[
260
+ ruby.integer_from_u64(region.start as u64).as_value(),
261
+ ruby.integer_from_u64(region.end as u64).as_value(),
262
+ ])
263
+ .as_value(),
264
+ Err(e) => ruby.str_new(e.rule()).as_value(),
265
+ },
266
+ )
231
267
  }
232
268
 
233
269
  /// Which renderer runs over the expanded document.
@@ -344,6 +380,12 @@ fn render_with_includes_json(
344
380
  if !sections {
345
381
  render_options = render_options.with_sections(false);
346
382
  }
383
+ // Positions let the engine record the directive chain behind a warning
384
+ // (`includedBy`). The AST target keeps them off: spec I4 leaves position
385
+ // remapping out of scope, so a child's spans would point into the wrong file.
386
+ if which != IncludeTarget::Ast {
387
+ render_options = render_options.with_positions(true);
388
+ }
347
389
  let mut include_options = carve_rs::IncludeOptions::new()
348
390
  .with_resolver(&resolver)
349
391
  .with_source_path(source_real.to_string_lossy());
@@ -419,6 +461,17 @@ fn render_with_includes_json(
419
461
  "rule": warning.rule,
420
462
  "message": warning.message,
421
463
  "file": warning.file.as_deref().map(&safe_path),
464
+ "includedBy": warning
465
+ .included_by
466
+ .iter()
467
+ .map(|site| {
468
+ serde_json::json!({
469
+ "file": safe_path(&site.file),
470
+ "line": site.line,
471
+ "column": site.column,
472
+ })
473
+ })
474
+ .collect::<Vec<_>>(),
422
475
  })
423
476
  })
424
477
  .collect::<Vec<_>>();
@@ -897,7 +950,11 @@ guarded!(g_to_html(source: Source) -> String => to_html);
897
950
  guarded!(g_to_markdown(source: Source) -> String => to_markdown);
898
951
  guarded!(g_to_plain_text(source: Source) -> String => to_plain_text);
899
952
  guarded!(g_to_ansi(source: Source) -> String => to_ansi);
900
- guarded!(g_to_carve(source: Source) -> String => to_carve);
953
+ guarded!(g_to_carve(source: Source, align_tables: bool) -> String => to_carve);
954
+ guarded!(
955
+ g_select_include_region(ruby: &Ruby, source: Source, name: String, mode: String) -> Value
956
+ =>? select_include_region
957
+ );
901
958
  guarded!(g_from_markdown_json(ruby: &Ruby, source: Source) -> String =>? from_markdown_json);
902
959
  guarded!(g_to_ast_json(source: Source) -> String => to_ast_json);
903
960
  guarded!(g_extension_names() -> Vec<String> => extension_names);
@@ -990,7 +1047,11 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
990
1047
  module.define_singleton_method("to_markdown", function!(g_to_markdown, 1))?;
991
1048
  module.define_singleton_method("to_plain_text", function!(g_to_plain_text, 1))?;
992
1049
  module.define_singleton_method("to_ansi", function!(g_to_ansi, 1))?;
993
- module.define_singleton_method("to_carve", function!(g_to_carve, 1))?;
1050
+ module.define_singleton_method("_to_carve", function!(g_to_carve, 2))?;
1051
+ module.define_singleton_method(
1052
+ "_select_include_region",
1053
+ function!(g_select_include_region, 3),
1054
+ )?;
994
1055
  module.define_singleton_method(
995
1056
  "_render_with_includes_json",
996
1057
  function!(g_render_with_includes_json, 15),
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.9"
11
+ VERSION = "0.1.10"
12
12
  end
data/lib/carve.rb CHANGED
@@ -8,11 +8,30 @@
8
8
  require "json"
9
9
  require_relative "carve/version"
10
10
 
11
- # Load the compiled native extension. Built by `rake compile` to
12
- # lib/carve/carve.so (or .bundle on macOS).
13
- require_relative "carve/carve"
11
+ # Load the compiled native extension. A precompiled platform gem ships one per
12
+ # Ruby ABI under lib/carve/<major.minor>/; a source install and `rake compile`
13
+ # build lib/carve/carve.so (or .bundle on macOS).
14
+ # Chosen by directory rather than by rescuing LoadError, so a binary that is
15
+ # present but cannot load (an older glibc, say) reports its own error.
16
+ abi = RUBY_VERSION[/\A\d+\.\d+/]
17
+ if File.directory?(File.join(__dir__, "carve", abi))
18
+ require_relative "carve/#{abi}/carve"
19
+ else
20
+ require_relative "carve/carve"
21
+ end
14
22
 
15
23
  module Carve
24
+ # Raised by .select_include_region when the region is missing or a marker is
25
+ # malformed, unpaired or duplicated. +rule+ is the engine's warning rule id.
26
+ class IncludeRegionError < ArgumentError
27
+ attr_reader :rule
28
+
29
+ def initialize(rule, name)
30
+ @rule = rule
31
+ super("#{rule}: region #{name.inspect} is missing or its markers are malformed")
32
+ end
33
+ end
34
+
16
35
  # Extension names the native binding understands.
17
36
  #
18
37
  # Taken from the engine itself rather than typed out here. This was a literal
@@ -55,7 +74,8 @@ module Carve
55
74
  #
56
75
  # Returns +{value:, warnings:, dependencies:, suppressedWarnings:}+. Paths
57
76
  # in +warnings+ and +dependencies+ are relative to the root, so a report can
58
- # be shown to a reader as it stands.
77
+ # be shown to a reader as it stands. Each warning's +includedBy+ lists the
78
+ # directives that reached its file, top-level first, as +{file:, line:, column:}+.
59
79
  #
60
80
  # Every render option +to_html+ takes is accepted and reaches the included
61
81
  # children too: an extension, a symbol map or a profile means the same thing
@@ -130,6 +150,28 @@ module Carve
130
150
  )
131
151
  end
132
152
 
153
+ # Format +source+ into canonical Carve. A writer refusal returns the source
154
+ # as authored. +align_tables:+ pads table cells into aligned columns.
155
+ def to_carve(source, align_tables: false)
156
+ _to_carve(source, align_tables ? true : false)
157
+ end
158
+
159
+ # Resolve <tt>@region:name</tt> in a file's text with the engine's marker
160
+ # rules, so an editor jumping to a region agrees with the render.
161
+ #
162
+ # Carve.select_include_region(text, "setup", mode: :carve) # => 3..4
163
+ #
164
+ # Returns the 1-based, inclusive Range of content lines; an empty region is
165
+ # an empty Range such as +2..1+. +mode: :carve+ accepts only
166
+ # <tt>%% #region</tt> markers, +mode: :code+ also the source-comment forms
167
+ # of an +@code+ include. Raises IncludeRegionError otherwise.
168
+ def select_include_region(source, name, mode:)
169
+ result = _select_include_region(source, name.to_s, mode.to_s)
170
+ raise IncludeRegionError.new(result, name.to_s) if result.is_a?(String)
171
+
172
+ result[0]..result[1]
173
+ end
174
+
133
175
  # Import HTML or Markdown into canonical Carve. Both methods return the
134
176
  # shared migration shape `{ value:, report: }`.
135
177
  def from_html(source, mode: :safe)
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.9
4
+ version: 0.1.10
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-18 00:00:00.000000000 Z
11
+ date: 2026-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys