inkmark 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75b9caec914a5e3b191911f77c83be71f81b729dc156461c30066de77c2e0f13
4
- data.tar.gz: 8ff22a0d8e12d2ae300d450edbdb0546de506993c9df47612a5c852018e9a8f9
3
+ metadata.gz: 426fe02b791d8fcf0b3d080cea874692f3027eb1528e07300de6685b869108e3
4
+ data.tar.gz: b1e8947dc0811dfcd70bc29b62c34c95536526f512941e73bd66664d5c5b7473
5
5
  SHA512:
6
- metadata.gz: '0709173de6887ccee58ace68ea2dca6b42fc1c6e44f4ac5b2510f37501bb876652e7cfb60d5dd6d35025d43a1769fa49ebfd95fbd9d873c1669c5dd8ce31db65'
7
- data.tar.gz: d8abaf48a2172226c0e39390989210dcb4f51df1d7ae627d99a72e65f2f6731cc85a99a1993827f0480f8da07da57d2a598c32c3272d8b4672cda0a1c5d6697e
6
+ metadata.gz: 07d38e9bf03538f52a444d033fa48648bd235673c3c435e7137bd5435b31e82dce00b758b038840577540d451a1d8d82bbd8660d9be99429b0a7ed35a666b4b3
7
+ data.tar.gz: 9898e5c9780a4ea2423861f62b3054459d7c3dadad7ae9fb999fe3b5416a51a513df9b7d428f1a4af5093a67cc91eb86f7be5fc2c3054d9456b805a65e7b4438
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/Cargo.lock CHANGED
@@ -10,9 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
10
10
 
11
11
  [[package]]
12
12
  name = "aho-corasick"
13
- version = "1.1.4"
13
+ version = "1.1.5"
14
14
  source = "registry+https://github.com/rust-lang/crates.io-index"
15
- checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
15
+ checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
16
16
  dependencies = [
17
17
  "memchr",
18
18
  ]
@@ -47,7 +47,7 @@ dependencies = [
47
47
  "regex",
48
48
  "rustc-hash",
49
49
  "shlex",
50
- "syn",
50
+ "syn 2.0.119",
51
51
  ]
52
52
 
53
53
  [[package]]
@@ -67,18 +67,18 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
67
67
 
68
68
  [[package]]
69
69
  name = "bitflags"
70
- version = "2.13.0"
70
+ version = "2.13.1"
71
71
  source = "registry+https://github.com/rust-lang/crates.io-index"
72
- checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
72
+ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
73
73
 
74
74
  [[package]]
75
75
  name = "bstr"
76
- version = "1.12.1"
76
+ version = "1.13.1"
77
77
  source = "registry+https://github.com/rust-lang/crates.io-index"
78
- checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
78
+ checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
79
79
  dependencies = [
80
80
  "memchr",
81
- "serde",
81
+ "serde_core",
82
82
  ]
83
83
 
84
84
  [[package]]
@@ -98,9 +98,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
98
98
 
99
99
  [[package]]
100
100
  name = "clang-sys"
101
- version = "1.8.1"
101
+ version = "1.9.1"
102
102
  source = "registry+https://github.com/rust-lang/crates.io-index"
