carve-lang 0.1.2 → 0.1.4
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 +61 -1
- data/README.md +58 -1
- data/ext/carve/Cargo.lock +69 -69
- data/ext/carve/Cargo.toml +2 -2
- data/ext/carve/src/lib.rs +247 -17
- data/lib/carve/version.rb +1 -1
- data/lib/carve.rb +92 -4
- 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: 8f7b8390c9d7d5e50b0f5272a47fad61194cfdb9f62a231a8087676aaa13feb5
|
|
4
|
+
data.tar.gz: b5dad730b6e6c6b7244cdd4b8080b4d23927f2c323055b0240c603e3c6230c6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f746efa34e732f4b0a855d60b61db5c8d13f767d4beeb4d0ec7d0a736daac5164b6c74941a30cba9472437e67734ed2d98d7837eac5a6ef4c7279169cc0b17e7
|
|
7
|
+
data.tar.gz: 587da609dfce7bab98b8333aba463ec784f5da814ec80b9624ed61c622e92c903586abeb33e3af3f33c2d387753a755cecaa82d3d9620b378b3533a337b900ad
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.4] - 2026-09-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `Carve.to_html_with_includes` renders a file-backed document with its
|
|
15
|
+
`{{ path }}` directives expanded, contained to an absolute root the caller
|
|
16
|
+
names. It returns the rendered HTML alongside sanitized warnings and
|
|
17
|
+
root-relative dependencies, and forwards extension and include budget
|
|
18
|
+
settings to the engine. The binding canonicalizes the root and the source
|
|
19
|
+
path and requires the source to sit inside the root (#123, #125).
|
|
20
|
+
- `Carve.parse_with_includes` publishes the expanded document as a tree, in the
|
|
21
|
+
shape `Carve.parse` returns, and the include path takes the same render
|
|
22
|
+
options the string entry points take. A consumer that draws from the tree
|
|
23
|
+
rather than from HTML, such as carve-hexapdf, was blocked without it. Its
|
|
24
|
+
nodes carry no `pos`: spec I4 leaves position remapping across an include out
|
|
25
|
+
of scope in every engine (#126, #127).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- HTML and Markdown migration reports use schema version 2 with shared
|
|
30
|
+
preserved, normalized, degraded, and dropped fidelity plus confidence.
|
|
31
|
+
Markdown retains the existing `source_format` key as a compatibility alias
|
|
32
|
+
for the shared `sourceFormat` spelling and now emits a conservative
|
|
33
|
+
`fidelity-unverified` dropped/fallback finding on every import instead of
|
|
34
|
+
the previous empty diagnostics array (#117).
|
|
35
|
+
- **Breaking:** A substitution node in the tree carries `old` and `new` as
|
|
36
|
+
arrays of inline nodes, where it carried the strings `oldText` and `newText`.
|
|
37
|
+
A caller reading that node walks the halves instead of reading them (#130,
|
|
38
|
+
markup-carve/carve-rs#1756).
|
|
39
|
+
- The engine moves from carve-rs `42df4092` to released 0.1.6 (`d7837249`).
|
|
40
|
+
Beyond the substitution change, the range brings the include pass this
|
|
41
|
+
release exposes, mention and tag handling (a nameless mention or tag is
|
|
42
|
+
dropped and reported, an unspellable name is refused, attributes survive the
|
|
43
|
+
editor bridge), and writer and parser fixes: the Markdown and Carve writers
|
|
44
|
+
escape what would reopen a construct on the way back in, and parsing tightens
|
|
45
|
+
around braced inlines, forced closers, escaped markers, adjacent links, blank
|
|
46
|
+
table rows and a code span's closer. 1740 of 1740 corpus documents render
|
|
47
|
+
byte-identically, and `resources/spec-drift.txt` is empty.
|
|
48
|
+
|
|
49
|
+
## [0.1.3] - 2026-09-08
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- The engine moves from carve-rs `da45f9d2` to `42df4092`, 46 commits, clearing
|
|
54
|
+
the binding-parity drift the nightly run found on 34 corpus documents (#110).
|
|
55
|
+
A comment leaf span begins at its markup, an emptied definition description
|
|
56
|
+
narrows to its marker line, a footnote nested past a footnote body becomes its
|
|
57
|
+
own note, a nested item's lead fence or colon container takes its flush-left
|
|
58
|
+
body, and an all-blank or lone-pipe row is no longer read as a table (the lone
|
|
59
|
+
pipe no longer panics).
|
|
60
|
+
- The engine moves from carve-rs `2e9c43f2` to `da45f9d2`, 20 commits. A colon
|
|
61
|
+
followed by a space and a tab opens no description, an authored task state
|
|
62
|
+
reaches the tree and the HTML, a titled medium emits one title attribute, an
|
|
63
|
+
empty external link target is omitted, and a table continuation survives Djot
|
|
64
|
+
migration. The stale pin is what failed PART 12 binding parity in
|
|
65
|
+
markup-carve/carve#1877 - a binding has no vote of its own.
|
|
66
|
+
|
|
10
67
|
## [0.1.2] - 2026-08-27
|
|
11
68
|
|
|
12
69
|
### Security
|
|
@@ -306,6 +363,9 @@ are not listed, because no release ever shipped them.
|
|
|
306
363
|
Arrays (every AST node type is covered), enabling custom renderers such as
|
|
307
364
|
[carve-hexapdf](https://github.com/markup-carve/carve-hexapdf).
|
|
308
365
|
|
|
309
|
-
[Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.
|
|
366
|
+
[Unreleased]: https://github.com/markup-carve/carve-rb/compare/v0.1.4...HEAD
|
|
367
|
+
[0.1.4]: https://github.com/markup-carve/carve-rb/compare/v0.1.3...v0.1.4
|
|
368
|
+
[0.1.3]: https://github.com/markup-carve/carve-rb/compare/v0.1.2...v0.1.3
|
|
369
|
+
[0.1.2]: https://github.com/markup-carve/carve-rb/compare/v0.1.1...v0.1.2
|
|
310
370
|
[0.1.1]: https://github.com/markup-carve/carve-rb/compare/v0.1.0...v0.1.1
|
|
311
371
|
[0.1.0]: https://github.com/markup-carve/carve-rb/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -37,15 +37,18 @@ time via `rb_sys`.
|
|
|
37
37
|
require "carve"
|
|
38
38
|
|
|
39
39
|
Carve.to_html("# Hello *world*")
|
|
40
|
+
# => "<section id=\"Hello-world\">\n <h1>Hello <strong>world</strong></h1>\n</section>"
|
|
40
41
|
|
|
41
42
|
# Every core engine target is available from the binding.
|
|
42
43
|
Carve.to_markdown(source)
|
|
43
44
|
Carve.to_plain_text(source)
|
|
44
45
|
Carve.to_ansi(source)
|
|
45
46
|
Carve.to_carve(source)
|
|
47
|
+
# Import reports use schema version 2, with fidelity and confidence per finding.
|
|
48
|
+
# Markdown emits fidelity-unverified/dropped/fallback until its engine path
|
|
49
|
+
# exposes construct-level fidelity.
|
|
46
50
|
Carve.from_html('<p>Hello <strong>world</strong></p>')
|
|
47
51
|
Carve.from_markdown('*em* and **strong**')
|
|
48
|
-
# => "<section id=\"Hello-world\">\n <h1>Hello <strong>world</strong></h1>\n</section>"
|
|
49
52
|
|
|
50
53
|
# Carve syntax note: *...* is STRONG (bold), /.../ is EMPHASIS (italic).
|
|
51
54
|
Carve.to_html("*bold* and /italic/")
|
|
@@ -77,6 +80,57 @@ this binding has always taken: `:math`, `:permalinks`, `:mermaid`, `:dot`,
|
|
|
77
80
|
|
|
78
81
|
An unknown extension name raises `ArgumentError`.
|
|
79
82
|
|
|
83
|
+
### File includes
|
|
84
|
+
|
|
85
|
+
Includes are opt-in and require an absolute containment root plus the absolute
|
|
86
|
+
path of the source document. The result carries the rendered value, warnings,
|
|
87
|
+
and root-relative dependencies. Other render methods leave include directives
|
|
88
|
+
literal.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
result = Carve.to_html_with_includes(
|
|
92
|
+
File.read("book.crv"),
|
|
93
|
+
root: File.expand_path("."),
|
|
94
|
+
source_path: File.expand_path("book.crv"),
|
|
95
|
+
max_depth: 16,
|
|
96
|
+
)
|
|
97
|
+
puts result[:value]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Every render option `Carve.to_html` takes is accepted here and reaches the
|
|
101
|
+
included children too, so a document renders the same way whether or not it
|
|
102
|
+
went through the include path: `extensions:`, `symbols:`, `profile:`, `mode:`,
|
|
103
|
+
`renderers:`, `safe:` and `sections:`.
|
|
104
|
+
|
|
105
|
+
`Carve.parse_with_includes` gives the expanded document as an AST instead, in
|
|
106
|
+
the shape `Carve.parse` returns, for a host that walks the tree rather than
|
|
107
|
+
rendering HTML:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
result = Carve.parse_with_includes(
|
|
111
|
+
File.read("book.crv"),
|
|
112
|
+
root: File.expand_path("."),
|
|
113
|
+
source_path: File.expand_path("book.crv"),
|
|
114
|
+
)
|
|
115
|
+
result[:value][:children]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Its nodes carry no `:pos`, unlike `Carve.parse`. Spec I4 leaves position
|
|
119
|
+
remapping out of scope in every engine, so a span on an included node would name
|
|
120
|
+
an offset in a document the caller never passed.
|
|
121
|
+
|
|
122
|
+
`Carve.render_with_includes` takes the same arguments plus `target:`, one of
|
|
123
|
+
`"html"`, `"markdown"`, `"plain"`, `"ansi"` or `"ast"`. There is no `"carve"`
|
|
124
|
+
target: spec I15 excludes the Carve writer from expansion, because inlining a
|
|
125
|
+
child into the formatter's output rewrites the author's document rather than
|
|
126
|
+
formatting it.
|
|
127
|
+
|
|
128
|
+
The budgets `max_depth:`, `max_bytes:`, `max_resolver_calls:` and
|
|
129
|
+
`max_warnings:` pass through to the engine; omit one to keep its default. A
|
|
130
|
+
target refused by the byte budget still reports `resolved: true`, because
|
|
131
|
+
section 19 charges the budget for what the resolver handed back and a target is
|
|
132
|
+
resolved before its size is known.
|
|
133
|
+
|
|
80
134
|
## Parsing to an AST
|
|
81
135
|
|
|
82
136
|
`Carve.parse` returns the parsed document as a tree of Ruby Hashes and Arrays,
|
|
@@ -267,6 +321,9 @@ a document change.
|
|
|
267
321
|
| `Carve.to_html(source, symbols: {...})` | Render with a `:name:` -> value symbol map (values are raw, see above). |
|
|
268
322
|
| `Carve.to_html(source, safe: true, profile: :comment)` | Render untrusted input: escape `=html` raw blocks/spans, restrict constructs. |
|
|
269
323
|
| `Carve.to_html(source, sections: false)` | Render headings flat, with the id on the `<h*>` instead of a `<section>` wrapper. |
|
|
324
|
+
| `Carve.to_html_with_includes(source, root:, source_path:)` | Render contained file includes and return warnings and dependencies. |
|
|
325
|
+
| `Carve.parse_with_includes(source, root:, source_path:)` | The same expansion, published as an AST instead of HTML. |
|
|
326
|
+
| `Carve.render_with_includes(source, root:, source_path:, target:)` | The expansion over any render target: html, markdown, plain, ansi or ast. |
|
|
270
327
|
| `Carve.read_stamp(source)` | Read a document's provenance marker: `{version:, generated_by:}` or `nil`. |
|
|
271
328
|
| `Carve.needs_review?(source)` | Whether a document predates this engine's spec version (unstamped counts as yes). |
|
|
272
329
|
| `Carve.to_html_with_extensions(source, names_array)` | Native primitive (Array of Strings). |
|
data/ext/carve/Cargo.lock
CHANGED
|
@@ -37,18 +37,21 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
|
|
37
37
|
|
|
38
38
|
[[package]]
|
|
39
39
|
name = "carve-lang"
|
|
40
|
-
version = "0.1.
|
|
41
|
-
source = "git+https://github.com/markup-carve/carve-rs?rev=
|
|
40
|
+
version = "0.1.6"
|
|
41
|
+
source = "git+https://github.com/markup-carve/carve-rs?rev=d7837249c64b88879b04cd71b8b5555ff246e16f#d7837249c64b88879b04cd71b8b5555ff246e16f"
|
|
42
42
|
dependencies = [
|
|
43
43
|
"html5ever",
|
|
44
44
|
"markup5ever_rcdom",
|
|
45
45
|
"pulldown-cmark",
|
|
46
46
|
"regex",
|
|
47
|
+
"serde",
|
|
48
|
+
"serde_json",
|
|
49
|
+
"unicode-normalization",
|
|
47
50
|
]
|
|
48
51
|
|
|
49
52
|
[[package]]
|
|
50
53
|
name = "carve-rb"
|
|
51
|
-
version = "0.1.
|
|
54
|
+
version = "0.1.4"
|
|
52
55
|
dependencies = [
|
|
53
56
|
"carve-lang",
|
|
54
57
|
"magnus",
|
|
@@ -89,14 +92,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
89
92
|
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
|
90
93
|
|
|
91
94
|
[[package]]
|
|
92
|
-
name = "
|
|
93
|
-
version = "
|
|
95
|
+
name = "fastrand"
|
|
96
|
+
version = "2.5.0"
|
|
94
97
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
95
|
-
checksum = "
|
|
96
|
-
dependencies = [
|
|
97
|
-
"mac",
|
|
98
|
-
"new_debug_unreachable",
|
|
99
|
-
]
|
|
98
|
+
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
|
100
99
|
|
|
101
100
|
[[package]]
|
|
102
101
|
name = "glob"
|
|
@@ -106,16 +105,12 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
|
|
106
105
|
|
|
107
106
|
[[package]]
|
|
108
107
|
name = "html5ever"
|
|
109
|
-
version = "0.
|
|
108
|
+
version = "0.39.0"
|
|
110
109
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
111
|
-
checksum = "
|
|
110
|
+
checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
|
|
112
111
|
dependencies = [
|
|
113
112
|
"log",
|
|
114
|
-
"mac",
|
|
115
113
|
"markup5ever",
|
|
116
|
-
"proc-macro2",
|
|
117
|
-
"quote",
|
|
118
|
-
"syn 2.0.118",
|
|
119
114
|
]
|
|
120
115
|
|
|
121
116
|
[[package]]
|
|
@@ -170,12 +165,6 @@ version = "0.4.33"
|
|
|
170
165
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
171
166
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
|
172
167
|
|
|
173
|
-
[[package]]
|
|
174
|
-
name = "mac"
|
|
175
|
-
version = "0.1.1"
|
|
176
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
177
|
-
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|
178
|
-
|
|
179
168
|
[[package]]
|
|
180
169
|
name = "magnus"
|
|
181
170
|
version = "0.8.2"
|
|
@@ -201,23 +190,20 @@ dependencies = [
|
|
|
201
190
|
|
|
202
191
|
[[package]]
|
|
203
192
|
name = "markup5ever"
|
|
204
|
-
version = "0.
|
|
193
|
+
version = "0.39.0"
|
|
205
194
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
206
|
-
checksum = "
|
|
195
|
+
checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
|
|
207
196
|
dependencies = [
|
|
208
197
|
"log",
|
|
209
|
-
"phf",
|
|
210
|
-
"phf_codegen",
|
|
211
|
-
"string_cache",
|
|
212
|
-
"string_cache_codegen",
|
|
213
198
|
"tendril",
|
|
199
|
+
"web_atoms",
|
|
214
200
|
]
|
|
215
201
|
|
|
216
202
|
[[package]]
|
|
217
203
|
name = "markup5ever_rcdom"
|
|
218
|
-
version = "0.
|
|
204
|
+
version = "0.39.0+unofficial"
|
|
219
205
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
220
|
-
checksum = "
|
|
206
|
+
checksum = "3ac010f19d6c4af81eeb4018a39d7a115de9d285af45c126a4ac02e6fc5716b7"
|
|
221
207
|
dependencies = [
|
|
222
208
|
"html5ever",
|
|
223
209
|
"markup5ever",
|
|
@@ -278,18 +264,19 @@ dependencies = [
|
|
|
278
264
|
|
|
279
265
|
[[package]]
|
|
280
266
|
name = "phf"
|
|
281
|
-
version = "0.
|
|
267
|
+
version = "0.13.1"
|
|
282
268
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
283
|
-
checksum = "
|
|
269
|
+
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
|
284
270
|
dependencies = [
|
|
285
271
|
"phf_shared",
|
|
272
|
+
"serde",
|
|
286
273
|
]
|
|
287
274
|
|
|
288
275
|
[[package]]
|
|
289
276
|
name = "phf_codegen"
|
|
290
|
-
version = "0.
|
|
277
|
+
version = "0.13.1"
|
|
291
278
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
292
|
-
checksum = "
|
|
279
|
+
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
|
293
280
|
dependencies = [
|
|
294
281
|
"phf_generator",
|
|
295
282
|
"phf_shared",
|
|
@@ -297,19 +284,19 @@ dependencies = [
|
|
|
297
284
|
|
|
298
285
|
[[package]]
|
|
299
286
|
name = "phf_generator"
|
|
300
|
-
version = "0.
|
|
287
|
+
version = "0.13.1"
|
|
301
288
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
302
|
-
checksum = "
|
|
289
|
+
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
|
303
290
|
dependencies = [
|
|
291
|
+
"fastrand",
|
|
304
292
|
"phf_shared",
|
|
305
|
-
"rand",
|
|
306
293
|
]
|
|
307
294
|
|
|
308
295
|
[[package]]
|
|
309
296
|
name = "phf_shared"
|
|
310
|
-
version = "0.
|
|
297
|
+
version = "0.13.1"
|
|
311
298
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
312
|
-
checksum = "
|
|
299
|
+
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
|
313
300
|
dependencies = [
|
|
314
301
|
"siphasher",
|
|
315
302
|
]
|
|
@@ -349,21 +336,6 @@ dependencies = [
|
|
|
349
336
|
"proc-macro2",
|
|
350
337
|
]
|
|
351
338
|
|
|
352
|
-
[[package]]
|
|
353
|
-
name = "rand"
|
|
354
|
-
version = "0.8.7"
|
|
355
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
356
|
-
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
|
357
|
-
dependencies = [
|
|
358
|
-
"rand_core",
|
|
359
|
-
]
|
|
360
|
-
|
|
361
|
-
[[package]]
|
|
362
|
-
name = "rand_core"
|
|
363
|
-
version = "0.6.4"
|
|
364
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
365
|
-
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
|
366
|
-
|
|
367
339
|
[[package]]
|
|
368
340
|
name = "rb-sys"
|
|
369
341
|
version = "0.9.128"
|
|
@@ -457,6 +429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
457
429
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
|
458
430
|
dependencies = [
|
|
459
431
|
"serde_core",
|
|
432
|
+
"serde_derive",
|
|
460
433
|
]
|
|
461
434
|
|
|
462
435
|
[[package]]
|
|
@@ -518,9 +491,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
|
|
518
491
|
|
|
519
492
|
[[package]]
|
|
520
493
|
name = "string_cache"
|
|
521
|
-
version = "0.
|
|
494
|
+
version = "0.9.0"
|
|
522
495
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
523
|
-
checksum = "
|
|
496
|
+
checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
|
524
497
|
dependencies = [
|
|
525
498
|
"new_debug_unreachable",
|
|
526
499
|
"parking_lot",
|
|
@@ -531,9 +504,9 @@ dependencies = [
|
|
|
531
504
|
|
|
532
505
|
[[package]]
|
|
533
506
|
name = "string_cache_codegen"
|
|
534
|
-
version = "0.
|
|
507
|
+
version = "0.6.1"
|
|
535
508
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
536
|
-
checksum = "
|
|
509
|
+
checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
|
|
537
510
|
dependencies = [
|
|
538
511
|
"phf_generator",
|
|
539
512
|
"phf_shared",
|
|
@@ -565,15 +538,28 @@ dependencies = [
|
|
|
565
538
|
|
|
566
539
|
[[package]]
|
|
567
540
|
name = "tendril"
|
|
568
|
-
version = "0.
|
|
541
|
+
version = "0.5.1"
|
|
569
542
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
570
|
-
checksum = "
|
|
543
|
+
checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08"
|
|
571
544
|
dependencies = [
|
|
572
|
-
"
|
|
573
|
-
"mac",
|
|
574
|
-
"utf-8",
|
|
545
|
+
"new_debug_unreachable",
|
|
575
546
|
]
|
|
576
547
|
|
|
548
|
+
[[package]]
|
|
549
|
+
name = "tinyvec"
|
|
550
|
+
version = "1.12.0"
|
|
551
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
552
|
+
checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
|
|
553
|
+
dependencies = [
|
|
554
|
+
"tinyvec_macros",
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
[[package]]
|
|
558
|
+
name = "tinyvec_macros"
|
|
559
|
+
version = "0.1.1"
|
|
560
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
561
|
+
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|
562
|
+
|
|
577
563
|
[[package]]
|
|
578
564
|
name = "unicase"
|
|
579
565
|
version = "2.9.0"
|
|
@@ -587,10 +573,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
587
573
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
588
574
|
|
|
589
575
|
[[package]]
|
|
590
|
-
name = "
|
|
591
|
-
version = "0.
|
|
576
|
+
name = "unicode-normalization"
|
|
577
|
+
version = "0.1.25"
|
|
578
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
579
|
+
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
|
580
|
+
dependencies = [
|
|
581
|
+
"tinyvec",
|
|
582
|
+
]
|
|
583
|
+
|
|
584
|
+
[[package]]
|
|
585
|
+
name = "web_atoms"
|
|
586
|
+
version = "0.2.6"
|
|
592
587
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
593
|
-
checksum = "
|
|
588
|
+
checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3"
|
|
589
|
+
dependencies = [
|
|
590
|
+
"phf",
|
|
591
|
+
"phf_codegen",
|
|
592
|
+
"string_cache",
|
|
593
|
+
"string_cache_codegen",
|
|
594
|
+
]
|
|
594
595
|
|
|
595
596
|
[[package]]
|
|
596
597
|
name = "windows-link"
|
|
@@ -600,12 +601,11 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
|
600
601
|
|
|
601
602
|
[[package]]
|
|
602
603
|
name = "xml5ever"
|
|
603
|
-
version = "0.
|
|
604
|
+
version = "0.39.0"
|
|
604
605
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
605
|
-
checksum = "
|
|
606
|
+
checksum = "5ab627f34ff61b80d756180d556f9c68801d836d271b3b8c094504ceca69d221"
|
|
606
607
|
dependencies = [
|
|
607
608
|
"log",
|
|
608
|
-
"mac",
|
|
609
609
|
"markup5ever",
|
|
610
610
|
]
|
|
611
611
|
|
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.4"
|
|
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", git = "https://github.com/markup-carve/carve-rs", rev = "
|
|
25
|
+
carve_rs = { package = "carve-lang", git = "https://github.com/markup-carve/carve-rs", rev = "d7837249c64b88879b04cd71b8b5555ff246e16f" }
|
data/ext/carve/src/lib.rs
CHANGED
|
@@ -28,6 +28,7 @@ use carve_rs::extensions::registry;
|
|
|
28
28
|
use carve_rs::{CarveExtension, Mode, Options, Profile, StaticRenderers};
|
|
29
29
|
use magnus::value::{InnerValue, Opaque};
|
|
30
30
|
use magnus::{function, prelude::*, Error, RArray, RHash, Ruby, Value};
|
|
31
|
+
use std::path::{Path, PathBuf};
|
|
31
32
|
|
|
32
33
|
/// HTML-escape a string for the renderer-failure fallback path.
|
|
33
34
|
///
|
|
@@ -215,6 +216,218 @@ fn to_carve(source: String) -> String {
|
|
|
215
216
|
carve_rs::to_carve(&source)
|
|
216
217
|
}
|
|
217
218
|
|
|
219
|
+
/// Which renderer runs over the expanded document.
|
|
220
|
+
///
|
|
221
|
+
/// `carve` is absent on purpose: spec I15 excludes the Carve writer from
|
|
222
|
+
/// expansion, because inlining a child into the formatter's output rewrites the
|
|
223
|
+
/// author's document rather than formatting it.
|
|
224
|
+
#[derive(Clone, Copy, PartialEq)]
|
|
225
|
+
enum IncludeTarget {
|
|
226
|
+
Html,
|
|
227
|
+
Markdown,
|
|
228
|
+
Plain,
|
|
229
|
+
Ansi,
|
|
230
|
+
Ast,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn parse_include_target(ruby: &Ruby, name: &str) -> Result<IncludeTarget, Error> {
|
|
234
|
+
match name {
|
|
235
|
+
"html" => Ok(IncludeTarget::Html),
|
|
236
|
+
"markdown" => Ok(IncludeTarget::Markdown),
|
|
237
|
+
"plain" => Ok(IncludeTarget::Plain),
|
|
238
|
+
"ansi" => Ok(IncludeTarget::Ansi),
|
|
239
|
+
"ast" => Ok(IncludeTarget::Ast),
|
|
240
|
+
other => Err(Error::new(
|
|
241
|
+
ruby.exception_arg_error(),
|
|
242
|
+
format!(
|
|
243
|
+
"Unknown Carve include target: {other:?} \
|
|
244
|
+
(supported: \"html\", \"markdown\", \"plain\", \"ansi\", \"ast\")"
|
|
245
|
+
),
|
|
246
|
+
)),
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[allow(clippy::too_many_arguments)]
|
|
251
|
+
fn render_with_includes_json(
|
|
252
|
+
ruby: &Ruby,
|
|
253
|
+
source: String,
|
|
254
|
+
root: String,
|
|
255
|
+
source_path: String,
|
|
256
|
+
target: String,
|
|
257
|
+
names: RArray,
|
|
258
|
+
mode: String,
|
|
259
|
+
renderers: RHash,
|
|
260
|
+
symbols: RHash,
|
|
261
|
+
safe: bool,
|
|
262
|
+
profile: Option<String>,
|
|
263
|
+
sections: bool,
|
|
264
|
+
max_depth: Option<usize>,
|
|
265
|
+
max_bytes: Option<usize>,
|
|
266
|
+
max_resolver_calls: Option<usize>,
|
|
267
|
+
max_warnings: Option<usize>,
|
|
268
|
+
) -> Result<String, Error> {
|
|
269
|
+
let which = parse_include_target(ruby, &target)?;
|
|
270
|
+
let root_path = Path::new(&root);
|
|
271
|
+
let source_path = Path::new(&source_path);
|
|
272
|
+
if !root_path.is_absolute() || !source_path.is_absolute() {
|
|
273
|
+
return Err(Error::new(
|
|
274
|
+
ruby.exception_arg_error(),
|
|
275
|
+
"include root and source_path must be absolute".to_string(),
|
|
276
|
+
));
|
|
277
|
+
}
|
|
278
|
+
let root_real = std::fs::canonicalize(root_path).map_err(|_| {
|
|
279
|
+
Error::new(
|
|
280
|
+
ruby.exception_arg_error(),
|
|
281
|
+
"include root is not a readable directory".to_string(),
|
|
282
|
+
)
|
|
283
|
+
})?;
|
|
284
|
+
let source_real = std::fs::canonicalize(source_path).map_err(|_| {
|
|
285
|
+
Error::new(
|
|
286
|
+
ruby.exception_arg_error(),
|
|
287
|
+
"include source_path is not a readable file".to_string(),
|
|
288
|
+
)
|
|
289
|
+
})?;
|
|
290
|
+
if !source_real.starts_with(&root_real) || !source_real.is_file() {
|
|
291
|
+
return Err(Error::new(
|
|
292
|
+
ruby.exception_arg_error(),
|
|
293
|
+
"include source_path must be a file inside the include root".to_string(),
|
|
294
|
+
));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let resolver = carve_rs::FileSystemResolver::new(&root_real).map_err(|_| {
|
|
298
|
+
Error::new(
|
|
299
|
+
ruby.exception_arg_error(),
|
|
300
|
+
"include root is not usable".to_string(),
|
|
301
|
+
)
|
|
302
|
+
})?;
|
|
303
|
+
// The same options `to_html` builds. A document rendered through the
|
|
304
|
+
// include path renders the way it would without one: jekyll-carve passes a
|
|
305
|
+
// symbol map on every conversion, and carve-hexapdf asks for one profile
|
|
306
|
+
// and extension set across parent and child alike.
|
|
307
|
+
let parsed_mode = parse_mode(ruby, &mode)?;
|
|
308
|
+
let parsed_profile = match profile.as_deref() {
|
|
309
|
+
None => None,
|
|
310
|
+
Some(name) => Some(parse_profile(ruby, name)?),
|
|
311
|
+
};
|
|
312
|
+
let static_renderers = build_renderers(ruby, renderers)?;
|
|
313
|
+
let boxed = boxed_extensions(ruby, names)?;
|
|
314
|
+
let symbol_pairs = build_symbols(symbols)?;
|
|
315
|
+
let mut render_options = Options::new()
|
|
316
|
+
.with_mode(parsed_mode)
|
|
317
|
+
.with_renderers(static_renderers);
|
|
318
|
+
for extension in &boxed {
|
|
319
|
+
render_options = render_options.with_extension(extension.as_ref());
|
|
320
|
+
}
|
|
321
|
+
for (name, value) in &symbol_pairs {
|
|
322
|
+
render_options = render_options.with_symbol(name.clone(), value.clone());
|
|
323
|
+
}
|
|
324
|
+
if safe {
|
|
325
|
+
render_options = render_options.with_raw_html(false);
|
|
326
|
+
}
|
|
327
|
+
if let Some(preset) = parsed_profile {
|
|
328
|
+
render_options = render_options.with_profile(preset);
|
|
329
|
+
}
|
|
330
|
+
if !sections {
|
|
331
|
+
render_options = render_options.with_sections(false);
|
|
332
|
+
}
|
|
333
|
+
let mut include_options = carve_rs::IncludeOptions::new()
|
|
334
|
+
.with_resolver(&resolver)
|
|
335
|
+
.with_source_path(source_real.to_string_lossy());
|
|
336
|
+
if let Some(value) = max_depth {
|
|
337
|
+
include_options = include_options.with_max_depth(value);
|
|
338
|
+
}
|
|
339
|
+
if let Some(value) = max_bytes {
|
|
340
|
+
include_options = include_options.with_max_bytes(value);
|
|
341
|
+
}
|
|
342
|
+
if let Some(value) = max_resolver_calls {
|
|
343
|
+
include_options = include_options.with_max_resolver_calls(value);
|
|
344
|
+
}
|
|
345
|
+
if let Some(value) = max_warnings {
|
|
346
|
+
include_options = include_options.with_max_warnings(value);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let target_is_html = which == IncludeTarget::Html;
|
|
350
|
+
let prepared = carve_rs::prepare_doc_with_includes(
|
|
351
|
+
&source,
|
|
352
|
+
&render_options,
|
|
353
|
+
&include_options,
|
|
354
|
+
// Every target but HTML is inherently static, so it prepares under the
|
|
355
|
+
// interactive mode the other renderers assume.
|
|
356
|
+
if target_is_html {
|
|
357
|
+
render_options.mode
|
|
358
|
+
} else {
|
|
359
|
+
Mode::Interactive
|
|
360
|
+
},
|
|
361
|
+
target_is_html,
|
|
362
|
+
)
|
|
363
|
+
.map_err(|error| Error::new(ruby.exception_arg_error(), error.to_string()))?;
|
|
364
|
+
let rendered = match which {
|
|
365
|
+
IncludeTarget::Html => carve_rs::render_html_with_options(&prepared.doc, &render_options),
|
|
366
|
+
IncludeTarget::Markdown => {
|
|
367
|
+
carve_rs::render_markdown_with_options(&prepared.doc, &render_options)
|
|
368
|
+
}
|
|
369
|
+
IncludeTarget::Plain => {
|
|
370
|
+
carve_rs::render_plain_text_with_options(&prepared.doc, &render_options)
|
|
371
|
+
}
|
|
372
|
+
IncludeTarget::Ansi => carve_rs::render_ansi_with_options(&prepared.doc, &render_options),
|
|
373
|
+
// The AST is published as a tree, not as a string, so a caller does not
|
|
374
|
+
// parse JSON twice. Positions stay OFF, unlike `to_ast_json`: spec I4
|
|
375
|
+
// leaves position remapping out of scope in every engine, so a child's
|
|
376
|
+
// spans would point into a document the caller never passed.
|
|
377
|
+
IncludeTarget::Ast => Ok(carve_rs::to_json(&prepared.doc)),
|
|
378
|
+
}
|
|
379
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
380
|
+
let value: serde_json::Value = if which == IncludeTarget::Ast {
|
|
381
|
+
serde_json::from_str(&rendered).map_err(|error| {
|
|
382
|
+
Error::new(
|
|
383
|
+
ruby.exception_runtime_error(),
|
|
384
|
+
format!("the engine's AST JSON did not parse: {error}"),
|
|
385
|
+
)
|
|
386
|
+
})?
|
|
387
|
+
} else {
|
|
388
|
+
serde_json::Value::String(rendered)
|
|
389
|
+
};
|
|
390
|
+
let safe_path = |value: &str| {
|
|
391
|
+
let path = PathBuf::from(value);
|
|
392
|
+
if path.is_absolute() {
|
|
393
|
+
path.strip_prefix(&root_real)
|
|
394
|
+
.map(|relative| relative.to_string_lossy().replace('\\', "/"))
|
|
395
|
+
.unwrap_or_else(|_| "[outside-root]".to_string())
|
|
396
|
+
} else {
|
|
397
|
+
value.to_string()
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
let warnings = prepared
|
|
401
|
+
.warnings
|
|
402
|
+
.iter()
|
|
403
|
+
.map(|warning| {
|
|
404
|
+
serde_json::json!({
|
|
405
|
+
"rule": warning.rule,
|
|
406
|
+
"message": warning.message,
|
|
407
|
+
"file": warning.file.as_deref().map(&safe_path),
|
|
408
|
+
})
|
|
409
|
+
})
|
|
410
|
+
.collect::<Vec<_>>();
|
|
411
|
+
let dependencies = prepared
|
|
412
|
+
.dependencies
|
|
413
|
+
.iter()
|
|
414
|
+
.map(|dependency| {
|
|
415
|
+
serde_json::json!({
|
|
416
|
+
"path": safe_path(&dependency.id),
|
|
417
|
+
"resolved": dependency.resolved,
|
|
418
|
+
"denial": dependency.denial.map(|denial| denial.as_str()),
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
.collect::<Vec<_>>();
|
|
422
|
+
Ok(serde_json::json!({
|
|
423
|
+
"value": value,
|
|
424
|
+
"warnings": warnings,
|
|
425
|
+
"dependencies": dependencies,
|
|
426
|
+
"suppressedWarnings": prepared.suppressed_warnings,
|
|
427
|
+
})
|
|
428
|
+
.to_string())
|
|
429
|
+
}
|
|
430
|
+
|
|
218
431
|
fn from_html_json(ruby: &Ruby, source: String, mode: String) -> Result<String, Error> {
|
|
219
432
|
let mode = carve_rs::HtmlImportMode::from_name(&mode).ok_or_else(|| {
|
|
220
433
|
Error::new(
|
|
@@ -222,7 +435,7 @@ fn from_html_json(ruby: &Ruby, source: String, mode: String) -> Result<String, E
|
|
|
222
435
|
"mode must be safe, semantic, or roundtrip".to_string(),
|
|
223
436
|
)
|
|
224
437
|
})?;
|
|
225
|
-
let result = carve_rs::
|
|
438
|
+
let result = carve_rs::migrate_html(
|
|
226
439
|
&source,
|
|
227
440
|
&carve_rs::HtmlImportOptions {
|
|
228
441
|
mode,
|
|
@@ -239,31 +452,44 @@ fn from_html_json(ruby: &Ruby, source: String, mode: String) -> Result<String, E
|
|
|
239
452
|
.report
|
|
240
453
|
.diagnostics
|
|
241
454
|
.iter()
|
|
242
|
-
.map(
|
|
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
|
-
})
|
|
455
|
+
.map(migration_diagnostic_json)
|
|
253
456
|
.collect::<Vec<_>>();
|
|
254
457
|
Ok(serde_json::json!({
|
|
255
458
|
"value": result.value,
|
|
256
459
|
"report": {
|
|
257
|
-
"
|
|
258
|
-
"
|
|
460
|
+
"schemaVersion": result.report.schema_version,
|
|
461
|
+
"sourceFormat": result.report.source_format.as_str(),
|
|
462
|
+
"mode": result.report.mode.map(|value| value.as_str()),
|
|
463
|
+
"adapter": result.report.adapter.map(|value| value.as_str()),
|
|
259
464
|
"diagnostics": diagnostics,
|
|
260
465
|
}
|
|
261
466
|
})
|
|
262
467
|
.to_string())
|
|
263
468
|
}
|
|
264
469
|
|
|
265
|
-
fn
|
|
266
|
-
carve_rs::
|
|
470
|
+
fn from_markdown_json(source: String) -> String {
|
|
471
|
+
let result = carve_rs::migrate_markdown(&source);
|
|
472
|
+
let diagnostics = result
|
|
473
|
+
.report
|
|
474
|
+
.diagnostics
|
|
475
|
+
.iter()
|
|
476
|
+
.map(migration_diagnostic_json)
|
|
477
|
+
.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()
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
fn migration_diagnostic_json(diagnostic: &carve_rs::MigrationDiagnostic) -> serde_json::Value {
|
|
482
|
+
let mut value = serde_json::json!({
|
|
483
|
+
"code": diagnostic.code,
|
|
484
|
+
"message": diagnostic.message,
|
|
485
|
+
"severity": diagnostic.severity.as_str(),
|
|
486
|
+
"fidelity": diagnostic.fidelity.as_str(),
|
|
487
|
+
"confidence": diagnostic.confidence.as_str(),
|
|
488
|
+
});
|
|
489
|
+
if let Some(path) = &diagnostic.path {
|
|
490
|
+
value["path"] = serde_json::json!(path);
|
|
491
|
+
}
|
|
492
|
+
value
|
|
267
493
|
}
|
|
268
494
|
|
|
269
495
|
/// Parse Carve source and return its AST as a JSON string.
|
|
@@ -523,8 +749,12 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
523
749
|
module.define_singleton_method("to_plain_text", function!(to_plain_text, 1))?;
|
|
524
750
|
module.define_singleton_method("to_ansi", function!(to_ansi, 1))?;
|
|
525
751
|
module.define_singleton_method("to_carve", function!(to_carve, 1))?;
|
|
752
|
+
module.define_singleton_method(
|
|
753
|
+
"_render_with_includes_json",
|
|
754
|
+
function!(render_with_includes_json, 15),
|
|
755
|
+
)?;
|
|
526
756
|
module.define_singleton_method("_from_html_json", function!(from_html_json, 2))?;
|
|
527
|
-
module.define_singleton_method("
|
|
757
|
+
module.define_singleton_method("_from_markdown_json", function!(from_markdown_json, 1))?;
|
|
528
758
|
module.define_singleton_method("_to_ast_json", function!(to_ast_json, 1))?;
|
|
529
759
|
module.define_singleton_method(
|
|
530
760
|
"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.
|
|
11
|
+
VERSION = "0.1.4"
|
|
12
12
|
end
|
data/lib/carve.rb
CHANGED
|
@@ -42,6 +42,94 @@ module Carve
|
|
|
42
42
|
RENDERER_KEYS = %i[mermaid chart graphviz math].freeze
|
|
43
43
|
|
|
44
44
|
class << self
|
|
45
|
+
# Render +source+ to HTML with its <tt>{{ path }}</tt> includes expanded,
|
|
46
|
+
# contained to +root:+.
|
|
47
|
+
#
|
|
48
|
+
# Carve.to_html_with_includes(File.read(page), root: "/srv/site",
|
|
49
|
+
# source_path: page, symbols: {smile: "\u{1F604}"})
|
|
50
|
+
#
|
|
51
|
+
# +root:+ and +source_path:+ are absolute, and the source file lives inside
|
|
52
|
+
# the root. A relative value raises ArgumentError rather than rooting
|
|
53
|
+
# containment at the process working directory, which is arbitrary with
|
|
54
|
+
# respect to the document.
|
|
55
|
+
#
|
|
56
|
+
# Returns +{value:, warnings:, dependencies:, suppressedWarnings:}+. Paths
|
|
57
|
+
# in +warnings+ and +dependencies+ are relative to the root, so a report can
|
|
58
|
+
# be shown to a reader as it stands.
|
|
59
|
+
#
|
|
60
|
+
# Every render option +to_html+ takes is accepted and reaches the included
|
|
61
|
+
# children too: an extension, a symbol map or a profile means the same thing
|
|
62
|
+
# in a child as in the parent.
|
|
63
|
+
def to_html_with_includes(source, root:, source_path:, extensions: nil, mode: nil,
|
|
64
|
+
renderers: nil, symbols: nil, safe: false, profile: nil,
|
|
65
|
+
sections: true, max_depth: nil, max_bytes: nil,
|
|
66
|
+
max_resolver_calls: nil, max_warnings: nil)
|
|
67
|
+
render_with_includes(
|
|
68
|
+
source, target: "html", root: root, source_path: source_path,
|
|
69
|
+
extensions: extensions, mode: mode, renderers: renderers, symbols: symbols,
|
|
70
|
+
safe: safe, profile: profile, sections: sections, max_depth: max_depth,
|
|
71
|
+
max_bytes: max_bytes, max_resolver_calls: max_resolver_calls,
|
|
72
|
+
max_warnings: max_warnings
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The expanded document as an AST, in the shape +.parse+ returns.
|
|
77
|
+
#
|
|
78
|
+
# Carve.parse_with_includes(File.read(page), root: root, source_path: page)
|
|
79
|
+
# # => {value: {type: "document", ...}, warnings: [], dependencies: [...], ...}
|
|
80
|
+
#
|
|
81
|
+
# For a host that walks the tree rather than rendering HTML - carve-hexapdf
|
|
82
|
+
# draws its PDF from this shape.
|
|
83
|
+
#
|
|
84
|
+
# Nodes carry NO +:pos+, unlike +.parse+. Spec I4 leaves position remapping
|
|
85
|
+
# out of scope in every engine, so a span on an included node would name an
|
|
86
|
+
# offset in a document the caller never passed.
|
|
87
|
+
def parse_with_includes(source, root:, source_path:, extensions: nil, profile: nil,
|
|
88
|
+
max_depth: nil, max_bytes: nil, max_resolver_calls: nil,
|
|
89
|
+
max_warnings: nil)
|
|
90
|
+
render_with_includes(
|
|
91
|
+
source, target: "ast", root: root, source_path: source_path,
|
|
92
|
+
extensions: extensions, profile: profile, max_depth: max_depth,
|
|
93
|
+
max_bytes: max_bytes, max_resolver_calls: max_resolver_calls,
|
|
94
|
+
max_warnings: max_warnings
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The include pass over any render target: +"html"+, +"markdown"+,
|
|
99
|
+
# +"plain"+, +"ansi"+ or +"ast"+.
|
|
100
|
+
#
|
|
101
|
+
# There is no +"carve"+ target. Spec I15 excludes the Carve writer from
|
|
102
|
+
# expansion, because inlining a child into the formatter's output rewrites
|
|
103
|
+
# the author's document rather than formatting it.
|
|
104
|
+
def render_with_includes(source, root:, source_path:, target: "html", extensions: nil,
|
|
105
|
+
mode: nil, renderers: nil, symbols: nil, safe: false,
|
|
106
|
+
profile: nil, sections: true, max_depth: nil, max_bytes: nil,
|
|
107
|
+
max_resolver_calls: nil, max_warnings: nil)
|
|
108
|
+
JSON.parse(
|
|
109
|
+
_render_with_includes_json(
|
|
110
|
+
source.to_s,
|
|
111
|
+
root.to_s,
|
|
112
|
+
source_path.to_s,
|
|
113
|
+
target.to_s,
|
|
114
|
+
Array(extensions).map(&:to_s),
|
|
115
|
+
(mode || :interactive).to_s,
|
|
116
|
+
renderers || {},
|
|
117
|
+
symbols || {},
|
|
118
|
+
!!safe,
|
|
119
|
+
profile&.to_s,
|
|
120
|
+
!!sections,
|
|
121
|
+
max_depth,
|
|
122
|
+
max_bytes,
|
|
123
|
+
max_resolver_calls,
|
|
124
|
+
max_warnings,
|
|
125
|
+
),
|
|
126
|
+
symbolize_names: true,
|
|
127
|
+
# The engine bounds nesting itself, above Ruby JSON's default of 100, so
|
|
128
|
+
# a legal document would otherwise raise here. Same reason as .parse.
|
|
129
|
+
max_nesting: false,
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
45
133
|
# Import HTML or Markdown into canonical Carve. Both methods return the
|
|
46
134
|
# shared migration shape `{ value:, report: }`.
|
|
47
135
|
def from_html(source, mode: :safe)
|
|
@@ -49,10 +137,10 @@ module Carve
|
|
|
49
137
|
end
|
|
50
138
|
|
|
51
139
|
def from_markdown(source)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
140
|
+
result = JSON.parse(_from_markdown_json(source), symbolize_names: true)
|
|
141
|
+
# Keep the 0.1.x spelling while exposing the shared cross-language key.
|
|
142
|
+
result[:report][:source_format] = result[:report][:sourceFormat]
|
|
143
|
+
result
|
|
56
144
|
end
|
|
57
145
|
|
|
58
146
|
# Render Carve +source+ to an HTML string.
|
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.4
|
|
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-09-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|