inkmark 0.1.4-aarch64-linux → 0.2.0-aarch64-linux
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 +7 -0
- data/README.md +25 -3
- data/lib/inkmark/3.3/inkmark.so +0 -0
- data/lib/inkmark/3.4/inkmark.so +0 -0
- data/lib/inkmark/4.0/inkmark.so +0 -0
- data/lib/inkmark/options.rb +97 -36
- data/lib/inkmark/version.rb +1 -1
- data/lib/inkmark.rb +70 -24
- data/sig/inkmark.rbs +1 -0
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a62cd9232907088d1781df13ebc9772f6d2ec3a20a3f331bf1edc07ab6cfa4e
|
|
4
|
+
data.tar.gz: e5e2ebb5eac180adc92c780473aacddbbd6185d8c97101bf58afa2825ce5ce73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5630ebc0b28c23efac86f7ff45cdb4367b5af804c923ff386a7b310020624e69f9cba7d824f40267e7d062e9ae45aac6c112ab330bb1737dfc0333b149eb5d1
|
|
7
|
+
data.tar.gz: 3e338f4b6e7418c24e4cd2b55813038c4224af2e56135b9c5a7b1dc905406ccc0c3bd0283ccb73e46d96977a39a285a3b8888b075e28dfa1f2b4c05194b076a2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [0.2.0] - 2026-09-06
|
|
2
|
+
|
|
3
|
+
- Ractor-safe: the native extension declares `rb_ext_ractor_safe`, and every public API works from non-main Ractors.
|
|
4
|
+
- `Inkmark.default_options` is now frozen; configure process-wide defaults with `Inkmark.configure { |o| ... }` or `Inkmark.default_options=`.
|
|
5
|
+
- Load YAML on the first `frontmatter` parse instead of at require time.
|
|
6
|
+
- Updated dependencies.
|
|
7
|
+
|
|
1
8
|
## [0.1.4] - 2026-06-25
|
|
2
9
|
|
|
3
10
|
- Fix `frontmatter: true` leaking the frontmatter block into `to_markdown`, `chunks_by_heading`, and `chunks_by_size` output. Bug report by @freesteph [#3]
|
data/README.md
CHANGED
|
@@ -38,6 +38,7 @@ A very fast, feature-packed, AI-first Markdown gem for Ruby.
|
|
|
38
38
|
- [Plain-text extraction](#plain-text-extraction)
|
|
39
39
|
- [Markdown-to-Markdown pipeline](#markdown-to-markdown-pipeline)
|
|
40
40
|
- [Event handlers](#event-handlers)
|
|
41
|
+
- [Ractors](#ractors)
|
|
41
42
|
- [Benchmarks](#benchmarks)
|
|
42
43
|
- [Contributing](#contributing)
|
|
43
44
|
- [Acknowledgements](#acknowledgements)
|
|
@@ -177,9 +178,15 @@ g.options.math = true
|
|
|
177
178
|
g.options.tables = false
|
|
178
179
|
g.to_html
|
|
179
180
|
|
|
180
|
-
# Process-
|
|
181
|
-
Inkmark.
|
|
182
|
-
|
|
181
|
+
# Process-wide defaults, set once in your application initializer
|
|
182
|
+
Inkmark.configure do |options|
|
|
183
|
+
options.math = true
|
|
184
|
+
options.links = { nofollow: true }
|
|
185
|
+
end
|
|
186
|
+
Inkmark.new(md).to_html # picks up the defaults
|
|
187
|
+
|
|
188
|
+
# Or replace them wholesale
|
|
189
|
+
Inkmark.default_options = { preset: :recommended, math: true }
|
|
183
190
|
```
|
|
184
191
|
|
|
185
192
|
Unknown option keys raise `ArgumentError` immediately, including via the
|
|
@@ -1029,6 +1036,21 @@ Post-render filters (`syntax_highlight`, allowlists, `images: { lazy: true }`,
|
|
|
1029
1036
|
highlighter
|
|
1030
1037
|
- Handler-set `dest=` values pass through host and scheme allowlists
|
|
1031
1038
|
|
|
1039
|
+
## Ractors
|
|
1040
|
+
|
|
1041
|
+
Inkmark is Ractor-safe: every public API works inside non-main Ractors,
|
|
1042
|
+
so documents can be rendered in parallel without leaving the native fast
|
|
1043
|
+
path.
|
|
1044
|
+
|
|
1045
|
+
```ruby
|
|
1046
|
+
workers = sources.each_slice(250).map do |slice|
|
|
1047
|
+
Ractor.new(slice) do |batch|
|
|
1048
|
+
batch.map { |src| Inkmark.to_html(src, options: { preset: :recommended }) }
|
|
1049
|
+
end
|
|
1050
|
+
end
|
|
1051
|
+
html = workers.flat_map(&:value) # Ruby 4.0; use #take on 3.3 and 3.4
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1032
1054
|
## Benchmarks
|
|
1033
1055
|
|
|
1034
1056
|
Inkmark ships a benchmark harness comparing it against `kramdown`,
|
data/lib/inkmark/3.3/inkmark.so
CHANGED
|
Binary file
|
data/lib/inkmark/3.4/inkmark.so
CHANGED
|
Binary file
|
data/lib/inkmark/4.0/inkmark.so
CHANGED
|
Binary file
|
data/lib/inkmark/options.rb
CHANGED
|
@@ -22,22 +22,28 @@ class Inkmark
|
|
|
22
22
|
# Per-element-policy schemas. Each entry is +{ default:, types: }+; the
|
|
23
23
|
# validators use +types+ for type checking and +default+ to seed fresh
|
|
24
24
|
# nested hashes. Keep in sync with {NESTED_TO_FLAT}.
|
|
25
|
+
#
|
|
26
|
+
# Every constant in this class is frozen all the way down, not just
|
|
27
|
+
# at the top level: a non-main Ractor may only read constants whose
|
|
28
|
+
# whole object graph is frozen (+Ractor.shareable?+), so the inner
|
|
29
|
+
# Hashes and Arrays are frozen explicitly and the larger nested
|
|
30
|
+
# tables go through +Ractor.make_shareable+.
|
|
25
31
|
HEADINGS_SCHEMA = {
|
|
26
|
-
attributes: {default: false, types: [TrueClass, FalseClass]},
|
|
27
|
-
ids: {default: false, types: [TrueClass, FalseClass]}
|
|
32
|
+
attributes: {default: false, types: [TrueClass, FalseClass].freeze}.freeze,
|
|
33
|
+
ids: {default: false, types: [TrueClass, FalseClass].freeze}.freeze
|
|
28
34
|
}.freeze
|
|
29
35
|
|
|
30
36
|
IMAGES_SCHEMA = {
|
|
31
|
-
lazy: {default: false, types: [TrueClass, FalseClass]},
|
|
32
|
-
allowed_hosts: {default: nil, types: [NilClass, Array]},
|
|
33
|
-
allowed_schemes: {default: nil, types: [NilClass, Array]}
|
|
37
|
+
lazy: {default: false, types: [TrueClass, FalseClass].freeze}.freeze,
|
|
38
|
+
allowed_hosts: {default: nil, types: [NilClass, Array].freeze}.freeze,
|
|
39
|
+
allowed_schemes: {default: nil, types: [NilClass, Array].freeze}.freeze
|
|
34
40
|
}.freeze
|
|
35
41
|
|
|
36
42
|
LINKS_SCHEMA = {
|
|
37
|
-
autolink: {default: false, types: [TrueClass, FalseClass]},
|
|
38
|
-
nofollow: {default: false, types: [TrueClass, FalseClass]},
|
|
39
|
-
allowed_hosts: {default: nil, types: [NilClass, Array]},
|
|
40
|
-
allowed_schemes: {default: nil, types: [NilClass, Array]}
|
|
43
|
+
autolink: {default: false, types: [TrueClass, FalseClass].freeze}.freeze,
|
|
44
|
+
nofollow: {default: false, types: [TrueClass, FalseClass].freeze}.freeze,
|
|
45
|
+
allowed_hosts: {default: nil, types: [NilClass, Array].freeze}.freeze,
|
|
46
|
+
allowed_schemes: {default: nil, types: [NilClass, Array].freeze}.freeze
|
|
41
47
|
}.freeze
|
|
42
48
|
|
|
43
49
|
# Registry of nested hash options => their schemas. Iterated by the
|
|
@@ -52,7 +58,7 @@ class Inkmark
|
|
|
52
58
|
# Map from +(parent, child)+ user-facing keys to the flat key name the
|
|
53
59
|
# Rust side reads. Used by {#to_native_hash} / {#to_native_hash_frozen}
|
|
54
60
|
# to serialize the user-shaped hash into the FFI wire format.
|
|
55
|
-
NESTED_TO_FLAT = {
|
|
61
|
+
NESTED_TO_FLAT = Ractor.make_shareable({
|
|
56
62
|
[:headings, :attributes] => :heading_attributes,
|
|
57
63
|
[:headings, :ids] => :heading_ids,
|
|
58
64
|
[:images, :lazy] => :lazy_images,
|
|
@@ -62,7 +68,7 @@ class Inkmark
|
|
|
62
68
|
[:links, :nofollow] => :nofollow_external_links,
|
|
63
69
|
[:links, :allowed_hosts] => :allowed_link_hosts,
|
|
64
70
|
[:links, :allowed_schemes] => :allowed_link_schemes
|
|
65
|
-
}
|
|
71
|
+
})
|
|
66
72
|
|
|
67
73
|
# Build a frozen defaults hash for a nested schema from its +default+
|
|
68
74
|
# entries.
|
|
@@ -200,11 +206,11 @@ class Inkmark
|
|
|
200
206
|
# the accepted type set (nil-default-but-Array-when-set, polymorphic
|
|
201
207
|
# +toc+, nested-hash element-policy groups).
|
|
202
208
|
TYPES = {
|
|
203
|
-
extract: [NilClass, Hash],
|
|
204
|
-
toc: [TrueClass, FalseClass, Hash],
|
|
205
|
-
headings: [Hash],
|
|
206
|
-
images: [Hash],
|
|
207
|
-
links: [Hash]
|
|
209
|
+
extract: [NilClass, Hash].freeze,
|
|
210
|
+
toc: [TrueClass, FalseClass, Hash].freeze,
|
|
211
|
+
headings: [Hash].freeze,
|
|
212
|
+
images: [Hash].freeze,
|
|
213
|
+
links: [Hash].freeze
|
|
208
214
|
}.freeze
|
|
209
215
|
|
|
210
216
|
# Element kinds accepted inside +extract: { ... }+. Mirrors the match
|
|
@@ -237,7 +243,7 @@ class Inkmark
|
|
|
237
243
|
# The GFM tagfilter stays on. **Dangerous.** Use only for content
|
|
238
244
|
# the caller fully trusts (internal team-authored docs). The
|
|
239
245
|
# caller is fully responsible for sanitizing output.
|
|
240
|
-
PRESETS = {
|
|
246
|
+
PRESETS = Ractor.make_shareable({
|
|
241
247
|
commonmark: {
|
|
242
248
|
gfm: false,
|
|
243
249
|
gfm_tag_filter: false,
|
|
@@ -245,7 +251,7 @@ class Inkmark
|
|
|
245
251
|
strikethrough: false,
|
|
246
252
|
tasklists: false,
|
|
247
253
|
footnotes: false
|
|
248
|
-
}
|
|
254
|
+
},
|
|
249
255
|
|
|
250
256
|
gfm: {
|
|
251
257
|
gfm: true,
|
|
@@ -254,7 +260,7 @@ class Inkmark
|
|
|
254
260
|
strikethrough: true,
|
|
255
261
|
tasklists: true,
|
|
256
262
|
footnotes: true
|
|
257
|
-
}
|
|
263
|
+
},
|
|
258
264
|
|
|
259
265
|
recommended: {
|
|
260
266
|
gfm: true,
|
|
@@ -272,7 +278,7 @@ class Inkmark
|
|
|
272
278
|
syntax_highlight: true,
|
|
273
279
|
hard_wrap: true,
|
|
274
280
|
frontmatter: true
|
|
275
|
-
}
|
|
281
|
+
},
|
|
276
282
|
|
|
277
283
|
trusted: {
|
|
278
284
|
gfm: true,
|
|
@@ -290,8 +296,8 @@ class Inkmark
|
|
|
290
296
|
syntax_highlight: true,
|
|
291
297
|
hard_wrap: true,
|
|
292
298
|
frontmatter: true
|
|
293
|
-
}
|
|
294
|
-
}
|
|
299
|
+
}
|
|
300
|
+
})
|
|
295
301
|
|
|
296
302
|
# Preset applied by {#initialize} when the caller doesn't pass
|
|
297
303
|
# +preset:+. +:gfm+ matches {DEFAULTS}, so the default constructor
|
|
@@ -387,7 +393,16 @@ class Inkmark
|
|
|
387
393
|
# hashes; the input value as-is otherwise)
|
|
388
394
|
# @raise [ArgumentError] if +key+ is unknown, or the value (or any
|
|
389
395
|
# nested sub-value) has the wrong type
|
|
396
|
+
# @raise [FrozenError] if this instance is frozen (as
|
|
397
|
+
# {Inkmark.default_options} always is)
|
|
390
398
|
def []=(key, value)
|
|
399
|
+
if frozen?
|
|
400
|
+
raise FrozenError.new(
|
|
401
|
+
"can't modify frozen #{self.class}: use Inkmark.configure to change " \
|
|
402
|
+
"the process-wide defaults, or dup for a mutable copy",
|
|
403
|
+
receiver: self
|
|
404
|
+
)
|
|
405
|
+
end
|
|
391
406
|
validate_key!(key)
|
|
392
407
|
# Deep-merge partial nested-hash overrides (+:headings+,
|
|
393
408
|
# +:images+, +:links+) so callers pass only the sub-keys they
|
|
@@ -429,11 +444,17 @@ class Inkmark
|
|
|
429
444
|
# FFI calls that don't need to add per-call params. The cache is
|
|
430
445
|
# invalidated in {#[]=} and {#initialize_copy}.
|
|
431
446
|
#
|
|
432
|
-
#
|
|
433
|
-
#
|
|
447
|
+
# The hash is frozen all the way down, as a copy: nested Arrays and
|
|
448
|
+
# Hashes are duplicated before freezing so caller-supplied values
|
|
449
|
+
# (an +allowed_hosts+ Array, say) stay mutable in the caller's hands.
|
|
450
|
+
# A deeply frozen memo is what lets a frozen +Options+ be shared
|
|
451
|
+
# across Ractors.
|
|
452
|
+
#
|
|
453
|
+
# @return [Hash{Symbol => Object}] deeply frozen, shared across calls
|
|
454
|
+
# until a mutation invalidates it
|
|
434
455
|
# @api private
|
|
435
456
|
def to_native_hash_frozen
|
|
436
|
-
@frozen_native_hash ||= build_native_hash
|
|
457
|
+
@frozen_native_hash ||= deep_frozen_copy(build_native_hash)
|
|
437
458
|
end
|
|
438
459
|
|
|
439
460
|
# Return a new Options instance with +other+'s values applied on top.
|
|
@@ -457,6 +478,18 @@ class Inkmark
|
|
|
457
478
|
end
|
|
458
479
|
alias_method :eql?, :==
|
|
459
480
|
|
|
481
|
+
# Freeze this instance. The memoized FFI hash is computed first, while
|
|
482
|
+
# the instance is still mutable, so {#to_native_hash_frozen} never has
|
|
483
|
+
# to write to a frozen object. +Ractor.make_shareable+ calls +freeze+
|
|
484
|
+
# on every object it visits, which is what makes a shared
|
|
485
|
+
# {Inkmark.default_options} renderable from any Ractor.
|
|
486
|
+
#
|
|
487
|
+
# @return [self]
|
|
488
|
+
def freeze
|
|
489
|
+
to_native_hash_frozen
|
|
490
|
+
super
|
|
491
|
+
end
|
|
492
|
+
|
|
460
493
|
# Duplicate this instance, deep-copying the internal values hash so the
|
|
461
494
|
# clone is fully independent from the original.
|
|
462
495
|
def initialize_copy(orig)
|
|
@@ -466,14 +499,26 @@ class Inkmark
|
|
|
466
499
|
@frozen_native_hash = nil
|
|
467
500
|
end
|
|
468
501
|
|
|
469
|
-
#
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
#
|
|
473
|
-
#
|
|
502
|
+
# Reader and writer for every option key. The writer routes through
|
|
503
|
+
# {#[]=} so key validation and (for nested groups) deep-merge apply
|
|
504
|
+
# uniformly.
|
|
505
|
+
#
|
|
506
|
+
# Generated from source strings rather than +define_method+ blocks:
|
|
507
|
+
# a block-defined method carries its Proc, and Ruby refuses to call
|
|
508
|
+
# such a method from a non-main Ractor ("defined with an un-shareable
|
|
509
|
+
# Proc in a different Ractor"). Plain +def+ bodies have no such
|
|
510
|
+
# baggage. +key+ is always a Symbol from {DEFAULTS}, so interpolating
|
|
511
|
+
# it is safe.
|
|
474
512
|
DEFAULTS.each_key do |key|
|
|
475
|
-
|
|
476
|
-
|
|
513
|
+
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
514
|
+
def #{key} # def tables
|
|
515
|
+
@values[:#{key}] # @values[:tables]
|
|
516
|
+
end # end
|
|
517
|
+
|
|
518
|
+
def #{key}=(value) # def tables=(value)
|
|
519
|
+
self[:#{key}] = value # self[:tables] = value
|
|
520
|
+
end # end
|
|
521
|
+
RUBY
|
|
477
522
|
end
|
|
478
523
|
|
|
479
524
|
private
|
|
@@ -517,6 +562,19 @@ class Inkmark
|
|
|
517
562
|
def validate_key!(key) = self.class.send(:validate_key!, key)
|
|
518
563
|
def validate_value!(key, value) = self.class.send(:validate_value!, key, value)
|
|
519
564
|
|
|
565
|
+
# Return a frozen copy of +value+ with every nested Hash, Array, and
|
|
566
|
+
# String frozen too. Scalars (booleans, nil, Integers, Symbols) are
|
|
567
|
+
# returned as is. Only the container shapes {#build_native_hash} can
|
|
568
|
+
# produce are handled.
|
|
569
|
+
def deep_frozen_copy(value)
|
|
570
|
+
case value
|
|
571
|
+
when Hash then value.transform_values { |v| deep_frozen_copy(v) }.freeze
|
|
572
|
+
when Array then value.map { |v| deep_frozen_copy(v) }.freeze
|
|
573
|
+
when String then -value
|
|
574
|
+
else value
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
520
578
|
# Build the Rust-facing flat hash: nested element-policy hashes expand
|
|
521
579
|
# into their flat keys via {NESTED_TO_FLAT}; the internal +@toc_depth+
|
|
522
580
|
# is injected when set.
|
|
@@ -624,10 +682,13 @@ class Inkmark
|
|
|
624
682
|
# +options: { preset: :name }+ call pattern, which would otherwise
|
|
625
683
|
# build a fresh +Options+ instance (seed defaults, 6–14 +[]=+
|
|
626
684
|
# with validation, +build_native_hash+) on every call. The cached
|
|
627
|
-
# hashes are frozen and safe to share across threads
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
685
|
+
# hashes are deeply frozen and safe to share across threads and
|
|
686
|
+
# Ractors.
|
|
687
|
+
PRESETS_NATIVE_HASH = Ractor.make_shareable(
|
|
688
|
+
PRESETS.keys.each_with_object({}) do |name, h|
|
|
689
|
+
h[name] = new(preset: name).to_native_hash_frozen
|
|
690
|
+
end
|
|
691
|
+
)
|
|
631
692
|
|
|
632
693
|
# Build a Rust-facing flat hash from +overrides+ without allocating
|
|
633
694
|
# an +Options+ instance or walking +build_native_hash+. Starts from
|
data/lib/inkmark/version.rb
CHANGED
data/lib/inkmark.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "yaml"
|
|
4
|
-
|
|
5
3
|
# Inkmark is a very fast, feature-rich, AI-first CommonMark/GFM
|
|
6
4
|
# markdown renderer backed by the Rust pulldown-cmark parser.
|
|
7
5
|
#
|
|
@@ -64,12 +62,11 @@ class Inkmark
|
|
|
64
62
|
# This is a class-method fast path that skips Inkmark instance and
|
|
65
63
|
# Options copy allocation for the common one-shot render pattern.
|
|
66
64
|
# When the caller passes +options: nil+ (the default), we reuse the
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# bugs.
|
|
65
|
+
# frozen hash memoized on {default_options} by
|
|
66
|
+
# {Inkmark::Options#to_native_hash_frozen}. {configure} and
|
|
67
|
+
# {default_options=} install a whole new frozen instance rather than
|
|
68
|
+
# mutating the shared one, so the next render sees the new values
|
|
69
|
+
# without any stale-cache bugs.
|
|
73
70
|
#
|
|
74
71
|
# **Raw HTML safety.** +raw_html: false+ (the default) escapes
|
|
75
72
|
# every raw HTML tag in the source—safe for untrusted input.
|
|
@@ -351,33 +348,65 @@ class Inkmark
|
|
|
351
348
|
end
|
|
352
349
|
|
|
353
350
|
# Return an array of available syntax-highlighting theme names.
|
|
354
|
-
# Memoized—the theme list is fixed at compile time.
|
|
351
|
+
# Memoized—the theme list is fixed at compile time. The memo is
|
|
352
|
+
# warmed while this file loads (see the bottom of the file), so
|
|
353
|
+
# non-main Ractors only ever read it.
|
|
355
354
|
#
|
|
356
|
-
# @return [Array<String>]
|
|
355
|
+
# @return [Array<String>] frozen, with frozen elements
|
|
357
356
|
def highlight_themes
|
|
358
|
-
@highlight_themes ||= _syntax_themes.freeze
|
|
357
|
+
@highlight_themes ||= _syntax_themes.each(&:freeze).freeze
|
|
359
358
|
end
|
|
360
359
|
|
|
361
|
-
# The
|
|
360
|
+
# The process-wide default options, used when a render is given no
|
|
361
|
+
# options of its own. The instance is frozen all the way down and
|
|
362
|
+
# shared by every thread and Ractor in the process; change it with
|
|
363
|
+
# {configure} or {default_options=}, never in place.
|
|
362
364
|
#
|
|
363
|
-
# @return [Inkmark::Options]
|
|
365
|
+
# @return [Inkmark::Options] frozen
|
|
364
366
|
def default_options
|
|
365
|
-
@default_options
|
|
367
|
+
@default_options || DEFAULT_OPTIONS
|
|
366
368
|
end
|
|
367
369
|
|
|
368
|
-
# Replace the
|
|
370
|
+
# Replace the process-wide default options. The value is copied and
|
|
371
|
+
# frozen all the way down (+Ractor.make_shareable+), so the caller
|
|
372
|
+
# keeps its own object mutable and worker Ractors can read the
|
|
373
|
+
# result. Call this on the main Ractor before spawning workers.
|
|
369
374
|
#
|
|
370
|
-
# @param value [Hash, Inkmark::Options] new defaults; a Hash is
|
|
371
|
-
#
|
|
372
|
-
# @return [Inkmark::Options] the stored options
|
|
375
|
+
# @param value [Hash, Inkmark::Options] new defaults; a Hash is
|
|
376
|
+
# converted to Inkmark::Options
|
|
377
|
+
# @return [Inkmark::Options] the stored, frozen options
|
|
373
378
|
# @raise [TypeError] if +value+ is not a Hash or Inkmark::Options
|
|
379
|
+
# @example
|
|
380
|
+
# Inkmark.default_options = { preset: :recommended, math: true }
|
|
374
381
|
def default_options=(value)
|
|
375
|
-
|
|
382
|
+
options =
|
|
376
383
|
case value
|
|
377
|
-
when Inkmark::Options then value
|
|
384
|
+
when Inkmark::Options then value
|
|
378
385
|
when Hash then Inkmark::Options.new(value)
|
|
379
386
|
else raise TypeError, "default_options must be a Hash or Inkmark::Options, got #{value.class}"
|
|
380
387
|
end
|
|
388
|
+
# Copy-on-write of a shareable value: main-Ractor configuration.
|
|
389
|
+
@default_options = Ractor.make_shareable(options, copy: true) # audition:disable class-level-state
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Adjust the process-wide default options. Yields a mutable copy of
|
|
393
|
+
# the current {default_options}; the result is stored frozen through
|
|
394
|
+
# {default_options=}. Successive calls build on each other. Call this
|
|
395
|
+
# on the main Ractor before spawning workers.
|
|
396
|
+
#
|
|
397
|
+
# @yieldparam options [Inkmark::Options] a mutable copy of the
|
|
398
|
+
# current defaults
|
|
399
|
+
# @return [Inkmark::Options] the stored, frozen options
|
|
400
|
+
# @example In an application initializer
|
|
401
|
+
# Inkmark.configure do |options|
|
|
402
|
+
# options.math = true
|
|
403
|
+
# options.links = { nofollow: true }
|
|
404
|
+
# end
|
|
405
|
+
def configure
|
|
406
|
+
options = default_options.dup
|
|
407
|
+
yield options
|
|
408
|
+
self.default_options = options
|
|
409
|
+
default_options
|
|
381
410
|
end
|
|
382
411
|
|
|
383
412
|
private
|
|
@@ -413,12 +442,18 @@ class Inkmark
|
|
|
413
442
|
end
|
|
414
443
|
end
|
|
415
444
|
|
|
445
|
+
# Built-in defaults, served by {default_options} until {configure} or
|
|
446
|
+
# {default_options=} installs a replacement. Frozen all the way down
|
|
447
|
+
# (which also memoizes its FFI hash, see {Inkmark::Options#freeze}).
|
|
448
|
+
DEFAULT_OPTIONS = Ractor.make_shareable(Inkmark::Options.new)
|
|
449
|
+
private_constant :DEFAULT_OPTIONS
|
|
450
|
+
|
|
416
451
|
# Create a new renderer for +source+.
|
|
417
452
|
#
|
|
418
453
|
# @param source [String, nil] markdown source; +nil+ is treated as an
|
|
419
454
|
# empty string
|
|
420
455
|
# @param options [Hash, Inkmark::Options, nil] rendering options; falls back
|
|
421
|
-
# to a
|
|
456
|
+
# to a mutable copy of {Inkmark.default_options} when nil
|
|
422
457
|
# @raise [TypeError] if +options+ is not a Hash, Inkmark::Options, or nil
|
|
423
458
|
def initialize(source = nil, options: nil)
|
|
424
459
|
self.source = source
|
|
@@ -459,8 +494,8 @@ class Inkmark
|
|
|
459
494
|
|
|
460
495
|
# Set rendering options.
|
|
461
496
|
#
|
|
462
|
-
# @param value [Hash, Inkmark::Options, nil] new options; nil resets to a
|
|
463
|
-
# of {Inkmark.default_options}
|
|
497
|
+
# @param value [Hash, Inkmark::Options, nil] new options; nil resets to a
|
|
498
|
+
# mutable copy of {Inkmark.default_options}
|
|
464
499
|
# @return [Inkmark::Options] the stored options object
|
|
465
500
|
# @raise [TypeError] if +value+ is not a Hash, Inkmark::Options, or nil
|
|
466
501
|
def options=(value)
|
|
@@ -681,6 +716,9 @@ class Inkmark
|
|
|
681
716
|
# The raw YAML text is extracted by Rust during the event walk;
|
|
682
717
|
# parsing uses Ruby's stdlib +YAML.safe_load+ so all standard YAML
|
|
683
718
|
# types (strings, numbers, arrays, nested hashes) are supported.
|
|
719
|
+
# Psych is loaded here, on first use, rather than with the gem: front
|
|
720
|
+
# matter is opt-in, and this keeps Psych's load time and its own
|
|
721
|
+
# constants out of processes that never enable it.
|
|
684
722
|
#
|
|
685
723
|
# @return [Hash, nil] parsed frontmatter or nil
|
|
686
724
|
# @example
|
|
@@ -691,7 +729,10 @@ class Inkmark
|
|
|
691
729
|
return @frontmatter if defined?(@frontmatter)
|
|
692
730
|
return @frontmatter = nil unless @options[:frontmatter]
|
|
693
731
|
to_html unless @frontmatter_raw
|
|
694
|
-
@frontmatter =
|
|
732
|
+
return @frontmatter = nil unless @frontmatter_raw
|
|
733
|
+
|
|
734
|
+
require "yaml" unless defined?(::YAML) # audition:disable runtime-require
|
|
735
|
+
@frontmatter = YAML.safe_load(@frontmatter_raw)
|
|
695
736
|
end
|
|
696
737
|
|
|
697
738
|
private
|
|
@@ -715,3 +756,8 @@ class Inkmark
|
|
|
715
756
|
extract.is_a?(Hash) && extract.any? { |_, v| v }
|
|
716
757
|
end
|
|
717
758
|
end
|
|
759
|
+
|
|
760
|
+
# Warm the class-level memo on the main Ractor while loading, so worker
|
|
761
|
+
# Ractors only ever read it (a first write from a worker would raise
|
|
762
|
+
# Ractor::IsolationError).
|
|
763
|
+
Inkmark.highlight_themes
|
data/sig/inkmark.rbs
CHANGED
|
@@ -179,6 +179,7 @@ class Inkmark
|
|
|
179
179
|
def self.highlight_themes: () -> Array[String]
|
|
180
180
|
def self.default_options: () -> Inkmark::Options
|
|
181
181
|
def self.default_options=: (options_input value) -> Inkmark::Options
|
|
182
|
+
def self.configure: () { (Inkmark::Options options) -> void } -> Inkmark::Options
|
|
182
183
|
|
|
183
184
|
# ── Native bindings (private in spirit; registered on the class) ──────
|
|
184
185
|
def self._native_to_html: (String source, Hash[Symbol, untyped]? opts_hash) -> String
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inkmark
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Markin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06
|
|
11
|
+
date: 2026-09-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -44,14 +44,14 @@ dependencies:
|
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '4.0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '4.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: yard
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|