103
- checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
103
+ checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
104
104
  dependencies = [
105
105
  "glob",
106
106
  "libc",
@@ -109,9 +109,9 @@ dependencies = [
109
109
 
110
110
  [[package]]
111
111
  name = "crc32fast"
112
- version = "1.5.0"
112
+ version = "1.5.1"
113
113
  source = "registry+https://github.com/rust-lang/crates.io-index"
114
- checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
114
+ checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
115
115
  dependencies = [
116
116
  "cfg-if",
117
117
  ]
@@ -124,26 +124,26 @@ checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
124
124
 
125
125
  [[package]]
126
126
  name = "displaydoc"
127
- version = "0.2.6"
127
+ version = "0.2.7"
128
128
  source = "registry+https://github.com/rust-lang/crates.io-index"
129
- checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
129
+ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
130
130
  dependencies = [
131
131
  "proc-macro2",
132
132
  "quote",
133
- "syn",
133
+ "syn 3.0.5",
134
134
  ]
135
135
 
136
136
  [[package]]
137
137
  name = "either"
138
- version = "1.16.0"
138
+ version = "1.18.0"
139
139
  source = "registry+https://github.com/rust-lang/crates.io-index"
140
- checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
140
+ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
141
141
 
142
142
  [[package]]
143
143
  name = "emojis"
144
- version = "0.8.2"
144
+ version = "0.9.0"
145
145
  source = "registry+https://github.com/rust-lang/crates.io-index"
146
- checksum = "0a4d5d50b0b58df5173d8ff1192b4d1422ceae5d981b30d4b6f8ed1d673a2bc4"
146
+ checksum = "0b1514ced566c94991ed9563258ecf61e311fd773bf0a3f20606830386bf66e3"
147
147
  dependencies = [
148
148
  "phf",
149
149
  ]
@@ -167,12 +167,13 @@ dependencies = [
167
167
 
168
168
  [[package]]
169
169
  name = "flate2"
170
- version = "1.1.9"
170
+ version = "1.1.10"
171
171
  source = "registry+https://github.com/rust-lang/crates.io-index"
172
- checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
172
+ checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
173
173
  dependencies = [
174
174
  "crc32fast",
175
175
  "miniz_oxide",
176
+ "zlib-rs",
176
177
  ]
177
178
 
178
179
  [[package]]
@@ -198,15 +199,15 @@ dependencies = [
198
199
 
199
200
  [[package]]
200
201
  name = "glob"
201
- version = "0.3.3"
202
+ version = "0.3.4"
202
203
  source = "registry+https://github.com/rust-lang/crates.io-index"
203
- checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
204
+ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
204
205
 
205
206
  [[package]]
206
207
  name = "globset"
207
- version = "0.4.18"
208
+ version = "0.4.20"
208
209
  source = "registry+https://github.com/rust-lang/crates.io-index"
209
- checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
210
+ checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
210
211
  dependencies = [
211
212
  "aho-corasick",
212
213
  "bstr",
@@ -228,9 +229,9 @@ dependencies = [
228
229
 
229
230
  [[package]]
230
231
  name = "icu_collections"
231
- version = "2.2.0"
232
+ version = "2.3.0"
232
233
  source = "registry+https://github.com/rust-lang/crates.io-index"
233
- checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
234
+ checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
234
235
  dependencies = [
235
236
  "displaydoc",
236
237
  "potential_utf",
@@ -242,9 +243,9 @@ dependencies = [
242
243
 
243
244
  [[package]]
244
245
  name = "icu_locale_core"
245
- version = "2.2.0"
246
+ version = "2.3.0"
246
247
  source = "registry+https://github.com/rust-lang/crates.io-index"
247
- checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
248
+ checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
248
249
  dependencies = [
249
250
  "displaydoc",
250
251
  "litemap",
@@ -255,9 +256,9 @@ dependencies = [
255
256
 
256
257
  [[package]]
257
258
  name = "icu_normalizer"
258
- version = "2.2.0"
259
+ version = "2.3.0"
259
260
  source = "registry+https://github.com/rust-lang/crates.io-index"
260
- checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
261
+ checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
261
262
  dependencies = [
262
263
  "icu_collections",
263
264
  "icu_normalizer_data",
@@ -269,16 +270,17 @@ dependencies = [
269
270
 
270
271
  [[package]]
271
272
  name = "icu_normalizer_data"
272
- version = "2.2.0"
273
+ version = "2.3.0"
273
274
  source = "registry+https://github.com/rust-lang/crates.io-index"
274
- checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
275
+ checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
275
276
 
276
277
  [[package]]
277
278
  name = "icu_properties"
278
- version = "2.2.0"
279
+ version = "2.3.0"
279
280
  source = "registry+https://github.com/rust-lang/crates.io-index"
280
- checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
281
+ checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
281
282
  dependencies = [
283
+ "displaydoc",
282
284
  "icu_collections",
283
285
  "icu_locale_core",
284
286
  "icu_properties_data",
@@ -289,15 +291,15 @@ dependencies = [
289
291
 
290
292
  [[package]]
291
293
  name = "icu_properties_data"
292
- version = "2.2.0"
294
+ version = "2.3.0"
293
295
  source = "registry+https://github.com/rust-lang/crates.io-index"
294
- checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
296
+ checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
295
297
 
296
298
  [[package]]
297
299
  name = "icu_provider"
298
- version = "2.2.0"
300
+ version = "2.3.1"
299
301
  source = "registry+https://github.com/rust-lang/crates.io-index"
300
- checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
302
+ checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
301
303
  dependencies = [
302
304
  "displaydoc",
303
305
  "icu_locale_core",
@@ -331,7 +333,7 @@ dependencies = [
331
333
 
332
334
  [[package]]
333
335
  name = "inkmark"
334
- version = "0.1.4"
336
+ version = "0.2.0"
335
337
  dependencies = [
336
338
  "deunicode",
337
339
  "emojis",
@@ -367,9 +369,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
367
369
 
368
370
  [[package]]
369
371
  name = "libc"
370
- version = "0.2.186"
372
+ version = "0.2.189"
371
373
  source = "registry+https://github.com/rust-lang/crates.io-index"
372
- checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
374
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
373
375
 
374
376
  [[package]]
375
377
  name = "libloading"
@@ -392,15 +394,15 @@ dependencies = [
392
394
 
393
395
  [[package]]
394
396
  name = "litemap"
395
- version = "0.8.2"
397
+ version = "0.8.3"
396
398
  source = "registry+https://github.com/rust-lang/crates.io-index"
397
- checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
399
+ checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
398
400
 
399
401
  [[package]]
400
402
  name = "log"
401
- version = "0.4.33"
403
+ version = "0.4.34"
402
404
  source = "registry+https://github.com/rust-lang/crates.io-index"
403
- checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
405
+ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
404
406
 
405
407
  [[package]]
406
408
  name = "magnus"
@@ -422,14 +424,14 @@ checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
422
424
  dependencies = [
423
425
  "proc-macro2",
424
426
  "quote",
425
- "syn",
427
+ "syn 2.0.119",
426
428
  ]
427
429
 
428
430
  [[package]]
429
431
  name = "memchr"
430
- version = "2.8.2"
432
+ version = "2.8.3"
431
433
  source = "registry+https://github.com/rust-lang/crates.io-index"
432
- checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
434
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
433
435
 
434
436
  [[package]]
435
437
  name = "minimal-lexical"
@@ -439,9 +441,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
439
441
 
440
442
  [[package]]
441
443
  name = "miniz_oxide"
442
- version = "0.8.9"
444
+ version = "0.9.1"
443
445
  source = "registry+https://github.com/rust-lang/crates.io-index"
444
- checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
446
+ checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
445
447
  dependencies = [
446
448
  "adler2",
447
449
  "simd-adler32",
@@ -489,18 +491,18 @@ dependencies = [
489
491
 
490
492
  [[package]]
491
493
  name = "potential_utf"
492
- version = "0.1.5"
494
+ version = "0.1.6"
493
495
  source = "registry+https://github.com/rust-lang/crates.io-index"
494
- checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
496
+ checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
495
497
  dependencies = [
496
498
  "zerovec",
497
499
  ]
498
500
 
499
501
  [[package]]
500
502
  name = "proc-macro2"
501
- version = "1.0.106"
503
+ version = "1.0.107"
502
504
  source = "registry+https://github.com/rust-lang/crates.io-index"
503
- checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
505
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
504
506
  dependencies = [
505
507
  "unicode-ident",
506
508
  ]
@@ -525,36 +527,36 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
525
527
 
526
528
  [[package]]
527
529
  name = "pulldown-cmark-to-cmark"
528
- version = "22.0.0"
530
+ version = "22.0.1"
529
531
  source = "registry+https://github.com/rust-lang/crates.io-index"
530
- checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90"
532
+ checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60"
531
533
  dependencies = [
532
534
  "pulldown-cmark",
533
535
  ]
534
536
 
535
537
  [[package]]
536
538
  name = "quote"
537
- version = "1.0.45"
539
+ version = "1.0.47"
538
540
  source = "registry+https://github.com/rust-lang/crates.io-index"
539
- checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
541
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
540
542
  dependencies = [
541
543
  "proc-macro2",
542
544
  ]
543
545
 
544
546
  [[package]]
545
547
  name = "rb-sys"
546
- version = "0.9.128"
548
+ version = "0.9.130"
547
549
  source = "registry+https://github.com/rust-lang/crates.io-index"
548
- checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
550
+ checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
549
551
  dependencies = [
550
552
  "rb-sys-build",
551
553
  ]
552
554
 
553
555
  [[package]]
554
556
  name = "rb-sys-build"
555
- version = "0.9.128"
557
+ version = "0.9.130"
556
558
  source = "registry+https://github.com/rust-lang/crates.io-index"
557
- checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
559
+ checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
558
560
  dependencies = [
559
561
  "bindgen",
560
562
  "lazy_static",
@@ -562,7 +564,7 @@ dependencies = [
562
564
  "quote",
563
565
  "regex",
564
566
  "shell-words",
565
- "syn",
567
+ "syn 2.0.119",
566
568
  ]
567
569
 
568
570
  [[package]]
@@ -573,9 +575,9 @@ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
573
575
 
574
576
  [[package]]
575
577
  name = "rb-sys-test-helpers"
576
- version = "0.2.2"
578
+ version = "0.3.0"
577
579
  source = "registry+https://github.com/rust-lang/crates.io-index"
578
- checksum = "d6ccb543252549fc28f5d290322e041cd682bd199a8e1caaa813fb6e63dd221e"
580
+ checksum = "ccc637c53cad6a6b49ad51c5857c64e36bd70c955ffafe9dc9cc5bc9dd0b3b18"
579
581
  dependencies = [
580
582
  "rb-sys",
581
583
  "rb-sys-env",
@@ -584,19 +586,19 @@ dependencies = [
584
586
 
585
587
  [[package]]
586
588
  name = "rb-sys-test-helpers-macros"
587
- version = "0.2.2"
589
+ version = "0.3.0"
588
590
  source = "registry+https://github.com/rust-lang/crates.io-index"
589
- checksum = "1508caed999cb659ab1b3308e7b2985186b3b550ef5492dc18da71b560c55615"
591
+ checksum = "b1c1fce35d9ac77a2745e539232fb82a75856675a5daf4ef8b1ca994d89f8b3f"
590
592
  dependencies = [
591
593
  "quote",
592
- "syn",
594
+ "syn 2.0.119",
593
595
  ]
594
596
 
595
597
  [[package]]
596
598
  name = "regex"
597
- version = "1.12.4"
599
+ version = "1.13.1"
598
600
  source = "registry+https://github.com/rust-lang/crates.io-index"
599
- checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
601
+ checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
600
602
  dependencies = [
601
603
  "aho-corasick",
602
604
  "memchr",
@@ -606,9 +608,9 @@ dependencies = [
606
608
 
607
609
  [[package]]
608
610
  name = "regex-automata"
609
- version = "0.4.14"
611
+ version = "0.4.18"
610
612
  source = "registry+https://github.com/rust-lang/crates.io-index"
611
- checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
613
+ checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
612
614
  dependencies = [
613
615
  "aho-corasick",
614
616
  "memchr",
@@ -623,9 +625,9 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
623
625
 
624
626
  [[package]]
625
627
  name = "rustc-hash"
626
- version = "2.1.2"
628
+ version = "2.1.3"
627
629
  source = "registry+https://github.com/rust-lang/crates.io-index"
628
- checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
630
+ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
629
631
 
630
632
  [[package]]
631
633
  name = "same-file"
@@ -644,31 +646,31 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
644
646
 
645
647
  [[package]]
646
648
  name = "serde"
647
- version = "1.0.228"
649
+ version = "1.0.229"
648
650
  source = "registry+https://github.com/rust-lang/crates.io-index"
649
- checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
651
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
650
652
  dependencies = [
651
653
  "serde_core",
652
654
  ]
653
655
 
654
656
  [[package]]
655
657
  name = "serde_core"
656
- version = "1.0.228"
658
+ version = "1.0.229"
657
659
  source = "registry+https://github.com/rust-lang/crates.io-index"
658
- checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
660
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
659
661
  dependencies = [
660
662
  "serde_derive",
661
663
  ]
662
664
 
663
665
  [[package]]
664
666
  name = "serde_derive"
665
- version = "1.0.228"
667
+ version = "1.0.229"
666
668
  source = "registry+https://github.com/rust-lang/crates.io-index"
667
- checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
669
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
668
670
  dependencies = [
669
671
  "proc-macro2",
670
672
  "quote",
671
- "syn",
673
+ "syn 3.0.5",
672
674
  ]
673
675
 
674
676
  [[package]]
@@ -685,9 +687,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
685
687
 
686
688
  [[package]]
687
689
  name = "simd-adler32"
688
- version = "0.3.9"
690
+ version = "0.3.10"
689
691
  source = "registry+https://github.com/rust-lang/crates.io-index"
690
- checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
692
+ checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
691
693
 
692
694
  [[package]]
693
695
  name = "siphasher"
@@ -697,9 +699,9 @@ checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
697
699
 
698
700
  [[package]]
699
701
  name = "smallvec"
700
- version = "1.15.2"
702
+ version = "1.16.0"
701
703
  source = "registry+https://github.com/rust-lang/crates.io-index"
702
- checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
704
+ checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
703
705
 
704
706
  [[package]]
705
707
  name = "stable_deref_trait"
@@ -709,9 +711,20 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
709
711
 
710
712
  [[package]]
711
713
  name = "syn"
712
- version = "2.0.118"
714
+ version = "2.0.119"
715
+ source = "registry+https://github.com/rust-lang/crates.io-index"
716
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
717
+ dependencies = [
718
+ "proc-macro2",
719
+ "quote",
720
+ "unicode-ident",
721
+ ]
722
+
723
+ [[package]]
724
+ name = "syn"
725
+ version = "3.0.5"
713
726
  source = "registry+https://github.com/rust-lang/crates.io-index"
714
- checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
727
+ checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
715
728
  dependencies = [
716
729
  "proc-macro2",
717
730
  "quote",
@@ -726,7 +739,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
726
739
  dependencies = [
727
740
  "proc-macro2",
728
741
  "quote",
729
- "syn",
742
+ "syn 2.0.119",
730
743
  ]
731
744
 
732
745
  [[package]]
@@ -749,29 +762,29 @@ dependencies = [
749
762
 
750
763
  [[package]]
751
764
  name = "thiserror"
752
- version = "2.0.18"
765
+ version = "2.0.20"
753
766
  source = "registry+https://github.com/rust-lang/crates.io-index"
754
- checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
767
+ checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
755
768
  dependencies = [
756
769
  "thiserror-impl",
757
770
  ]
758
771
 
759
772
  [[package]]
760
773
  name = "thiserror-impl"
761
- version = "2.0.18"
774
+ version = "2.0.20"
762
775
  source = "registry+https://github.com/rust-lang/crates.io-index"
763
- checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
776
+ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
764
777
  dependencies = [
765
778
  "proc-macro2",
766
779
  "quote",
767
- "syn",
780
+ "syn 3.0.5",
768
781
  ]
769
782
 
770
783
  [[package]]
771
784
  name = "tinystr"
772
- version = "0.8.3"
785
+ version = "0.8.4"
773
786
  source = "registry+https://github.com/rust-lang/crates.io-index"
774
- checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
787
+ checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
775
788
  dependencies = [
776
789
  "displaydoc",
777
790
  "zerovec",
@@ -858,9 +871,9 @@ dependencies = [
858
871
 
859
872
  [[package]]
860
873
  name = "writeable"
861
- version = "0.6.3"
874
+ version = "0.6.4"
862
875
  source = "registry+https://github.com/rust-lang/crates.io-index"
863
- checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
876
+ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
864
877
 
865
878
  [[package]]
866
879
  name = "yoke"
@@ -881,7 +894,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
881
894
  dependencies = [
882
895
  "proc-macro2",
883
896
  "quote",
884
- "syn",
897
+ "syn 2.0.119",
885
898
  "synstructure",
886
899
  ]
887
900
 
@@ -902,15 +915,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
902
915
  dependencies = [
903
916
  "proc-macro2",
904
917
  "quote",
905
- "syn",
918
+ "syn 2.0.119",
906
919
  "synstructure",
907
920
  ]
908
921
 
909
922
  [[package]]
910
923
  name = "zerotrie"
911
- version = "0.2.4"
924
+ version = "0.2.5"
912
925
  source = "registry+https://github.com/rust-lang/crates.io-index"
913
- checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
926
+ checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
914
927
  dependencies = [
915
928
  "displaydoc",
916
929
  "yoke",
@@ -919,9 +932,9 @@ dependencies = [
919
932
 
920
933
  [[package]]
921
934
  name = "zerovec"
922
- version = "0.11.6"
935
+ version = "0.11.8"
923
936
  source = "registry+https://github.com/rust-lang/crates.io-index"
924
- checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
937
+ checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
925
938
  dependencies = [
926
939
  "yoke",
927
940
  "zerofrom",
@@ -930,11 +943,17 @@ dependencies = [
930
943
 
931
944
  [[package]]
932
945
  name = "zerovec-derive"
933
- version = "0.11.3"
946
+ version = "0.11.6"
934
947
  source = "registry+https://github.com/rust-lang/crates.io-index"
935
- checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
948
+ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
936
949
  dependencies = [
937
950
  "proc-macro2",
938
951
  "quote",
939
- "syn",
952
+ "syn 3.0.5",
940
953
  ]
954
+
955
+ [[package]]
956
+ name = "zlib-rs"
957
+ version = "0.6.7"
958
+ source = "registry+https://github.com/rust-lang/crates.io-index"
959
+ checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
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-level defaults, to set in your application initializer
181
- Inkmark.default_options.math = true
182
- Inkmark.new(md).to_html # picks up the default
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`,
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "inkmark"
3
- version = "0.1.4"
3
+ version = "0.2.0"
4
4
  edition = "2021"
5
5
  authors = ["Yaroslav Markin <yaroslav@markin.net>"]
6
6
  license = "MIT"
@@ -11,11 +11,11 @@ crate-type = ["cdylib"]
11
11
 
12
12
  [dependencies]
13
13
  magnus = { version = "0.8.2" }
14
- rb-sys = { version = "0.9.126", features = ["stable-api-compiled-fallback"] }
14
+ rb-sys = { version = "0.9.130", features = ["stable-api-compiled-fallback"] }
15
15
  pulldown-cmark = { version = "0.13", default-features = false, features = ["html", "simd"] }
16
16
  pulldown-cmark-escape = "0.11"
17
17
  deunicode = "1.6"
18
- emojis = "0.8"
18
+ emojis = "0.9"
19
19
  whatlang = "0.18"
20
20
  unicode-segmentation = "1.12"
21
21
  syntect = { version = "5.2", default-features = false, features = ["parsing", "html", "default-themes", "default-syntaxes", "regex-fancy"] }
@@ -28,4 +28,4 @@ pulldown-cmark-to-cmark = "22"
28
28
  rb-sys-env = "0.2.2"
29
29
 
30
30
  [dev-dependencies]
31
- rb-sys-test-helpers = { version = "0.2.2" }
31
+ rb-sys-test-helpers = { version = "0.3.0" }
@@ -1,4 +1,6 @@
1
- #![forbid(unsafe_code)]
1
+ // `deny` rather than `forbid`: the single `unsafe` block in `init` is
2
+ // allowed explicitly; everything else stays unsafe-free.
3
+ #![deny(unsafe_code)]
2
4
 
3
5
  use magnus::{function, prelude::*, Error, Ruby};
4
6
 
@@ -22,11 +24,34 @@ mod truncate;
22
24
  mod url_match;
23
25
 
24
26
  #[magnus::init]
27
+ #[allow(unsafe_code)]
25
28
  fn init(ruby: &Ruby) -> Result<(), Error> {
29
+ // Declare every method defined below Ractor-safe. Ruby marks the methods an
30
+ // extension defines while `Init_*` runs as unsafe unless this flag is set,
31
+ // and calling such a method from a non-main Ractor raises
32
+ // `Ractor::UnsafeError`. The extension keeps no Ruby `VALUE`s in
33
+ // process-global state: the `OnceLock` caches in `highlight` hold plain
34
+ // syntect data (`Sync`, enforced by the compiler) and the `sym_id!`
35
+ // statics in `options` hold interned symbol ids, both safe to share across
36
+ // Ractors. This must run before `define_class` so the flag covers every
37
+ // method registered below.
38
+ //
39
+ // SAFETY: `rb_ext_ractor_safe` takes no pointers and only flips a flag on
40
+ // the VM's extension-loading state. Its one precondition is being called
41
+ // while `Init_*` runs, which is exactly where `#[magnus::init]` invokes
42
+ // this function.
43
+ unsafe { rb_sys::rb_ext_ractor_safe(true) };
44
+
26
45
  let inkmark = ruby.define_class("Inkmark", ruby.class_object())?;
27
46
  inkmark.define_singleton_method("_native_to_html", function!(document::native_to_html, 2))?;
28
- inkmark.define_singleton_method("_native_to_markdown", function!(document::native_to_markdown, 2))?;
29
- inkmark.define_singleton_method("_native_to_plain_text", function!(document::native_to_plain_text, 2))?;
47
+ inkmark.define_singleton_method(
48
+ "_native_to_markdown",
49
+ function!(document::native_to_markdown, 2),
50
+ )?;
51
+ inkmark.define_singleton_method(
52
+ "_native_to_plain_text",
53
+ function!(document::native_to_plain_text, 2),
54
+ )?;
30
55
  inkmark.define_singleton_method(
31
56
  "_native_chunks_by_heading",
32
57
  function!(chunks_by_heading::native_chunks_by_heading, 2),
@@ -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
- }.freeze
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
- }.freeze,
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
- }.freeze,
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
- }.freeze,
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
- }.freeze
294
- }.freeze
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
- # @return [Hash{Symbol => Object}] frozen, shared across calls until
433
- # a mutation invalidates it
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.freeze
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
- # @!macro [attach] inkmark_options_accessor
470
- # @!attribute [rw] $1
471
- # Reader and writer for the +$1+ option. The writer routes through
472
- # {#[]=} so key validation and (for nested groups) deep-merge apply
473
- # uniformly.
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
- define_method(key) { @values[key] }
476
- define_method("#{key}=") { |value| self[key] = value }
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
- PRESETS_NATIVE_HASH = PRESETS.keys.each_with_object({}) do |name, h|
629
- h[name] = new(preset: name).to_native_hash_frozen
630
- end.freeze
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  class Inkmark
4
4
  # Current gem version.
5
- VERSION = "0.1.4"
5
+ VERSION = "0.2.0"
6
6
  end
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
- # cached frozen hash that {Inkmark::Options#to_native_hash_frozen}
68
- # returns; the cache lives on the Options instance itself and is
69
- # invalidated by the Options mutation methods, so
70
- # +Inkmark.default_options.tables = false+ followed by
71
- # +Inkmark.to_html(src)+ picks up the new value without stale-cache
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 class-level default options used when no per-instance options are given.
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 ||= Inkmark::Options.new
367
+ @default_options || DEFAULT_OPTIONS
366
368
  end
367
369
 
368
- # Replace the class-level default options.
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 converted to
371
- # Inkmark::Options, a Inkmark::Options is duped
372
- # @return [Inkmark::Options] the stored options object
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
- @default_options =
382
+ options =
376
383
  case value
377
- when Inkmark::Options then value.dup
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 dup of {Inkmark.default_options} when nil
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 dup
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 = @frontmatter_raw ? YAML.safe_load(@frontmatter_raw) : nil
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inkmark
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Markin
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.9.126
18
+ version: 0.9.130
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.9.126
25
+ version: 0.9.130
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -57,14 +57,14 @@ dependencies:
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '3.9'
60
+ version: '4.0'
61
61
  type: :development
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '3.9'
67
+ version: '4.0'
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: yard
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -202,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
202
202
  - !ruby/object:Gem::Version
203
203
  version: '0'
204
204
  requirements: []
205
- rubygems_version: 4.0.10
205
+ rubygems_version: 4.0.16
206
206
  specification_version: 4
207
207
  summary: Very fast, feature-packed, AI-first Markdown gem for Ruby.
208
208
  test_files: []