carve-lang 0.1.6 → 0.1.8
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 +4 -4
- data/CHANGELOG.md +65 -1
- data/README.md +23 -0
- data/ext/carve/Cargo.lock +12 -3
- data/ext/carve/Cargo.toml +2 -2
- data/ext/carve/src/lib.rs +223 -20
- data/lib/carve/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 701231311b5228a33a5ca37b7855209d3521f8ece697824f0b3e3322cb69a30a
|
|
4
|
+
data.tar.gz: 869fc6cbce2b49755f7c9e734afd1ec1afc860f508cec75f4e8f3b13b7ed91db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c46d50c7fe1dde855c0d4959067b8d99c45a905610c156a68d40ebf1f5b241d8fc036754755309c8a58d54d41e85c0bd3418031eed1fd393f3dfb6e9b5226ee1
|
|
7
|
+
data.tar.gz: 23f7732176af5392bd32839ddffc77e565ed0a9d3a68677f9206d19c71db796922301fcc6231f028dc7bf3ca250ccab54912d879ed8b3c2305eff18123cd7e17
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.8] - 2026-10-14
|
|
11
|
+
|
|
12
|
+
### Breaking
|
|
13
|
+
|
|
14
|
+
- 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).
|
|
15
|
+
- 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).
|
|
16
|
+
- 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).
|
|
17
|
+
|
|
18
|
+
### Fixes
|
|
19
|
+
|
|
20
|
+
- A Markdown writer refusal raises `ArgumentError` instead of panicking in the engine (#184).
|
|
21
|
+
- Adjacent alphabetic and Roman ordered lists stay separate through HTML import and formatting (#185).
|
|
22
|
+
- Under the article profile, denied raw blocks become code blocks that keep their payload (#185).
|
|
23
|
+
|
|
24
|
+
### Improvements
|
|
25
|
+
|
|
26
|
+
- Markdown import reports assess construct fidelity, including preserved constructs as well as losses (#185).
|
|
27
|
+
- Ordered lists keep a `)` delimiter as `data-delim` in HTML and through Markdown import (#185).
|
|
28
|
+
|
|
29
|
+
## [0.1.7] - 2026-10-07
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- The engine is the published `carve-lang` 0.1.8 crate, up from 0.1.7. It closes
|
|
34
|
+
every row `resources/spec-drift.txt` declared: the gem renders all 2225 corpus
|
|
35
|
+
documents spec main declares byte-identically, where 0.1.7 rendered 30 of them
|
|
36
|
+
by a superseded rule. The ledger is empty again, which is the only state a tag
|
|
37
|
+
may ship.
|
|
38
|
+
- **Breaking for a `Carve.to_html` consumer:** a heading cross-reference, a
|
|
39
|
+
numbered caption or equation reference, and a collapsed reference falling back
|
|
40
|
+
to heading text compare their target case exactly. `</#target>` against a
|
|
41
|
+
`# Target` heading no longer resolves and stays literal text; link-definition
|
|
42
|
+
labels, footnote labels and include fragment selectors already matched case
|
|
43
|
+
exactly. `carve fmt --migrate` in carve-rs repairs unambiguous case-only misses
|
|
44
|
+
(markup-carve/carve#2732, markup-carve/carve-rs#2320).
|
|
45
|
+
- **Breaking for a `Carve.to_html` consumer:** a glossary reference matches its
|
|
46
|
+
term exactly and links to the matched entry, and two terms differing only in
|
|
47
|
+
case take two ids (markup-carve/carve#2739).
|
|
48
|
+
- A render that blanks a denied destination scheme reports one
|
|
49
|
+
`destination-denied` loss, so a checked render of a `javascript:` destination
|
|
50
|
+
refuses where it used to pass. The emitted `href=""` does not move
|
|
51
|
+
(markup-carve/carve#2679).
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- `Carve::EnginePanic`, a `StandardError` subclass raised when the engine
|
|
56
|
+
panics, so a host rendering untrusted input can rescue one and keep serving.
|
|
57
|
+
The message carries the panic text and its location in the engine source, and
|
|
58
|
+
the usual panic report still reaches stderr (#170).
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- An engine panic arrives as a rescuable Ruby exception rather than `fatal`.
|
|
63
|
+
magnus catches the unwind but raises it as `fatal`, which no host can stop,
|
|
64
|
+
not even with `rescue Exception`, so any panic ended the process. The binding
|
|
65
|
+
now converts a caught panic itself (#170).
|
|
66
|
+
- A line holding a single `|` followed by an attribute block, such as `|{.r}`,
|
|
67
|
+
renders as paragraph text. Under the 0.1.7 engine it panicked inside the table
|
|
68
|
+
check, and a panic crossing the FFI boundary reached Ruby as `fatal`, which
|
|
69
|
+
`rescue` cannot catch, so five bytes of input terminated the host process
|
|
70
|
+
(markup-carve/carve-rs#2341).
|
|
71
|
+
|
|
10
72
|
## [0.1.6] - 2026-09-29
|
|
11
73
|
|
|
12
74
|
### Changed
|
|
@@ -401,7 +463,9 @@ are not listed, because no release ever shipped them.
|
|
|
401
463
|
Arrays (every AST node type is covered), enabling custom renderers such as
|
|
402
464
|
[carve-hexapdf](https://github.com/markup-carve/carve-hexapdf).
|
|
403
465
|
|
|
404
|
-
[Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.
|
|
466
|
+
[Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.8...HEAD
|
|
467
|
+
[0.1.8]: https://github.com/markup-carve/carve-rb/compare/v0.1.7...v0.1.8
|
|
468
|
+
[0.1.7]: https://github.com/markup-carve/carve-rb/compare/v0.1.6...v0.1.7
|
|
405
469
|
[0.1.6]: https://github.com/markup-carve/carve-rb/compare/v0.1.5...v0.1.6
|
|
406
470
|
[0.1.5]: https://github.com/markup-carve/carve-rb/compare/v0.1.4...v0.1.5
|
|
407
471
|
[0.1.4]: https://github.com/markup-carve/carve-rb/compare/v0.1.3...v0.1.4
|
data/README.md
CHANGED
|
@@ -45,6 +45,29 @@ Carve.to_html(
|
|
|
45
45
|
`Carve::EXTENSIONS` reports the names accepted by the bundled engine. Unknown
|
|
46
46
|
names raise `ArgumentError`.
|
|
47
47
|
|
|
48
|
+
## Errors
|
|
49
|
+
|
|
50
|
+
An invalid argument raises `ArgumentError`, and a render the engine refuses -
|
|
51
|
+
input past a profile's `max_length`, or a denied construct - also raises
|
|
52
|
+
`ArgumentError` with the engine's reason.
|
|
53
|
+
|
|
54
|
+
If the engine panics, the call raises `Carve::EnginePanic`, a `StandardError`
|
|
55
|
+
subclass carrying the panic message and its location in the engine source. A
|
|
56
|
+
panic means the engine reached a state it believed impossible, so the document
|
|
57
|
+
is not renderable, but the process survives and a host can serve an error
|
|
58
|
+
instead of losing the worker:
|
|
59
|
+
|
|
60
|
+
``` ruby
|
|
61
|
+
begin
|
|
62
|
+
Carve.to_html(untrusted_source)
|
|
63
|
+
rescue Carve::EnginePanic => e
|
|
64
|
+
logger.error("carve engine panic: #{e.message}")
|
|
65
|
+
"<p>This document could not be rendered.</p>"
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Report any input that raises it: a panic is an engine defect, not a rejection.
|
|
70
|
+
|
|
48
71
|
## Migration and AST access
|
|
49
72
|
|
|
50
73
|
`Carve.from_html` and `Carve.from_markdown` return canonical Carve with
|
data/ext/carve/Cargo.lock
CHANGED
|
@@ -37,22 +37,24 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
|
|
37
37
|
|
|
38
38
|
[[package]]
|
|
39
39
|
name = "carve-lang"
|
|
40
|
-
version = "0.1.
|
|
40
|
+
version = "0.1.9"
|
|
41
41
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
-
checksum = "
|
|
42
|
+
checksum = "8747e096cacdd7097d884ffb9dd15dc70dd7e636081a31335bb786313924f341"
|
|
43
43
|
dependencies = [
|
|
44
44
|
"html5ever",
|
|
45
45
|
"markup5ever_rcdom",
|
|
46
|
+
"memchr",
|
|
46
47
|
"pulldown-cmark",
|
|
47
48
|
"regex",
|
|
48
49
|
"serde",
|
|
49
50
|
"serde_json",
|
|
51
|
+
"smallvec",
|
|
50
52
|
"unicode-normalization",
|
|
51
53
|
]
|
|
52
54
|
|
|
53
55
|
[[package]]
|
|
54
56
|
name = "carve-rb"
|
|
55
|
-
version = "0.1.
|
|
57
|
+
version = "0.1.8"
|
|
56
58
|
dependencies = [
|
|
57
59
|
"carve-lang",
|
|
58
60
|
"magnus",
|
|
@@ -325,9 +327,16 @@ checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
|
|
|
325
327
|
dependencies = [
|
|
326
328
|
"bitflags",
|
|
327
329
|
"memchr",
|
|
330
|
+
"pulldown-cmark-escape",
|
|
328
331
|
"unicase",
|
|
329
332
|
]
|
|
330
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
|
+
|
|
331
340
|
[[package]]
|
|
332
341
|
name = "quote"
|
|
333
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.
|
|
3
|
+
version = "0.1.8"
|
|
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.
|
|
25
|
+
carve_rs = { package = "carve-lang", version = "=0.1.9" }
|
data/ext/carve/src/lib.rs
CHANGED
|
@@ -27,8 +27,12 @@
|
|
|
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, RArray, RHash, Ruby, Value};
|
|
30
|
+
use magnus::{function, prelude::*, Error, ExceptionClass, RArray, RHash, Ruby, Value};
|
|
31
|
+
use std::any::Any;
|
|
32
|
+
use std::cell::RefCell;
|
|
33
|
+
use std::panic::{self, AssertUnwindSafe};
|
|
31
34
|
use std::path::{Path, PathBuf};
|
|
35
|
+
use std::sync::Once;
|
|
32
36
|
|
|
33
37
|
/// HTML-escape a string for the renderer-failure fallback path.
|
|
34
38
|
///
|
|
@@ -467,15 +471,20 @@ fn from_html_json(ruby: &Ruby, source: String, mode: String) -> Result<String, E
|
|
|
467
471
|
.to_string())
|
|
468
472
|
}
|
|
469
473
|
|
|
470
|
-
fn from_markdown_json(source: String) -> String {
|
|
471
|
-
let result = carve_rs::
|
|
474
|
+
fn from_markdown_json(ruby: &Ruby, source: String) -> Result<String, Error> {
|
|
475
|
+
let result = carve_rs::try_migrate_markdown(&source).map_err(|error| {
|
|
476
|
+
Error::new(
|
|
477
|
+
ruby.exception_arg_error(),
|
|
478
|
+
format!("Markdown import failed: {error}"),
|
|
479
|
+
)
|
|
480
|
+
})?;
|
|
472
481
|
let diagnostics = result
|
|
473
482
|
.report
|
|
474
483
|
.diagnostics
|
|
475
484
|
.iter()
|
|
476
485
|
.map(migration_diagnostic_json)
|
|
477
486
|
.collect::<Vec<_>>();
|
|
478
|
-
serde_json::json!({"value": result.value, "report": {"schemaVersion": result.report.schema_version, "sourceFormat": result.report.source_format.as_str(), "diagnostics": diagnostics}}).to_string()
|
|
487
|
+
Ok(serde_json::json!({"value": result.value, "report": {"schemaVersion": result.report.schema_version, "sourceFormat": result.report.source_format.as_str(), "diagnostics": diagnostics}}).to_string())
|
|
479
488
|
}
|
|
480
489
|
|
|
481
490
|
fn migration_diagnostic_json(diagnostic: &carve_rs::MigrationDiagnostic) -> serde_json::Value {
|
|
@@ -732,6 +741,192 @@ fn stamp_needs_review(source: String, current_version: Option<String>) -> bool {
|
|
|
732
741
|
carve_rs::needs_review(&source, ¤t)
|
|
733
742
|
}
|
|
734
743
|
|
|
744
|
+
/// Panic deliberately inside the extension, so a test can observe the FFI
|
|
745
|
+
/// panic contract rather than assuming it.
|
|
746
|
+
///
|
|
747
|
+
/// Nothing but a test calls this. It exists because no Carve input panics the
|
|
748
|
+
/// pinned engine: the one that did (`|{.r}`) is fixed, and a test that cannot
|
|
749
|
+
/// reach a panic cannot tell a working safety net from a missing one.
|
|
750
|
+
fn panic_probe() -> String {
|
|
751
|
+
panic!("deliberate panic from the Carve extension panic probe");
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
// FFI panic safety
|
|
756
|
+
// ---------------------------------------------------------------------------
|
|
757
|
+
//
|
|
758
|
+
// magnus wraps every exposed call in catch_unwind, but `Error::from_panic`
|
|
759
|
+
// raises the caught panic as Ruby's `fatal`, and Ruby does not let a host stop
|
|
760
|
+
// a `fatal` -- not with `rescue Exception`, not with anything. So the unwind
|
|
761
|
+
// that was supposed to protect the host still ends the process, and magnus
|
|
762
|
+
// offers no way to choose a different class.
|
|
763
|
+
//
|
|
764
|
+
// Everything Ruby can reach therefore runs inside `guard`, which catches the
|
|
765
|
+
// unwind FIRST and raises `Carve::EnginePanic` (a StandardError) instead. The
|
|
766
|
+
// `panic = "unwind"` compile flag is still what makes any of this possible,
|
|
767
|
+
// which is why `scripts/check-panic-unwind.sh` stays.
|
|
768
|
+
|
|
769
|
+
thread_local! {
|
|
770
|
+
/// Where the most recent panic on this thread came from.
|
|
771
|
+
///
|
|
772
|
+
/// The payload `catch_unwind` hands back carries the message but not the
|
|
773
|
+
/// location, and the location is the half that identifies the engine bug.
|
|
774
|
+
static PANIC_LOCATION: RefCell<Option<String>> = const { RefCell::new(None) };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/// Record panic locations without taking over panic reporting.
|
|
778
|
+
///
|
|
779
|
+
/// The previous hook still runs, so the `thread '<unnamed>' panicked at ...`
|
|
780
|
+
/// line and any `RUST_BACKTRACE` output a developer relies on are unchanged.
|
|
781
|
+
fn install_panic_hook() {
|
|
782
|
+
static ONCE: Once = Once::new();
|
|
783
|
+
ONCE.call_once(|| {
|
|
784
|
+
let previous = panic::take_hook();
|
|
785
|
+
panic::set_hook(Box::new(move |info| {
|
|
786
|
+
let location = info
|
|
787
|
+
.location()
|
|
788
|
+
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()));
|
|
789
|
+
PANIC_LOCATION.with(|slot| *slot.borrow_mut() = location);
|
|
790
|
+
previous(info);
|
|
791
|
+
}));
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/// `Carve::EnginePanic`, or `RuntimeError` if the class cannot be looked up.
|
|
796
|
+
///
|
|
797
|
+
/// The fallback is still rescuable, which is the property that matters; losing
|
|
798
|
+
/// the specific class is better than losing the process.
|
|
799
|
+
fn engine_panic_class(ruby: &Ruby) -> ExceptionClass {
|
|
800
|
+
ruby.define_module("Carve")
|
|
801
|
+
.ok()
|
|
802
|
+
.and_then(|module| module.const_get::<_, ExceptionClass>("EnginePanic").ok())
|
|
803
|
+
.unwrap_or_else(|| ruby.exception_runtime_error())
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/// Turn a caught unwind payload into a rescuable Ruby exception.
|
|
807
|
+
fn engine_panic_error(payload: Box<dyn Any + Send>) -> Error {
|
|
808
|
+
let message = if let Some(m) = payload.downcast_ref::<&'static str>() {
|
|
809
|
+
(*m).to_string()
|
|
810
|
+
} else if let Some(m) = payload.downcast_ref::<String>() {
|
|
811
|
+
m.clone()
|
|
812
|
+
} else {
|
|
813
|
+
"panic".to_string()
|
|
814
|
+
};
|
|
815
|
+
let message = match PANIC_LOCATION.with(|slot| slot.borrow_mut().take()) {
|
|
816
|
+
Some(at) => format!("the Carve engine panicked at {at}: {message}"),
|
|
817
|
+
None => format!("the Carve engine panicked: {message}"),
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// Unchecked for the same reason magnus does it in `Error::from_panic`: this
|
|
821
|
+
// only runs while a Ruby thread is calling into the extension.
|
|
822
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
823
|
+
Error::new(engine_panic_class(&ruby), message)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/// Run an exposed call with the panic net in front of magnus's.
|
|
827
|
+
fn guard<T>(f: impl FnOnce() -> Result<T, Error>) -> Result<T, Error> {
|
|
828
|
+
match panic::catch_unwind(AssertUnwindSafe(f)) {
|
|
829
|
+
Ok(result) => result,
|
|
830
|
+
Err(payload) => Err(engine_panic_error(payload)),
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/// Declare the guarded wrapper Ruby is given for an implementation function.
|
|
835
|
+
///
|
|
836
|
+
/// `=>` wraps an infallible implementation, `=>?` one that already returns
|
|
837
|
+
/// `Result`. Registering a bare implementation instead of its wrapper would
|
|
838
|
+
/// leave that one call raising `fatal` again, so `test/panic_unwind_test.rb`
|
|
839
|
+
/// reads the `function!` registrations below and fails on any name that is not
|
|
840
|
+
/// a `g_` wrapper.
|
|
841
|
+
macro_rules! guarded {
|
|
842
|
+
($wrapper:ident ( $($arg:ident : $ty:ty),* ) -> $ret:ty => $inner:ident) => {
|
|
843
|
+
#[allow(clippy::too_many_arguments)]
|
|
844
|
+
fn $wrapper($($arg: $ty),*) -> Result<$ret, Error> {
|
|
845
|
+
guard(|| Ok($inner($($arg),*)))
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
($wrapper:ident ( $($arg:ident : $ty:ty),* ) -> $ret:ty =>? $inner:ident) => {
|
|
849
|
+
#[allow(clippy::too_many_arguments)]
|
|
850
|
+
fn $wrapper($($arg: $ty),*) -> Result<$ret, Error> {
|
|
851
|
+
guard(|| $inner($($arg),*))
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
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);
|
|
863
|
+
guarded!(g_extension_names() -> Vec<String> => extension_names);
|
|
864
|
+
guarded!(g_panic_probe() -> String => panic_probe);
|
|
865
|
+
guarded!(
|
|
866
|
+
g_stamp_needs_review(source: String, current_version: Option<String>) -> bool
|
|
867
|
+
=> stamp_needs_review
|
|
868
|
+
);
|
|
869
|
+
guarded!(
|
|
870
|
+
g_from_html_json(ruby: &Ruby, source: String, mode: String) -> String =>? from_html_json
|
|
871
|
+
);
|
|
872
|
+
guarded!(g_read_stamp(ruby: &Ruby, source: String) -> Value =>? read_stamp);
|
|
873
|
+
guarded!(
|
|
874
|
+
g_to_html_with_extensions(ruby: &Ruby, source: String, names: RArray) -> String
|
|
875
|
+
=>? to_html_with_extensions
|
|
876
|
+
);
|
|
877
|
+
guarded!(
|
|
878
|
+
g_to_html_full(
|
|
879
|
+
ruby: &Ruby,
|
|
880
|
+
source: String,
|
|
881
|
+
names: RArray,
|
|
882
|
+
mode: String,
|
|
883
|
+
renderers: RHash
|
|
884
|
+
) -> String =>? to_html_full
|
|
885
|
+
);
|
|
886
|
+
guarded!(
|
|
887
|
+
g_to_html_full_with_symbols(
|
|
888
|
+
ruby: &Ruby,
|
|
889
|
+
source: String,
|
|
890
|
+
names: RArray,
|
|
891
|
+
mode: String,
|
|
892
|
+
renderers: RHash,
|
|
893
|
+
symbols: RHash
|
|
894
|
+
) -> String =>? to_html_full_with_symbols
|
|
895
|
+
);
|
|
896
|
+
guarded!(
|
|
897
|
+
g_to_html_safe(
|
|
898
|
+
ruby: &Ruby,
|
|
899
|
+
source: String,
|
|
900
|
+
names: RArray,
|
|
901
|
+
mode: String,
|
|
902
|
+
renderers: RHash,
|
|
903
|
+
symbols: RHash,
|
|
904
|
+
safe: bool,
|
|
905
|
+
profile: Option<String>,
|
|
906
|
+
sections: bool
|
|
907
|
+
) -> String =>? to_html_safe
|
|
908
|
+
);
|
|
909
|
+
guarded!(
|
|
910
|
+
g_render_with_includes_json(
|
|
911
|
+
ruby: &Ruby,
|
|
912
|
+
source: String,
|
|
913
|
+
root: String,
|
|
914
|
+
source_path: String,
|
|
915
|
+
target: String,
|
|
916
|
+
names: RArray,
|
|
917
|
+
mode: String,
|
|
918
|
+
renderers: RHash,
|
|
919
|
+
symbols: RHash,
|
|
920
|
+
safe: bool,
|
|
921
|
+
profile: Option<String>,
|
|
922
|
+
sections: bool,
|
|
923
|
+
max_depth: Option<usize>,
|
|
924
|
+
max_bytes: Option<usize>,
|
|
925
|
+
max_resolver_calls: Option<usize>,
|
|
926
|
+
max_warnings: Option<usize>
|
|
927
|
+
) -> String =>? render_with_includes_json
|
|
928
|
+
);
|
|
929
|
+
|
|
735
930
|
/// Entry point invoked by Ruby when the extension is loaded.
|
|
736
931
|
///
|
|
737
932
|
/// `name = "carve"` makes the macro emit the `Init_carve` symbol that matches
|
|
@@ -739,35 +934,43 @@ fn stamp_needs_review(source: String, current_version: Option<String>) -> bool {
|
|
|
739
934
|
/// package is named `carve-rb`.
|
|
740
935
|
#[magnus::init(name = "carve")]
|
|
741
936
|
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
937
|
+
install_panic_hook();
|
|
938
|
+
|
|
742
939
|
let module = ruby.define_module("Carve")?;
|
|
940
|
+
// Raised when the engine panics. A StandardError subclass on purpose:
|
|
941
|
+
// magnus would otherwise surface the panic as `fatal`, which no host can
|
|
942
|
+
// rescue, so an embedder taking untrusted input had no defense at all
|
|
943
|
+
// (markup-carve/carve-rb#170).
|
|
944
|
+
module.define_error("EnginePanic", ruby.exception_standard_error())?;
|
|
743
945
|
// Native primitives. The pure-Ruby wrapper in lib/carve.rb defines the
|
|
744
946
|
// public `Carve.to_html(source, extensions:, mode:, renderers:)` on top of
|
|
745
947
|
// these. `_to_html` is the no-extension fast path; the wrapper owns the
|
|
746
948
|
// bare `to_html` name.
|
|
747
|
-
module.define_singleton_method("_to_html", function!(
|
|
748
|
-
module.define_singleton_method("to_markdown", function!(
|
|
749
|
-
module.define_singleton_method("to_plain_text", function!(
|
|
750
|
-
module.define_singleton_method("to_ansi", function!(
|
|
751
|
-
module.define_singleton_method("to_carve", function!(
|
|
949
|
+
module.define_singleton_method("_to_html", function!(g_to_html, 1))?;
|
|
950
|
+
module.define_singleton_method("to_markdown", function!(g_to_markdown, 1))?;
|
|
951
|
+
module.define_singleton_method("to_plain_text", function!(g_to_plain_text, 1))?;
|
|
952
|
+
module.define_singleton_method("to_ansi", function!(g_to_ansi, 1))?;
|
|
953
|
+
module.define_singleton_method("to_carve", function!(g_to_carve, 1))?;
|
|
752
954
|
module.define_singleton_method(
|
|
753
955
|
"_render_with_includes_json",
|
|
754
|
-
function!(
|
|
956
|
+
function!(g_render_with_includes_json, 15),
|
|
755
957
|
)?;
|
|
756
|
-
module.define_singleton_method("_from_html_json", function!(
|
|
757
|
-
module.define_singleton_method("_from_markdown_json", function!(
|
|
758
|
-
module.define_singleton_method("_to_ast_json", function!(
|
|
958
|
+
module.define_singleton_method("_from_html_json", function!(g_from_html_json, 2))?;
|
|
959
|
+
module.define_singleton_method("_from_markdown_json", function!(g_from_markdown_json, 1))?;
|
|
960
|
+
module.define_singleton_method("_to_ast_json", function!(g_to_ast_json, 1))?;
|
|
759
961
|
module.define_singleton_method(
|
|
760
962
|
"to_html_with_extensions",
|
|
761
|
-
function!(
|
|
963
|
+
function!(g_to_html_with_extensions, 2),
|
|
762
964
|
)?;
|
|
763
|
-
module.define_singleton_method("to_html_full", function!(
|
|
965
|
+
module.define_singleton_method("to_html_full", function!(g_to_html_full, 4))?;
|
|
764
966
|
module.define_singleton_method(
|
|
765
967
|
"to_html_full_with_symbols",
|
|
766
|
-
function!(
|
|
968
|
+
function!(g_to_html_full_with_symbols, 5),
|
|
767
969
|
)?;
|
|
768
|
-
module.define_singleton_method("_to_html_safe", function!(
|
|
769
|
-
module.define_singleton_method("_extension_names", function!(
|
|
770
|
-
module.define_singleton_method("_read_stamp", function!(
|
|
771
|
-
module.define_singleton_method("_stamp_needs_review", function!(
|
|
970
|
+
module.define_singleton_method("_to_html_safe", function!(g_to_html_safe, 8))?;
|
|
971
|
+
module.define_singleton_method("_extension_names", function!(g_extension_names, 0))?;
|
|
972
|
+
module.define_singleton_method("_read_stamp", function!(g_read_stamp, 1))?;
|
|
973
|
+
module.define_singleton_method("_stamp_needs_review", function!(g_stamp_needs_review, 2))?;
|
|
974
|
+
module.define_singleton_method("_panic_probe", function!(g_panic_probe, 0))?;
|
|
772
975
|
Ok(())
|
|
773
976
|
}
|
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.
|
|
11
|
+
VERSION = "0.1.8"
|
|
12
12
|
end
|
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.
|
|
4
|
+
version: 0.1.8
|
|
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-
|
|
11
|
+
date: 2026-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|